Spring security 學習記錄
1、Spring security 簡介
Spring Security 為 Java EE-based 企業軟件應用程序提供全面的安全服務(也就是用戶登錄頁面和相關權限的控制),應用的安全性包括用戶認證( Authentication )和用戶權限( Authorization )兩部分。 用戶認證是確定某個用戶是否有進入系統的權限,使用用戶名密碼去認證,也就是所謂的登錄;用戶權限是確定哪些用戶有哪些功能權限,一般都是按角色。
2、主要過濾器
眾所周知 想要對對Web資源進行保護,最好的辦法莫過於Filter,要想對方法調用進行保護,最好的辦法莫過於AOP。所以springSecurity在我們進行用戶認證以及授予權限的時候,通過各種各樣的攔截器來控制權限的訪問,從而實現安全。
有篇關於過濾器的文章,有需要可以查看下
https://blog.csdn.net/andy_zhang2007/article/details/84726992
-
WebAsyncManagerIntegrationFilter
為請求處理過程中可能發生的異步調用准備安全上下文獲取途徑
-
SecurityContextPersistenceFilter
整個請求處理過程所需的安全上下文對象SecurityContext的准備和清理不管請求是否針對需要登錄才能訪問的頁面,這里都會確保SecurityContextHolder中出現一個SecurityContext對象:
1.未登錄狀態訪問登錄保護頁面:空SecurityContext對象,所含Authentication為null
2.登錄狀態訪問某個頁面:從SecurityContextRepository獲取的SecurityContext對象 -
HeaderWriterFilter
將指定的頭部信息寫入響應對象
-
CorsFilter
對請求進行csrf保護
-
LogoutFilter
檢測用戶退出登錄請求並做相應退出登錄處理
-
RequestCacheAwareFilter
提取請求緩存中緩存的請求
1.請求緩存在安全機制啟動時指定
2.請求寫入緩存在其他地方完成
3.典型應用場景:
用戶請求保護的頁面,
系統引導用戶完成登錄認證,
然后自動跳轉到到用戶最初請求頁面 -
SecurityContextHolderAwareRequestFilter
包裝請求對象使之可以訪問SecurityContextHolder,從而使請求真正意義上擁有接口HttpServletRequest中定義的getUserPrincipal這種訪問安全信息的能力
-
AnonymousAuthenticationFilter
如果當前SecurityContext屬性Authentication為null,將其替換為一個AnonymousAuthenticationToken`
-
SessionManagementFilter
檢測從請求處理開始到目前是否有用戶登錄認證,如果有做相應的session管理,比如針對為新登錄用戶創建新的session(session fixation防護)和設置新的csrf token等。
-
ExceptionTranslationFilter
處理AccessDeniedException和 AuthenticationException異常,將它們轉換成相應的HTTP響應
-
FilterSecurityInterceptor
一個請求處理的安全處理過濾器鏈的最后一個,檢查用戶是否已經認證,如果未認證執行必要的認證,對目標資源的權限檢查,如果認證或者權限不足,拋出相應的異常:AccessDeniedException或者AuthenticationException
-
UsernamePasswordAuthenticationFilter
檢測用戶名/密碼表單登錄認證請求並作相應認證處理:
1.session管理,比如為新登錄用戶創建新session(session fixation防護)和設置新的csrf token等
2.經過完全認證的Authentication對象設置到SecurityContextHolder中的SecurityContext上;
3.發布登錄認證成功事件InteractiveAuthenticationSuccessEvent
4.登錄認證成功時的Remember Me處理
5.登錄認證成功時的頁面跳轉 -
BasicAuthenticationFilter 檢測和處理http basic認證
-
DefaultLoginPageGeneratingFilter 生成缺省的登錄頁面
-
DefaultLogoutPageGeneratingFilter 生成缺省的退出登錄頁面
-
RememberMeAuthenticationFilter 針對Remember Me登錄認證機制的處理邏輯 (免登陸)
3、security核心組件
- SecurityContextHolder:提供對SecurityContext的訪問
- SecurityContext,:持有Authentication對象和其他可能需要的信息
- UsernamePasswordAuthenticationFilter 檢測用戶民密碼並做處理
- AuthenticationManager 其中可以包含多個AuthenticationProvider
- ProviderManager對象為AuthenticationManager接口的實現類
- AuthenticationProvider 主要用來進行認證操作的類 調用其中的authenticate()方法去進行認證操作
- Authentication:Spring Security方式的認證主體
- GrantedAuthority:對認證主題的應用層面的授權,含當前用戶的權限信息,通常使用角色表示
- UserDetails:構建Authentication對象必須的信息,可以自定義,可能需要訪問DB得到
- UserDetailsService:通過username構建UserDetails對象,通過loadUserByUsername根據userName獲取UserDetail對象 (可以在這里基於自身業務進行自定義的實現 如通過數據庫,xml,緩存獲取等)
- passwordEncoder 密碼加密器
4、主要流程
-
如圖:當用戶登錄時,前端將用戶輸入的用戶名、密碼信息傳輸到后台( UsernamePasswordAuthenticationFilter ),后台用一個類對象將其封裝起來,通常使用的是UsernamePasswordAuthenticationToken這個類,然后在 AuthenticationManager 中獲取用戶,進行加密,認證對比等相關操作,返回是否是系統用戶。
-
比較兩者的密碼,如果密碼正確就成功登陸,同時把包含着用戶的用戶名、密碼、所具有的權限等信息的類對象放到SecurityContextHolder(安全上下文容器,類似Session)中去。
-
用戶訪問一個資源的時候,首先判斷是否是受限資源。如果是的話還要判斷當前是否未登錄,沒有的話就跳到登錄頁面。
-
如果用戶已經登錄,訪問一個受限資源的時候,程序要根據url去數據庫中取出該資源所對應的所有可以訪問的角色,然后拿着當前用戶的所有角色一一對比,判斷用戶是否可以訪問。
-
用戶認證的實現方式有很多種,主要體現在 AuthenticationProvider 接口的實現中,目前主流的實現:
DaoAuthenticationProvider 利用數據庫數據進行登錄認證
JassAuthenticationProvider Java 認證和授權服務
CasAuthenticationProvider 利用單點登錄進行登錄認證
LdapAuthenticationProvider 跨域身份認證
5、簡單代碼實現
本例子是基於老版本3.2.7的XML一種實現,需要基於confirm配置,請查看更高版本,地址如下:
https://www.docs4dev.com/docs/zh/spring-security/4.2.10.RELEASE/reference
web.xml配置,添加springSecurityFilterChain配置
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Security.xml配置
具體配置講解可以參考
<sec:http pattern="/loggedOut.html" security="none" />
<!-- 配置免登錄的資源 -->
<sec:http pattern="/toLogin" security="none" />
<sec:http pattern="/loginError" security="none" />
<!-- IS_AUTHENTICATED_ANONYMOUSLY 允許匿名用戶進入 -->
<!-- IS_AUTHENTICATED_FULLY 允許登錄用戶進入 -->
<!-- IS_AUTHENTICATED_REMEMBERED 允許登錄用戶和rememberMe用戶進入 -->
<!--auto-config = true 則使用from-login. 如果不使用該屬性 則默認為http-basic(沒有session).-->
<!-- lowercase-comparisons:表示URL比較前先轉為小寫。-->
<!-- path-type:表示使用Apache Ant的匹配模式。-->
<!--access-denied-page:訪問拒絕時轉向的頁面。-->
<!-- access-decision-manager-ref:指定了自定義的訪問策略管理器。-->
<!--isAuthenticated() 當前用戶是否已通過身份驗證-->
<!--access="hasRole('SUPER_ADMIN') 代表該地址只有權限SUPER_ADMIN才能查看-->
<sec:http use-expressions="true" auto-config="true" >
<sec:intercept-url pattern="/user/*" access="hasRole('SUPER_ADMIN')" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<!--login-page:指定登錄頁面。 -->
<!-- login-processing-url:指定了客戶在登錄頁面中按下 Sign In 按鈕時要訪問的 URL。-->
<!-- authentication-failure-url:指定了身份驗證失敗時跳轉到的頁面。-->
<!-- default-target-url:指定了成功進行身份驗證和授權后默認呈現給用戶的頁面。-->
<!-- always-use-default-target:指定了是否在身份驗證通過后總是跳轉到default-target-url屬性指定的URL。
-->
<sec:form-login login-page="/toLogin"
login-processing-url="/login"
always-use-default-target="true"
default-target-url="/index"
authentication-failure-url="/toLogin?error=1"
/>
<!--logout-url:指定了用於響應退出系統請求的URL。其默認值為:/j_spring_security_logout。-->
<!-- logout-success-url:退出系統后轉向的URL。-->
<!-- invalidate-session:指定在退出系統時是否要銷毀Session。-->
<sec:logout invalidate-session="true" logout-success-url="/toLogin"
logout-url="/j_spring_cas_security_logout" />
<!-- 實現免登陸驗證 -->
<sec:remember-me />
</sec:http>
<bean id="myFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="casAuthenticationManager" />
</bean>
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"
p:userDetailsService-ref="userDetailsService" p:passwordEncoder-ref="md5PasswordEncoder">
</bean>
<!--md5加密方式-->
<bean id = "md5PasswordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"></bean>
<!--數據使用直接查詢數據庫方式,也可以實現userDetailsService接口-->
<bean id="casDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${JDBC.driverClassName}" />
<property name="url" value="${JDBC.url}" />
<property name="username" value="${JDBC.username}" />
<property name="password" value="${JDBC.password}" />
</bean>
<sec:jdbc-user-service id="userDetailsService" data-source-ref="casDataSource"
users-by-username-query="
SELECT
u.account username,
u.`password`,
u.displayFlag enabled
FROM
core_user AS u
WHERE
u.displayFlag = 1
AND
u.enabledFlag = 1
AND
u.account = ? "
authorities-by-username-query="
SELECT
u.account username,
r.`code` role
FROM
core_role AS r
LEFT JOIN core_user_role AS ur ON r.id = ur.roleId
INNER JOIN core_user AS u ON ur.userId = u.id
WHERE
u.displayFlag = 1
AND
u.enabledFlag = 1
AND
u.account = ?" />
login.jsp如下
<form id="fm1" action="/login" method="post" novalidate="novalidate">
<div class="form-group form-username">
<input type="text" class="form-control" id="username" name="j_username" placeholder="Name">
</div>
<div class="form-group form-password">
<input id="password" name="j_password" type="password">
</div>
<div class="form-checkbox">
<span class="checkbox pointer checked">
<input type="checkbox" id="_spring_security_remember_me" name="_spring_security_remember_me" checked="checked">
</span> 7天免登錄
</div>
<div class="form-group">
<button type="submit" class="btn id="loginBtn">登 錄
</button>
</div>
</form>
注意:如果是使用UsernamePasswordAuthenticationFilter ,得注意使用的版本,每個版本對應的字段可能不一樣,需要打開源碼看看。
在3.2.7中,用戶名(j_username),密碼(j_password),記住密碼(_spring_security_remember_me)。
在4.2中,用戶名(username),密碼(password)
在學習的過程中,借鑒了前輩們的文章,參考如下
https://blog.csdn.net/liushangzaibeijing/article/details/81220610
https://blog.csdn.net/andy_zhang2007/article/details/84726992
https://www.docs4dev.com/docs/zh/spring-security/4.2.10.RELEASE/reference