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

.NET中HttpContext.User.Claims为空问题排查求助

问题:HttpContext.User.Claims为空,但JWT令牌本身包含Claims

实现持久登录时遇到异常:JWT令牌本身包含6个Claims,但通过HttpContext.User访问对应Claim(如ClaimTypes.Email)返回null。已添加[Authorize]特性,且认证配置在授权之前,但问题仍存在。


接口代码

[Authorize]
[HttpGet("getUser")]
public async Task<ActionResult<UserDTO>> GetUser()
{
    
        var token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
        if (string.IsNullOrEmpty(token))
        {
            return Unauthorized(new { message = "Token is required." });
        }

        var handler = new JwtSecurityTokenHandler();
        if (!handler.CanReadToken(token))
        {
            return BadRequest(new { message = "Invalid token format." });
        }

        var jwtToken = handler.ReadJwtToken(token);
        var claims = jwtToken.Claims;

        
        
        var email = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.Email);
        
        var result = await Mediator.Send(new GetUser.Command());
        return HandleResults(result);
    
}

令牌生成代码

public string Token(ApplicationUser user)
{
    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(ClaimTypes.NameIdentifier, user.Id),
        new Claim(ClaimTypes.Email, user.Email),
    };


    var jwtKey = _configuration["JwtConfig:Key"];
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),
        Expires = DateTime.UtcNow.AddDays(1),
        SigningCredentials = creds
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
}

private TokenValidationParameters GetValidationParameters()
{
    return new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtConfig:Key"])),
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = true,
        ClockSkew = TimeSpan.Zero
    };
}

已尝试的认证配置

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = key,
            ValidateIssuer = false,
            ValidateAudience = false,
        };
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];
                if (!string.IsNullOrEmpty(accessToken) &&
                    (context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
                {
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {

                System.Console.WriteLine(context.Exception);
                return Task.CompletedTask;
            },
        };
    });

解决方案

1. 修复认证配置中的Events覆盖问题

配置JwtBearerEvents时,两次赋值options.Events导致OnMessageReceived被覆盖,合并Events配置:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        var jwtKey = builder.Configuration["JwtConfig:Key"];
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey));
        
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = key,
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero
        };
        
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];
                if (!string.IsNullOrEmpty(accessToken) &&
                    (context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
                {
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            },
            OnAuthenticationFailed = context =>
            {
                System.Console.WriteLine(context.Exception);
                return Task.CompletedTask;
            }
        };
    });

2. 确保中间件顺序正确

Program.cs中必须严格遵循以下顺序:

// 服务注册
builder.Services.AddAuthentication(...);
builder.Services.AddAuthorization(...);

// 中间件配置
app.UseAuthentication();
app.UseAuthorization();

// 路由相关中间件
app.UseRouting();
app.MapControllers();

UseAuthentication必须在UseAuthorization之前,且置于路由中间件之前。

3. 检查Claims映射(可选)

若JWT中Claim名称与ClaimTypes的标准URI不匹配(比如用email而非完整URI),可添加映射逻辑:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        // 其他配置...
        
        options.MapInboundClaims = false; // 禁用默认映射
        
        options.Events.OnTokenValidated = context =>
        {
            var jwtToken = context.SecurityToken as JwtSecurityToken;
            var identity = context.Principal.Identity as ClaimsIdentity;
            
            if (jwtToken != null && identity != null)
            {
                var emailClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == "email");
                if (emailClaim != null && !identity.HasClaim(ClaimTypes.Email, emailClaim.Value))
                {
                    identity.AddClaim(new Claim(ClaimTypes.Email, emailClaim.Value));
                }
            }
            return Task.CompletedTask;
        };
    });

4. 移除手动令牌解析(可选)

[Authorize]特性会自动解析令牌并填充HttpContext.User,无需手动读取令牌,简化接口代码:

[Authorize]
[HttpGet("getUser")]
public async Task<ActionResult<UserDTO>> GetUser()
{
    var email = User.FindFirstValue(ClaimTypes.Email); // 直接使用Controller的User属性
    
    var result = await Mediator.Send(new GetUser.Command());
    return HandleResults(result);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 21:33:22