天天看點

spring security_一文肝爆Spring安全架構Spring Security

長按識别下方二維碼,即可"關注"公衆号

每天早晨,幹貨準時奉上!

spring security_一文肝爆Spring安全架構Spring Security
作者:HallowCoder
           

  1. Spring Security的架構及核心元件:(1)認證;(2)權限攔截;(3)資料庫管理;(4)權限緩存;(5)自定義決策;
  2. 環境搭建與使用,使用目前熱門的Spring Boot來搭建環境,結合項目中實際的例子來做幾個Case;
  3. 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_一文肝爆Spring安全架構Spring Security

Spring Security主要元件

1.1 認證

Spring Security提供了很多過濾器,它們攔截Servlet請求,并将這些請求轉交給認證處理過濾器和通路決策過濾器進行處理,并強制安全性認證使用者身份和使用者權限以達到保護WEB資源的目的,Spring Security安全機制包括兩個主要的操作,「認證」和「驗證」,驗證也可以稱為權限控制,這是Spring Security兩個主要的方向,認證是為使用者建立一個他所聲明的主體的過程,這個主體一般是指使用者裝置或可以在系統中執行行動的其他系統,驗證指使用者能否在應用中執行某個操作,在到達授權判斷之前身份的主體已經由身份認證過程建立了。下面列出幾種常用認證模式,這裡不對它們作詳細介紹,需要詳細了解的老鐵們可以自行查查對應的資料。

  1. Basic

    HTTP1.0

    提出,一種基于challenge/response的認證模式,針對特定的realm需要提供使用者名和密碼認證後才可通路,其中密碼使用明文傳輸。缺點:①無狀态導緻每次通信都要帶上認證資訊,即使是已經認證過的資源;②傳輸安全性不足,認證資訊用

    Base64

    編碼,基本就是明文傳輸,很容易對封包截取并盜用認證資訊。
  2. Digest

    HTTP1.1

    提出,它主要是為了解決Basic模式安全問題,用于替代原來的Basic認證模式,Digest認證也是采用challenge/response認證模式,基本的認證流程比較類似。Digest模式避免了密碼在網絡上明文傳輸,提高了安全性,但它仍然存在缺點,例如認證封包被攻擊者攔截到攻擊者可以擷取到資源。
  3. X.509

    :證書認證,

    X.509

    是一種非常通用的證書格式,證書包含版本号、序列号(唯一)、簽名、頒發者、有效期、主體、主體公鑰。
  4. LDAP

    :輕量級目錄通路協定(Lightweight Directory Access Protocol)。
  5. Form

    :基于表單的認證模式。

1.2 權限攔截

spring security_一文肝爆Spring安全架構Spring Security

使用者請求

spring security_一文肝爆Spring安全架構Spring Security

過濾器

Spring Security提供了很多過濾器,其中

SecurityContextPersistenceFilter

UsernamePasswordAuthenticationFilter

FilterSecurityInterceptor

分别對應

SecurityContext

AuthenticationManager

AccessDecisionManager

的處理。

spring security_一文肝爆Spring安全架構Spring Security

Spring Security過濾鍊流程圖

下面分别介紹各個過濾器的功能。

過濾器 描述

WebAsyncManagerIntegrationFilter

設定

SecurityContext

到異步線程中,用于擷取使用者上下文資訊

SecurityContextPersistenceFilter

整個請求過程中

SecurityContext

的建立和清理   未登入,

SecurityContext

為null,建立一個新的

ThreadLocal

SecurityContext

填充

SecurityContextHolder

    2.已登入,從

SecurityContextRepository

擷取的

SecurityContext

對象     兩個請求完成後都清空

SecurityContextHolder

,并更新

SecurityContextRepository

HeaderWriterFilter

添加頭資訊到響應對象

CsrfFilter

防止csrf攻擊(跨站請求僞造)的過濾器

LogoutFilter

登出處理

UsernamePasswordAuthenticationFilter

擷取表單使用者名和密碼,處理基于表單的登入請求

DefaultLoginPageGeneratingFilter

配置登入頁面

BasicAuthenticationFilter

檢測和處理http basic認證,将結果放進

SecurityContextHolder

RequestCacheAwareFilter

處理請求request的緩存

SecurityContextHolderAwareRequestFilter

包裝請求request,便于通路

SecurityContextHolder

AnonymousAuthenticationFilter

匿名身份過濾器,不存在使用者資訊時調用該過濾器

SessionManagementFilter

檢測有使用者登入認證時做相應的session管理

ExceptionTranslationFilter

處理

AccessDeniedException

通路異常和

AuthenticationException

認證異常

FilterSecurityInterceptor

檢測使用者是否具有通路資源路徑的權限

1.3 資料庫管理

spring security_一文肝爆Spring安全架構Spring Security

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下載下傳。

spring security_一文肝爆Spring安全架構Spring Security

解壓下載下傳的檔案,用idea打開,可以看到這是一個可以直接啟動的demo,因為我們是web項目,是以這裡添加一個接口看一下。

啟動後我們在位址欄輸入locahost:8080會自動跳轉到/login路徑,說明Spring Security就已經直接參與進來了。

spring security_一文肝爆Spring安全架構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 --

spring security_一文肝爆Spring安全架構Spring Security

 | 更多精彩文章 -

  • 你還在用Fastjson?你沒了解過Jackson?
  • 震驚!你還不知道SpringBoot真正的啟動引導類
  • Unicode 字元到底能玩出什麼花樣
  • [文末贈書] 别因為字元串再吃大虧了!
  • 震驚!你還不知道SpringBoot真正的啟動引導類
  • HR直呼,這鎖的了解,有深度!

《Java進階知識手冊》

spring security_一文肝爆Spring安全架構Spring Security

公衆号背景回複「手冊」擷取資料

spring security_一文肝爆Spring安全架構Spring Security
>>> 點選進入技術讨論群 <<<▽想知道更多?長按/掃碼關注我吧↑↑↑
           

覺得不錯就點個在看吧 

spring security_一文肝爆Spring安全架構Spring Security

繼續閱讀