1. 程式人生 > >你還不瞭解基於session的授權認證嗎?

你還不瞭解基於session的授權認證嗎?

### 前言 在漫長的開發過程中,許可權認證是一個永恆不變的話題,隨著技術的發展,從以前的基於sessionId的方式,變為如今的token方式。session常用於單體應用,後來由於微服務的興起,分散式應用佔了很大的一部分。本文將為大家介紹基於session的單體應用授權認證方式。後續會介紹基於token的認證方式。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090526482-1600951939.png) ### 什麼是認證 輸入賬號和密碼登入的過程就是認證,看是否合法。認證是為了保護系統的隱私資料和資源。使用者的身份合法才能訪問該系統資源。 使用者認證就是判斷一個使用者身份是否合法的過程,合法繼續訪問,不合法拒絕訪問。 常見的使用者認證方式有:使用者密碼登入,手機簡訊登入,指紋認證等。 ### 什麼是會話 使用者認證通過後,為了避免使用者的每次操作都進行認證,可以將使用者的資訊儲存在會話中,會話就是為了保持當前使用者的登入狀態,提供的一種機制。 常見的有基於session方式、基於token方式。 #### session方式會話 使用者認證成功後,在服務端生成使用者相關的資料儲存在session中,發給客戶端的session_id存放到cookie中。使用者客戶端請求的時候帶上session_id就可以驗證伺服器是否存在session資料,以此完成使用者的合法校驗。當用戶退出系統或session過期銷燬,客戶端的session_id也就無效了。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090603537-1347365359.png) #### token方式會話 使用者認證成功後,服務端生成一個token發給客戶端,客戶端放到cookie或localstorage等儲存中。每次請求時,帶上token,服務端收到token通過驗證後,可以確認使用者身份。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090616427-392248531.png) #### 總結 基於session的認證方式由servlet規範定製,服務端要儲存session資訊需要佔用記憶體資源,客戶端需要支援cookie。基於token的方式一般不需要服務端儲存token,並且不限制客戶端的儲存方式。如今網際網路時代多客戶端,所以token更合適。 ### 什麼是授權 比如微信,發紅包功能,發朋友圈功能都是微信資源,使用者有發紅包的功能才能正常使用,比如一開始使用者沒有繫結銀行卡,使用者就沒有發紅包許可權。 認證是為了保護使用者身份的合法性,授權則是為了更細粒度的對隱私資料進行劃分,授權是認證通過後發生的,控制不同的使用者能夠訪問不同的資源。 授權是使用者認證通過根據使用者的許可權來控制使用者訪問資源的過程,擁有資源的訪問許可權則正常訪問,沒有許可權則拒絕訪問。 #### 授權的資料模型 授權可簡單理解為Who對What(which)進行How操作,包括如下: Who,即主體(Subject),主體一般是指使用者,也可以是程式,需要訪問系統中的資源。 What,即資源(Resource),如系統選單、頁面、按鈕、程式碼方法、系統商品資訊、系統訂單資訊等。系統選單、頁面、按鈕、程式碼方法都屬於系統功能資源,對於web系統每個功能資源通常對應一個URL;系統商品資訊、系統訂單資訊都屬於實體資源(資料資源),實體資源由資源型別和資源例項組成,比如商品資訊為資源型別,商品編號為001的商品為資源例項。 How,許可權/許可(Permission),規定了使用者對資源的操作許可,許可權離開資源沒有意義,如使用者查詢許可權、使用者新增許可權、某個程式碼方法的呼叫許可權、編號為001的使用者的修改許可權等,通過許可權可知使用者對哪些資源都有哪些操作許可。 主體、資源、許可權關係如下: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090657855-1493708004.png) 主體、資源、許可權相關的資料模型如下: 主體(使用者id、賬號、密碼、...) 資源(資源id、資源名稱、訪問地址、...) 許可權(許可權id、許可權標識、許可權名稱、資源id、...) 角色(角色id、角色名稱、...) 角色和許可權關係(角色id、許可權id、...) 主體(使用者)和角色關係(使用者id、角色id、...) 主體(使用者)、資源、許可權關係如下圖: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090702355-1884950238.png) 通常企業開發中將資源和許可權表合併為一張許可權表,如下: 資源(資源id、資源名稱、訪問地址、...) 許可權(許可權id、許可權標識、許可權名稱、資源id、...) 合併為: 許可權(許可權id、許可權標識、許可權名稱、資源名稱、資源訪問地址、...) 修改後資料模型之間的關係如下圖: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090706135-1856945910.png) #### RBAC ##### 基於角色的訪問控制 RBAC基於角色的訪問控制(Role-Based Access Control)是按角色進行授權,比如:主體的角色為總經理可以查詢企業運營報表,查詢員工工資資訊等,訪問控制流程如下: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090731261-503433366.png) 根據上圖中的判斷邏輯,授權程式碼可表示如下: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090739804-490311003.png) 如果上圖中查詢工資所需要的角色變化為總經理和部門經理,此時就需要修改判斷邏輯為“判斷使用者的角色是否是 總經理或部門經理”,修改程式碼如下: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090742648-697175866.png) 根據上邊的例子發現,當需要修改角色的許可權時就需要修改授權的相關程式碼,系統可擴充套件性差。 ##### 基於資源的訪問控制 RBAC基於資源的訪問控制(Resource-Based Access Control)是按資源(或許可權)進行授權,比如:使用者必須具有查詢工資許可權才可以查詢員工工資資訊等,訪問控制流程如下: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090813416-1559684998.png) 根據上圖中的判斷,授權程式碼可以表示為: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090816642-1313044338.png) 優點:系統設計時定義好查詢工資的許可權標識,即使查詢工資所需要的角色變化為總經理和部門經理也不需要修改授權程式碼,系統可擴充套件性強。 ### 基於session的認證方式 基於Session認證方式的流程是,使用者認證成功後,在服務端生成使用者相關的資料儲存在session(當前會話),而發給客戶端的 sesssion_id 存放到 cookie 中,這樣用客戶端請求時帶上 session_id 就可以驗證伺服器端是否存在 session 資料,以此完成使用者的合法校驗。當用戶退出系統或session過期銷燬時,客戶端的session_id也就無效了。 下圖是session認證方式的流程圖: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090825417-1444247410.png) 基於Session的認證機制由Servlet規範定製,Servlet容器已實現,使用者通過HttpSession的操作方法即可實現,如下是HttpSession相關的操作API。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521090829458-1115172484.png) ### 基於session的工程 本案例工程使用maven進行構建,使用SpringMVC、Servlet3.0實現。 #### 建立maven工程 建立maven工程 security-session,引入如下依賴如下: 注意: 1、由於是web工程,packaging設定為war 2、使用tomcat7-maven-plugin外掛來執行工程 ``` 4.0.0 com.jichi.security security‐session 1.0‐SNAPSHOT war UTF‐8 1.8 1.8 org.springframework spring‐webmvc 5.1.5.RELEASE javax.servlet javax.servlet‐api 3.0.1 provided org.projectlombok lombok 1.18.8 security‐springmvc org.apache.tomcat.maven tomcat7‐maven‐plugin 2.2
org.apache.maven.plugins maven‐compiler‐plugin 1.8 1.8 maven‐resources‐plugin utf‐8 true src/main/resources true **/* src/main/java **/*.xml
``` #### Spring容器配置 在config包下定義ApplicationConfig.java,它對應web.xml中ContextLoaderListener的配置。 這裡除了springmvc的controller,都在這裡配置。 #### servletcontext配置 本案例採用Servlet3.0無web.xml方式,在config包下定義WebConfig.java,它對應於DispatcherServlet配置。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091107653-621619872.png) #### 載入spring容器 在init包下定義Spring容器初始化類SpringApplicationInitializer,此類實現WebApplicationInitializer介面, Spring容器啟動時載入WebApplicationInitializer介面的所有實現類。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091121452-2114742179.png) SpringApplicationInitializer相當於web.xml,使用了servlet3.0開發則不需要再定義web.xml, ApplicationConfig.class對應以下配置的application-context.xml,WebConfig.class對應以下配置的spring- mvc.xml,web.xml的內容參考: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091134423-382572788.png) #### 實現認證功能 ##### 認證頁面 在webapp/WEB-INF/views下定義認證頁面login.jsp,本案例只是測試認證流程,頁面沒有新增css樣式,頁面實 現可填入使用者名稱,密碼,觸發登入將提交表單資訊至/login,內容如下: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091208564-530935087.png) 在WebConfig中新增如下配置,將/直接導向login.jsp頁面: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091212837-5925704.png) 啟動專案,訪問/路徑地址,進行測試 ##### 認證介面 使用者進入認證頁面,輸入賬號和密碼,點選登入,請求/login進行身份認證。 (1)定義認證介面,此介面用於對傳來的使用者名稱、密碼校驗,若成功則返回該使用者的詳細資訊,否則丟擲錯誤異常: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091226779-1320534618.png) ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091234708-1200737906.png) ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091248359-523430064.png) (2)認證實現類,根據使用者名稱查詢使用者資訊,並校驗密碼,這裡模擬了兩個使用者: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091255551-545076995.png) ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091259414-1134040613.png) (3)登入Controller,對/login請求處理,它呼叫AuthenticationService完成認證並返回登入結果提示資訊: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091306737-671903668.png) (4)測試 啟動專案,訪問/路徑地址,進行測試 #### 實現會話功能 會話是指使用者登入系統後,系統會記住該使用者的登入狀態,他可以在系統連續操作直到退出系統的過程。 認證的目的是對系統資源的保護,每次對資源的訪問,系統必須得知道是誰在訪問資源,才能對該請求進行合法性攔截。因此,在認證成功後,一般會把認證成功的使用者資訊放入Session中,在後續的請求中,系統能夠從Session 中獲取到當前使用者,用這樣的方式來實現會話機制。 (1)增加會話控制 首先在UserDto中定義一個SESSION_USER_KEY,作為Session中存放登入使用者資訊的key。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091329652-1671137869.png) 然後修改LoginController,認證成功後,將使用者資訊放入當前會話。並增加使用者登出方法,登出時將session置為失效。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091336030-52541551.png) ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091340148-821321564.png) (2)增加測試資源 修改LoginController,增加測試資源1,它從當前會話session中獲取當前登入使用者,並返回提示資訊給前臺。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091345779-1573671574.png) (3)測試 未登入情況下直接訪問測試資源/r/r1: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091349939-964958537.png) 成功登入的情況下訪問測試資源/r/r1: ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091352798-1806316284.png) 測試結果說明,在使用者登入成功時,該使用者資訊已被成功放入session,並且後續請求可以正常從session中獲取當 前登入使用者資訊,符合預期結果。 #### 實現授權功能 現在我們已經完成了使用者身份憑證的校驗以及登入的狀態保持,並且我們也知道了如何獲取當前登入使用者(從 Session中獲取)的資訊,接下來,使用者訪問系統需要經過授權,即需要完成如下功能: 匿名使用者(未登入使用者)訪問攔截:禁止匿名使用者訪問某些資源。 登入使用者訪問攔截:根據使用者的許可權決定是否能訪問某些資源。 (1)增加許可權資料 為了實現這樣的功能,我們需要在UserDto裡增加許可權屬性,用於表示該登入使用者所擁有的許可權,同時修改 UserDto的構造方法。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091413045-1326844501.png) 並在AuthenticationServiceImpl中為模擬使用者初始化許可權,其中張三給了p1許可權,李四給了p2許可權。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091417384-2017587963.png) (2)增加測試資源 我們想實現針對不同的使用者能訪問不同的資源,前提是得有多個資源,因此在LoginController中增加測試資源2。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091421971-1510565013.png) (3)實現授權攔截器 在interceptor包下定義SimpleAuthenticationInterceptor攔截器,實現授權攔截: 1、校驗使用者是否登入 2、校驗使用者是否擁有操作許可權 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091425904-917120084.png) ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091429034-767303596.png) 在WebConfig中配置攔截器,匹配/r/**的資源為受保護的系統資源,訪問該資源的請求進入 SimpleAuthenticationInterceptor攔截器。 ![](https://img2020.cnblogs.com/blog/1534147/202005/1534147-20200521091433706-1983329228.png) (4)測試 未登入情況下,/r/r1與/r/r2均提示 “請先登入”。 張三登入情況下,由於張三有p1許可權,因此可以訪問/r/r1,張三沒有p2許可權,訪問/r/r2時提示 “許可權不足 “。 李四登入情況下,由於李四有p2許可權,因此可以訪問/r/r2,李四沒有p1許可權,訪問/r/r1時提示 “許可權不足 “。 測試結果全部符合預期結果。 5.8.總結 基於Session的認證方式是一種常見的認證方式,至今還有非常多的系統在使用。我們在此小節使用Spring mvc技 術對它進行簡單實現,旨在讓大家更清晰實在的瞭解使用者認證、授權以及會話的功能意義及實現套路,也就是它們 分別幹了哪些事兒?大概需要怎麼做? 而在正式生產專案中,我們往往會考慮使用第三方安全框架(如 spring security,shiro等安全框架)來實現認證 授權功能,因為這樣做能一定程度提高生產力,提高軟體標準化程度,另外往往這些框架的可擴充套件性考慮的非常全 面。但是缺點也非常明顯,這些通用化元件為了提高支援範圍會增加很多可能我們不需要的功能,結構上也會比較抽象,如果我們不夠了解它,一旦出現問題,將會很難