.Net Core 基于JWT签发Token
如果不了解JWT可以先了解 。 这里主要是来记录一下怎样使用Jwt 自己来签发和刷新Token,很多地方不符合实际使用,只是为了在这里测试达到效果,正式使用根据实际情况修改代码
1. 添加Nuget引用
Microsoft.AspNetCore.Authentication.JwtBeare
System.IdentityModel.Tokens.Jwt
2. 添加简单封装的工具类
public class JwtHelper { public IConfiguration _configuration { get; set; } public JwtHelper(IConfiguration configuration) { _configuration = configuration; } ////// 生成AccessToken /// /// 这里测试用的是用户信息,可以传入其他信息 ///public string GenerateAccessToken(string username) { var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); string issuer = _configuration.GetSection("JwtConfig:Issuer").Value; // 获取SecurityKey string securityKey = _configuration.GetSection("JwtConfig:SecurityKey").Value; //------------生成AccessToken---------------------------------- // token中的claims用于储存自定义信息,如登录之后的用户id等 var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub,username), new Claim(ClaimTypes.Role,"admin") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)); //生成Token两种方式 //方式一 //var tokenDescriptor = new SecurityTokenDescriptor //{ // Issuer = issuer, // Audience = "testClient", // NotBefore = DateTime.Now, // 预设值就是 DateTime.Now // IssuedAt = DateTime.Now, // 预设值就是 DateTime.Now // Subject = new ClaimsIdentity(claims), // Expires = DateTime.Now.AddMinutes(30), // SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256) //}; //var securityToken = jwtSecurityTokenHandler.CreateToken(tokenDescriptor); //var serializeToken = jwtSecurityTokenHandler.WriteToken(securityToken); //方式二 var token = new JwtSecurityToken( issuer: issuer, // 发布者 audience: "testClient", // 接收者 notBefore: DateTime.Now, // token签发时间 expires: DateTime.Now.AddMinutes(30), // token过期时间 claims: claims, // 该token内存储的自定义字段信息 signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) // 用于签发token的秘钥算法 ); return jwtSecurityTokenHandler.WriteToken(token); } /// /// 生成RefreshToken /// ///public string GenerateRefreshToken() { string issuer = _configuration.GetSection("JwtConfig:Issuer").Value; // 获取SecurityKey string securityKey = _configuration.GetSection("JwtConfig:SecurityKey").Value; var refClaims = new[] { new Claim("role","refresh") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)); var refreshToken = new JwtSecurityToken( issuer: issuer, // 发布者 audience: "testClient", // 接收者 notBefore: DateTime.Now, // token签发时间 expires: DateTime.Now.AddDays(7), // token过期时间 claims: refClaims, // 该token内存储的自定义字段信息 signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) // 用于签发token的秘钥算法 ); // 返回成功信息,写出token return new JwtSecurityTokenHandler().WriteToken(refreshToken); } /// /// 刷新accessToken /// /// 过期的accessToken ////// public string RefreshToken(string accessToken) { string issuer = _configuration.GetSection("JwtConfig:Issuer").Value; // 获取SecurityKey string securityKey = _configuration.GetSection("JwtConfig:SecurityKey").Value; var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); bool isCan = jwtSecurityTokenHandler.CanReadToken(accessToken);//验证Token格式 if (!isCan) throw new Exception("传入访问令牌格式错误"); //var jwtToken = jwtSecurityTokenHandler.ReadJwtToken(refreshtoken);//转换类型为token,不用这一行 var validateParameter = new TokenValidationParameters()//验证参数 { ValidateAudience = true, // 验证发布者 ValidateIssuer = true, // 验证过期时间 ValidateLifetime = false, // 验证秘钥 ValidateIssuerSigningKey = true, // 读配置Issure ValidIssuer = issuer, // 读配置Audience ValidAudience = "testClient", // 设置生成token的秘钥 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)) }; //验证传入的过期的AccessToken SecurityToken validatedToken = null; try { jwtSecurityTokenHandler.ValidateToken(accessToken, validateParameter, out validatedToken);//微软提供的验证方法。那个out传出的参数,类型是是个抽象类,记得转换 } catch (SecurityTokenException) { throw new Exception("传入AccessToken被修改"); } // 获取SecurityKey var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)); var jwtToken = validatedToken as JwtSecurityToken;//转换一下 var accClaims = jwtToken.Claims; var access_Token = new JwtSecurityToken( issuer: "fcb", // 发布者 //audience: "myClient", // 接收者 notBefore: DateTime.Now, // token签发时间 expires: DateTime.Now.AddMinutes(30), // token过期时间 claims: accClaims, // 该token内存储的自定义字段信息 signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) // 用于签发token的秘钥算法 ); // 返回成功信息,写出token return new JwtSecurityTokenHandler().WriteToken(access_Token); } }
3. 修改Program.cs
(这里多设置了swagger,方便测试,如果实际情况不需要swagger进行测试可以去掉 AddSwaggerGen 中参数配置)
builder.Services.AddSwaggerGen(c => { c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() { Description = "在下框中输入请求头中需要添加Jwt授权Token:Bearer Token", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, BearerFormat = "JWT", Scheme = "Bearer" }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, new string[] { } } }); }); builder.Services.AddScoped(); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // 当验证失败时,表头WWW-Authenticate会返回失败原因 options.IncludeErrorDetails = true;
//配置Token的验证 options.TokenValidationParameters = new TokenValidationParameters { // 可以从 "sub" 取值并设定給 User.Identity.Name NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", // 可以从 "roles" 取值,并可以从 [Authorize] 设置角色 RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", // 一般我们都要验证 Issuer ValidateIssuer = true, ValidIssuer = builder.Configuration.GetValue("JwtConfig:Issuer"), // 通常不太需要验证 Audience ValidateAudience = false, //ValidAudience = "JwtAuthDemo", // 不验证就不需要 // 一般我们都会验证 Token 的有效期 ValidateLifetime = true, // 如果 Token 中包含 key 才需要验证,一般都只有前面而已 ValidateIssuerSigningKey = false, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetValue ("JwtConfig:SecurityKey"))) }; });
添加认证管道,系统里面已经存在 UseAuthorization ,这里 在 UseAuthorization 之前添加 UseAuthentication
app.UseAuthentication(); app.UseAuthorization();
4. 添加控制器
这里刷新Token的接口限制了 [Authorize(Roles = "refresh")] ,只有 refreshToken 才有相应的角色,所以 需要换成 refreshToken ,并且传参之前过期的accessToken,目的主要是拿取token中的claim信息,方便生成新的accessToken重新写入进去, 当前也可以特别处理refreashToken,而取消传入失效的accessToken,我这里没有试过,理论上是可以的。这里还存在一个问题就是可以通过refreshToken 去请求 其他 需要的 accessToken 验证的接口 ,所以可以给相应的接口新增一些限制,只能通过 accessToken 去请求,比如下面的接口 Test2 限制了 [Authorize(Roles = "admin")] ,accessToken 才有admin的角色权限,只能通过accessToken 去请求
[Route("api/[controller]")] [ApiController] public class AccessController : ControllerBase { private JwtHelper _jwtHelper; public AccessController(JwtHelper jwtHelper) { _jwtHelper = jwtHelper; } [Authorize] [HttpGet("test")] public ActionResult Test() { return Ok(HttpContext.User.Claims.Count()); } [Authorize(Roles = "admin")] [HttpGet("test2")] public ActionResult Test2() { return Ok(HttpContext.User.Claims.Count()); } [HttpGet("login")] public ActionResult Login(string username, string password) { //校验账号密码,这里省略 if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) { var access_token= _jwtHelper.GenerateAccessToken(username); var refresh_token = _jwtHelper.GenerateRefreshToken(); // 返回成功信息,写出token return Ok(new { code = 200, message = "登录成功", accessToken = access_token, refreshToken = refresh_token }); } // 返回错误请求信息 return BadRequest(new { code = 400, message = "登录失败,用户名或密码为空" }); } //此方法用来刷新令牌,逻辑是验证refToken才能进入方法,进入后验证accessToken除了过期时间项的其他所有项,目的是防止用户修改权限等 [HttpGet("refresh")] [Authorize(Roles = "refresh")]//验证权限 public ActionResult Refresh(string accessToken) { var newAccessToken = _jwtHelper.RefreshToken(accessToken); //重新生成refeashToken var refresh_token = _jwtHelper.GenerateRefreshToken(); // 返回成功信息,写出token return Ok(new { code = 200, message = "令牌刷新成功", refreshToken = refresh_token, accessToken = newAccessToken }); } }
5. 运行一下
获取到accessToken之后授权swagger (Bearer+" " + accessToken )
再请求一下刷新Token的接口
文章参考文档:
https://docs.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.jwt.jwtsecuritytokenhandler?view=azure-dotnet(官网)
https://www.cnblogs.com/zxy001126/p/15530864.html
https://www.cnblogs.com/hot-tofu-curd/p/15115844.html