1, 前言
这块儿当时在 IdentityServer4 和 JWT 之间犹豫了一下, 后来考虑到现状, 出于 3 个原因, 暂时放弃了 IdentityServer4 选择了 JWT:
(1)目前这个前端框架更适配 JWT;
(2)前后端分离的项目, 如果上 IdentityServer4, 还要折腾点儿工作, 比如前端配置, 多余的回调等;
(3)跨度太大, 团队, 系统, 历史数据接入都是问题, 解决是可以解决, 但时间有限, 留待后续吧;
当然, 只是暂时放弃, 理想中的最佳实践还是 IdentityServer4 做统一鉴权的.
2,JWT 认证实现
(1)Common 项目下定义 JWTConfig 配置对象
(2)系统配置文件中增加 JWT 参数配置
此处配置与 (1) 中的配置对象是对应的.
(3)JWT 处理程序及相关服务注册
- services.Configure<JWTConfig>(Configuration.GetSection("JWT"));
- var jwtConfig = Configuration.GetSection("JWT").Get<JWTConfig>();
- services.AddAuthentication(options =>
- {
- options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
- options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
- })
- .AddJwtBearer(options =>
- {
- options.TokenValidationParameters = new TokenValidationParameters
- {
- ValidateIssuer = true,
- ValidIssuer = jwtConfig.Issuer,
- ValidateIssuerSigningKey = true,
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SymmetricSecurityKey)),
- ValidateAudience = false,
- ValidateLifetime = true,
- ClockSkew = TimeSpan.FromMinutes(5)
- };
- options.Events = new JwtBearerEvents
- {
- OnTokenValidated = context =>
- {
- var userContext = context.HttpContext.RequestServices.GetService<UserContext>();
- var claims = context.Principal.Claims;
- userContext.ID = long.Parse(claims.First(x => x.Type == JwtRegisteredClaimNames.Sub).Value);
- userContext.Account = claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
- userContext.Name = claims.First(x => x.Type == ClaimTypes.Name).Value;
- userContext.Email = claims.First(x => x.Type == JwtRegisteredClaimNames.Email).Value;
- userContext.RoleId = claims.First(x => x.Type == ClaimTypes.Role).Value;
- return Task.CompletedTask;
- }
- };
- });
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
上述代码中注意红色那部分 Token 验证成功的事件注册, 其目的是认证成功之后, 从 JWT 中取出必要信息构建当前用户上下文, 这个上下文信息非常重要, 但凡涉及到需要获取当前用户相关信息的部分, 都要依赖它, 后续文章中对应部分还会提及.
(4)登录并写入 Token
- /// <summary>
- /// 登录
- /// </summary>
- /// <param name="userDto"></param>
- /// <returns></returns>
- [AllowAnonymous]
- [HttpPost("login")]
- public async Task<IActionResult> Login([FromBody]SysUserDto userDto)
- {
- var validateResult = await _accountService.ValidateCredentials(userDto.Account, userDto.Password);
- if (!validateResult.Item1)
- {
- return new NotFoundObjectResult("用户名或密码错误");
- }
- var user = validateResult.Item2;
- var claims = new Claim[]
- {
- new Claim(ClaimTypes.NameIdentifier, user.Account),
- new Claim(ClaimTypes.Name, user.Name),
- new Claim(JwtRegisteredClaimNames.Email, user.Email),
- new Claim(JwtRegisteredClaimNames.Sub, user.ID.ToString()),
- new Claim(ClaimTypes.Role, user.RoleId)
- };
- var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.SymmetricSecurityKey));
- var token = new JwtSecurityToken(
- issuer: _jwtConfig.Issuer,
- audience: null,
- claims: claims,
- notBefore: DateTime.Now,
- expires: DateTime.Now.AddHours(2),
- signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
- );
- var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
- return new JsonResult(new { token = jwtToken });
- }
(5)前端 Token 状态保存
一般来讲, 在后端登录成功返回前端之后, 前端需要保存此 token 以保持状态, 否则一刷新全完蛋, 那我们来看看前端怎么实现. 由于前端项目引入了 vuex 来保持状态, 那 API 调用, 状态操作自然就放在 store 中, 我们来看看登录对应的 store. 打开前端源码, 找到 user 这个 store:
我们看到, 登录完毕, 调用 SET_TOKEN 这个 mutation 提交 token 状态变更保存 Token, 这个 mutation 及其背后的 state 如下如下:
同时, 在登录 action 中, 登录成功之后, 我们还发现了一行代码:
此 setToken 引自前端工具类, auth.JS, 代码如下:
- import Cookies from 'js-cookie'
- const TokenKey = 'ngcc_mis_token'
- export function getToken() {
- return Cookies.get(TokenKey)
- }
- export function setToken(token) {
- return Cookies.set(TokenKey, token)
- }
- export function removeToken() {
- return Cookies.remove(TokenKey)
- }
至此我们明白了前端的机制, 把 token 写入 Vuex 状态的同时, 再写入 cookie. 那这里问一句, 只写入 Vuex 状态, 行不行呢? 不行, 因为浏览器一刷新, 所有前端对象就会销毁, 包括 Vuex 对象, 这样会导致前端丢失会话. 同时, 我们再扩展深入下, 假如这里我们要做点单登录, 不借助 IdentityServer4 这种统一认证平台的话, 怎么做呢? 其实很简单, 这里写入 cookie 改为写入 localstoage 这种浏览器中可以跨域共享的存储就可以了.
3, 总结
以上就是系统认证的实现, 大家摸清楚各种认证方案, 优缺点, 特点, 多深入源码, 机制, 遇到问题自然会手到擒来.
SET_TOKEN
来源: https://www.cnblogs.com/guokun/p/12483215.html