一、前言前端
這塊兒當時在IdentityServer4和JWT之間猶豫了一下,後來考慮到現狀,出於3個緣由,暫時放棄了IdentityServer4選擇了JWT:後端
(1)目前這個前端框架更適配JWT;api
(2)先後端分離的項目,若是上IdentityServer4,還要折騰點兒工做,好比前端配置、多餘的回調等;跨域
(3)跨度太大,團隊、系統、歷史數據接入都是問題,解決是能夠解決,但時間有限,留待後續吧;瀏覽器
固然,只是暫時放棄,理想中的最佳實踐仍是IdentityServer4作統一鑑權的。前端框架
二、JWT認證明現cookie
(1)Common項目下定義JWTConfig配置對象框架
(2)系統配置文件中增長JWT參數配置前後端分離
此處配置與(1)中的配置對象是對應的。async
(3)JWT處理程序及相關服務註冊
1 services.Configure<JWTConfig>(Configuration.GetSection("JWT")); 2 var jwtConfig = Configuration.GetSection("JWT").Get<JWTConfig>(); 3 services.AddAuthentication(options => 4 { 5 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 6 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 7 }) 8 .AddJwtBearer(options => 9 { 10 options.TokenValidationParameters = new TokenValidationParameters 11 { 12 ValidateIssuer = true, 13 ValidIssuer = jwtConfig.Issuer, 14 ValidateIssuerSigningKey = true, 15 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SymmetricSecurityKey)), 16 ValidateAudience = false, 17 ValidateLifetime = true, 18 ClockSkew = TimeSpan.FromMinutes(5) 19 }; 20 options.Events = new JwtBearerEvents 21 { 22 OnTokenValidated = context => 23 { 24 var userContext = context.HttpContext.RequestServices.GetService<UserContext>(); 25 var claims = context.Principal.Claims; 26 userContext.ID = long.Parse(claims.First(x => x.Type == JwtRegisteredClaimNames.Sub).Value); 27 userContext.Account = claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; 28 userContext.Name = claims.First(x => x.Type == ClaimTypes.Name).Value; 29 userContext.Email = claims.First(x => x.Type == JwtRegisteredClaimNames.Email).Value; 30 userContext.RoleId = claims.First(x => x.Type == ClaimTypes.Role).Value; 31 32 return Task.CompletedTask; 33 } 34 }; 35 }); 36 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
上述代碼中注意紅色那部分Token驗證成功的事件註冊,其目的是認證成功以後,從JWT中取出必要信息構建當前用戶上下文,這個上下文信息很是重要,但凡涉及到須要獲取當前用戶相關信息的部分,都要依賴它,後續文章中對應部分還會說起。
(4)登陸並寫入Token
1 /// <summary> 2 /// 登陸 3 /// </summary> 4 /// <param name="userDto"></param> 5 /// <returns></returns> 6 [AllowAnonymous] 7 [HttpPost("login")] 8 public async Task<IActionResult> Login([FromBody]SysUserDto userDto) 9 { 10 var validateResult = await _accountService.ValidateCredentials(userDto.Account, userDto.Password); 11 if (!validateResult.Item1) 12 { 13 return new NotFoundObjectResult("用戶名或密碼錯誤"); 14 } 15 16 var user = validateResult.Item2; 17 var claims = new Claim[] 18 { 19 new Claim(ClaimTypes.NameIdentifier, user.Account), 20 new Claim(ClaimTypes.Name, user.Name), 21 new Claim(JwtRegisteredClaimNames.Email, user.Email), 22 new Claim(JwtRegisteredClaimNames.Sub, user.ID.ToString()), 23 new Claim(ClaimTypes.Role, user.RoleId) 24 }; 25 26 var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.SymmetricSecurityKey)); 27 28 var token = new JwtSecurityToken( 29 issuer: _jwtConfig.Issuer, 30 audience: null, 31 claims: claims, 32 notBefore: DateTime.Now, 33 expires: DateTime.Now.AddHours(2), 34 signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) 35 ); 36 37 var jwtToken = new JwtSecurityTokenHandler().WriteToken(token); 38 39 return new JsonResult(new { token = jwtToken }); 40 }
(5)前端Token狀態保存
通常來說,在後端登陸成功返回前端以後,前端須要保存此token以保持狀態,不然一刷新全完蛋,那咱們來看看前端怎麼實現。因爲前端項目引入了Vuex來保持狀態,那api調用、狀態操做天然就放在store中,咱們來看看登陸對應的store。打開前端源碼,找到user這個store:
咱們看到,登陸完畢,調用SET_TOKEN這個mutation提交token狀態變動保存Token,這個mutation及其背後的state以下以下:
同時,在登陸action中,登陸成功以後,咱們還發現了一行代碼:
此setToken引自前端工具類,auth.js,代碼以下:
1 import Cookies from 'js-cookie' 2 3 const TokenKey = 'ngcc_mis_token' 4 5 export function getToken() { 6 return Cookies.get(TokenKey) 7 } 8 9 export function setToken(token) { 10 return Cookies.set(TokenKey, token) 11 } 12 13 export function removeToken() { 14 return Cookies.remove(TokenKey) 15 }
至此咱們明白了前端的機制,把token寫入Vuex狀態的同時,再寫入cookie。那這裏問一句,只寫入Vuex狀態,行不行呢?不行,由於瀏覽器一刷新,全部前端對象就會銷燬,包括Vuex對象,這樣會致使前端丟失會話。同時,咱們再擴展深刻下,假如這裏咱們要作點單登陸,不借助IdentityServer4這種統一認證平臺的話,怎麼作呢?其實很簡單,這裏寫入cookie改成寫入localstoage這種瀏覽器中能夠跨域共享的存儲就能夠了。
三、總結
以上就是系統認證的實現,你們摸清楚各類認證方案、優缺點、特色,多深刻源碼、機制,遇到問題天然會手到擒來。