再來回顧下5.2節中請求數據時的header信息:

此header中帶有很多項信息,比如:Accept的值為application/json,text/plain,*/*; Accept-Encoding的值為:gzip, deflate, br。我們把這種存儲方式稱為:鍵值對存儲。由于http協議傳輸的特性,在header中`鍵`與`值`的類型均為string(有人說也可以是number,其實這并不重要,重要的是明了只能傳字符串過來就行)。所以若要實現使用header傳遞令牌,首先要為令牌在header設置一個專業的`鍵`。此鍵的名稱可以隨意起,但一般習慣的命名為`auth-token 認證令牌`。
確認了令牌的鍵為`auth-token`后,按以下流程來分別完成開發:

## 獲取header中的令牌
filter/TokenFilter.java
```java
private String TOKEN_KEY = "auth-token";
...
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 獲取 header中的token
String token = request.getHeader(this.TOKEN_KEY);
```
## 有效性判斷
filter/TokenFilter.java
```java
// 獲取 header中的token
String token = request.getHeader(this.TOKEN_KEY);
if (!this.validateToken(token)) {
// 如果無效則分發送的token
}
...
}
/**
* 驗證token的有效性
* @param token token
* @return
*/
private boolean validateToken(String token) {
// 驗證token的有效性
}
```
令牌是否有效,需要判斷其是否為系統分發的。傳入的令牌如果為系統分發,則有效;如系統未分發過該令牌,視為無效。因而,令牌有效性判斷的前提是系統存儲了分發過的令牌可以驗證用戶傳入的令牌是否存在于系統分發的令牌中。而java中的HashSet恰恰能夠解決當前問題:
filter/TokenFilter.java
```java
/** 存儲已分發過的令牌 */
private HashSet<String> tokens = new HashSet<>();
```
則驗證token是否有效的代碼如下:
filter/TokenFilter.java
```java
/**
* 驗證token的有效性
* @param token token
* @return
*/
private boolean validateToken(String token) {
if (token == null) {
return false;
}
return this.tokens.contains(token);
}
```
## 分發新token
發新token時主要注意要將生成的token存入歷史表中,再次接收到該token時以便進行校驗。
filter/TokenFilter.java
```java
// 有效性判斷
if (!this.validateToken(token)) {
// 如果無效則分發送的token
token = this.makeToken();
}
...
/**
* 生成token
* 將生成的token存入已分發的tokens中
* @return token
*/
private String makeToken() {
String token = UUID.randomUUID().toString()?;
this.tokens.add(token);
return token;
}
```
* ? 獲取非重復的隨機字符串
## 測試
寫一點測試一點絕對是個好習慣。在代碼中加入一些日志語句,然后階段性的測試一下以驗證當前的代碼是符合預期的。
filter/TokenFilter.java
```java
// 獲取 header中的token
String token = request.getHeader(this.TOKEN_KEY);
logger.info("獲取到的token為" + token);
// 有效性判斷
if (!this.validateToken(token)) {
// 如果無效則分發送的token
token = this.makeToken();
logger.info("原token無效,發布的新的token為" + token);
}
```
重啟應用后,使用http request進行測試。
### 測試一:未傳token
```
2020-02-10 17:54:06.298 INFO 86327 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 獲取到的token為null
2020-02-10 17:54:06.298 INFO 86327 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 原token無效,發布的新的token為e953808a-015e-4b59-b348-6978f8d7c9cb
```
未傳入token時分發新token符合預期。
### 測試二:傳入有效的token
在測試文件中如下加入header信息。鍵為auth-token,值為剛剛控制臺打印的token。
```
GET http://localhost:8080/Teacher
auth-token: e953808a-015e-4b59-b348-6978f8d7c9cb
```
發起請求:
```
2020-02-10 17:57:26.775 INFO 86327 --- [nio-8080-exec-9] c.m.springbootstudy.filter.TokenFilter : 獲取到的token為e953808a-015e-4b59-b348-6978f8d7c9cb
```
使用已分發的token進行請求,獲取了token并被驗證為有效。
### 測試三:傳入無效的token
隨便的修正一下auth-token的值,再進行測試:
```
GET http://localhost:8080/Teacher
auth-token: e953808a-015e-4b59-b348-12342321
```
測試結果:
```
2020-02-10 17:58:51.978 INFO 86327 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 獲取到的token為e953808a-015e-4b59-b348-12342321
2020-02-10 17:58:51.978 INFO 86327 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 原token無效,發布的新的token為34d471c9-fc54-4ad6-8163-6a52a2ad2efe
```
# 生活中的令牌
生活中的令牌隨處可見:
* 商鋪的會員卡就是一個令牌,每張會員卡都是一個令牌。會員卡在我們與商家家進行傳遞,商家根據會員卡來確認我們可以享有何種權益。
* 信用卡也是一個令牌,這個令牌更加智能。信用卡的密碼機制使該令牌擁有一種數據加密解密的能力;信用卡還擁有過期時間,只能在有效的時間內使用,這便是令牌的過期機制;同時當老的信用卡快過期時,發卡行還會為我們郵寄新卡,這便是令牌的過期更新機制。
# 參考文檔
| 名稱 | 鏈接 | 預計學習時長(分) |
| --- | --- | --- |
| 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.2](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.2) | - |
- 序言
- 第一章: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
- 總結
- 開發規范
- 備用