Jwt的自定义使用 中间件+身份验证鉴权方式
本文实例环境及版本 NetCore 3.1
一、JWT简介
JWT其全称是JSON Web Token
,官网地址:https://jwt.io/
通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token
,并且这个JWT token
带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。
JWT认证的优势
对比传统的session认证方式,JWT的优势是:
1、JWT Token数据量小,传输速度也很快。因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
2、不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务
3、单点登录友好 使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题
适合移动端应用 使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端
二、具体使用
1、NuGet导入Microsoft.AspNetCore.Authentication、Microsoft.AspNetCore.Authentication.JwtBearer、Microsoft.IdentityModel.Tokens
2、定义一个认证用户信息实体类如下
/// <summary> /// 用户信息model /// </summary> public class UserInfo { /// <summary> /// 用户编号 /// </summary> public string userId { get; set; } /// <summary> /// 用户名称 /// </summary> public string userName { get; set; } /// <summary> /// 角色、标识 /// </summary> public string roleType { get; set; }/// <summary> /// Token的过期时间 /// </summary> public DateTime expiresDate { get; set; } }
3、Jwt配置对象
/// <summary> /// jwt配置对象 /// </summary> public class JWTTokenOptions { /// <summary> /// Jwt认证Key /// </summary> public string SecurityKey { get; set; } /// <summary> /// 过期时间 单位为小时 /// </summary> public int Expire { get; set; } /// <summary> /// 观众 相当于接受者受众者 /// </summary> public string Audience { get; set; } /// <summary> /// 发行者 /// </summary> public string Issuer { get; set; } }
4、配置文件appsettings.json 添加如下
"JWTTokenOptions": { "Audience": "http://localhost:5200", "Issuer": "http://localhost:5200", "SecurityKey": "**************************", //用于生成token的秘钥 至少16位此处24位 "Expire": "7" //过期时间 单位为天 }
5、Jwt管理接口
/// <summary> /// 用于生成Token的接口 /// </summary> public interface IAuthManage { /// <summary> /// 生成JwtToken /// </summary> /// <param name="user">用户信息</param> /// <returns></returns> string GenerateJwtToken(UserInfo user); }
6、接口实现类
/// <summary> /// 用于生成token的实现类 /// </summary> public class MicrosoftJwtAuthManage : IAuthManage { private readonly JWTTokenOptions _authOptions; public MicrosoftJwtAuthManage(JWTTokenOptions authOptions) { _authOptions = authOptions; } public string GenerateJwtToken(UserInfo user) { var days = _authOptions.Expire.ToString(); var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_authOptions.SecurityKey); //获取用于生成Token的key var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new Claim[] { new Claim("userInfo",user.ToJson()) //将实体类转json存入Token中 }), Expires = DateTime.UtcNow.AddDays(_authOptions.Expire),//过期时间 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } }
7、验证Token的中间件
/// <summary> /// JWT中间件 用于验证Token信息的 /// </summary> public class JwtMiddleware { private readonly RequestDelegate _next; private readonly JWTTokenOptions _authOptions; public JwtMiddleware(RequestDelegate next, JWTTokenOptions authOptions) { _next = next; _authOptions = authOptions; } public async Task Invoke(HttpContext context) { //获取传递过来的Token,可自定义扩展 var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last() ?? context.Request.Headers["Token"].FirstOrDefault() ?? context.Request.Query["Token"].FirstOrDefault() ?? context.Request.Cookies["Token"]; if (token != null) AttachUserToContext(context, token); await _next(context); } private void AttachUserToContext(HttpContext context, string token) { try { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.UTF8.GetBytes(_authOptions.SecurityKey); //获取到用于生成token的加密key tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, //是否验证Key SecurityKey IssuerSigningKey = new SymmetricSecurityKey(key), //拿到SecurityKey ValidateIssuer = false, //是否验证Issuer ValidateAudience = false, //是否验证Audience ValidateLifetime = true, //是否验证Token的失效时间 }, out SecurityToken validatedToken); var jwtToken = (JwtSecurityToken)validatedToken; var user = jwtToken.Claims.First(x => x.Type == "userInfo").Value; //获取Token中存入的用户信息 var userModel = Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(user); //写入认证信息,方便业务类使用 var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim("userInfo", jwtToken.Claims.First(x => x.Type == "userInfo").Value) }); Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); //将Token中解析出来的用户信息存入当前请求中 context.Items["UserModel"] = userModel; } catch(Exception ex) { context.Items["UserModel"] = null; } } }
8、全局权限认证过滤器
/// <summary> /// 鉴权,身份验证授权的过滤器 /// </summary> public class ApiAuthorizeAttribute : IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { var user = context.HttpContext.Items["UserModel"]; //context.HttpContext.Response.WriteAsync("我是身份验证过滤器"); //验证是否需要授权和授权信息 if (HasAllowAnonymous(context) == false && user == null) { context.Result = new JsonResult(new { code = StatusCodes.Status401Unauthorized, message = "Token验证失败或已过期请重新登录并获取!" }); //也可自定义返回的HTTP code //{ StatusCode = StatusCodes.Status401Unauthorized }; } } private static bool HasAllowAnonymous(AuthorizationFilterContext context) { var filters = context.Filters; if (filters.OfType<IAllowAnonymousFilter>().Any()) { return true; } var endpoint = context.HttpContext.GetEndpoint(); return endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null; } }
9、Startup里 ConfigureServices 添加
#region JWT配置 //读取配置文件中的JWTTokenOptions字段 services.Configure<JWTTokenOptions>(this.Configuration.GetSection("JWTTokenOptions")); //将字段中的值赋值给 JWTTokenOptions 实体类 JWTTokenOptions tokenOptions = new JWTTokenOptions(); Configuration.Bind("JWTTokenOptions", tokenOptions); //依赖注入 将配置注入 services.AddSingleton(tokenOptions); services.AddSingleton<IAuthManage>(new MicrosoftJwtAuthManage(tokenOptions)); #endregion
10、Startup里 Configure 添加
//启用jwt认证中间件 app.UseMiddleware<JwtMiddleware>();
11、生成Token
public class LoginController : Controller { private IAuthManage _AuthManage = null; /// <summary> /// 用于获取jwt配置 /// </summary> private readonly JWTTokenOptions _JWTTokenOptions; public LoginController(IAuthManage AuthManage, IOptionsMonitor<JWTTokenOptions> jwtTokenOptions) { _AuthManage = AuthManage; _JWTTokenOptions = jwtTokenOptions.CurrentValue; } /// <summary> /// 登录操作 /// </summary> /// <returns></returns> [HttpGet("Login")] public IActionResult Index() { UserInfo user = new UserInfo(); user.userId = "55114d44-a2d6-4f1a-a749-0618ca542591"; user.userName = "张三"; user.roleType = "sys_p_user";//读取配置的过期时间 单位为天 double expire = Convert.ToDouble(_JWTTokenOptions.Expire); var expireTime = DateTime.Now.AddDays(expire); //Token的过期日期 user.expiresDate = expireTime;//生成Token string token = this._AuthManage.GenerateJwtToken(user); var result = new { Result = true, Token= token
};
return Ok(result);
}
获取存入请求中的UserInfo信息
var userInfo= (UserInfo)this.HttpContext.Items["UserModel"];
如果个别接口不需要认证,可以使用 [AllowAnonymous] 特性,可以放在接口控制器或方法上都可以。
总结
1、JwtMiddleware 类文件为验证Token的中间件,如果验证通过把token中的信息存入上下文中
2、ApiAuthorizeAttribute 类为鉴权的过滤器,查看当前上下文中是否有存入的token信息,如果没有则返回自定义的消息
3、在Startup中无需配置太多东西,只需要注入JwtMiddleware中间件、注入JWTTokenOptions(jwt的配置)、添加一个全局的鉴权认证过滤器(ApiAuthorizeAttribute)即可
使用方便可且可以自由定义和扩展!
才疏学浅,相关文档等仅供自我总结,如有相关问题可留言交流谢谢。