jwt测试
1引用包IdentityModel和System.IdentityModel.Tokens.Jwt和Microsoft.AspNetCore.Authorization
2在appsetting设置相关参数(根据自己需求)
"JwtSettings": { "PrivateKey": "TheKeyOfPrivate", "Issuer": "https://localhost:5000", "Audience": "https://localhost:5001", "SecurityKey": "Hellokeydfasdfasoajfaspa", "ExpireSeconds": 2000 }
3创建一个帮助类
public class JwtSetting { /// <summary> /// 私钥 /// </summary> public string PrivateKey { get; set; } /// <summary> /// token是谁颁发的 /// </summary> public string Issuer { get; set; } /// <summary> /// token可以给哪些客户端使用 /// </summary> public string Audience { get; set; } /// <summary> /// 加密的key(SecurityKey /// 必须大于16个,是大于,不是大于等于) /// </summary> public string SecurityKey { get; set; } /// <summary> /// 过期时间 /// </summary> public int ExpireSeconds { get; set; } }
4在startup中注册
在swagger中添加
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT Authorization header using the Bearer scheme.", Name = "Authorization", In = ParameterLocation.Header, //net core 3.1和net core2.2 在这里有差距 Scheme = "bearer", Type = SecuritySchemeType.Http, //这里也是一样 BearerFormat = "JWT" }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference=new OpenApiReference{ Type=ReferenceType.SecurityScheme, Id="Bearer"} }, new List<string>() } });
再注册
//将appsettings.json中的JwtSettings部分文件读取到JwtSettings中,这是给其他地方用的 services.Configure<JwtSetting>(Configuration.GetSection("JwtSettings")); //由于初始化的时候我们就需要用,所以使用Bind的方式读取配置 //将配置绑定到JwtSettings实例中 var jwtSetting = new JwtSetting(); Configuration.Bind("JwtSettings", jwtSetting); //添加身份验证 services.AddAuthentication(options => { //认证middleware配置 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o => { //jwt token参数设置 验证参数是否一样 o.TokenValidationParameters = new TokenValidationParameters { NameClaimType = JwtClaimTypes.Name, RoleClaimType = JwtClaimTypes.Role, ValidIssuer = jwtSetting.Issuer, ValidAudience = jwtSetting.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)), /***********************************TokenValidationParameters的参数默认值***********************************/ // RequireSignedTokens = true, // SaveSigninToken = false, // ValidateActor = false, // 将下面两个参数设置为false,可以不验证Issuer和Audience,但是不建议这样做。 // ValidateAudience = true, // ValidateIssuer = true, // ValidateIssuerSigningKey = false, // 是否要求Token的Claims中必须包含Expires // RequireExpirationTime = true, // 允许的服务器时间偏移量 // ClockSkew = TimeSpan.FromSeconds(300), // 是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比 // ValidateLifetime = true }; });
services.AddCors(options => options.AddPolicy("AllowCors", ///添加跨域 builder => { builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }));
在configure
//身份授权认证 app.UseAuthentication(); app.UseAuthorization(); //跨域 app.UseCors("AllowCors"); //这里是webapi ,mvc模式要放mvc前面
5创建service
public string GetToken(string privateKey) { if (privateKey != _jwtSetting.PrivateKey) { return null; } var claims = new List<Claim> { //jwt的唯一身份标识,主要用来作为一次性token,从而避免重放攻击 new Claim(JwtRegisteredClaimNames.Jti,privateKey), // 令牌颁发时间 new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"), //定义在什么时间之前,该jwt都是不可用的 new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"), // 过期时间 100秒 new Claim(JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(_jwtSetting.ExpireSeconds)).ToUnixTimeSeconds()}"), new Claim(JwtRegisteredClaimNames.Iss,"API"), // 签发者 new Claim(JwtRegisteredClaimNames.Aud,"User") // 接收者 }; //sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit. var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSetting.SecurityKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); //.NET Core’s JwtSecurityToken class takes on the heavy lifting and actually creates the token. /** * Claims (Payload) Claims 部分包含了一些跟这个 token 有关的重要信息。 JWT 标准规定了一些字段,下面节选一些字段: iss: The issuer of the token,token 是给谁的 sub: The subject of the token,token 主题 exp: Expiration Time。 token 过期时间,Unix 时间戳格式 iat: Issued At。 token 创建时间, Unix 时间戳格式 jti: JWT ID。针对当前 token 的唯一标识 除了规定的字段外,可以包含其他任何 JSON 兼容的字段。 * */ var token = new JwtSecurityToken( issuer: _jwtSetting.Issuer, //这里有默认值null,如果有值,它会把claim中的iss对应的值加上,其他的也一样 audience: _jwtSetting.Audience, claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddHours(1), signingCredentials: creds); string returnToken = new JwtSecurityTokenHandler().WriteToken(token); _memoryCacheHelper.Set(privateKey, returnToken, System.TimeSpan.FromSeconds(1800.00)); //这里设置缓存,不需要的可以去掉 return returnToken; }
解析
public string SerializeJwt(string jwtStr) { var token = _memoryCacheHelper.Get("TheKeyOfPrivate"); var jwtHandler = new JwtSecurityTokenHandler(); JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr); object role; try { jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role); } catch(Exception e) { Console.WriteLine(e); throw; } var secret = jwtToken.Payload["jti"]; //测试 foreach (var item in jwtToken.Claims) { var value = item.Value; Console.WriteLine(value); } return "test"; }
6在cotroller中调用service中的生成方法,生成token
7在swagger中的authorize把生成的token加入,有的前面需要加Bearer +空格+token ,这里直接保存token的值就行
7在其他需要鉴权的cotroller中下action添加 [Authorize] 就行
刷新refreshing Token
var jwtTokenHandler = new JwtSecurityTokenHandler(); try { // Validation 1 - Validation JWT token format // 此验证功能将确保 Token 满足验证参数,并且它是一个真正的 token 而不仅仅是随机字符串 var tokenInVerification = jwtTokenHandler.ValidateToken(tokenRequest.Token, tokenValidationParams, out var validatedToken); // Validation 2 - Validate encryption alg // 检查 token 是否有有效的安全算法 if (validatedToken is JwtSecurityToken jwtSecurityToken) { var result = jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase); if (result == false) { return null; } } // Validation 3 - validate expiry date // 验证原 token 的过期时间,得到 unix 时间戳 var utcExpiryDate = long.Parse(tokenInVerification.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp).Value); var expiryDate = DateTimeHelper.UnixTimeStampToDateTime(utcExpiryDate); if (expiryDate > DateTime.Now) { return new AuthResult() { Success = false, Errors = new List<string>() { "Token has not yet expired" } }; } // validation 4 - validate existence of the token // 验证 refresh token 是否存在,是否是保存在数据库的 refresh token var storedRefreshToken = await db.Queryable<RefreshToken>().FirstAsync(x => x.Token == tokenRequest.RefreshToken); if (storedRefreshToken == null) { return new AuthResult() { Success = false, Errors = new List<string>() { "Refresh Token does not exist" } }; } // Validation 5 - 检查存储的 RefreshToken 是否已过期 // Check the date of the saved refresh token if it has expired if (DateTime.Now > storedRefreshToken.ExpiryDate) { return new AuthResult() { Errors = new List<string>() { "Refresh Token has expired, user needs to re-login" }, Success = false }; } // Validation 8 - validate the id // 这里获得原 JWT token Id var jti = tokenInVerification.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Jti).Value; // 根据数据库中保存的 Id 验证收到的 token 的 Id if (storedRefreshToken.JwtId != jti) { return new AuthResult() { Success = false, Errors = new List<string>() { "The token doesn't mateched the saved token" } }; } // 生成一个新的 token var dbUser = await db.Queryable<SysUser>().FirstAsync(c => c.UserGuid == storedRefreshToken.UserId); return await GenerateJwtToken(jwtSetting, dbUser); } catch (Exception ex) { if (ex.Message.Contains("Lifetime validation failed. The token is expired.")) { return new AuthResult() { Success = false, Errors = new List<string>() { "Token has expired please re-login" } }; } else { return new AuthResult() { Success = false, Errors = new List<string>() { "Something went wrong." } }; } }
public class TokenRequest { public string Token { get; set; } /// <summary> /// Refresh Token /// </summary> public string RefreshToken { get; set; } }
public class AuthResult { public string Token { get; set; } public string RefreshToken { get; set; } public bool Success { get; set; } public List<string> Errors { get; set; } }
SysUser也是一个实体,登录验证的时候,返回sysuser的信息和token,refreshtoken的值,用来调用刷新token的参数,也可以存缓存里面比如redis中
授权不通过自定义返回数据类型,方便前端识别
public class ApiResponseHandler : AuthenticationHandler<AuthenticationSchemeOptions> { public ApiResponseHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } protected override Task<AuthenticateResult> HandleAuthenticateAsync() { throw new NotImplementedException(); } protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { Response.ContentType = "application/json"; Response.StatusCode = StatusCodes.Status401Unauthorized; await Response.WriteAsync(JsonConvert.SerializeObject(new AuthResult(StatusCode.CODE401))); AuthResult返回的结果列 } protected override async Task HandleForbiddenAsync(AuthenticationProperties properties) { Response.ContentType = "application/json"; Response.StatusCode = StatusCodes.Status403Forbidden; await Response.WriteAsync(JsonConvert.SerializeObject(new AuthResult(StatusCode.CODE403))); } }
在startup中
services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.DefaultChallengeScheme = nameof(ApiResponseHandler); o.DefaultForbidScheme = nameof(ApiResponseHandler); }) // 添加JwtBearer服务 .AddJwtBearer(o => { o.TokenValidationParameters = new TokenValidationParameters { NameClaimType = JwtClaimTypes.Name, RoleClaimType = JwtClaimTypes.Role, ValidIssuer = jwtSetting.Issuer, ValidAudience = jwtSetting.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)), //是否验证token有效期,使用当前时间与token的claims中的notbefore和expires对比 ValidateLifetime = true, //注意这里是缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟 ClockSkew = System.TimeSpan.FromSeconds(5) }; }) .AddScheme<AuthenticationSchemeOptions, ApiResponseHandler>(nameof(ApiResponseHandler), o => { });
后续还有一个问题,当验证token失败或者过期时,返回的都是401,我怎么识别时过期还是没有其他?