AccessToken过期未自动登出问题:ASP.NET Core+IdentityServer4+Angular10
问题背景
我手头有个基于ASP.NET Core + IdentityServer4做认证授权的项目,前端用Angular 10搭配oidc-client实现登录逻辑。现在遇到个头疼的问题:明明把AccessToken的有效期设成了5分钟,但用户并不会在令牌过期后自动登出,反而系统会自动静默刷新令牌,让用户一直保持登录状态。目前的配置是AccessTokenLifetime = 5分钟,IdentityServer的CookieSlidingTime = 10分钟,相关代码如下:
前端oidc-client配置
const idServerSettings = { authority: Constants.stsAuthority, client_id: Constants.clientId, scope: 'openid profile', response_type: 'code', redirect_uri: `${Constants.clientRoot}signin-callback`, post_logout_redirect_uri: `${Constants.clientRoot}signout-callback`, store: new WebStorageStateStore({ store: localStorage }), automaticSilentRenew: true, loadUserInfo: true };
IdentityServer客户端配置
new Client { ClientName="test", ClientId="client-spa", AllowedGrantTypes = GrantTypes.Code, AlwaysIncludeUserClaimsInIdToken = true, RedirectUris = new List<string>() { "https://localhost:44383/signin-callback" }, PostLogoutRedirectUris = {"https://localhost:44383/signout-callback" }, AllowedCorsOrigins = { "https://localhost:44383" }, AccessTokenLifetime = 60*5, // 5分钟 AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "propel-api" }, RequireClientSecret=false } var builder = services.AddIdentityServer(options => { options.Events.RaiseErrorEvents = true; options.Events.RaiseInformationEvents = true; options.Events.RaiseFailureEvents = true; options.Events.RaiseSuccessEvents = true; options.UserInteraction.LoginUrl = "/Account/Login"; options.UserInteraction.LogoutUrl = "/Account/Logout"; options.Authentication = new AuthenticationOptions() { CookieLifetime = TimeSpan.FromMinutes(10), CookieSlidingExpiration = true, }; }
问题根源
其实这个现象是符合预期的——因为你开启了oidc-client的automaticSilentRenew: true,这个配置会让前端自动检测AccessToken的过期时间,在快过期时悄悄用Refresh Token去IdentityServer申请新的AccessToken。而IdentityServer这边的Cookie设置了10分钟滑动过期,只要用户在10分钟内有交互(包括静默刷新的请求),Cookie就会自动续期,所以静默刷新请求会一直成功,用户自然不会被登出。
解决方案
根据你的需求(希望AccessToken过期后用户登出),可以从以下几个方向调整:
1. 关闭自动静默刷新,或监听过期事件手动登出
如果你不想让系统自动刷新令牌,直接把automaticSilentRenew设为false,这样AccessToken过期后,用户下次发起API请求时会因为令牌无效被拒绝,不过这种方式体验不太友好。
更优雅的方式是保留自动刷新,但监听AccessToken过期事件,一旦过期就触发登出:
const userManager = new UserManager(idServerSettings); // 监听AccessToken过期事件 userManager.events.addAccessTokenExpired(() => { // 触发登出跳转 userManager.signoutRedirect().catch(err => console.error('登出失败:', err)); });
2. 限制Refresh Token的有效期
默认情况下,IdentityServer的Refresh Token有效期很长(30天),而且是滑动过期的。你可以在Client配置里缩短Refresh Token的有效期,或者改成一次性令牌,让它没法反复刷新:
new Client { // ...其他配置 AccessTokenLifetime = 60*5, // 设置Refresh Token有效期和AccessToken一致(5分钟) RefreshTokenLifetime = 60*5, // 设为一次性令牌,刷新后旧令牌失效 RefreshTokenUsage = TokenUsage.OneTimeOnly, // 绝对过期,不启用滑动刷新 RefreshTokenExpiration = TokenExpiration.Absolute }
这样5分钟后,Refresh Token也过期了,静默刷新会失败,前端就能触发登出。
3. 调整IdentityServer的Cookie有效期
你现在的CookieLifetime是10分钟,滑动过期,这意味着用户只要10分钟内有操作,Cookie就会续期。如果希望用户在AccessToken过期后立即登出,可以把Cookie的有效期和AccessToken对齐,或者关闭滑动过期:
options.Authentication = new AuthenticationOptions() { // 把Cookie有效期设为5分钟,和AccessToken一致 CookieLifetime = TimeSpan.FromMinutes(5), // 关闭滑动过期,到点就失效 CookieSlidingExpiration = false, };
不过这种方式会让用户5分钟不操作就被踢,需要权衡用户体验和安全需求。
内容的提问来源于stack exchange,提问作者Sarahbe

