tha*_*guy 5 authentication asp.net-mvc oauth-2.0 asp.net-mvc-5 azure-active-directory
背景故事
我有一个现有的 ASP.NET MVC5 应用程序,我正在连接它以使用 oauth2/Azure AD 身份验证。实际上我已经在多个环境中工作了(dev/qa/prod/localhost)。
在 Azure 中,我有一个具有多个返回 URI 的应用程序注册,示例:
我的部署管道设置了一个配置变量来更改 oauth 代码的 ReturnURI,并且一切正常。
以下是代码的设置方式。我的内部控制器有一个 Authorize 属性,当用户未经身份验证时,会将其重定向到/Account/Index进行 oauth 质询的位置。以下是我提出 oauth 挑战的方法:
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties
{
RedirectUri = Url.Action("ValidateOAuth", "Account", new { returnUrl })
},
OpenIdConnectAuthenticationDefaults.AuthenticationType);
Run Code Online (Sandbox Code Playgroud)
用户将被重定向到 Microsoft 进行登录,然后返回到/Account/ValidateOAuth. 在该操作中,我检查Request.IsAuthenticated,如果是真的,我会构建一个本地会话变量来获取有关用户的额外信息,并将它们推送到内部页面。
问题
我在特定场景中遇到了问题。在我的 QA 环境中,我托管了两个网站副本。一个位于基本 URI ( https://mywebsite.qa.com),另一个作为 IIS 中的子应用程序位于https://mywebsite.qa.com/staging。这样我们的测试人员就可以将分支部署到 /staging 站点并测试它们,而不会干扰我们通常的 QA 用户。
当用户访问临时站点时,他们会被重定向到我的/staging/Account/Index操作,而质询会将他们重定向到 Azure AD 登录站点。在那里,一旦他们进行了身份验证,他们就会被重定向回我的网站。然而,它们没有按/staging/Account/ValidateOAuth预期重定向。相反,它们被重定向到/. 这导致他们经历与主 QA 站点相同的身份验证周期。
通过设置 IIS Express 在本地运行时,我可以重现此情况,将站点托管http://localhost:43000/staging在http://localhost:43000. 我看到它会重定向到的完全相同的行为,/并且收到错误,因为我没有本地托管的网站。
这是我的 Startup.cs 和 oauth 配置:
public class Startup
{
// The Client ID is used by the application to uniquely identify itself to Azure AD.
string clientId = ConfigurationManager.AppSettings["aad:ClientId"];
string clientSecret = ConfigurationManager.AppSettings["aad:ClientSecret"];
// RedirectUri is the URL where the user will be redirected to after they sign in.
string redirectUri = ConfigurationManager.AppSettings["aad:RedirectUri"];
// Tenant is the tenant ID (e.g. contoso.onmicrosoft.com, or 'common' for multi-tenant)
static string tenant = ConfigurationManager.AppSettings["aad:Tenant"];
// Authority is the URL for authority, composed by Microsoft identity platform endpoint and the tenant name (e.g. https://login.microsoftonline.com/contoso.onmicrosoft.com/v2.0)
string authority = string.Format(CultureInfo.InvariantCulture, ConfigurationManager.AppSettings["aad:Authority"], tenant);
static string graphScopes = ConfigurationManager.AppSettings["aad:GraphScopes"];
/// <summary>
/// Configure OWIN to use OpenIdConnect
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieManager = new SystemWebCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
notification.Response.Redirect("/Errors/Error?message=" + notification?.Exception?.Message);
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
{
var secret = clientSecret;
if (string.IsNullOrEmpty(secret))
{
string filePath = HostingEnvironment.MapPath(@"~/local.aadclientsecret.txt");
if (!File.Exists(filePath))
{
throw new FileNotFoundException("AAD Client Secret not found in Web.Config and local.aadclientsecret.txt not found on server!");
}
secret = File.ReadAllText(filePath);
if (string.IsNullOrEmpty(secret))
{
throw new SettingsPropertyNotFoundException("AAD Client not found in Web.Config and local.aadclientsecret.txt was empty!");
}
}
var idClient = ConfidentialClientApplicationBuilder.Create(clientId)
.WithTenantId(tenant)
.WithRedirectUri(redirectUri)
.WithClientSecret(secret)
.Build();
try
{
string[] scopes = graphScopes.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(
scopes, notification.Code).ExecuteAsync();
var userDetails = await GraphHelper.GetUserDetailsAsync(result.AccessToken);
string alias = "";
object aliasObj = null;
if (userDetails.AdditionalData.TryGetValue(GraphHelper.Attributes.Alias, out aliasObj))
{
alias = aliasObj.ToString();
}
// Create a new identity and copy all the claims.
// Add in extra claims that are needed.
var id = new ClaimsIdentity(notification.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(notification.AuthenticationTicket.Identity.Claims);
id.AddClaim(new Claim("alias", alias));
notification.AuthenticationTicket = new AuthenticationTicket
(
new ClaimsIdentity(id.Claims, notification.AuthenticationTicket.Identity.AuthenticationType),
notification.AuthenticationTicket.Properties
);
}
catch (MsalException ex)
{
string message = "AcquireTokenByAuthorizationCodeAsync threw an exception";
notification.HandleResponse();
notification.Response.Redirect($"/Errors/Error?message={message}&&debug={ex.Message}");
}
catch (Microsoft.Graph.ServiceException ex)
{
string message = "GetUserDetailsAsync threw an exception";
notification.HandleResponse();
notification.Response.Redirect($"/Errors/Error?message={message}&debug={ex.Message}");
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果我在通知中放置断点,则两者都不会被命中(如预期),因为来自 Azure 的重定向甚至不会将我返回到托管于 的网站/staging。
这是我的配置值(省略敏感值):
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties
{
RedirectUri = Url.Action("ValidateOAuth", "Account", new { returnUrl })
},
OpenIdConnectAuthenticationDefaults.AuthenticationType);
Run Code Online (Sandbox Code Playgroud)
尝试的解决方案
如果我将新的重定向 URI 添加到 Azure 应用程序注册以指向http://localhost:44300/staging,它将成功地将我返回到 /staging 子目录中的本地托管站点,但Request.IsAuthenticated始终为 false,并且我陷入无限重定向循环中。我被发送到 Azure 进行登录,它会自动将我重定向回我的站点,然后将我重定向回 Azure 登录页面,永远重复。
我也尝试在我的实时 QA 环境中执行此操作。我在 Azure 中添加了一个要访问的重定向 URI https://mywebsite.qa.com/staging,然后将我的配置中的 RedirectUri 更改为相同的。当我/staging现在访问该网站时,它具有相同的行为。我被重定向到 Azure 进行登录,然后返回到/而不是/staging/Account/ValidateOAuth.
帮助!
我花了一天的时间进行搜索和霰弹枪调试,但不知道是什么原因造成的。我究竟做错了什么?
这就是我最终所做的。
我的redirectUri 指向我的域的根目录https://mywebsite.qa.com。
在 Azure 中,我有多个重定向 URI,包括https://mywebsite.qa.com和https://mywebsite.qa.com/staging。
然后,在我处理登录的帐户控制器中,我有一段代码可以将用户重定向到 Azure 进行登录:
if (!Request.IsAuthenticated || forceLogin)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties
{
RedirectUri = Url.Action("ValidateOAuth", "Account", new { returnUrl })
},
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
else
{
HttpContext.Response.Redirect(Url.Action("ValidateOAuth", "Account"));
}
Run Code Online (Sandbox Code Playgroud)
我不确定我之前所做的事情和现在所做的事情有什么不同,因为距离我遇到这个问题已经过去了 3 个月。
仅供参考,在 ValidateOAuth 操作中,我只检查 IsAuthenticated,然后在将用户转发到站点内部之前执行一些其他维护操作(例如在数据库中查找用户等)。
| 归档时间: |
|
| 查看次数: |
1554 次 |
| 最近记录: |