如何使用 JWT 令牌在 .NET Core API 中检索当前用户数据?

Posted

技术标签:

【中文标题】如何使用 JWT 令牌在 .NET Core API 中检索当前用户数据?【英文标题】:How use a JWT token to retrieve current user data in .NET Core API? 【发布时间】:2021-07-22 10:02:57 【问题描述】:

我已经实现了 JWT 令牌,以了解当前用户在 MVC 控制器正在使用的 API 应用程序中的身份。 我正在构建类似论坛应用程序的东西。用户必须登录才能发帖,所以我基本上是在尝试使用 JWT 令牌来存储当前用户的电子邮件。 当用户单击“创建帖子”时,操作应该获取令牌及其值,问题是我不知道如何使用令牌来保护控制器或从当前用户检索数据,我已经复制并粘贴了jwt.io中的token,检查数据是否正确存储在token中,值(用户的email)是否正确存储。

具有“登录”操作的 API 控制器:

    public async Task<IActionResult> login([FromBody] Usuario model)
    
                //check if user exists and the password is correct

                //generates the token
                    var SecretKey = config.GetValue<string>("SecretKey");
                    var key = Encoding.ASCII.GetBytes(SecretKey);

                    var claims = new ClaimsIdentity(new Claim[] 
                    
                        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                        new Claim(ClaimTypes.Name, user.Mail)
                    );
                    claims.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Mail));

                    var tokenDesc = new SecurityTokenDescriptor
                    
                        Subject = claims,
                        Expires = DateTime.UtcNow.AddMinutes(20),
                        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                    ;

                    var tokenHandler = new JwtSecurityTokenHandler();
                    var createdToken = tokenHandler.CreateToken(tokenDesc);

                    string bearer_token = tokenHandler.WriteToken(createdToken);

                    using(var client = new HttpClient())
                    
                        client.DefaultRequestHeaders.Add("Authorization", "Bearer" + bearer_token);
                    

                    return Ok(bearer_token);
                
    

使用 API 的 MVC 控制器:

    public async Task<IActionResult> login(Usuario model)
    
            HttpClient hc = new HttpClient();
            hc.BaseAddress = new Uri("https://localhost:44325/api/Usuarios/");

            var login = await hc.PostAsJsonAsync<Usuario>("login", model);

            //check the response

            var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
            identity.AddClaim(new Claim(ClaimTypes.Name, model.Email));

            var principal = new ClaimsPrincipal(identity);

            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);

            HttpContext.Session.SetString("JWToken", login.ToString());

            hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", login.ToString());

            return RedirectToAction("IndexForumList", "ForumControllerMVC");
        
    

这是“创建帖子”的 API 方法以及应使用令牌的位置,这里 userId 为空:

    public async Task<IActionResult> createPost([FromForm]ForumModel model)
    
        string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        
        //retrieves the current user email, validates and save the content to database
        
    

这是“创建帖子”的 MVC 方法:

public async Task<IActionResult> createPost(ForumModel model)
    
        HttpClient hc = new HttpClient();
        hc.BaseAddress = new Uri("https://localhost:44325/api/Usuarios/");

        //var userPost = hc.PostAsJsonAsync<ForumModel>("Usuarios/createPost", model);

        var userPost = await hc.PostAsync("createPost", formContent);

        if(userPost.IsSuccessStatusCode == true)
        
            return RedirectToAction("IndexForumList", "ForoControllerMVC");
        
    

由于我对 JWT 缺乏了解,我一直受此困扰,感谢任何帮助。

更新

startup.cs

public void ConfigureServices(IServiceCollection services)
    
        var key = Encoding.ASCII.GetBytes(Configuration.GetValue<string>("SecretKey"));

        services.AddAuthentication(x =>
        
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        ).AddJwtBearer(x =>
        
            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            ;
        );
        services.AddSession(
            options =>
            
                options.IdleTimeout = TimeSpan.FromMinutes(10);
                options.Cookie.HttpOnly = true;
                options.Cookie.IsEssential = true;
            );
    

【问题讨论】:

当您从 mvc 调用时,您需要添加令牌作为标头。然后你需要在 api 控制器或操作中添加授权属性。我相信你已经在配置服务中设置了所有内容。 您已经添加了两次ClaimTypes.NameIdentifierUserManager 将寻找声明 IdentityOptions.ClaimsIdentity.UserIdClaimType / .UserNameClaimType 以获取数据库用户。您可以在启动时更改声明类型名称。您的“API 应用程序”和“MVC 控制器”是同一个程序还是分开的? @Selik 好吧,我认为服务的部分配置正确,我已经更新了问题的那部分。现在我想我还有一个问题,如何将令牌添加为标头? @JeremyLakeman 所以,我只需要留下一个带有电子邮件值的ClaimTypes.NameIdentifier,或者类似的东西?是的,API 与 MVC 在同一个项目中。 在您的 createpost MVC 方法中,您必须为 httpclient 调用添加授权标头,并将授权属性添加到 api 操作/控制器。 httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer" + "your_token"); 【参考方案1】:

如果我正确理解您的问题, 为了保护您的 API,您可以使用 [Authorize] 属性对其进行装饰。例如 -

    [Authorize]
    [HttpGet]
    public IActionResult GetAll()
    
        var users = _userService.GetAll();
        return Ok(users);
    

并且要验证您的令牌,因为您使用 .netcore 作为您的 api,您必须创建一个中间件,该中间件将在您的请求到达 API 端点之前验证令牌。您可以关注this 教程,了解如何将 JWT 与 ASP.NET 核心一起使用。

要在您的情况下获取用户 ID,您必须先验证令牌,然后提取用户 ID。尝试将 createPost api 中的代码更改为此 -

public async Task<IActionResult> createPost([FromForm]ForumModel model)

    var tokenHandler = new JwtSecurityTokenHandler();
    var SecretKey = config.GetValue<string>("SecretKey");
    var key = Encoding.ASCII.GetBytes(SecretKey);
    var token = HttpContext.Request.Headers["Authorization"];       
    
    tokenHandler.ValidateToken(token, new TokenValidationParameters
    
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        ValidateIssuer = false,
        ValidateAudience = false,
        ClockSkew = TimeSpan.Zero
            , out SecurityToken validatedToken);

        var jwtToken = (JwtSecurityToken)validatedToken;
        var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "NameIdentifier").Value);

虽然这应该在中间件中处理,然后您可以将经过身份验证的用户附加到当前的 HttpContext.Items 集合,以使其在当前请求的范围内可访问。所有这些都在教程中详细解释。希望对您有所帮助!

【讨论】:

感谢您的帮助,我一直在阅读这篇文章,现在就tokenHandler.ValidateToken(token, new TokenValidationParameters... 而言,token 参数指的是什么,或者它是在哪里创建的以便在那里使用? 哦,我明白了。我错过了在代码中添加令牌参数。它基本上是您在登录 api 中创建的 jwt 令牌。当您调用您的 api 并附加授权标头时,您应该能够通过 - var token = HttpContext.Request.Headers["Authorization"]; 访问它我也更新了代码。很抱歉错过了。

以上是关于如何使用 JWT 令牌在 .NET Core API 中检索当前用户数据?的主要内容,如果未能解决你的问题,请参考以下文章