【问题标题】:HttpContext.User.Claims doesnt match JWT Token - "Sub" changes to "NameIdentifier"HttpContext.User.Claims 与 JWT 令牌不匹配 - “Sub”更改为“NameIdentifier”
【发布时间】:2021-09-16 00:31:00
【问题描述】:

我遇到以下情况,非常感谢您的帮助:

我在我的用户界面(Blazor 服务器应用程序)中使用 JSON Web 令牌 (jwtbearer) 进行身份验证和授权,以访问我的 Web API (Asp.net core 5.0)。

我尝试从按 UserId 过滤的 API 返回数据(我在创建令牌时也将其添加为 NameIdentifier)。然后我注意到,点击控制器时从ControllerBase.User 返回的ClaimsPrincipal 的声明与我的令牌不同。

我有两个NameIdentifier 声明,并且“子”丢失了。相反,看起来 sub 已更改为第二个 NameIdentifier

Debugging Screenshot - Claims of this.User in Controller

但是,当我随请求一起发送时,令牌的有效负载似乎是正确的。这是我的令牌的有效负载:

{
  "sub": "admin@caliprog.com",
  "jti": "15c23576-9f5b-4f81-a985-ab236830c7b5",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "9b437d6c-8fb8-455d-87f5-986d36b26dcf",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Administrator",
  "exp": 1625472724,
  "iss": "test.com",
  "aud": "test.com"
}

显然我在某处犯了错误,但我不知道错误在哪里。如果有人能指出我的方向,那就太好了。我很高兴自己做更多的研究,但我不知道从哪里开始。

以下是一些可能相关的代码部分,如果我遗漏了任何重要的内容,请告诉我:

我的 GetRequest(它有效,但我想用 var userId = this.User.FindFirst(ClaimTypes.NameIdentifier).Value; 替换 this.User.Claims.ToList()[2].Value

    [HttpGet]
    public async Task<IActionResult> GetWorkouts()
    {
        try
        {
            _logger.LogInfo(LogMessages.AttemptMsg, ControllerContext);

            var userId = this.User.Claims.ToList()[2].Value;
            var workouts = await _workoutRepository.FindAllByUser(userId);
            var response = _mapper.Map<IList<WorkoutDTO>>(workouts);
            _logger.LogInfo(LogMessages.SuccessActionMsg,ControllerContext);
            return Ok(response);
        }
        catch (Exception e)
        {

            return _logger.LogInternalErrorResult(e, ControllerContext);
        }
    }

UsersController -> 登录并生成令牌:

    [Route("login")]
    [AllowAnonymous]
    [HttpPost]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> Login([FromBody] UserDTO userDTO)
    {   
        try
        {
            var username = userDTO.EmailAddress;
            var password = userDTO.Password;

            _logger.LogInfo(string.Concat(LogMessages.AttemptMsg, $"With username: {username}."), ControllerContext);

            var result = await _signInManager.PasswordSignInAsync(username, password, false, false);

            if (result.Succeeded)
            {
                var user = await _userManager.FindByNameAsync(username);
                var tokenstring = await GenerateJSONWebToken(user);
                _logger.LogInfo(LogMessages.SuccessActionMsg, ControllerContext);
                return Ok(new { token = tokenstring});
            }
            _logger.LogInfo(string.Concat($"{username}: ", LogMessages.NotAuthenticated), ControllerContext);
            return Unauthorized(userDTO);
        }
        catch (Exception e)
        {
            return _logger.LogInternalErrorResult(e, ControllerContext);
        }
    }

    private async Task<string> GenerateJSONWebToken(IdentityUser user)
    {
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
        var claims = new List<Claim>
        {
            new Claim(JwtRegisteredClaimNames.Sub,user.Email),
            new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
            new Claim(ClaimTypes.NameIdentifier,user.Id)
        };
        var roles = await _userManager.GetRolesAsync(user);
        claims.AddRange(roles.Select(r => new Claim(ClaimsIdentity.DefaultRoleClaimType, r)));

        var token = new JwtSecurityToken(
            _config["Jwt:Issuer"],
            _config["Jwt:Issuer"],
            claims,
            null,
            expires: DateTime.Now.AddHours(2),
            signingCredentials: credentials);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

UI 基础存储库

    public async Task<IList<T>> Get(string url)
    {
        var request = new HttpRequestMessage(HttpMethod.Get, url);

        var client = _client.CreateClient();
        client.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("bearer", await GetBearerToken());

        HttpResponseMessage response = await client.SendAsync(request);

        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            var content = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<List<T>>(content);
        }

        return null;
    }


    private async Task<string> GetBearerToken()
    {
        return await _localStorage.GetItemAsync<string>("authToken");
    }

API AuthenticationStateProvider

    public async Task LoggedIn()
    {
        var savedToken = await _localStorage.GetItemAsync<string>("authToken");
        var tokenContent = _tokenHandler.ReadJwtToken(savedToken);
        var claims = ParseClaims(tokenContent);
        var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));
        var authState = Task.FromResult(new AuthenticationState(user));
        NotifyAuthenticationStateChanged(authState);
    }

    public void LoggedOut()
    {
        var nobody = new ClaimsPrincipal(new ClaimsIdentity());
        var authState = Task.FromResult(new AuthenticationState(nobody));
        NotifyAuthenticationStateChanged(authState);
    }

    private static IList<Claim> ParseClaims(JwtSecurityToken tokenContent)
    {
        var claims = tokenContent.Claims.ToList();
        claims.Add(new Claim(ClaimTypes.Name, tokenContent.Subject));
        return claims;
    } 

以下是一些项目细节供参考:

API
Microsoft.AspNetCore.Authentication.JwtBearer 版本="5.0.5"
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore 版本="5.0.5"
Microsoft.AspNetCore.Identity.EntityFrameworkCore 版本="5.0.5"
Microsoft.AspNetCore.Identity.UI 版本="5.0.4"
Microsoft.AspNetCore.Mvc.NewtonsoftJson Version="5.0.6"

UI
Blazored.LocalStorage 版本="4.0.0"
Newtonsoft.Json 版本="13.0.1"
System.IdentityModel.Tokens.Jwt 版本="6.11.0"

【问题讨论】:

    标签: c# asp.net-core jwt blazor blazor-server-side


    【解决方案1】:

    在您的 Startup.cs 文件中,您需要更改您的 ConfigureServices 方法。

    services.AddAuthentication(auth =>
                {
                    auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                }).AddJwtBearer(token =>
                {
                    // ... Your extra options
    
                    // Don't map the in bound claims.
                    token.MapInboundClaims = false;
                });
    

    这将保留所有进入您的 API 的声明。

    【讨论】:

    • 非常感谢!我使用了您的代码,它立即生效。惊人的。问题出在我的问题中未提及的少数代码部分之一中......
    • @Brocco-Lee 几次碰到这个......我真的应该写博客。
    • 我一定会阅读这篇博文!如果你这样做,你可以考虑包括一些解释,.AddAuthentication 中的第一个 lamda 表达式正在做什么。在我的第一个版本中,我只有 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 并且它有效。我很想知道有什么区别。而且我可能并不孤单:)
    • 将 MapInboundClaims 设置为假中断 基于角色的授权(即使令牌中存在所需角色,端点也会返回 403),您有解决方法吗?
    • @fbede 如果您将角色声明类型设置为您自己的角色声明,那么这应该解决您的问题token.TokenValidationParameters.RoleClaimType = "https://my.company/claims/role";
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-12
    • 1970-01-01
    • 2019-12-08
    • 2017-12-23
    • 2019-02-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多