在上節中花費了大量精力實現了用戶登錄、注銷的基本功能。仿佛早已忘掉了加入用戶登錄功能的初衷:之所以加入用戶登錄功能是為了保護一些后臺的資源,使得只有當用戶成功登錄后才可以獲取后臺的一些數據。比如認為只有擁有權理員權限的用戶才可以獲取后臺的全部教師數據、認為只有班級對應的教師才能獲取該班級中的學生信息等。
當前的豆腐渣工程的登錄注銷功能形同虛設,用戶無需認證便可以通過瀏覽器或是其它的rest工具(比如postman或idea自帶的rest client、http request)對后臺進行肆意操作,無論是教師用戶的獲取還是新增。一個形同虛設的登錄功能只是擋住了一些**正常**的用戶,但對一些**非法**用戶的入侵卻毫無防范。甚至于后臺根本就沒有能力判斷是誰正在進行數據請求,權限控制當然也就無從談起。
此系統若被上線到生產環境,便開始詮釋什么是徹頭徹尾的裸奔。
# 你是誰
要解決用戶認證的關鍵問題便是**你是誰**的問題,需要為前后的交互制定一個規則,后臺可以根據此規則獲取(計算)當前的訪問是由哪個用戶具體發起的。有此基礎以后,便可以根據當前的訪問用戶進行真正的用戶認證了:后臺可以對每次請求進行相應的權限判斷,拒絕沒有權限的訪問請求,允許有訪問權限的請求。
## 用戶名認證
只要前臺的每次訪問請求都帶有**我是誰**的信息,那么后臺便可以根據前臺帶有**我是誰**的信息進行權限判斷。

上圖這種每次請求都傳輸**我是誰**的信息的致命缺點是后臺無法確認前臺是否在**撒謊**,前臺說自己是張三而實際上是不是張三后臺無從得知。所以這種認證模式往往應用在前端可信的場合。比如服務器各個web服務間的內部調用。而當前的系統將發布到internet,世界上任何接入互聯網的用戶均可以向它發起訪問,這個環境是個完全不可信的環境,所以這種認證方式并不適合。
## 用戶名+密碼認證
為了防止前臺**說謊**,可以加一個密碼做為校驗。前臺每次向后臺發起請求時把用戶名和密碼一并傳給后臺。后臺接收到帶有用戶名密碼的請求后,查詢相應的數據庫以進行匹配。用戶名密碼匹配成功則說明前臺沒有說謊,用戶名密碼匹配失敗則說明前臺在說謊。

在簡單的應用中上述方案是可行的。由于登錄組件是用戶初次使用本系統時的第一個組件,所以可以在用戶登錄成功后將登錄的用戶名與密碼信息存儲到瀏覽器緩存中(參考上一小節存儲登錄狀態的思想)。然后在后續的所以請求中,都獲取緩存中的用戶名與密碼信息,并加入到請求數據中。但此方案最少面臨以下兩個問題:
問題一:如果系統部署后采用的是http傳輸協議,那么所有的數據在網絡上都將以明文進行傳輸,提供網絡服務的任何結點都可以捕獲此傳輸信息并且可以輕松的獲取傳輸中的用戶名及密碼的值。
問題二:無法處理單用戶多點登錄的問題。比如有用戶同時使用了手機應用及WEB應用進行登錄,則當用戶的任意終端進行密碼修改操作后都將使得另一終端的登錄信息失敗。


基于以上原因該認證方法被使用在一些對安全性要求不高的環境中。
## 一卡通認證
學校的一卡通已存在很長時間,相信也將在很長的一段時間內繼續存在。本質上來講一卡通的存在正是解決了校園中**你是誰**的問題。一卡通做為在校園中的認證憑證可以用在上機、洗澡、考試、食堂等眾多校園服務中。機房中使用一卡通來確認**你是誰**、考試中使用一卡通來確認**你是誰**,食堂中使用一卡通來確認**你是誰**。
在前臺端的認證領域,**一卡通**仍然是當下最受歡迎的認證方式。既然如此,讓我們共同回顧一下**一卡通**的使用流程。
* ? 開學初憑入學通知書、身份證、報考證明等材料確認你的身份,并將你的身份系統維護至數據庫
* ? 發放在校園內具有唯一性的**一卡通**,并在數據庫中將此**一卡通**與你的身份進行關聯
* ? 在以后的校園活動中,將此**一卡通**做為唯一的憑證。
**一卡通**原理應用到前后端認證中解決了前面兩種方案面臨的基本問題:
* ? 對用戶的認證環節只發生在核驗身份一個環節。涉及的環節越少,數據被截取的風險就越低。解決了由于多次傳輸用戶名密碼帶來的用戶名密碼泄漏風險。同時在傳輸過程(比如同學替你在食堂打飯)中的**一卡通**僅僅顯示了學生的基本信息,即使**一卡通**被截取,也無法通過直接獲取到學生的敏感信息。
* ? 有效的解決了多點登錄的問題:由于前后臺交互是使用的**一卡通**,并不涉及用戶的**密碼**,所以當**密碼**進行變更時不會對**一卡通**的有效性造成任何的問題。
前后臺分離的應用在進行認證時與現實生活中學校發放**一卡通**稍有不同,具體描述如下:
* ① 后臺在發放**一卡通**時更加激進一些,它會為每位進入學校的人(前臺發起的請求)發送一張具有一定有效期的**一卡通**,而無論進入學校的人是否是學校的學生。
* ② 有了**一卡通**的人以后再訪問后臺時都會主動的攜帶該**一卡通**,后臺會在人們進入校園的時候審核該**一卡通**是否有效,如果發現攜帶的**一卡通**失敗則會重新為其發送一張全新的**一卡通**。
* ③ 后臺提供**一卡通**與在校學生的綁定服務,在校學生可以攜帶有效證件將**一卡通**與自己進行綁定。
* ④ 后臺還提供**一卡通**與在校學生的解綁或重新綁定服務,這使得**一卡通**可以在有效期內可以被多個學生使用。
* ⑤ 進入校園的人在訪問后臺的公共資源時,所有人都會得到放行。
* ⑥ 只有當人們想訪問一些只提供給本校學生的特定資源時,后臺才會校驗**一卡通**是否已綁定了在校學生。
>[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`,近期在控制臺打印了相關的異常信息,便是瀏覽器通過分析后臺返回的附加數據實現的。比如在新增學生時,實際上發生的遠比我們前面想到的多:

<hr>
在控制臺中打開網絡選項卡后,新增一個學生并查看生成的網絡請求信息,驗證如下:
發起請求時附帶的header信息:

發起請求時傳遞的主體數據body:

響應的header信息:

響應的主體數據body:

<hr>
我們所要完成的token的傳遞,也將借助于前后臺進行數據交互時的header部分:

理論有了接下來讓我們step by step逐步地實現用戶認證過程。
# 預期效果

- 序言
- 第一章: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
- 總結
- 開發規范
- 備用