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

如何在Identity Server 4登录后从客户端注入自定义Claims?

可行性说明与实现方案

完全可行,但客户端无法直接修改已颁发的令牌(令牌由Identity Server 4签名,篡改后会失效)。正确的做法是通过刷新令牌流程,将客户端侧选择的信息传递给ID4服务器,由服务器重新颁发包含新Claims的令牌。

实现步骤与示例代码

一、Identity Server 4 服务器端配置

1. 客户端配置(允许刷新令牌)

在ID4的客户端配置类(如Config.cs)中,确保客户端支持离线访问(刷新令牌):

public static IEnumerable<Client> GetClients()
{
    return new List<Client>
    {
        new Client
        {
            ClientId = "web_app",
            ClientName = "Web应用客户端",
            AllowedGrantTypes = GrantTypes.Code,
            RequirePkce = true,
            ClientSecrets = { new Secret("your_client_secret".Sha256()) },
            RedirectUris = { "https://your-client-domain/signin-oidc" },
            PostLogoutRedirectUris = { "https://your-client-domain/signout-callback-oidc" },
            AllowedScopes = { "openid", "profile", "api1", "offline_access" },
            AllowOfflineAccess = true, // 开启刷新令牌支持
            AccessTokenLifetime = 3600, // 按需设置令牌有效期
        }
    };
}

2. 自定义ProfileService(添加客户端传递的Claims)

修改或实现IProfileService,读取客户端提交的参数并注入Claims:

public class CustomProfileService : IProfileService
{
    private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
    private readonly UserManager<ApplicationUser> _userManager;

    public CustomProfileService(UserManager<ApplicationUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory)
    {
        _userManager = userManager;
        _claimsFactory = claimsFactory;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var userId = context.Subject.GetSubjectId();
        var user = await _userManager.FindByIdAsync(userId);
        if (user == null) throw new ArgumentException("用户不存在");

        // 获取基础用户Claims
        var principal = await _claimsFactory.CreateAsync(user);
        var claims = principal.Claims.ToList();

        // 读取客户端传递的额外参数
        if (context.Request.Raw.TryGetValue("company", out var company))
            claims.Add(new Claim("company", company));
        if (context.Request.Raw.TryGetValue("position", out var position))
            claims.Add(new Claim("position", position));
        if (context.Request.Raw.TryGetValue("fiscal_year", out var fiscalYear))
            claims.Add(new Claim("fiscal_year", fiscalYear));

        context.IssuedClaims = claims;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        var userId = context.Subject.GetSubjectId();
        var user = await _userManager.FindByIdAsync(userId);
        context.IsActive = user != null;
    }
}

3. 注册ProfileService

在服务器启动类(Program.cs或Startup.cs)中注册自定义服务:

builder.Services.AddIdentityServer()
    .AddAspNetIdentity<ApplicationUser>()
    .AddProfileService<CustomProfileService>()
    // 其他ID4配置(如资源、客户端)...

二、客户端(ASP.NET Core Web应用)实现

1. 用户选择信息页面(示例视图)

创建用户选择公司、职位、财年的表单(SelectInfo.cshtml):

<form method="post" asp-action="UpdateUserClaims">
    <div>
        <label>公司名称:</label>
        <input type="text" name="company" required />
    </div>
    <div>
        <label>职位:</label>
        <input type="text" name="position" required />
    </div>
    <div>
        <label>财年:</label>
        <input type="text" name="fiscalYear" required />
    </div>
    <button type="submit">确认并更新身份信息</button>
</form>

2. 刷新令牌并获取新Claims的Controller方法

public class AccountController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _config;

    public AccountController(IHttpClientFactory httpClientFactory, IConfiguration config)
    {
        _httpClientFactory = httpClientFactory;
        _config = config;
    }

    [HttpPost]
    public async Task<IActionResult> UpdateUserClaims(string company, string position, string fiscalYear)
    {
        // 从Cookie/Session中获取刷新令牌(需确保客户端配置中开启SaveTokens)
        var refreshToken = await HttpContext.GetTokenAsync("refresh_token");
        if (string.IsNullOrEmpty(refreshToken))
            return RedirectToAction("Login");

        // 构造令牌刷新请求
        var client = _httpClientFactory.CreateClient();
        var tokenEndpoint = $"{_config["IdentityServer:Authority"]}/connect/token";
        var requestContent = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["grant_type"] = "refresh_token",
            ["client_id"] = "web_app",
            ["client_secret"] = "your_client_secret",
            ["refresh_token"] = refreshToken,
            ["scope"] = "openid profile api1 offline_access",
            ["company"] = company,
            ["position"] = position,
            ["fiscal_year"] = fiscalYear
        });

        var response = await client.PostAsync(tokenEndpoint, requestContent);
        if (!response.IsSuccessStatusCode)
            return RedirectToAction("Login");

        // 解析新令牌并更新客户端身份
        var tokenResponse = await response.Content.ReadFromJsonAsync<TokenResponse>();
        if (tokenResponse == null)
            return RedirectToAction("Login");

        // 更新Cookie中的令牌
        var authProperties = new AuthenticationProperties();
        authProperties.StoreTokens(new[]
        {
            new AuthenticationToken { Name = "access_token", Value = tokenResponse.AccessToken },
            new AuthenticationToken { Name = "refresh_token", Value = tokenResponse.RefreshToken }
        });
        await HttpContext.SignInAsync("Cookies", User, authProperties);

        return RedirectToAction("Index", "Home");
    }

    // 辅助类:解析令牌响应
    private class TokenResponse
    {
        public string AccessToken { get; set; }
        public string RefreshToken { get; set; }
    }
}

3. 客户端认证配置(确保支持刷新令牌)

在客户端启动类中,配置OpenID Connect时开启令牌保存:

builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", options =>
    {
        options.Authority = builder.Configuration["IdentityServer:Authority"];
        options.ClientId = "web_app";
        options.ClientSecret = "your_client_secret";
        options.ResponseType = "code";
        options.SaveTokens = true; // 保存令牌到Cookie中
        options.Scope.Add("offline_access");
        // 其他配置...
    });

三、注意事项

  • 服务器端需对客户端传递的参数做合法性校验(如验证用户是否有权限选择该公司),避免恶意注入。
  • 若无需将这些Claims传递给API服务,也可直接存储在客户端Session中,无需修改令牌。
  • 刷新令牌的有效期需合理设置,避免频繁请求服务器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 03:54:58