我在玩Azure AD B2C,并且行为异常。我根据以下示例创建了一个新应用:AzureADQuickStarts/B2C-WebApp-OpenIdConnect-DotNet ,它的工作原理就像一个魅力。
然后,我将代码移植到现有的应用程序中,然后出现问题。在控制器中,我有以下方法:
[PolicyAuthorize(Policy = "b2c_1_signin01")]
public ActionResult Index()
{
var vm = new IndexModel
{
FundsDocumentsModel = new FundsDocumentsModel { DocumentTypes = this.DocumentTypes_ReadDictionary() }
};
if (this.FundId != Guid.Empty)
{
var data = new FinanceDataProvider();
var fund = data.GetFundById(this.FundId);
if (fund != null)
{
this.ViewBag.LocalSubTitle = "for " + fund.Name;
}
}
return this.View("~/Areas/DataRoom/Views/Index.cshtml", vm);
}
PolicyAuthorize属性的代码与上述示例中的代码相同:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class PolicyAuthorize : AuthorizeAttribute
{
public string Policy { get; set; }
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties(
new Dictionary<string, string>
{
{ Constants.POLICY_KEY, this.Policy }
})
{
RedirectUri = "/",
}, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
在调试中访问Web应用程序时,我将自动进入控制器的Index方法。然后,进入属性的HandleUnauthorizedRequest并调用Challenge方法。
但是我没有重定向到B2C登录页面。相反,调试器将返回Index方法,就像我不需要身份验证一样。
现在,如果我转到/ Account / SignIn(与示例应用程序中的实现相同),则会重定向到B2C登录页面。
问题在于,在示例应用程序中,每当我使用PolicyAuthorize属性时,我都会被重定向到B2C登录页面。
因此,我不知道这种差异可能来自何处。你们有什么主意吗?
更新:
这里是更多代码,以显示所有内容均已移植。
Startup.Auth.cs:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
var options = new OpenIdConnectAuthenticationOptions
{
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ConfigurationHelper.Authentication.CLIENT_ID,
RedirectUri = ConfigurationHelper.Authentication.REDIRECT_URI,
PostLogoutRedirectUri = ConfigurationHelper.Authentication.REDIRECT_URI,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = this.AuthenticationFailed,
RedirectToIdentityProvider = this.OnRedirectToIdentityProvider
},
Scope = "openid",
ResponseType = "id_token",
// The PolicyConfigurationManager takes care of getting the correct Azure AD authentication
// endpoints from the OpenID Connect metadata endpoint. It is included in the PolicyAuthHelpers folder.
ConfigurationManager = new PolicyConfigurationManager(
string.Format(CultureInfo.InvariantCulture, ConfigurationHelper.Authentication.AAD_INSTANCE, ConfigurationHelper.Authentication.TENANT, "/v2.0", Constants.OIDC_METADATA_SUFFIX),
new[] { ConfigurationHelper.Authentication.SIGNUP_POLICY_ID, ConfigurationHelper.Authentication.SIGNIN_POLICY_ID, ConfigurationHelper.Authentication.PROFILE_POLICY_ID }),
// This piece is optional - it is used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
},
};
app.UseOpenIdConnectAuthentication(options);
}
/// <summary>
/// This notification can be used to manipulate the OIDC request before it is sent. Here we use it to send the correct policy.
/// </summary>
/// <param name="notification">The notification.</param>
private async Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
PolicyConfigurationManager mgr = notification.Options.ConfigurationManager as PolicyConfigurationManager;
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
OpenIdConnectConfiguration config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None, notification.OwinContext.Authentication.AuthenticationResponseRevoke.Properties.Dictionary[Constants.POLICY_KEY]);
notification.ProtocolMessage.IssuerAddress = config.EndSessionEndpoint;
}
else
{
OpenIdConnectConfiguration config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None, notification.OwinContext.Authentication.AuthenticationResponseChallenge.Properties.Dictionary[Constants.POLICY_KEY]);
notification.ProtocolMessage.IssuerAddress = config.AuthorizationEndpoint;
}
}
/// <summary>
/// Used for avoiding yellow-screen-of-death
/// </summary>
/// <param name="notification">The notification.</param>
private Task AuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
return Task.FromResult(0);
}
}
HttpDocumentRetriever.cs:
public class HttpDocumentRetriever : IDocumentRetriever
{
private readonly HttpClient _httpClient;
public HttpDocumentRetriever()
: this(new HttpClient())
{ }
public HttpDocumentRetriever(HttpClient httpClient)
{
Guard.AgainstNullArgument(nameof(httpClient), httpClient);
this._httpClient = httpClient;
}
public async Task<string> GetDocumentAsync(string address, CancellationToken cancel)
{
Guard.AgainstNullArgument(nameof(address), address);
try
{
HttpResponseMessage response = await this._httpClient.GetAsync(address, cancel).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
throw new IOException("Unable to get document from: " + address, ex);
}
}
}
PolicyConfigurationManager.cs:
// This class is a temporary workaround for AAD B2C,
// while our current libraries are unable to support B2C
// out of the box. For the original source code (with comments)
// visit https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/master/src/Microsoft.IdentityModel.Protocol.Extensions/Configuration/ConfigurationManager.cs
public class PolicyConfigurationManager : IConfigurationManager<OpenIdConnectConfiguration>
{
public static readonly TimeSpan DefaultAutomaticRefreshInterval = new TimeSpan(5, 0, 0, 0);
public static readonly TimeSpan DefaultRefreshInterval = new TimeSpan(0, 0, 0, 30);
public static readonly TimeSpan MinimumAutomaticRefreshInterval = new TimeSpan(0, 0, 5, 0);
public static readonly TimeSpan MinimumRefreshInterval = new TimeSpan(0, 0, 0, 1);
private const string policyParameter = "p";
private TimeSpan _automaticRefreshInterval = DefaultAutomaticRefreshInterval;
private TimeSpan _refreshInterval = DefaultRefreshInterval;
private Dictionary<string, DateTimeOffset> _syncAfter;
private Dictionary<string, DateTimeOffset> _lastRefresh;
private readonly SemaphoreSlim _refreshLock;
private readonly string _metadataAddress;
private readonly IDocumentRetriever _docRetriever;
private readonly OpenIdConnectConfigurationRetriever _configRetriever;
private Dictionary<string, OpenIdConnectConfiguration> _currentConfiguration;
public PolicyConfigurationManager(string metadataAddress, string[] policies)
: this(metadataAddress, policies, new HttpDocumentRetriever())
{
}
public PolicyConfigurationManager(string metadataAddress, string[] policies, IDocumentRetriever docRetriever)
{
if (string.IsNullOrWhiteSpace(metadataAddress))
{
throw new ArgumentNullException("metadataAddress");
}
if (docRetriever == null)
{
throw new ArgumentNullException("retriever");
}
_metadataAddress = metadataAddress;
_docRetriever = docRetriever;
_configRetriever = new OpenIdConnectConfigurationRetriever();
_refreshLock = new SemaphoreSlim(1);
_syncAfter = new Dictionary<string, DateTimeOffset>();
_lastRefresh = new Dictionary<string, DateTimeOffset>();
_currentConfiguration = new Dictionary<string, OpenIdConnectConfiguration>();
foreach (string policy in policies)
{
_currentConfiguration.Add(policy, null);
}
}
public TimeSpan AutomaticRefreshInterval
{
get { return _automaticRefreshInterval; }
set
{
if (value < MinimumAutomaticRefreshInterval)
{
throw new ArgumentOutOfRangeException("value", value, string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10107, MinimumAutomaticRefreshInterval, value));
}
_automaticRefreshInterval = value;
}
}
public TimeSpan RefreshInterval
{
get { return _refreshInterval; }
set
{
if (value < MinimumRefreshInterval)
{
throw new ArgumentOutOfRangeException("value", value, string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10106, MinimumRefreshInterval, value));
}
_refreshInterval = value;
}
}
// Takes the ohter and copies it to source, preserving the source's multi-valued attributes as a running sum.
private OpenIdConnectConfiguration MergeConfig(OpenIdConnectConfiguration source, OpenIdConnectConfiguration other)
{
ICollection<SecurityToken> existingSigningTokens = source.SigningTokens;
ICollection<string> existingAlgs = source.IdTokenSigningAlgValuesSupported;
ICollection<SecurityKey> existingSigningKeys = source.SigningKeys;
foreach (SecurityToken token in existingSigningTokens)
{
other.SigningTokens.Add(token);
}
foreach (string alg in existingAlgs)
{
other.IdTokenSigningAlgValuesSupported.Add(alg);
}
foreach (SecurityKey key in existingSigningKeys)
{
other.SigningKeys.Add(key);
}
return other;
}
// This non-policy specific method effectively gets the metadata for all policies specified in the constructor,
// and merges their signing key metadata. It selects the other metadata from one of the policies at random.
// This is done so that the middleware can take an incoming id_token and validate it against all signing keys
// for the app, selecting the appropriate signing key based on the key identifiers.
public async Task<OpenIdConnectConfiguration> GetConfigurationAsync(CancellationToken cancel)
{
OpenIdConnectConfiguration configUnion = new OpenIdConnectConfiguration();
Dictionary<string, OpenIdConnectConfiguration> clone = new Dictionary<string, OpenIdConnectConfiguration>(_currentConfiguration);
foreach (KeyValuePair<string, OpenIdConnectConfiguration> entry in clone)
{
OpenIdConnectConfiguration config = await GetConfigurationByPolicyAsync(cancel, entry.Key);
configUnion = MergeConfig(configUnion, config);
}
return configUnion;
}
public async Task<OpenIdConnectConfiguration> GetConfigurationByPolicyAsync(CancellationToken cancel, string policyId)
{
DateTimeOffset now = DateTimeOffset.UtcNow;
DateTimeOffset sync;
if (!_syncAfter.TryGetValue(policyId, out sync))
{
sync = DateTimeOffset.MinValue;
}
OpenIdConnectConfiguration config;
if (!_currentConfiguration.TryGetValue(policyId, out config))
{
config = null;
}
if (config != null && sync > now)
{
return config;
}
await _refreshLock.WaitAsync(cancel);
try
{
Exception retrieveEx = null;
if (sync <= now)
{
try
{
// We're assuming the metadata address provided in the constructor does not contain qp's
config = await OpenIdConnectConfigurationRetriever.GetAsync(String.Format(_metadataAddress + "?{0}={1}", policyParameter, policyId), _docRetriever, cancel);
_currentConfiguration[policyId] = config;
Contract.Assert(_currentConfiguration[policyId] != null);
_lastRefresh[policyId] = now;
_syncAfter[policyId] = now.UtcDateTime.Add(_automaticRefreshInterval);
}
catch (Exception ex)
{
retrieveEx = ex;
_syncAfter[policyId] = now.UtcDateTime.Add(_automaticRefreshInterval < _refreshInterval ? _automaticRefreshInterval : _refreshInterval);
}
}
if (config == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10803, _metadataAddress ?? "null"), retrieveEx);
}
return config;
}
finally
{
_refreshLock.Release();
}
}
public void RequestRefresh(string policyId)
{
DateTimeOffset now = DateTimeOffset.UtcNow;
DateTimeOffset refresh;
if (!_lastRefresh.TryGetValue(policyId, out refresh) || now >= _lastRefresh[policyId].UtcDateTime.Add(RefreshInterval))
{
_syncAfter[policyId] = now;
}
}
public void RequestRefresh()
{
foreach (KeyValuePair<string, OpenIdConnectConfiguration> entry in _currentConfiguration)
{
RequestRefresh(entry.Key);
}
}
}
Global.asax.cs:
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
Web.config:
<?xml version="1.0" encoding="utf-8"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=301880
-->
<configuration>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<!-- Azure AD B2C -->
<add key="ida:Tenant" value="xxx" />
<add key="ida:ClientId" value="xxx" />
<add key="ida:ClientSecret" value="xxx"/>
<add key="ida:AadInstance" value="https://login.microsoftonline.com/{0}{1}{2}" />
<add key="ida:RedirectUri" value="https://localhost:44300/" />
<add key="ida:PostLogoutRedirectUri" value="https://localhost:44300/" />
<add key="ida:SignUpPolicyId" value="b2c_1_signup01" />
<add key="ida:SignInPolicyId" value="b2c_1_signin01" />
<add key="ida:UserProfilePolicyId" value="b2c_1_profile01" />
<!-- /Azure AD B2C -->
<add key="appinsights:instrumentationKey" value="xxx" />
</appSettings>
<system.web>
<customErrors mode="Off" />
<compilation debug="true" targetFramework="4.6.1" />
<httpRuntime targetFramework="4.6.1" maxRequestLength="1048576" />
<pages>
<namespaces>
<add namespace="Kendo.Mvc.UI" />
</namespaces>
</pages>
<httpModules>
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" />
</httpModules>
</system.web>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="1073741824" />
</requestFiltering>
</security>
<staticContent>
<remove fileExtension=".json" />
<mimeMap fileExtension=".json" mimeType="application/json" />
</staticContent>
<rewrite>
<rules>
<!-- Enfore HTTPS -->
<rule name="Force HTTPS" enabled="true">
<match url="(.*)" ignoreCase="false" />
<conditions>
<add input="{HTTPS}" pattern="off" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" appendQueryString="true" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
<validation validateIntegratedModeConfiguration="false" />
<modules>
<remove name="ApplicationInsightsWebTracking" />
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler" />
</modules>
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-5.1.0.0" newVersion="5.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Antlr3.Runtime" publicKeyToken="eb42632606e9261f" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.5.0.2" newVersion="3.5.0.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.ApplicationInsights" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.0.4220" newVersion="1.0.0.4220" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.20622.1351" newVersion="4.0.20622.1351" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Protocol.Extensions" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.2.33" newVersion="1.0.2.33" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.AI.Agent.Intercept" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.1.0" newVersion="1.2.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.Services.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.7.0.0" newVersion="5.7.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.OData" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.7.0.0" newVersion="5.7.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.Edm" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.7.0.0" newVersion="5.7.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+" />
</compilers>
</system.codedom>
</configuration>
现在,对于PolicyAuthorize我有一个“肮脏”的解决方法:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (this.Policy.Equals(ConfigurationHelper.Authentication.SIGNIN_POLICY_ID, StringComparison.InvariantCultureIgnoreCase))
{
filterContext.HttpContext.Response.Redirect("/Account/SignIn", true);
}
else if (this.Policy.Equals(ConfigurationHelper.Authentication.SIGNUP_POLICY_ID, StringComparison.InvariantCultureIgnoreCase))
{
filterContext.HttpContext.Response.Redirect("/Account/SignUp", true);
}
else
{
throw new NotSupportedException($"Policy ID {this.Policy} is not supported.");
}
}
但这不是完美的,调试器仍然输入我的Controller方法,但是随后我被重定向到登录页面。因此,即使不是很完美,我现在也正在使用它。
最佳答案
检查您的重定向uri在配置中是否正确。
您可以添加AuthorizationCodeRecieved
事件处理程序,以查看挑战后返回的内容。
就像示例中一样-即ConfigureAuth()
:
B2C quickstart Web-api-dotnet - with AuthorizationCodeRecieved eventHandler
关于c# - Azure AD B2C-身份验证质询未触发身份验证,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34907859/