在 ASP.NET 核心中创建 JWT 是不是有更简单的方法?又名“JWT 身份验证的最佳实践?”

Posted

技术标签:

【中文标题】在 ASP.NET 核心中创建 JWT 是不是有更简单的方法?又名“JWT 身份验证的最佳实践?”【英文标题】:Is there a simpler way for creating JWTs in ASP.NET core? aka “Best Practices for JWT Authentication?”在 ASP.NET 核心中创建 JWT 是否有更简单的方法?又名“JWT 身份验证的最佳实践?” 【发布时间】:2021-10-23 22:38:54 【问题描述】:

.NET5 拥有大量用于身份验证的中间件和基础设施。您可以包含一些 NuGet 包,在 Startup.cs 中连接一些配置,并且您的应用程序可以理解 cookie 和会话。我在网上为基于 JWT 的身份验证找到的所有示例都让我手动实例化 JwtSecurityDescriptor 并在其中设置属性。这真的感觉像是应该成为SignInManager.PasswordSignInAsync 流程的一部分。例如:

    https://www.faciletechnolab.com/Blog/2021/4/5/how-to-implement-jwt-token-authentication-in-aspnet-core-50-web-api-using-jwt https://weblog.west-wind.com/posts/2021/Mar/09/Role-based-JWT-Tokens-in-ASPNET-Core http://www.binaryintellect.net/articles/1fdc8b3f-06a1-4f36-8c0b-7852bf850f52.aspx

我错过了什么吗?这是在线示例跳过几个步骤以支持“简化”演示的情况吗?前面的每个示例都手动创建 JWT,但将其大量解释委托给中间件。

为了澄清,我不是在寻找有关如何验证密码的说明(我使用 IPasswordHasher 执行此操作)或定位用户(我使用 IUserPasswordStore 执行此操作)。我特别想知道我是否缺少一些东西来导致 ASP.NET 为我处理 JWT 生成工作流。我不知道在线示例是否省略了很多“用于演示”的功能(类似于许多在线指南如何将数据库调用直接放在控制器中以实现“简单性”)。

【问题讨论】:

【参考方案1】:

一般来说,当我们使用 JWT 认证时,工作流程如下:

    客户端向服务器发送请求(包含用户信息,如:用户名和密码)获取token 服务器接收用户信息并检查授权。如果验证成功,服务器会生成一个 JWT 令牌。 客户端收到令牌并将其存储在本地某处。 客户端在以后的请求中发送令牌。 服务器从请求标头中获取令牌,再次计算哈希,方法是使用 a) 令牌中的标头 b) 令牌中的有效负载 c) 服务器已经拥有的密钥。 如果 ("newly computed hash" = "hash come in token"),则 token 有效,否则它被调和或无效

