September 11, 2020

DefaultInboundClaimTypeMap.Clear()

在 AspNet Core 中使用 AspNetCoreIdentity 常常伴随着这么一条语句

public void ConfigureServices(IServiceCollection services) {
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
}

这次正好遇到了一个问题代码

var user = await _userManager.GetUserAsync(HttpContext.User);

IdentityServer4(简称ids4) 与 AspNetCoreIdentity 部署在同一个项目中

由 ids4 生成的 jwt 包含 sub 为 1 的 claim ,HttpContext.User 也包含 jwt 中的 claims,所以理应代码中的 user 变量不为 null,但实际为 null。先看看源码

GetUserAsync 源码:

public virtual Task<TUser> GetUserAsync(ClaimsPrincipal principal)
{
    if (principal == null)
    {
        throw new ArgumentNullException("principal");
    }

    string userId = GetUserId(principal);
    if (userId != null)
    {
        return FindByIdAsync(userId);
    }

    return Task.FromResult<TUser>(null);
}

GetUserId 源码:

public virtual string GetUserId(ClaimsPrincipal principal)
{
    if (principal == null)
    {
        throw new ArgumentNullException("principal");
    }

    return principal.FindFirstValue(Options.ClaimsIdentity.UserIdClaimType);
}

ClaimsIdentity.UserIdClaimType 值:
2020-09-11_16-03-07

对我的应用代码 User.Claims 调试,发现 jwt 中的 sub 修改为了 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier ,所以理应是能找到用户信息的。

我怀疑 UserIdClaimType 的值并不是 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier,我的代码中并没有对 ClaimsIdentity 进行重新赋值,只能调试 dll 文件了。

使用 VS 的插件 reflector(收费)为程序集 Microsoft.Extensions.Identity.Core 生成 pdb 文件进行调试,如我所想 UserIdClaimType 的值是 sub。

原因是 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap 进行了映射,所以解决 user 为 null 有两种方法:

  1. JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear() 清除映射
  2. 在服务注入中对 ClaimsIdentity 重新赋值
services.Configure<ClaimsIdentityOptions>(options =>
{
    options.UserIdClaimType = "与 User.Claims 中的 id 一致";
});