如何使用 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 中检索当前用户数据?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Razor 时如何在每个请求处传输 JWT 令牌(.NET CORE 2.2 MVC)

如何在 ASP.NET Core 中转储解析的 JWT 令牌?

如何在 .NET Core 中为 JWT 令牌设计自定义验证器?

如何使用 Microsoft 身份平台身份验证在 ASP.NET Core Web 应用程序中获取 JWT 令牌?

如何在 Asp.NET Core WEB API 中使用 .Net (C#) 在 Payload 中创建具有自定义 JSON 声明的 JWT 令牌

如何在 asp.net core 中设置 jwt 令牌的生命周期