.Net Core 基于JWT签发Token
如果不了解JWT可以先了解这篇文章 。 这里主要是来记录一下怎样使用Jwt 自己来签发和刷新Token,很多地方不符合实际使用,只是为了在这里测试达到效果,正式使用根据实际情况修改代码
1. 添加Nuget引用
1 | Microsoft.AspNetCore.Authentication.JwtBeare |
1 | System.IdentityModel.Tokens.Jwt |
2. 添加简单封装的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | public class JwtHelper { public IConfiguration _configuration { get ; set ; } public JwtHelper(IConfiguration configuration) { _configuration = configuration; } /// <summary> /// 生成AccessToken /// </summary> /// <param name="username">这里测试用的是用户信息,可以传入其他信息</param> /// <returns></returns> 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); } /// <summary> /// 生成RefreshToken /// </summary> /// <returns></returns> 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); } /// <summary> /// 刷新accessToken /// </summary> /// <param name="accessToken">过期的accessToken</param> /// <returns></returns> /// <exception cref="Exception"></exception> 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 中参数配置)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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<JwtHelper>(); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // 当验证失败时,表头WWW-Authenticate会返回失败原因 options.IncludeErrorDetails = true ; <br> //配置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< string >( "JwtConfig:Issuer" ), // 通常不太需要验证 Audience ValidateAudience = false , //ValidAudience = "JwtAuthDemo", // 不验证就不需要 // 一般我们都会验证 Token 的有效期 ValidateLifetime = true , // 如果 Token 中包含 key 才需要验证,一般都只有前面而已 ValidateIssuerSigningKey = false , IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetValue< string >( "JwtConfig:SecurityKey" ))) }; }); |
添加认证管道,系统里面已经存在 UseAuthorization ,这里 在 UseAuthorization 之前添加 UseAuthentication
1 2 | 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 去请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | [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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异