一、前言前端
这块儿当时在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这种浏览器中能够跨域共享的存储就能够了。
三、总结
以上就是系统认证的实现,你们摸清楚各类认证方案、优缺点、特色,多深刻源码、机制,遇到问题天然会手到擒来。