[英]Cookie expiry in ASP NET Core Authentication using Azure AD OpenIdConnect and custom middleware
[英]How do I add a custom claim to authentication cookie generated by OpenIdConnect middleware in ASP.Net Core after authentication?
我有一个使用IdentityServer4混合身份验证流的ASP.Net Core应用程序项目。 设置如下,
public void ConfigureServices(IServiceCollection 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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = IdentityServerUrl;
options.RequireHttpsMetadata = false;
options.ClientId = ClientId;
options.ClientSecret = ClientSecret;
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("offline_access");
options.Scope.Add("ApiAuthorizedBasedOnIdentity");
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters.NameClaimType = JwtClaimTypes.Name;
options.TokenValidationParameters.RoleClaimType = JwtClaimTypes.Role;
});
//Setup Tenant Role based authorization
services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
services.AddProxy();
}
我能够进行身份验证,并且SaveTokens = true可以成功将访问令牌保存在ASP.Net身份验证cookie中。 现在,我需要从ASP.Net Core客户端项目中的Controller Action( 不是通过中间件 )中向同一身份验证cookie添加自定义声明。 例如,假设HomeController的Index操作。
我还需要此声明保留在身份验证cookie中,以便它可以在请求和控制器操作之间保留。
我进行了一些挖掘,发现可以使用ASP.Net Identity进行此操作
if (User.Identity.IsAuthenticated)
{
var claimsIdentity = ((ClaimsIdentity)User.Identity);
if (!claimsIdentity.HasClaim(c => c.Type == "your-claim"))
{
((ClaimsIdentity)User.Identity).AddClaim(new Claim("your-claim", "your-value"));
var appUser = await userManager.GetUserAsync(User).ConfigureAwait(false);
await signInManager.RefreshSignInAsync(appUser).ConfigureAwait(false);
}
}
身份验证由IdentityServer使用该项目中设置的ASP.Net Identity完成。 但是,要在客户端项目中使用SignInManager,UserManager等,我需要将ASP.Net Identity引入其中。 设置ASP.Net身份并将其存储在客户端项目中,只是为了通过附加声明更新身份验证cookie似乎有些过头。 还有其他方法吗?
您当然不需要在客户端项目中包括ASP.NET Core Identity,但是您可以将其用于启发如何实现所需的功能。 让我们先来看一下RefreshSignInAsync
的实现:
public virtual async Task RefreshSignInAsync(TUser user)
{
var auth = await Context.AuthenticateAsync(IdentityConstants.ApplicationScheme);
var authenticationMethod = auth?.Principal?.FindFirstValue(ClaimTypes.AuthenticationMethod);
await SignInAsync(user, auth?.Properties, authenticationMethod);
}
从上面可以看出,这也调用SignInAsync
,如下所示:
public virtual async Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod = null)
{
var userPrincipal = await CreateUserPrincipalAsync(user);
// Review: should we guard against CreateUserPrincipal returning null?
if (authenticationMethod != null)
{
userPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod));
}
await Context.SignInAsync(IdentityConstants.ApplicationScheme,
userPrincipal,
authenticationProperties ?? new AuthenticationProperties());
}
我们最感兴趣的两个电话是:
Context.AuthenticateAsync
,它创建一个AuthenticateResult
,其中包含从cookie读取的ClaimsPrincipal
和AuthenticationProperties
。 Context.SignInAsync
,最终使用ClaimsPrincipal
和关联的AuthenticationProperties
重写cookie。 ASP.NET Core Identity创建一个全新的ClaimsPrincipal
,通常从数据库中获取它,以“刷新”它。 您不需要这样做,因为您只是想将现有的 ClaimsPrincipal
与其他索赔一起使用。 这是满足您要求的完整解决方案:
var authenticateResult = await HttpContext.AuthenticateAsync();
if (authenticateResult.Succeeded)
{
var claimsIdentity = (ClaimsIdentity)authenticateResult.Principal.Identity;
if (!claimsIdentity.HasClaim(c => c.Type == "your-claim"))
{
claimsIdentity.AddClaim(new Claim("your-claim", "your-value"));
await HttpContext.SignInAsync(authenticateResult.Principal, authenticateResult.Properties);
}
}
HttpContext.AuthenticateAsync
的调用将使用您已经在配置中设置的默认方案( "Cookies"
)来访问ClaimsPrincipal
和AuthenticationProperties
。 之后,只是添加新声明并执行对HttpContext.SignInAsync
的调用的一种情况,该调用还将使用默认方案( "Cookies"
)。
如果您的项目中没有UserStore,而我只是在使用UserManager,则可以通过继承UserManager并重写GetClaimsAsync方法轻松完成此操作
public override Task<IList<Claim>> GetClaimsAsync(T user)
{
// here you can return a list of claims and it will be in the cookie
return base.GetClaimsAsync(user);
}
顺便说一下,base.GetClaimsAsync(user)的默认实现; 就是调用相关的UserStore。 因此,如果您没有UserManager的UserStore,则可以将其删除。
选项1
您将需要使用HttpContext.SignInAsync
方法。 另外,一旦您向用户添加了其他声明,就需要使用新的ClaimsPrincipal
更新HttpContext.User
。 请参见下面的代码:
var identity = (ClaimsIdentity)User.Identity;
identity.AddClaim(new Claim("your-claim", "your-value"));
// genereate the new ClaimsPrincipal
var claimsPrincipal = new ClaimsPrincipal(identity);
// store the original tokens in the AuthenticationProperties
var props = new AuthenticationProperties();
// get the current tokens
var accessToken = await HttpContext.GetTokenAsync("access_token");
var refreshToken = await HttpContext.GetTokenAsync("refresh_token");
// create the enumerable list
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = "access_token", Value = accessToken},
new AuthenticationToken {Name = "refresh_token", Value = refreshToken}
};
//store the tokens
props.StoreTokens(tokens);
// update the thread's current principal as it is changed, otherwise
// System.Security.Claims.ClaimsPrincipal.Current is referring to the
// ClaimsPrincipal created from the cookie on the initial request. This is required
// so that the next instance of HttpContext will be injected with the updated claims
HttpContext.User = claimsPrincipal;
Thread.CurrentPrincipal = claimsPrincipal;
// sign in using the built-in Authentication Manager and ClaimsPrincipal
// this will create a cookie as defined in CookieAuthentication middleware
await HttpContext.SignInAsync("your scheme", claimsPrincipal, props);
请确保用您使用的方案名称替换“您的方案”。 希望对您有所帮助。
选项2
@Ruard van Elburg正确指出,上述解决方案将覆盖访问令牌。 ( 已更新以允许存储原始令牌 )
如果您是在用户OnTokenValidated
后立即添加声明,则可以使用OnTokenValidated
事件
.AddOpenIdConnect("oidc", options =>
{
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = async ctx =>
{
var claim = new Claim("your-claim", "your-value");
var identity = new ClaimsIdentity(new[] { claim });
ctx.Principal.AddIdentity(identity);
await Task.CompletedTask;
}
};
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.