­

Core + Vue 後台管理基礎框架2——認證

  • 2020 年 3 月 12 日
  • 筆記

1、前言

  這塊兒當時在IdentityServer4和JWT之間猶豫了一下,後來考慮到現狀,出於3個原因,暫時放棄了IdentityServer4選擇了JWT:

(1)目前這個前端框架更適配JWT;

(2)前後端分離的項目,如果上IdentityServer4,還要折騰點兒工作,比如前端配置、多餘的回調等;

(3)跨度太大,團隊、系統、歷史數據接入都是問題,解決是可以解決,但時間有限,留待後續吧;

  當然,只是暫時放棄,理想中的最佳實踐還是IdentityServer4做統一鑒權的。

2、JWT認證實現

(1)Common項目下定義JWTConfig配置對象

 

 (2)系統配置文件中增加JWT參數配置

 

 

 此處配置與(1)中的配置對象是對應的。

 

(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這種瀏覽器中可以跨域共享的存儲就可以了。

3、總結

  以上就是系統認證的實現,大家摸清楚各種認證方案、優缺點、特點,多深入源碼、機制,遇到問題自然會手到擒來。

SET_TOKEN