前言
在漫長的開發過程中,權限認證是一個永恆不變的話題,隨着技術的發展,從以前的基於sessionId的方式,變為如今的token方式。session常用於單體應用,后來由於微服務的興起,分布式應用占了很大的一部分。本文將為大家介紹基於session的單體應用授權認證方式。后續會介紹基於token的認證方式。
什么是認證
輸入賬號和密碼登錄的過程就是認證,看是否合法。認證是為了保護系統的隱私數據和資源。用戶的身份合法才能訪問該系統資源。
用戶認證就是判斷一個用戶身份是否合法的過程,合法繼續訪問,不合法拒絕訪問。
常見的用戶認證方式有:用戶密碼登錄,手機短信登錄,指紋認證等。
什么是會話
用戶認證通過后,為了避免用戶的每次操作都進行認證,可以將用戶的信息保存在會話中,會話就是為了保持當前用戶的登錄狀態,提供的一種機制。
常見的有基於session方式、基於token方式。
session方式會話
用戶認證成功后,在服務端生成用戶相關的數據保存在session中,發給客戶端的session_id存放到cookie中。用戶客戶端請求的時候帶上session_id就可以驗證服務器是否存在session數據,以此完成用戶的合法校驗。當用戶退出系統或session過期銷毀,客戶端的session_id也就無效了。
token方式會話
用戶認證成功后,服務端生成一個token發給客戶端,客戶端放到cookie或localstorage等存儲中。每次請求時,帶上token,服務端收到token通過驗證后,可以確認用戶身份。
總結
基於session的認證方式由servlet規范定制,服務端要存儲session信息需要占用內存資源,客戶端需要支持cookie。基於token的方式一般不需要服務端存儲token,並且不限制客戶端的存儲方式。如今互聯網時代多客戶端,所以token更合適。
什么是授權
比如微信,發紅包功能,發朋友圈功能都是微信資源,用戶有發紅包的功能才能正常使用,比如一開始用戶沒有綁定銀行卡,用戶就沒有發紅包權限。
認證是為了保護用戶身份的合法性,授權則是為了更細粒度的對隱私數據進行划分,授權是認證通過后發生的,控制不同的用戶能夠訪問不同的資源。
授權是用戶認證通過根據用戶的權限來控制用戶訪問資源的過程,擁有資源的訪問權限則正常訪問,沒有權限則拒絕訪問。
授權的數據模型
授權可簡單理解為Who對What(which)進行How操作,包括如下:
Who,即主體(Subject),主體一般是指用戶,也可以是程序,需要訪問系統中的資源。
What,即資源(Resource),如系統菜單、頁面、按鈕、代碼方法、系統商品信息、系統訂單信息等。系統菜單、頁面、按鈕、代碼方法都屬於系統功能資源,對於web系統每個功能資源通常對應一個URL;系統商品信息、系統訂單信息都屬於實體資源(數據資源),實體資源由資源類型和資源實例組成,比如商品信息為資源類型,商品編號為001的商品為資源實例。
How,權限/許可(Permission),規定了用戶對資源的操作許可,權限離開資源沒有意義,如用戶查詢權限、用戶添加權限、某個代碼方法的調用權限、編號為001的用戶的修改權限等,通過權限可知用戶對哪些資源都有哪些操作許可。
主體、資源、權限關系如下:
主體、資源、權限相關的數據模型如下:
主體(用戶id、賬號、密碼、...)
資源(資源id、資源名稱、訪問地址、...)
權限(權限id、權限標識、權限名稱、資源id、...)
角色(角色id、角色名稱、...)
角色和權限關系(角色id、權限id、...)
主體(用戶)和角色關系(用戶id、角色id、...)
主體(用戶)、資源、權限關系如下圖:
通常企業開發中將資源和權限表合並為一張權限表,如下:
資源(資源id、資源名稱、訪問地址、...)
權限(權限id、權限標識、權限名稱、資源id、...)
合並為:
權限(權限id、權限標識、權限名稱、資源名稱、資源訪問地址、...)
修改后數據模型之間的關系如下圖:
RBAC
基於角色的訪問控制
RBAC基於角色的訪問控制(Role-Based Access Control)是按角色進行授權,比如:主體的角色為總經理可以查詢企業運營報表,查詢員工工資信息等,訪問控制流程如下:
根據上圖中的判斷邏輯,授權代碼可表示如下:
如果上圖中查詢工資所需要的角色變化為總經理和部門經理,此時就需要修改判斷邏輯為“判斷用戶的角色是否是 總經理或部門經理”,修改代碼如下:
根據上邊的例子發現,當需要修改角色的權限時就需要修改授權的相關代碼,系統可擴展性差。
基於資源的訪問控制
RBAC基於資源的訪問控制(Resource-Based Access Control)是按資源(或權限)進行授權,比如:用戶必須具有查詢工資權限才可以查詢員工工資信息等,訪問控制流程如下:
根據上圖中的判斷,授權代碼可以表示為:
優點:系統設計時定義好查詢工資的權限標識,即使查詢工資所需要的角色變化為總經理和部門經理也不需要修改授權代碼,系統可擴展性強。
基於session的認證方式
基於Session認證方式的流程是,用戶認證成功后,在服務端生成用戶相關的數據保存在session(當前會話),而發給客戶端的 sesssion_id 存放到 cookie 中,這樣用客戶端請求時帶上 session_id 就可以驗證服務器端是否存在 session 數據,以此完成用戶的合法校驗。當用戶退出系統或session過期銷毀時,客戶端的session_id也就無效了。
下圖是session認證方式的流程圖:
基於Session的認證機制由Servlet規范定制,Servlet容器已實現,用戶通過HttpSession的操作方法即可實現,如下是HttpSession相關的操作API。
基於session的工程
本案例工程使用maven進行構建,使用SpringMVC、Servlet3.0實現。
創建maven工程
創建maven工程 security-session,引入如下依賴如下:
注意: 1、由於是web工程,packaging設置為war
2、使用tomcat7-maven-plugin插件來運行工程
<?xml version="1.0" encoding="UTF‐8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven‐4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jichi.security</groupId>
<artifactId>security‐session</artifactId>
<version>1.0‐SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF‐8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring‐webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet‐api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
<build>
<finalName>security‐springmvc</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7‐maven‐plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven‐compiler‐plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven‐resources‐plugin</artifactId>
<configuration>
<encoding>utf‐8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Spring容器配置
在config包下定義ApplicationConfig.java,它對應web.xml中ContextLoaderListener的配置。
這里除了springmvc的controller,都在這里配置。
servletcontext配置
本案例采用Servlet3.0無web.xml方式,在config包下定義WebConfig.java,它對應於DispatcherServlet配置。
加載spring容器
在init包下定義Spring容器初始化類SpringApplicationInitializer,此類實現WebApplicationInitializer接口, Spring容器啟動時加載WebApplicationInitializer接口的所有實現類。
SpringApplicationInitializer相當於web.xml,使用了servlet3.0開發則不需要再定義web.xml, ApplicationConfig.class對應以下配置的application-context.xml,WebConfig.class對應以下配置的spring- mvc.xml,web.xml的內容參考:
實現認證功能
認證頁面
在webapp/WEB-INF/views下定義認證頁面login.jsp,本案例只是測試認證流程,頁面沒有添加css樣式,頁面實 現可填入用戶名,密碼,觸發登錄將提交表單信息至/login,內容如下:
在WebConfig中新增如下配置,將/直接導向login.jsp頁面:
啟動項目,訪問/路徑地址,進行測試
認證接口
用戶進入認證頁面,輸入賬號和密碼,點擊登錄,請求/login進行身份認證。
(1)定義認證接口,此接口用於對傳來的用戶名、密碼校驗,若成功則返回該用戶的詳細信息,否則拋出錯誤異常:
(2)認證實現類,根據用戶名查找用戶信息,並校驗密碼,這里模擬了兩個用戶:
(3)登錄Controller,對/login請求處理,它調用AuthenticationService完成認證並返回登錄結果提示信息:
(4)測試
啟動項目,訪問/路徑地址,進行測試
實現會話功能
會話是指用戶登入系統后,系統會記住該用戶的登錄狀態,他可以在系統連續操作直到退出系統的過程。 認證的目的是對系統資源的保護,每次對資源的訪問,系統必須得知道是誰在訪問資源,才能對該請求進行合法性攔截。因此,在認證成功后,一般會把認證成功的用戶信息放入Session中,在后續的請求中,系統能夠從Session 中獲取到當前用戶,用這樣的方式來實現會話機制。
(1)增加會話控制
首先在UserDto中定義一個SESSION_USER_KEY,作為Session中存放登錄用戶信息的key。
然后修改LoginController,認證成功后,將用戶信息放入當前會話。並增加用戶登出方法,登出時將session置為失效。
(2)增加測試資源
修改LoginController,增加測試資源1,它從當前會話session中獲取當前登錄用戶,並返回提示信息給前台。
(3)測試
未登錄情況下直接訪問測試資源/r/r1:
成功登錄的情況下訪問測試資源/r/r1:
測試結果說明,在用戶登錄成功時,該用戶信息已被成功放入session,並且后續請求可以正常從session中獲取當 前登錄用戶信息,符合預期結果。
實現授權功能
現在我們已經完成了用戶身份憑證的校驗以及登錄的狀態保持,並且我們也知道了如何獲取當前登錄用戶(從 Session中獲取)的信息,接下來,用戶訪問系統需要經過授權,即需要完成如下功能:
匿名用戶(未登錄用戶)訪問攔截:禁止匿名用戶訪問某些資源。
登錄用戶訪問攔截:根據用戶的權限決定是否能訪問某些資源。
(1)增加權限數據
為了實現這樣的功能,我們需要在UserDto里增加權限屬性,用於表示該登錄用戶所擁有的權限,同時修改 UserDto的構造方法。
並在AuthenticationServiceImpl中為模擬用戶初始化權限,其中張三給了p1權限,李四給了p2權限。
(2)增加測試資源
我們想實現針對不同的用戶能訪問不同的資源,前提是得有多個資源,因此在LoginController中增加測試資源2。
(3)實現授權攔截器
在interceptor包下定義SimpleAuthenticationInterceptor攔截器,實現授權攔截:
1、校驗用戶是否登錄 2、校驗用戶是否擁有操作權限
在WebConfig中配置攔截器,匹配/r/**的資源為受保護的系統資源,訪問該資源的請求進入 SimpleAuthenticationInterceptor攔截器。
(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等安全框架)來實現認證 授權功能,因為這樣做能一定程度提高生產力,提高軟件標准化程度,另外往往這些框架的可擴展性考慮的非常全 面。但是缺點也非常明顯,這些通用化組件為了提高支持范圍會增加很多可能我們不需要的功能,結構上也會比較抽象,如果我們不夠了解它,一旦出現問題,將會很難定位。