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

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认证后,需确保以下配置正确:

  1. 客户端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;
    });
    
  2. 授权服务器声明配置:确保颁发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);
    
  3. 控制器授权配置:受保护控制器/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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 01:58:15