如何解决在没有身份的情况下使用Azure SSO实施将用户过早重定向到登录页面
我有一个带有Azure SSO身份验证实现的ASP.NET MVC Core 2.2应用程序。 Azure SSO是使用快速入门教程为可在Azure门户中访问的已注册应用程序设置的。另外,我通过在数据库中手动查询角色列表并将声明添加到Principal.Identity
中来实现授权。我还需要一个自定义的Authorize
属性,因为在AJAX调用的控制器操作上很难使用它。
我的团队遇到的一个问题正在尽早重定向到登录页面。当CustomAuthorize
为false时,重定向本身可能会在HttpContext.User.Identity.IsAuthenticated
过滤器内发生,因为它重定向到登录页面。在30分钟左右左右未激活应用程序后,用户的请求将被重定向。正如您在下面的代码中看到的那样,将cookie的过期时间设置为true,cookie的过期时间为120分钟。
请注意,我们只是被重定向到登录页面,就像HttpContext.User.Identity.IsAuthenticated
变成了false
一样。我们没有退出我们的Microsoft帐户。当我们单击以重新登录时,我们将自动进行重新身份验证并带到应用程序,而无需输入我们的凭据。
最大的困难是,此问题似乎仅在生产服务器上发生。我们可以在登台上使用相同的代码,并且在登台上可以正常使用,但问题仍然存在。这表明服务器配置可能有问题,但是我想确保它不是代码。
我可以确认本地cookie的到期时间以及在登台服务器和本地版本上的滑动到期都可以正常工作。
我发现了几篇关于人的问题非常相似的帖子,这是由于Identity的securityStamp
验证存在错误所致:post1,post2,post3。现在,我不太确定,但是我知道securityStamp
的问题仅在您通过添加AddIdentity()
服务使用身份的情况下才会发生。我的代码无法做到这一点,如下所示。
Startup.cs> ConfigureServices方法:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection Services)
{
//https://weblog.west-wind.com/posts/2017/dec/12/easy-configuration-binding-in-aspnet-core-revisited
IAppSettings config = new AppSettings();
Configuration.Bind("AppSettings",config);
Services.AddMvc();
Services.AddSingleton(config);
Services.AddScoped<IUserAccess,UserAccess>();
//..more services..
Services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
Services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options =>
{
options.Instance = config.AzureAD.Instance;
options.Domain = config.AzureAD.Domain;
options.TenantId = config.AzureAD.TenantId;
options.ClientId = config.AzureAD.ClientId;
options.CallbackPath = config.AzureAD.CallbackPath;
});
Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme,options =>
{
options.UsetokenLifetime = false;
options.Authority = options.Authority + "/v2.0/"; //Microsoft identity platform
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.Validissuers = config.AzureAD.Organizations;
//Code below is to add claims during login (we add roles from db): https://stackoverflow.com/questions/51965665/adding-custom-claims-to-claimsprincipal-when-using-addazureadb2c-in-mvc-core-app
//some discussion about this here: https://stackoverflow.com/questions/59564952/net-core-add-claim-after-azuerad-authentication
//and here: https://stackoverflow.com/questions/52727146/net-core-2-openid-connect-authentication-and-multiple-identities
options.Events.OnTicketReceived = context =>
{
string ObjectID = context.Principal.FindFirst(CustomClaimTypes.ObjectIdentifier).Value;
//I get an instance of UserAccess service so I can get user ID,termination status,roles.
IUserAccess userAccess = context.HttpContext.RequestServices.GetService<IUserAccess>();
int userID = userAccess.GetUserIDByObjectID(ObjectID,config.ConnectionString);
//if UserID is 0 (wasn't found in Users table),then that means user is not activated or not in Users table. So
//we don't try to get their roles because we can't.
ClaimsIdentity claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
if (userID == 0)
{
//this claim is checked in CustomAuthorize authorize filter
claimsIdentity.AddClaim(new Claim(CustomClaimTypes.UserNotFound,true.ToString()));
return Task.CompletedTask;
}
claimsIdentity.AddClaim(new Claim(CustomClaimTypes.UserID,userID.ToString()));
bool terminated = userAccess.IsUserTerminatedByUserID(userID,config.ConnectionString);
if (terminated == false)
{
List<string> roles = userAccess.GetUserRoleNamesByUserID(userID,config.ConnectionString);
foreach (var role in roles)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role,role));
}
}
//In the region below,we sign in user manually instead of allowing the RemoteAuthenticationHandler
//complete the sign in. We do this because the 'OnTicketReceived' event runs right before the user
//is signed into the application and we want to make sure that there is no error when we sign
//in the user before we log a message that they have signed in.
//Explanation of what's happening here: https://www.jerriepelser.com/blog/managing-session-lifetime-aspnet-core-oauth-providers/
#region User sign-in code
// Sign the user in ourselves
context.HttpContext.SignInAsync(context.Options.SignInScheme,context.Principal,context.Properties);
// Indicate that we handled the sign in
context.HandleResponse();
// Default redirect path is the base path
if (string.IsNullOrEmpty(context.ReturnUri))
{
context.ReturnUri = "/";
}
context.Response.Redirect(context.ReturnUri);
#endregion User sign-in code
_logger.LogTrace("User {ObjectID} logged in",context.Principal.Identity.Name);
return Task.CompletedTask;
};
});
Services.Configure<CookieAuthenticationoptions>(AzureADDefaults.CookieScheme,options =>
{
options.AccessDeniedpath = "/UserAccess/NotAuthorized";
options.logoutPath = "/UserAccess/SignOut";
options.ExpireTimeSpan = TimeSpan.FromMinutes(120);
options.SlidingExpiration = true;
});
}
CustomAuthorize.cs属性
public class CustomAuthorize : AuthorizeAttribute,IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
string signInUrl = "/UserAccess/Index";
if (context.HttpContext.User.Identity.IsAuthenticated)
{
if (context.HttpContext.User.HasClaim(CustomClaimTypes.UserNotFound,true.ToString()))
{
context.Result = new RedirectResult("/UserAccess/NotAuthorized");
}
}
else
{
if (context.HttpContext.Request.IsAjaxRequest())
{
context.HttpContext.Response.StatusCode = 401;
JsonResult jsonResult = new JsonResult(new { redirectUrl = signInUrl });
context.Result = jsonResult;
}
else
{
context.Result = new RedirectResult(signInUrl);
}
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。