在上节中花费了大量精力实现了用户登录、注销的基本功能。仿佛早已忘掉了加入用户登录功能的初衷:之所以加入用户登录功能是为了保护一些后台的资源,使得只有当用户成功登录后才可以获取后台的一些数据。比如认为只有拥有权理员权限的用户才可以获取后台的全部教师数据、认为只有班级对应的教师才能获取该班级中的学生信息等。
当前的豆腐渣工程的登录注销功能形同虚设,用户无需认证便可以通过浏览器或是其它的rest工具(比如postman或idea自带的rest client、http request)对后台进行肆意操作,无论是教师用户的获取还是新增。一个形同虚设的登录功能只是挡住了一些**正常**的用户,但对一些**非法**用户的入侵却毫无防范。甚至于后台根本就没有能力判断是谁正在进行数据请求,权限控制当然也就无从谈起。
此系统若被上线到生产环境,便开始诠释什么是彻头彻尾的裸奔。
# 你是谁
要解决用户认证的关键问题便是**你是谁**的问题,需要为前后的交互制定一个规则,后台可以根据此规则获取(计算)当前的访问是由哪个用户具体发起的。有此基础以后,便可以根据当前的访问用户进行真正的用户认证了:后台可以对每次请求进行相应的权限判断,拒绝没有权限的访问请求,允许有访问权限的请求。
## 用户名认证
只要前台的每次访问请求都带有**我是谁**的信息,那么后台便可以根据前台带有**我是谁**的信息进行权限判断。
![](https://img.kancloud.cn/56/17/5617536e047d3408a482178f95581844_606x371.png)
上图这种每次请求都传输**我是谁**的信息的致命缺点是后台无法确认前台是否在**撒谎**,前台说自己是张三而实际上是不是张三后台无从得知。所以这种认证模式往往应用在前端可信的场合。比如服务器各个web服务间的内部调用。而当前的系统将发布到internet,世界上任何接入互联网的用户均可以向它发起访问,这个环境是个完全不可信的环境,所以这种认证方式并不适合。
## 用户名+密码认证
为了防止前台**说谎**,可以加一个密码做为校验。前台每次向后台发起请求时把用户名和密码一并传给后台。后台接收到带有用户名密码的请求后,查询相应的数据库以进行匹配。用户名密码匹配成功则说明前台没有说谎,用户名密码匹配失败则说明前台在说谎。
![](https://img.kancloud.cn/2a/82/2a82e3e307dad64ec1e4b383b64602d1_652x329.png)
在简单的应用中上述方案是可行的。由于登录组件是用户初次使用本系统时的第一个组件,所以可以在用户登录成功后将登录的用户名与密码信息存储到浏览器缓存中(参考上一小节存储登录状态的思想)。然后在后续的所以请求中,都获取缓存中的用户名与密码信息,并加入到请求数据中。但此方案最少面临以下两个问题:
问题一:如果系统部署后采用的是http传输协议,那么所有的数据在网络上都将以明文进行传输,提供网络服务的任何结点都可以捕获此传输信息并且可以轻松的获取传输中的用户名及密码的值。
问题二:无法处理单用户多点登录的问题。比如有用户同时使用了手机应用及WEB应用进行登录,则当用户的任意终端进行密码修改操作后都将使得另一终端的登录信息失败。
![](https://img.kancloud.cn/7c/ec/7cecf21370687a481b3cbea73472bf24_509x359.png)
![](https://img.kancloud.cn/78/38/7838034c5070a7718ed7edf31ffbc00d_614x363.png)
基于以上原因该认证方法被使用在一些对安全性要求不高的环境中。
## 一卡通认证
学校的一卡通已存在很长时间,相信也将在很长的一段时间内继续存在。本质上来讲一卡通的存在正是解决了校园中**你是谁**的问题。一卡通做为在校园中的认证凭证可以用在上机、洗澡、考试、食堂等众多校园服务中。机房中使用一卡通来确认**你是谁**、考试中使用一卡通来确认**你是谁**,食堂中使用一卡通来确认**你是谁**。
在前台端的认证领域,**一卡通**仍然是当下最受欢迎的认证方式。既然如此,让我们共同回顾一下**一卡通**的使用流程。
* ➊ 开学初凭入学通知书、身份证、报考证明等材料确认你的身份,并将你的身份系统维护至数据库
* ➋ 发放在校园内具有唯一性的**一卡通**,并在数据库中将此**一卡通**与你的身份进行关联
* ➌ 在以后的校园活动中,将此**一卡通**做为唯一的凭证。
**一卡通**原理应用到前后端认证中解决了前面两种方案面临的基本问题:
* ➊ 对用户的认证环节只发生在核验身份一个环节。涉及的环节越少,数据被截取的风险就越低。解决了由于多次传输用户名密码带来的用户名密码泄漏风险。同时在传输过程(比如同学替你在食堂打饭)中的**一卡通**仅仅显示了学生的基本信息,即使**一卡通**被截取,也无法通过直接获取到学生的敏感信息。
* ➋ 有效的解决了多点登录的问题:由于前后台交互是使用的**一卡通**,并不涉及用户的**密码**,所以当**密码**进行变更时不会对**一卡通**的有效性造成任何的问题。
前后台分离的应用在进行认证时与现实生活中学校发放**一卡通**稍有不同,具体描述如下:
* ① 后台在发放**一卡通**时更加激进一些,它会为每位进入学校的人(前台发起的请求)发送一张具有一定有效期的**一卡通**,而无论进入学校的人是否是学校的学生。
* ② 有了**一卡通**的人以后再访问后台时都会主动的携带该**一卡通**,后台会在人们进入校园的时候审核该**一卡通**是否有效,如果发现携带的**一卡通**失败则会重新为其发送一张全新的**一卡通**。
* ③ 后台提供**一卡通**与在校学生的绑定服务,在校学生可以携带有效证件将**一卡通**与自己进行绑定。
* ④ 后台还提供**一卡通**与在校学生的解绑或重新绑定服务,这使得**一卡通**可以在有效期内可以被多个学生使用。
* ⑤ 进入校园的人在访问后台的公共资源时,所有人都会得到放行。
* ⑥ 只有当人们想访问一些只提供给本校学生的特定资源时,后台才会校验**一卡通**是否已绑定了在校学生。
>[success] 在前后台分离的应用中,将**一卡通**称为令牌(token),将用于认证的令牌习惯性的称为认证令牌(auth-token)。
结合刚刚的描述信息,我们认为实现一个完整的用户认证功能需要依次解决以下问题:
① 后台向前台发送token的问题
② 前台获取token的问题
③ 前后发请请求携带后台分发的token的问题
④ token与认证用户绑定(重新绑定)的问题
⑤ token与认证用户解绑的问题
⑥ 只对特定的后台请求进行认证的问题
# header
前面的章节中进行后台请求时,大量的使用了`HttpClient`,比如`httpClient.get()`,`httpClient.post()`等。使用`HttpClient`发起后台请求时,我们更多的是在关心请求的地址(url)及在请求时发送的数据。比如`httpClient.post(url, data);`表示向`url`地址发起`post`请求并发送`data`数据。其实在前后台的数据传输过程中,被传输的数据由两部分组成。一部分是用于发送`data`的主体,又被称为`body`;还有一部分用于发送一些附加数据,又被称为`header`。在使用`httpClient.post(url, data)`等发送数据请求时,浏览器与spring都会在这个附加数据上做文章,比如在教程开始时浏览器识别到后台并没有设置`CORS`,近期在控制台打印了相关的异常信息,便是浏览器通过分析后台返回的附加数据实现的。比如在新增学生时,实际上发生的远比我们前面想到的多:
![](https://img.kancloud.cn/54/29/5429b57134f856048b782b6e7fd138f6_1009x720.png)
<hr>
在控制台中打开网络选项卡后,新增一个学生并查看生成的网络请求信息,验证如下:
发起请求时附带的header信息:
![](https://img.kancloud.cn/c6/ad/c6ad92a7720f4d71211ed4fd48a58d37_989x478.png)
发起请求时传递的主体数据body:
![](https://img.kancloud.cn/86/0c/860cabcf90bb271f8ace45a67947a8ff_423x240.png)
响应的header信息:
![](https://img.kancloud.cn/48/6b/486bea67940870a0a70805564c410ed2_408x295.png)
响应的主体数据body:
![](https://img.kancloud.cn/a5/7f/a57f44a3fa4ca7f004a1192f847c3579_465x143.png)
<hr>
我们所要完成的token的传递,也将借助于前后台进行数据交互时的header部分:
![](https://img.kancloud.cn/70/53/70532586c14221fabdca8b91ed7ecb99_743x552.png)
理论有了接下来让我们step by step逐步地实现用户认证过程。
# 预期效果
![](https://img.kancloud.cn/b6/5f/b65f4927c97f95a83b637ff1e16baee8_1418x395.gif)
- 序言
- 第一章:Hello World
- 第一节:Angular准备工作
- 1 Node.js
- 2 npm
- 3 WebStorm
- 第二节:Hello Angular
- 第三节:Spring Boot准备工作
- 1 JDK
- 2 MAVEN
- 3 IDEA
- 第四节:Hello Spring Boot
- 1 Spring Initializr
- 2 Hello Spring Boot!
- 3 maven国内源配置
- 4 package与import
- 第五节:Hello Spring Boot + Angular
- 1 依赖注入【前】
- 2 HttpClient获取数据【前】
- 3 数据绑定【前】
- 4 回调函数【选学】
- 第二章 教师管理
- 第一节 数据库初始化
- 第二节 CRUD之R查数据
- 1 原型初始化【前】
- 2 连接数据库【后】
- 3 使用JDBC读取数据【后】
- 4 前后台对接
- 5 ng-if【前】
- 6 日期管道【前】
- 第三节 CRUD之C增数据
- 1 新建组件并映射路由【前】
- 2 模板驱动表单【前】
- 3 httpClient post请求【前】
- 4 保存数据【后】
- 5 组件间调用【前】
- 第四节 CRUD之U改数据
- 1 路由参数【前】
- 2 请求映射【后】
- 3 前后台对接【前】
- 4 更新数据【前】
- 5 更新某个教师【后】
- 6 路由器链接【前】
- 7 观察者模式【前】
- 第五节 CRUD之D删数据
- 1 绑定到用户输入事件【前】
- 2 删除某个教师【后】
- 第六节 代码重构
- 1 文件夹化【前】
- 2 优化交互体验【前】
- 3 相对与绝对地址【前】
- 第三章 班级管理
- 第一节 JPA初始化数据表
- 第二节 班级列表
- 1 新建模块【前】
- 2 初识单元测试【前】
- 3 初始化原型【前】
- 4 面向对象【前】
- 5 测试HTTP请求【前】
- 6 测试INPUT【前】
- 7 测试BUTTON【前】
- 8 @RequestParam【后】
- 9 Repository【后】
- 10 前后台对接【前】
- 第三节 新增班级
- 1 初始化【前】
- 2 响应式表单【前】
- 3 测试POST请求【前】
- 4 JPA插入数据【后】
- 5 单元测试【后】
- 6 惰性加载【前】
- 7 对接【前】
- 第四节 编辑班级
- 1 FormGroup【前】
- 2 x、[x]、{{x}}与(x)【前】
- 3 模拟路由服务【前】
- 4 测试间谍spy【前】
- 5 使用JPA更新数据【后】
- 6 分层开发【后】
- 7 前后台对接
- 8 深入imports【前】
- 9 深入exports【前】
- 第五节 选择教师组件
- 1 初始化【前】
- 2 动态数据绑定【前】
- 3 初识泛型
- 4 @Output()【前】
- 5 @Input()【前】
- 6 再识单元测试【前】
- 7 其它问题
- 第六节 删除班级
- 1 TDD【前】
- 2 TDD【后】
- 3 前后台对接
- 第四章 学生管理
- 第一节 引入Bootstrap【前】
- 第二节 NAV导航组件【前】
- 1 初始化
- 2 Bootstrap格式化
- 3 RouterLinkActive
- 第三节 footer组件【前】
- 第四节 欢迎界面【前】
- 第五节 新增学生
- 1 初始化【前】
- 2 选择班级组件【前】
- 3 复用选择组件【前】
- 4 完善功能【前】
- 5 MVC【前】
- 6 非NULL校验【后】
- 7 唯一性校验【后】
- 8 @PrePersist【后】
- 9 CM层开发【后】
- 10 集成测试
- 第六节 学生列表
- 1 分页【后】
- 2 HashMap与LinkedHashMap
- 3 初识综合查询【后】
- 4 综合查询进阶【后】
- 5 小试综合查询【后】
- 6 初始化【前】
- 7 M层【前】
- 8 单元测试与分页【前】
- 9 单选与多选【前】
- 10 集成测试
- 第七节 编辑学生
- 1 初始化【前】
- 2 嵌套组件测试【前】
- 3 功能开发【前】
- 4 JsonPath【后】
- 5 spyOn【后】
- 6 集成测试
- 7 @Input 异步传值【前】
- 8 值传递与引入传递
- 9 @PreUpdate【后】
- 10 表单验证【前】
- 第八节 删除学生
- 1 CSS选择器【前】
- 2 confirm【前】
- 3 功能开发与测试【后】
- 4 集成测试
- 5 定制提示框【前】
- 6 引入图标库【前】
- 第九节 集成测试
- 第五章 登录与注销
- 第一节:普通登录
- 1 原型【前】
- 2 功能设计【前】
- 3 功能设计【后】
- 4 应用登录组件【前】
- 5 注销【前】
- 6 保留登录状态【前】
- 第二节:你是谁
- 1 过滤器【后】
- 2 令牌机制【后】
- 3 装饰器模式【后】
- 4 拦截器【前】
- 5 RxJS操作符【前】
- 6 用户登录与注销【后】
- 7 个人中心【前】
- 8 拦截器【后】
- 9 集成测试
- 10 单例模式
- 第六章 课程管理
- 第一节 新增课程
- 1 初始化【前】
- 2 嵌套组件测试【前】
- 3 async管道【前】
- 4 优雅的测试【前】
- 5 功能开发【前】
- 6 实体监听器【后】
- 7 @ManyToMany【后】
- 8 集成测试【前】
- 9 异步验证器【前】
- 10 详解CORS【前】
- 第二节 课程列表
- 第三节 果断
- 1 初始化【前】
- 2 分页组件【前】
- 2 分页组件【前】
- 3 综合查询【前】
- 4 综合查询【后】
- 4 综合查询【后】
- 第节 班级列表
- 第节 教师列表
- 第节 编辑课程
- TODO返回机制【前】
- 4 弹出框组件【前】
- 5 多路由出口【前】
- 第节 删除课程
- 第七章 权限管理
- 第一节 AOP
- 总结
- 开发规范
- 备用