長按識别下方二維碼,即可"關注"公衆号
每天早晨,幹貨準時奉上!
作者:HallowCoder
序
- Spring Security的架構及核心元件:(1)認證;(2)權限攔截;(3)資料庫管理;(4)權限緩存;(5)自定義決策;
- 環境搭建與使用,使用目前熱門的Spring Boot來搭建環境,結合項目中實際的例子來做幾個Case;
- Spring Security的優缺點總結,結合第二部分中幾個Case的實作來總結Spring Security的優點和缺點。
1、Spring Security介紹
整體介紹,Spring Security為基于J2EE開發的企業應用軟體提供了全面的安全服務,特别是使用Spring開發的企業軟體項目,如果你熟悉Spring,尤其是Spring的依賴注入原理,這将幫助你更快掌握Spring Security,目前使用Spring Security有很多原因,通常因為在J2EE的Servlet規範和EJB規範中找不到典型應用場景的解決方案,提到這些規範,特别要指出的是它們不能在WAR或EAR級别進行移植,這樣如果你需要更換伺服器環境,就要在新的目标環境中進行大量的工作,對你的應用進行重新配置安全,使用Spring Security就解決了這些問題,也為你提供了很多很有用的可定制的安全特性。Spring Security包含三個主要的元件:
SecurityContext
、
AuthenticationManager
、
AccessDecisionManager
.
Spring Security主要元件
1.1 認證
Spring Security提供了很多過濾器,它們攔截Servlet請求,并将這些請求轉交給認證處理過濾器和通路決策過濾器進行處理,并強制安全性認證使用者身份和使用者權限以達到保護WEB資源的目的,Spring Security安全機制包括兩個主要的操作,「認證」和「驗證」,驗證也可以稱為權限控制,這是Spring Security兩個主要的方向,認證是為使用者建立一個他所聲明的主體的過程,這個主體一般是指使用者裝置或可以在系統中執行行動的其他系統,驗證指使用者能否在應用中執行某個操作,在到達授權判斷之前身份的主體已經由身份認證過程建立了。下面列出幾種常用認證模式,這裡不對它們作詳細介紹,需要詳細了解的老鐵們可以自行查查對應的資料。
-
:Basic
提出,一種基于challenge/response的認證模式,針對特定的realm需要提供使用者名和密碼認證後才可通路,其中密碼使用明文傳輸。缺點:①無狀态導緻每次通信都要帶上認證資訊,即使是已經認證過的資源;②傳輸安全性不足,認證資訊用HTTP1.0
編碼,基本就是明文傳輸,很容易對封包截取并盜用認證資訊。Base64
-
:Digest
提出,它主要是為了解決Basic模式安全問題,用于替代原來的Basic認證模式,Digest認證也是采用challenge/response認證模式,基本的認證流程比較類似。Digest模式避免了密碼在網絡上明文傳輸,提高了安全性,但它仍然存在缺點,例如認證封包被攻擊者攔截到攻擊者可以擷取到資源。HTTP1.1
-
:證書認證,X.509
是一種非常通用的證書格式,證書包含版本号、序列号(唯一)、簽名、頒發者、有效期、主體、主體公鑰。X.509
-
:輕量級目錄通路協定(Lightweight Directory Access Protocol)。LDAP
-
:基于表單的認證模式。Form
1.2 權限攔截
使用者請求
過濾器
Spring Security提供了很多過濾器,其中
SecurityContextPersistenceFilter
、
UsernamePasswordAuthenticationFilter
、
FilterSecurityInterceptor
分别對應
SecurityContext
、
AuthenticationManager
、
AccessDecisionManager
的處理。
Spring Security過濾鍊流程圖
下面分别介紹各個過濾器的功能。
過濾器 | 描述 |
---|---|
| 設定 到異步線程中,用于擷取使用者上下文資訊 |
| 整個請求過程中 的建立和清理 未登入, 為null,建立一個新的 的 填充 2.已登入,從 擷取的 對象 兩個請求完成後都清空 ,并更新 |
| 添加頭資訊到響應對象 |
| 防止csrf攻擊(跨站請求僞造)的過濾器 |
| 登出處理 |
| 擷取表單使用者名和密碼,處理基于表單的登入請求 |
| 配置登入頁面 |
| 檢測和處理http basic認證,将結果放進 |
| 處理請求request的緩存 |
| 包裝請求request,便于通路 |
| 匿名身份過濾器,不存在使用者資訊時調用該過濾器 |
| 檢測有使用者登入認證時做相應的session管理 |
| 處理 通路異常和 認證異常 |
| 檢測使用者是否具有通路資源路徑的權限 |
1.3 資料庫管理
Spring Security核心處理流程
上圖展示的Spring Security核心處理流程。當一個使用者登入時,會先進行身份認證,如果身份認證未通過會要求使用者重新認證,當使用者身份證通過後就會調用角色管理器判斷他是否可以通路,這裡,如果要實作資料庫管理使用者及權限,就需要自定義使用者登入功能,Spring Security已經提供好了一個接口
UserDetailsService
。
UserDetailService
該接口隻有一個方法,通過方法名可以看出方法是通過使用者名來擷取使用者資訊的,但傳回結果是
UserDetails
對象,
UserDetails
也是一個接口,接口中任何一個方法傳回false使用者的憑證就會被視為無效。
* Implementations are not used directly by Spring Security for security purposes. They
* simply store user information which is later encapsulated into {@link Authentication}
* objects. This allows non-security related user information (such as email addresses,
* telephone numbers etc) to be stored in a convenient location.
*
* Concrete implementations must take particular care to ensure the non-null contract
* detailed for each method is enforced. See
* {@link org.springframework.security.core.userdetails.User} for a reference
* implementation (which you might like to extend or use in your code).
*
* @see UserDetailsService
* @see UserCache
*
* @author Ben Alex
*/
public interface UserDetails extends Serializable {
// ~ Methods
// ========================================================================================================
Collection extends GrantedAuthority> getAuthorities(); //權限集合
String getPassword(); //密碼
String getUsername(); //使用者名
boolean isAccountNonExpired(); //賬戶是否過期
boolean isAccountNonLocked(); //賬戶是否被鎖定
boolean isCredentialsNonExpired(); //證書是否過期
boolean isEnabled(); //賬戶是否有效
}
這裡需要注意的是
Authentication
與
UserDetails
對象的區分,
Authentication
對象才是Spring Security使用的進行安全通路控制使用者資訊的安全對象,實際上
Authentication
對象有未認證和已認證兩種狀态,在作為參數傳入認證管理器的時候,它是一個為認證的對象,它從用戶端擷取使用者的身份認證資訊,如使用者名、密碼,可以是從一個登入頁面,也可以是從cookie中擷取,并由系統自動生成一個
Authentication
對象,而這裡的
UserDetails
代表的是一個使用者安全資訊的源,這個源可以是從資料庫、LDAP伺服器、CA中心傳回,Spring Security要做的就是将未認證的
Authentication
對象與
UserDetails
對象進行比對,成功後将
UserDetails
對象中的權限資訊拷貝到
Authentication
中,組成一個完整的
Authentication
對象,與其他元件進行共享。
* Implementations should
always
allow this method to be called with a
*
false
parameter, as this is used by various classes to specify the
* authentication token should not be trusted. If an implementation wishes to reject
* an invocation with a
true
parameter (which would indicate the
* authentication token is trusted - a potential security risk) the implementation
* should throw an {@link IllegalArgumentException}.
*
* @param isAuthenticated
true
if the token should be trusted (which may
* result in an exception) or
false
if the token should not be trusted
*
* @throws IllegalArgumentException if an attempt to make the authentication token
* trusted (by passing
true
as the argument) is rejected due to the
* implementation being immutable or implementing its own alternative approach to
* {@link #isAuthenticated()}
*/
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
了解了Spring Security的上面三個對象,當我們需要資料庫管理使用者時,我們需要手動實作
UserDetailsService
對象中的
loadUserByUsername
方法,這就需要我們同時準備以下幾張資料表,分别是使用者表(user)、角色表(role)、權限表(permission)、使用者和角色關系表(user_role)、權限和角色關系表(permission_role),
UserDetails
中的使用者狀态通過使用者表裡的屬性去填充,
UserDetails
中的權限集合則是通過角色表、權限表、使用者和角色關系表、權限和角色關系表構成的RBAC模型來提供,這樣就可以把使用者認證、使用者權限集合放在資料庫中進行管理了。
1.4 權限緩存
Spring Security的權限緩存和資料庫管理有關,都是在使用者認證上做文章,是以都與
UserDetails
有關,與資料庫管理不同的是,Spring Security提供了一個可以緩存
UserDetailsService
的實作類,這個類的名字是
CachingUserDetailsService
CachingUserDetailsService
類的構造接收一個用于真正加載
UserDetails
的
UserDetailsService
實作類,當需要加載
UserDetails
時,會首先從緩存中擷取,如果緩存中沒有
UserDetails
存在,則使用持有的
UserDetailsService
實作類進行加載,然後将加載後的結果存在緩存中,
UserDetails
與緩存的互動是通過
UserCache
接口來實作的,
CachingUserDetailsService
預設擁有一個
UserCache
的
NullUserCache()
實作。Spring Security提供的緩存都是基于記憶體的緩存,并且緩存的
UserDetails
對象,在實際應用中一般會用到更多的緩存,比如Redis,同時也會對權限相關的資訊等更多的資料進行緩存。
1.5 自定義決策
Spring Security在使用者身份認證通過後,會調用一個角色管理器判斷是否可以繼續通路,
AccessDecisionManager
就是Spring Security的角色管理器,它對應的抽象類為
AbstractAccessDecisionManager
,要自定義決策管理器的話一般是繼承這個抽象類,而不是去實作接口。
* Handles configuration of a bean context defined list of {@link AccessDecisionVoter}s
* and the access control behaviour if all voters abstain from voting (defaults to deny
* access).
*/
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager,InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(getClass());
private List> decisionVoters;protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private boolean allowIfAllAbstainDecisions = false;protected AbstractAccessDecisionManager(
List> decisionVoters) {
Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required");this.decisionVoters = decisionVoters;
}public void afterPropertiesSet() {
Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required");
Assert.notNull(this.messages, "A message source must be set");
}protected final void checkAllowIfAllAbstainDecisions() {if (!this.isAllowIfAllAbstainDecisions()) {throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}public List> getDecisionVoters() {return this.decisionVoters;
}public boolean isAllowIfAllAbstainDecisions() {return allowIfAllAbstainDecisions;
}public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
}public void setMessageSource(MessageSource messageSource) {this.messages = new MessageSourceAccessor(messageSource);
}public boolean supports(ConfigAttribute attribute) {for (AccessDecisionVoter voter : this.decisionVoters) {if (voter.supports(attribute)) {return true;
}
}return false;
}
public boolean supports(Class> clazz) {for (AccessDecisionVoter voter : this.decisionVoters) {if (!voter.supports(clazz)) {return false;
}
}return true;
}
}
裡面的核心方法是
supports
方法,方法中用到一個
decisionVoters
的集合,集合中的類型是
AccessDecisionVoter
,這是Spring Security引入的一個投票器,有無權限通路的最終決定權就是由投票器來決定的。
這裡有很多投票器,最常見的為
RoleVoter
投票器,
RoleVoter
定義了權限的字首"ROLE_",投票器的核心是靠
vote
這個選舉方法來實作的,方法中的參數
authentication
是使用者及權限資訊,
attributes
是通路資源需要的權限,代碼裡循環判斷使用者是否有通路資源需要的權限,如果有就傳回
ACCESS_GRANTED
,即有權限。
Spring Seucrity提供了三種投票決策,分别是
AffirmativeBased
:一票通過即可通路;
ConsensusBased
:一半以上通過才允許通路;
UnanimousBased
:全部通過才允許通路。自定義決策隻需要繼承
AbstractAccessDecisionManager
抽象類,可以自定義自己的投票器,比如需要同時滿足多個條件才能通路等,不需要使用Spring Security自帶的投票器。
2、環境搭建及使用
2.1 快速搭建Spring Boot + Spring Security環境
打開Spring Boot官網https://start.spring.io/,選擇Java語言,在Dependencies中添加Spring Web和Spring Security,最後點選GENERATE下載下傳。
解壓下載下傳的檔案,用idea打開,可以看到這是一個可以直接啟動的demo,因為我們是web項目,是以這裡添加一個接口看一下。
啟動後我們在位址欄輸入locahost:8080會自動跳轉到/login路徑,說明Spring Security就已經直接參與進來了。
然後我們建立一個繼承
WebSecurityConfigurerAdapter
的配置類,定義權限通路政策,同時再添加一個路徑為“/hello”的接口,根據代碼注釋我們可以看出,通路項目主路徑可以不需要驗證,通路其餘路徑則需要驗證。啟動項目,通路localhost:8080可以直接通過,但通路localhost:8080\hello則會自動跳轉到localhost:8080/login路徑要求登入。這樣說明Spring Security的安全政策已經生效了,Spring Boot與Spring Security的環境搭建也完成了。
2.2 常用Case實作
2.2.1 隻要能登入即可
隻要登入就可以通路項目所有資源路徑,也不用寫單獨的登入頁面,這裡就會用到Spring Security提供的基于記憶體的驗證。在
SpringSecurityConfig
類中繼續重寫
configure(AuthenticationManagerBuilder auth)
這個方法。Spring security 5.0之後新增了多種加密方式,改變了預設的密碼格式,新的密碼存儲格式是“{id}…………”.前面的id是加密方式,id可以是bcrypt、sha256等,後面跟着的是加密後的密碼。也就是說,程式拿到傳過來的密碼的時候,會首先查找被“{”和“}”包括起來的id,來确定後面的密碼是被怎麼樣加密的,如果找不到就認為id是null。這時候程式會報錯:There is no PasswordEncoder mapped for the id “null”.實際應用中也可以自定義加密方式,隻需要繼承
PasswordEncoder
接口即可。
2.2.2 有指定的角色,每個角色有指定的權限
添加一個限定角色的請求,需要有ADMIN角色的才能通路,“ROLE_”為
RoleVoter
中定義的字首,在前面自定義決策中提到過。同時,這裡還需要注意的是,使用
@PreAuthorize
這個注解時,一定要在類上加上
@EnableGlobalMethodSecurity(prePostEnabled = true)
注解
@PreAuthorize
才會生效。這樣admin使用者就可以通路/roleAuth,但zhangsan則不可以通路/roleAuth。
實際場景中使用者角色一般是存儲在資料庫中的,前面提到過Spring Security的資料庫管理需要實作
UserDetailsService
接口,定義資料庫相關查詢,傳回
UserDetails
對象。
資料庫管理在實際項目能更好的說明,這裡我們回到Spring Security權限配置,之前使用過
@PreAuthorize
這個注解來控制方法是否能被調用,實際上Spring Security提供了4個這樣的注解,分别是
@PreAuthorize
、
@PostAuthorize
、
@PreFilter
、
@PostFilter
,
@PreAuthorize
和
@PostAuthorize
的作用分别是在方法調用前和調用後對權限進行檢查,
@PreFilter
和
@PostFilter
的作用是對集合類的參數或傳回值進行過濾。
-- END --
-
| 更多精彩文章 -
- 你還在用Fastjson?你沒了解過Jackson?
- 震驚!你還不知道SpringBoot真正的啟動引導類
- Unicode 字元到底能玩出什麼花樣
- [文末贈書] 别因為字元串再吃大虧了!
- 震驚!你還不知道SpringBoot真正的啟動引導類
- HR直呼,這鎖的了解,有深度!
《Java進階知識手冊》
公衆号背景回複「手冊」擷取資料
>>> 點選進入技術讨論群 <<<▽想知道更多?長按/掃碼關注我吧↑↑↑
覺得不錯就點個在看吧