本文實例環境及版本 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)即可
使用方便可且可以自由定義和擴展!
才疏學淺,相關文檔等僅供自我總結,如有相關問題可留言交流謝謝。