OpenIddict Velusia示例UserInfo触发机制及JWT认证Claims加载问题
OpenIddict Velusia授权码流开发问题解答
开发场景概述
我基于OpenIddict的Velusia授权码流进行开发,客户端授权第一步是跳转至登录页,代码如下:
[HttpGet("~/login")] public ActionResult LogIn(string returnUrl) { var properties = new AuthenticationProperties(new Dictionary<string, string> { // 注:当客户端选项中仅注册了一个客户端时,无需设置issuer属性,可省略 [OpenIddictClientAspNetCoreConstants.Properties.Issuer] = "https://localhost:44313/" }) { // 仅允许本地返回URL,防止开放重定向攻击 RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/" }; // 让OpenIddict客户端中间件将用户代理重定向到身份提供商 return Challenge(properties, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme); }
该逻辑通过Challenge重定向至授权服务器的登录页,登录成功后进入服务器的/Authorize接口,因使用隐式同意直接进入Exchange接口,随后自动调用我实现的UserInfo接口:
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)] [HttpGet("~/connect/userinfo")] public async Task<IActionResult> Userinfo() { var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("无法检索OpenID Connect请求。"); var claimsPrincipal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; var user = await _userManager.FindByIdAsync(claimsPrincipal?.GetClaim(Claims.Subject) ?? throw new Exception("找不到主体信息!")); // 省略后续处理代码 }
之后返回客户端的LoginCallback,将所有声明存储到Cookie中,此时受保护控制器能正常加载指定Destinations.IdentityToken的声明。
但我需要改用JWT认证,目前JWT认证可正常工作,但受保护控制器无法加载用户声明,因此提出以下问题:
问题1:第一个示例中,UserInfo接口的触发条件是什么?为何不通过Challenge调用登录就无法触发该接口?
- 触发条件:UserInfo是OpenID Connect规范定义的端点,触发需要两个前提:一是客户端授权请求包含
openid范围,二是客户端已获取到合法的access_token。在你的示例中,OpenIddict客户端中间件完成授权码交换拿到access_token后,会自动调用UserInfo——这是因为客户端默认启用了用户信息获取逻辑,或授权请求中包含了需要从UserInfo补充的声明范围(如profile、email等未放入id_token的声明)。 - 不通过Challenge无法触发的原因:
Challenge是启动OpenID Connect完整授权流程的入口,只有通过它完成重定向登录、授权码交换、令牌获取这一系列步骤,客户端才能拿到有效的access_token,具备调用UserInfo的权限。直接调用UserInfo会因缺少有效令牌被授权服务器拒绝。
问题2:获取到的id_token应包含相关信息,是否无需调用UserInfo接口?
是否需要调用UserInfo取决于业务需求和声明的特性:
- 如果核心用户信息(如用户ID、用户名、角色)已包含在
id_token中,且完全满足客户端业务需求,确实可以不用调用UserInfo。 - 但存在以下情况时,仍需调用UserInfo:
- 声明过多导致
id_token体积过大(JWT通常通过URL或Cookie传递,体积过大会影响性能或触发浏览器限制); - 敏感或动态变化的信息不适合放入
id_token(id_token是签名静态数据,无法实时更新,UserInfo可获取授权服务器最新数据); - 授权请求包含
openid以外的范围(如profile、email),但这些范围对应的声明未配置到id_token中,需要从UserInfo获取。
- 声明过多导致
问题3:该场景下将用户声明存储到Cookie是否合适?如何让受保护控制器自动加载所有用户声明?
存储到Cookie是否合适?
分场景判断:
- 传统服务器端渲染(SSR)应用:将声明存储到Cookie是合理的——Cookie是ASP.NET Core默认的身份验证载体,服务器可直接从Cookie读取声明,无需每次请求都去授权服务器验证令牌。
- 单页应用(SPA)或API客户端:更适合将JWT令牌存储在本地存储或内存中,每次请求携带令牌到服务器验证。
让受保护控制器自动加载所有用户声明的方法
改用JWT认证后,需确保以下配置正确:
- 客户端JWT认证中间件配置:在Program.cs中确保JWT参数与授权服务器一致,开启声明映射:
builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.Authority = "https://localhost:44313/"; options.Audience = "your-client-id"; options.RequireHttpsMetadata = true; // 开启自动映射声明到User.Claims options.MapInboundClaims = true; }); - 授权服务器声明配置:确保颁发
id_token或access_token时包含所需声明,并配置正确的声明目的地(如Destinations.AccessToken或Destinations.IdentityToken)。若通过UserInfo补充声明,需在客户端LoginCallback中将UserInfo返回的声明添加到认证主体:// 在LoginCallback获取UserInfo响应后 var claims = new List<Claim>(); claims.AddRange(userInfoResponse.Claims); var identity = new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); // 完成认证登录 await HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme, principal); - 控制器授权配置:受保护控制器/Action需添加
[Authorize]特性,指定正确的认证方案(多认证方案场景下):[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class ProtectedController : Controller { public IActionResult Index() { // 从User.Claims获取声明 var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); return View(); } }
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

