若想在每次请求时均在后台处理以下逻辑:
![](https://img.kancloud.cn/70/fe/70fe2ea55b9d93f1a3a01ff719a3fec0_296x194.png)
按当前掌握的知识便需要在每个控制器的方法中加入相应的处理认证令牌的代码。以上个章节中刚刚完成的StudentController为例,示例代码如下:
```java
public class StudentController {
...
@GetMapping("{id}")
public Student getById(@PathVariable Long id) {
// 进行令牌认证与分发 ➊
return this.studentService.findById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Student save(@RequestBody Student student) {
// 进行令牌认证与分发 ➋
return studentService.save(student);
}
```
* ➊➋ 在进行逻辑处理前先进行令牌的验证与分发。
这种方案是可行的,按此方案则需要先开发一个验证与分发令牌的方法,然后在各个控制器的方法中来调用此方法来完成令牌分发的操作。
同时这种方案由于对原有代码进行了过多的干预,所以必然也不是最佳的。在团队开发的过程中,我们希望能够将认证的模块与其它的模块可以做到单独、并列开发。每个模块都是互相独立的。尽量的降低各个模块间的耦合度。
# filter过滤器
成熟的spring当前早早的就已经解决了此类问题并形成了最佳实践,那就是filter过滤器。在计算机的世界里,过滤器与现实生活中稍有不同。现实生活中的过滤器的最终目的都是把不符合要求的过滤掉,把符合要求的留下来,所以过滤后的物质不会多于过滤前的物质。而计算机中的过滤器即可以越过滤越少,也可以越过滤越多。过滤后的数据的多少完全取决于所实现的代码。
不止如此spring中的过滤器在进行http请求过滤时,不但可以过滤请求的数据,还可以过滤返回的数据。加入过滤器器具前后台的令牌交互的过程如下图:
![](https://img.kancloud.cn/2a/cd/2acdc94ee4440132f4a062dbf7232b9b_809x471.png)
## 实现过滤器
使用idea打开后台项目,并在项目根目录下新建`filter`包,然后在此包下新建`TokenFilter`类。
![](https://img.kancloud.cn/a4/60/a4603eb777d1481b5411047c21952271_506x321.png)
初始化如下:
```
package com.mengyunzhi.springbootstudy.filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 令牌过滤器
* 继承HttpFilter以过滤http请求与响应
* @author panjie
*/
public class TokenFilter extends HttpFilter ➊{
@Override ➋
protected void doFilter(HttpServletRequest request➌, HttpServletResponse response➍, FilterChain chain) throws IOException, ServletException {
// 获取 header中的token并做有效性验证
// 如果无效则分发送的token
// 转发数据。spring开始调用控制器中的特定方法
chain.doFilter(request, response);
// 为http响应加入新token后返回
}
}
```
* ➊ 继承HttpFilter。该HttpFilter进行一些自动判断后将数据发送到doFilter方法。
* ➋ 此doFilter方法已存在于HttpFilter中,所以在此以关键字声明
* ➌ 改变此对象可以达到改变http请求的目的
* ➍ 改变此对象可以达到改变http响应的目的
## 配置过滤器
若想使某个过滤器生效,还需要:一、对项目进行配置以启用其打描过滤器的功能;二、使用特定的注解来告知spring某个过滤器此时的状态为**生效**。
有了这两项配置以后spring在启动应用时便会将声明了**生效**状态的过滤器加入到项目中来了。此时若有http请求则会执行过滤器中的代码。在进行配置以前,分别于TokenFilter及TeacherController中加入以下日志代码以更好的在控制台中查看其执行过程。
filter/TokenFilter.java
```java
public class TokenFilter extends HttpFilter {
private final static Logger logger = LoggerFactory.getLogger(TokenFilter.class); ✚
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 获取 header中的token并做有效性验证
// 如果无效则分发送的token
logger.info("在控制器被调用以前执行"); ✚
// 转发数据。spring开始调用控制器中的特定方法
chain.doFilter(request, response);
logger.info("在控制器被调用以后执行"); ✚
// 为http响应加入新token后返回
}
}
```
controller/TeacherController.java
```java
@GetMapping
@CrossOrigin("*")
public List<Teacher> getAll() {
logger.info("调用TeacherController的getAll方法");
...
```
然后于IDEA中建立一个Http Request(忘记如何建立的话请参数2.4.2)测试,内容如下:
```
GET http://localhost:8080/Teacher
```
启动数据库及后台应用后,执行该测试并观察控制台:
![](https://img.kancloud.cn/70/03/70031102f47f7c183d6a6b531379b40b_654x136.png)
```
2020-02-10 16:39:50.733 INFO 24047 --- [nio-8080-exec-4] c.m.s.controller.TeacherController : 调用TeacherController的getAll方法
```
在执行http requrest时,控制台中新增了一条info等级的日志。说时此时TokenFilter中的doFilter方法并未执行。
### 启用过滤器扫描功能
SpringBootStudyApplication.java
```java
@SpringBootApplication
@ServletComponentScan ➊
public class SpringBootStudyApplication {
```
* ➊ 启用Servlet组件扫描功能。
>[info] ServletComponent有三种类型:WebServlet、WebFilter及WebListener
### 加入注解
filter/TokenFilter.java
```
@WebFilter ➊
public class TokenFilter extends HttpFilter {
```
* ➊ 告知spring: 本类是个生效的过滤器
## 测试
重新启动后台应用并发起http request测试,查看控制台得到如下info等级信息。
去除debug等级的日志后如下
```
2020-02-10 16:45:40.563 INFO 29832 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 在控制器被调用以前执行
...
2020-02-10 16:45:40.578 INFO 29832 --- [nio-8080-exec-1] c.m.s.controller.TeacherController : 调用TeacherController的getAll方法
...
2020-02-10 16:45:40.623 INFO 29832 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 在控制器被调用以后执行
```
# 参考文档
| 名称 | 链接 | 预计学习时长(分) |
| --- | --- | --- |
| 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.1](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.1) | - |
| servlet filter | [https://www.oracle.com/technetwork/java/filters-137243.html](https://www.oracle.com/technetwork/java/filters-137243.html) | - |
| @ServletComponentScan | [https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/web/servlet/ServletComponentScan.html](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/web/servlet/ServletComponentScan.html) | - |
| @WebFilter | [https://docs.oracle.com/javaee/7/api/javax/servlet/annotation/WebFilter.html?is-external=true](https://docs.oracle.com/javaee/7/api/javax/servlet/annotation/WebFilter.html?is-external=true) | - |
- 序言
- 第一章: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
- 总结
- 开发规范
- 备用