.Net Identity Bearer认证:移动端与Web端令牌过期时间差异化配置问题
实现不同客户端的Bearer令牌差异化过期时间
你遇到的问题核心在于BearerTokenOptions是全局绑定到单个认证Scheme的配置,运行时修改无法影响令牌生成逻辑(因为生成逻辑在启动阶段就已绑定配置)。以下是两种可行的解决方案:
方案一:创建独立的Bearer认证Scheme
为Web端和移动端分别注册不同的Bearer Scheme,各自配置过期时间,登录API对应使用不同Scheme生成令牌。
1. 注册多Scheme配置
// Web端Bearer配置(1天过期) builder.Services.AddOptions<BearerTokenOptions>("WebBearer") .Configure(options => { options.BearerTokenExpiration = TimeSpan.FromDays(1); }); // 移动端Bearer配置(14天过期) builder.Services.AddOptions<BearerTokenOptions>("MobileBearer") .Configure(options => { options.BearerTokenExpiration = TimeSpan.FromDays(14); }); // 配置认证中间件,添加两个Scheme builder.Services.AddAuthentication() .AddBearer("WebBearer", options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }) .AddBearer("MobileBearer", options => { options.TokenValidationParameters = new TokenValidationParameters { // 与Web端保持一致的验证规则,确保令牌能被正确校验 ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; });
2. 分客户端登录API生成对应令牌
Web端登录接口示例:
[ApiController] [Route("api/web/login")] public class WebLoginController : ControllerBase { private readonly SignInManager<IdentityUser> _signInManager; private readonly IOptionsSnapshot<BearerTokenOptions> _optionsSnapshot; public WebLoginController(SignInManager<IdentityUser> signInManager, IOptionsSnapshot<BearerTokenOptions> optionsSnapshot) { _signInManager = signInManager; _optionsSnapshot = optionsSnapshot; } [HttpPost] public async Task<IActionResult> Login(LoginModel model) { var signInResult = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false); if (!signInResult.Succeeded) return Unauthorized(); var user = await _signInManager.UserManager.FindByNameAsync(model.Username); // 获取Web端Scheme对应的配置 var bearerOptions = _optionsSnapshot.Get("WebBearer"); // 生成对应Scheme的令牌 var token = await _signInManager.UserManager.GenerateUserTokenAsync(user, "WebBearer", IdentityConstants.TokenProvider); return Ok(new { AccessToken = token, ExpiresIn = (int)bearerOptions.BearerTokenExpiration.TotalSeconds }); } }
移动端登录接口只需将Scheme名称替换为"MobileBearer"即可。
方案二:动态生成令牌时指定过期时间
无需新增Scheme,在令牌生成阶段根据客户端类型动态设置过期时间,适合不需要区分Scheme的简单场景。
1. 自定义令牌生成Provider
public class DynamicExpirationTokenProvider : DataProtectorTokenProvider<IdentityUser> { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IConfiguration _configuration; public DynamicExpirationTokenProvider(IDataProtectionProvider dataProtectionProvider, IOptions<DataProtectionTokenProviderOptions> options, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) : base(dataProtectionProvider, options) { _httpContextAccessor = httpContextAccessor; _configuration = configuration; } public override async Task<string> GenerateAsync(string purpose, UserManager<IdentityUser> manager, IdentityUser user) { // 从请求头获取客户端类型(也可从请求参数获取) var clientType = _httpContextAccessor.HttpContext.Request.Headers["X-Client-Type"].ToString().ToLower(); var expiration = clientType == "mobile" ? TimeSpan.FromDays(14) : TimeSpan.FromDays(1); // 手动生成JWT令牌并设置过期时间 var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Name, user.UserName) }), Expires = DateTime.UtcNow.Add(expiration), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature), Issuer = _configuration["Jwt:Issuer"], Audience = _configuration["Jwt:Audience"] }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } }
2. 注册自定义Provider
builder.Services.AddIdentity<IdentityUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders() .AddTokenProvider<DynamicExpirationTokenProvider>("DynamicExpiration");
3. 登录接口使用自定义Provider
[ApiController] [Route("api/login")] public class LoginController : ControllerBase { private readonly SignInManager<IdentityUser> _signInManager; public LoginController(SignInManager<IdentityUser> signInManager) { _signInManager = signInManager; } [HttpPost] public async Task<IActionResult> Login(LoginModel model) { var signInResult = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false); if (!signInResult.Succeeded) return Unauthorized(); var user = await _signInManager.UserManager.FindByNameAsync(model.Username); var token = await _signInManager.UserManager.GenerateUserTokenAsync(user, "DynamicExpiration", IdentityConstants.TokenProvider); return Ok(new { AccessToken = token }); } }
为什么原有方案不生效
BearerTokenOptions是启动阶段绑定到指定Scheme的全局配置,运行时修改CurrentValue无法改变已初始化的令牌生成逻辑(生成逻辑在服务注册时已绑定初始配置),且全局修改会导致所有客户端令牌过期时间混乱,因此这种方式不可行。
内容的提问来源于stack exchange,提问作者osmanc
相关产品推荐
相关产品推荐

