Core + Vue 后台管理基础框架2——认证
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对象,这样会导致前端丢失会话。
3、总结
以上就是系统认证的实现,大家摸清楚各种认证方案、优缺点、特点,多深入源码、机制,遇到问题自然会手到擒来。
SET_TOKEN
- JavaScript之arguements对象学习
- 让我们一起写出更有效的CSharp代码吧,少年们!
- SQL学习之计算字段的用法与解析
- JavaScript之JS的执行环境和作用域
- GOF设计模式快速学习
- JavaScript之面向对象学习一
- JavaScript之JS实现动画效果
- 深入入门系列--Data Structure--04树
- SQL学习之高级联结(自联结、自然联结、外联接)
- ExtJs学习笔记(21)-使用XTemplate结合WCF显示数据
- JavaScript之面向对象的概念,对象属性和对象属性的特性简介
- 快速入门系列--WebAPI--04在老版本MVC4下的调整
- SQL学习之SELECT子句顺序
- [图解]sqlserver中创建链接服务器
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 详解Android Gradle插件3.0挖坑日记
- Android开发之拼音转换工具类PinyinUtils示例
- Android多线程断点续传下载示例详解
- Android设备与外接U盘实现数据读取操作的示例
- [Alibaba-ARouter]浅谈简单好用的Android页面路由框架
- android屏幕圆角实现方法的示例代码
- Android开发中日期工具类DateUtil完整实例
- Android模仿实现微博详情页滑动固定顶部栏的效果实例
- Android EventBus(普通事件/粘性事件)详解
- Android实现EventBus登录界面与传值(粘性事件)
- Android自定义LinearLayout布局显示不完整的解决方法
- android短信管理器SmsManager实例详解
- Android开发判断一个app应用是否在运行的方法详解
- 收割腾讯等十几个Offer后,揭秘进大厂的秘诀和Android技术面试题汇总!
- Flutter BLoC 异步通信、BlocBuilder的基本使用、BlocProvider的初探