You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

服务器重启后IdentityServer4客户端token失效检测及新令牌申请方法

核心问题原因

服务端重启后token失效是默认配置下IdentityServer与ASP.NET Core Data Protection的密钥均存储在内存中导致的,服务重启密钥丢失后,所有已签发的JWT、会话凭证都会被判定为无效。未重启时自动静默刷新逻辑正常运行,所以次日访问、新开标签页无需重新登录。

优先推荐根本解决方案

通过持久化密钥避免服务重启导致的token批量失效,无需修改前端逻辑即可解决问题:

// Startup.cs 配置Data Protection密钥持久化
services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"自定义密钥存储目录路径"))
    .SetApplicationName("当前应用唯一标识");

// IdentityServer配置持久化签名密钥,生产环境建议使用正式证书
services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, AppDbContext>()
    // 开发环境可使用.AddDeveloperSigningCredential(true) 将自动生成的签名密钥存储到本地文件
    .AddSigningCredential(new X509Certificate2("证书文件路径", "证书访问密码"));

客户端token失效检测与主动刷新方案

如果需要实现需求指定的客户端检测、主动刷新能力,可按以下步骤实现:

a) 检测客户端token已失效

可通过两种方式组合检测:

  • 本地预校验:发送API请求前读取UserManager存储的用户对象的expires_at字段,对比当前时间判断token是否已经过期。
  • 响应拦截校验:给Angular添加全局HTTP拦截器,识别API请求返回的401状态码判定token失效(当前无报错是因为默认配置下服务端会把未认证的API请求重定向到登录页,返回200状态的HTML内容,前端无法识别异常,可通过下方服务端配置修正返回状态):
// Startup.cs 配置API请求未认证时返回401而非重定向
services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = context =>
    {
        if (context.Request.Path.StartsWithSegments("/api") && context.Response.StatusCode == StatusCodes.Status200OK)
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return Task.CompletedTask;
        }
        context.Response.Redirect(context.RedirectUri);
        return Task.CompletedTask;
    };
});

b) 主动向服务端请求新token

你已经开启了automaticSilentRenew配置,默认会在token过期前自动触发静默刷新,服务端重启导致的token提前失效可通过手动调用signinSilent方法实现刷新:

  1. 在authorize.service.ts中添加刷新token方法:
import { from, Observable } from 'rxjs';
import { User } from 'oidc-client';

// ...原有逻辑
public refreshToken(): Observable<User> {
  return from(this.userManager.signinSilent());
}
  1. 全局HTTP拦截器中集成自动刷新逻辑,示例代码:
import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { catchError, switchMap, throwError } from 'rxjs';
import { AuthorizeService } from './authorize.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthorizeService) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 401) {
          // 触发token静默刷新
          return this.authService.refreshToken().pipe(
            switchMap((newUser) => {
              // 携带新token重发原请求
              const clonedRequest = request.clone({
                setHeaders: {
                  Authorization: `Bearer ${newUser.access_token}`
                }
              });
              return next.handle(clonedRequest);
            }),
            catchError((refreshError) => {
              // 刷新失败跳转登录页
              this.authService.login();
              return throwError(() => refreshError);
            })
          );
        }
        return throwError(() => error);
      })
    );
  }
}
  1. 在Angular的模块中注册拦截器:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';

@NgModule({
  // ...其他配置
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  ]
})
export class AppModule { }

内容的提问来源于stack exchange,提问作者Boppity Bop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 23:36:02