您可以参考以下步骤在 ASP.NET Core 中使用 JWT 身份验证。

    使用 JWT 承载选项配置身份验证架构。

     public void ConfigureServices(IServiceCollection services)
     
    
         services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                
                    options.TokenValidationParameters = new TokenValidationParameters
                    
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = Configuration["Jwt:Issuer"],
                        ValidAudience = Configuration["Jwt:Issuer"],
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
                    ;
                );
         services.AddControllersWithViews();
    
    
    

    在本例中,我将这些值存储在 appsettings.json 文件中。

     
         "Jwt": 
           "Key": "ThisismySecretKey",
           "Issuer": "Test.com"
         
     
    

    在启动类的Configure方法中调用app.UseAuthentication()方法。

         app.UseRouting();
    
         app.UseAuthentication();
         app.UseAuthorization();
    
         app.UseEndpoints(endpoints =>
         
             endpoints.MapControllerRoute(
                 name: "default",
                 pattern: "controller=Home/action=Index/id?");
         );
    

    生成 JSON 网络令牌

     using System; 
     using System.IdentityModel.Tokens.Jwt; 
     using System.Security.Claims;
     using System.Text; 
     using Microsoft.AspNetCore.Authorization; 
     using Microsoft.AspNetCore.Mvc;
     using Microsoft.Extensions.Configuration;
     using Microsoft.IdentityModel.Tokens;
     using Test.Models;
    
     namespace Test.Controllers
     
         [Route("api/[controller]")]
         [ApiController]
         public class LoginController : ControllerBase
         
             private IConfiguration _config;
    
             public LoginController(IConfiguration config)
             
                 _config = config;
             
             [AllowAnonymous]
             [HttpPost]
             public IActionResult Login([FromBody] UserModel login)
             
                 IActionResult response = Unauthorized();
                 var user = AuthenticateUser(login);
    
                 if (user != null)
                 
                     var tokenString = GenerateJSONWebToken(user);
                     response = Ok(new  token = tokenString );
                 
    
                 return response;
             
    
             private string GenerateJSONWebToken(UserModel userInfo)
             
                 var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
                 var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
    
                 var claims = new[] 
                 new Claim(JwtRegisteredClaimNames.Sub, userInfo.Username),
                 new Claim(JwtRegisteredClaimNames.Email, userInfo.EmailAddress),
                 new Claim("DateOfJoing", userInfo.DateOfJoing.ToString("yyyy-MM-dd")),
                 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
             ;
    
                 var token = new JwtSecurityToken(_config["Jwt:Issuer"],
                     _config["Jwt:Issuer"],
                     claims,
                     expires: DateTime.Now.AddMinutes(120),
                     signingCredentials: credentials);
    
                 return new JwtSecurityTokenHandler().WriteToken(token);
             
    
             private UserModel AuthenticateUser(UserModel login)
             
                 UserModel user = null;
    
                 //Validate the User Credentials    
                 //Demo Purpose, I have Passed HardCoded User Information    
                 if (login.Username == "Jignesh")
                 
                     user = new UserModel  Username = "Jignesh Trivedi", EmailAddress = "test.btest@gmail.com" ;
                 
                 return user;
             
         
     
    

那么,如果你请求“API/login”方法生成令牌,你必须在请求正文中传递以下JSON。

"username": "Jignesh", "password": "password"

然后,在获取 JWT 令牌后,您可以在访问其他 API 控制器时在请求头中添加“授权”属性(带有[Authorize] 属性)。

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJKaWduZXNoIFRyaXZlZGkiLCJlbWFpbCI6InRlc3QuYnRlc3RAZ21haWwuY29tIiwiRGF0ZU9mSm9pbmciOiIwMDAxLTAxLTAxIiwianRpIjoiYzJkNTZjNzQtZTc3Yy00ZmUxLTgyYzAtMzlhYjhmNzFmYzUzIiwiZXhwIjoxNTMyMzU2NjY5LCJpc3MiOiJUZXN0LmNvbSIsImF1ZCI6IlRlc3QuY29tIn0.8hwQ3H9V8mdNYrFZSjbCpWSyR1CNyDYHcGf6GqqCGnY  

更多详细信息,请参考以下链接:

JWT Authentication In ASP.NET Core

JWT Validation on separate web Api

【讨论】:

感谢您的回答,非常完整且有帮助。但是,它仍然显示了我遇到的问题。您正在控制器操作中手动生成 JWT。这是正确的地方吗,或者这只是一个演示?作为 SignInManager.SignInAsync 工作流的一部分,为什么没有在某种 Microsoft.Identity 对象内部生成 JWT? 本教程和示例是一个基本教程,告诉我们JWT是如何工作的以及如何在我们的应用程序中使用它,您也可以创建custom middleware to handle the JWT authentication。那么,如果你使用 IdentityServer 或者外部提供者认证,JWT 令牌是由外部提供者生成的,拿到令牌后,你可以按照上面的教程使用它。

以上是关于在 ASP.NET 核心中创建 JWT 是不是有更简单的方法?又名“JWT 身份验证的最佳实践?”的主要内容,如果未能解决你的问题,请参考以下文章

asp.net 核心中的 jwt 令牌无效

uri查询参数中的asp.net核心JWT?

如何手动验证 JWT Asp.Net Core?

控制器 ASP.net 核心 2.1 中的 Jwt 角色身份验证

通过 ASP.NET 核心身份中的角色声明进行 JWT 身份验证

单次使用 Jwt Token 在 angular 6 的 asp.net 核心中进行电子邮件验证