spring security(1)配置說明

筆者所有文章第一時間發布于:
hhbbz的個人博客

Spring Security 模塊

  • 核心模塊 - spring-security-core.jar:包含核心驗證和訪問控制類和接口,遠程支 持的基本配置API,是基本模塊

  • 遠程調用 - spring-security-remoting.jar:提供與 Spring Remoting 集成

  • 網頁 - spring-security-web.jar:包括網站安全的模塊,提供網站認證服務和基于URL訪問控制

  • 配置 - spring-security-config.jar:包含安全命令空間解析代碼,若使用XML進行配置則需要

  • LDAP - spring-security-ldap.jar:LDAP 驗證和配置,若需要LDAP驗證和管理LDAP用戶實體

  • ACL訪問控制表 - spring-security-acl.jar:ACL專門領域對象的實現

  • CAS - spring-security-cas.jar:CAS客戶端繼承,若想用CAS的SSO服務器網頁驗證

  • OpenID - spring-security-openid.jar:OpenID網頁驗證支持

  • Test - spring-security-test.jar:支持Spring Security的測試

默認執行順序

UsernamePasswordAuthenticationFilter

  1. 用戶通過url:/login 登錄,該過濾器接收表單用戶名密碼
  2. 判斷用戶名密碼是否為空
  3. 生成 UsernamePasswordAuthenticationToken
  4. 將Authentiction 傳給 AuthenticationManager接口的 authenticate 方法進行認證處理
  5. AuthenticationManager 默認是實現類為 ProviderManager ,ProviderManager 委托給 AuthenticationProvider 進行處理
  6. UsernamePasswordAuthenticationFilter 繼承了 AbstractAuthenticationProcessingFilter 抽象類,AbstractAuthenticationProcessingFilter 在 successfulAuthentication 方法中對登錄成功進行了處理,通過 SecurityContextHolder.getContext().setAuthentication() 方法將 Authentication 認證信息對象綁定到 SecurityContext
  7. 下次請求時,在過濾器鏈頭的 SecurityContextPersistenceFilter 會從 Session 中取出用戶信息并生成 Authentication(默認為 UsernamePasswordAuthenticationToken),并通過 SecurityContextHolder.getContext().setAuthentication() 方法將 Authentication 認證信息對象綁定到 SecurityContext
  8. 需要權限才能訪問的請求會從 SecurityContext 中獲取用戶的權限進行驗證
    DaoAuthenticationProvider (實現了 AuthenticationProvider):
    通過 UserDetailsService 獲取 UserDetails
    將 UserDetails 和 UsernamePasswordAuthentionToken 進行認證匹配用戶名密碼是否正確
    若正確則通過 UserDetails 中用戶的權限、用戶名等信息生成一個新的 Authentication 認證對象并返回

DaoAuthenticationProvider (實現了 AuthenticationProvider)

  1. 通過 UserDetailsService 獲取 UserDetails
  2. 將 UserDetails 和 UsernamePasswordAuthentionToken 進行認證匹配用戶名密碼是否正確
  3. 若正確則通過 UserDetails 中用戶的權限、用戶名等信息生成一個新的 Authentication 認證對象并返回

相關類

WebSecurityConfigurerAdapter

  • 為創建 WebSecurityConfigurer 實例提供方便的基類,重寫它的 configure 方法來設置安全細節
    • configure(HttpSecurity http):重寫該方法,通過 http 對象的 authorizeRequests()方法定義URL訪問權限,默認會為 formLogin() 提供一個簡單的測試HTML頁面
    • _configureGlobal(AuthenticationManagerBuilder auth) _:通過 auth 對象的方法添加身份驗證

SecurityContextHolder

  • SecurityContextHolder 用于存儲安全上下文信息(如操作用戶是誰、用戶是否被認證、用戶權限有哪些),它用 ThreadLocal 來保存 SecurityContext,者意味著 Spring Security 在用戶登錄時自動綁定到當前現場,用戶退出時,自動清除當前線程認證信息,SecurityContext 中含有正在訪問系統用戶的詳細信息

AuthenticationManagerBuilder

  • 用于構建認證 AuthenticationManager 認證,允許快速構建內存認證、LDAP身份認證、JDBC身份驗證,添加 userDetailsService(獲取認證信息數據) 和 AuthenticationProvider's(定義認證方式)
  • 內存驗證:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.inMemoryAuthentication()
            .withUser("user").password("123").roles("USER").and()
            .withUser("admin").password("456").roles("USER","ADMIN");
}

JDBC 驗證:

@Autowired
private DataSource dataSource;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.jdbcAuthentication()
            .dataSource(dataSource)
            .withDefaultSchema()
            .withUser("linyuan").password("123").roles("USER").and()
            .withUser("linyuan2").password("456").roles("ADMIN");
}
  • userDetailsService(T userDetailsService):傳入自定義的 UserDetailsService 獲取認證信息數據
  • authenticationProvider(AuthenticationProvider authenticationProvider) :傳入自定義認證過程

UserDetailsService

  • 該接口僅有一個方法 loadUserByUsername,Spring Security 通過該方法獲取.可自定義。

UserDetails

  • 我們可以實現該接口來定義自己認證用戶的獲取方式(如數據庫中獲取),認證成功后會將 UserDetails 賦給 Authentication 的 principal 屬性,然后再把 Authentication 保存到 SecurityContext 中,之后需要實用用戶信息時通過 SecurityContextHolder 獲取存放在 SecurityContext 中的 Authentication 的 principal。

Authentication

  • Authentication 是一個接口,用來表示用戶認證信息,在用戶登錄認證之前相關信息(用戶傳過來的用戶名密碼)會封裝為一個 Authentication 具體實現類對象,默認情況下為 UsernamePasswordAuthenticationToken,登錄之后(通過AuthenticationManager認證)會生成一個更詳細的、包含權限的對象,然后把它保存在權限線程本地的 SecurityContext 中,供后續權限鑒定用

GrantedAuthority

  • GrantedAuthority 是一個接口,它定義了一個 getAuthorities() 方法返回當前 Authentication 對象的擁有權限字符串,用戶有權限是一個 GrantedAuthority 數組,每一個 GrantedAuthority 對象代表一種用戶
  • 通常搭配 SimpleGrantedAuthority 類使用

AuthenticationManager

  • AuthenticationManager 是用來處理認證請求的接口,它只有一個方法 authenticate(),該方法接收一個 Authentication 作為對象,如果認證成功則返回一個封裝了當前用戶權限信息的 Authentication 對象進行返回
  • 它默認的實現是 ProviderManager,但它不處理認證請求,而是將委托給 AuthenticationProvider 列表,然后依次使用 AuthenticationProvider 進行認證,如果有一個 AuthenticationProvider 認證的結果不為null,則表示成功(否則失敗,拋出 ProviderNotFoundException),之后不在進行其它 AuthenticationProvider 認證,并作為結果保存在 ProviderManager
  • 認證校驗時最常用的方式就是通過用戶名加載 UserDetails,然后比較 UserDetails 密碼與請求認證是否一致,一致則通過,Security 內部的 DaoAuthenticationProvider 就是實用這種方式
  • 認證成功后加載 UserDetails 來封裝要返回的 Authentication 對象,加載的 UserDetails 對象是包含用戶權限等信息的。認證成功返回的 Authentication 對象將會保存在當前的 SecurityContext 中

AuthenticationProvide

  • AuthenticationProvider 是一個身份認證接口,實現該接口來定制自己的認證方式,可通過 UserDetailsSevice 對獲取數據庫中的數據

  • 該接口中有兩個需要實現的方法:

    • Authentication authenticate(Authentication authentication):認證處理,返回一個 Authentication 的實現類則代表成功,返回 null 則為認證失敗
    • supports(Class<?> aClass):如果該 AuthenticationProvider 支持傳入的 Authentication 認證對象,則返回 true ,如:return aClass.equals(UsernamePasswordAuthenticationToken.class);

AuthorityUtils

  • 是一個工具包,用于操作 GrantedAuthority 集合的實用方法:
    • commaSeparatedStringToAuthorityList(String authorityString):從逗號分隔符中創建 GrantedAuthority 對象數組

AbstractAuthenticationProcessingFilter

  • 該抽象類繼承了 GenericFilterBean,是處理 form 登錄的過濾器,與 form 登錄相關的所有操作都在該抽象類的子類中進行(UsernamePasswordAuthenticationFilter 為其子類),比如獲取表單中的用戶名、密碼,然后進行認證等操作
  • 該類在 doFilter 方法中通過 attemptAuthentication() 方法進行用戶信息邏輯認證,認證成功會將用戶信息設置到 Session 中

UserDetails

  • 代表了Spring Security的用戶實體類,帶有用戶名、密碼、權限特性等性質,可以自己實現該接口,供 Spring Security 安全認證使用,Spring Security 默認使用的是內置的 User 類
  • 將從數據庫獲取的 User 對象傳入實現該接口的類,并獲取 User 對象的值來讓類實現該接口的方法
  • 通過 Authentication.getPrincipal() 的返回類型是 Object,但很多情況下其返回的其實是一個 UserDetails 的實例

HttpSecurity

  • 用于配置全局 Http 請求的權限控制規則,對哪些請求進行驗證、不驗證等
  • 常用方法:
  • authorizeRequests():返回一個配置對象用于配置請求的訪問限制
  • formLogin():返回表單配置對象,當什么都不指定時會提供一個默認的,如配置登錄請求,還有登錄成功頁面
  • logout():返回登出配置對象,可通過logoutUrl設置退出url
  • antMatchers:匹配請求路徑或請求動作類型,如:.antMatchers("/admin/**")
  • addFilterBefore: 在某過濾器之前添加 filter
  • addFilterAfter:在某過濾器之后添加 filter
  • addFilterAt:在某過濾器相同位置添加 filter,不會覆蓋相同位置的 filter
  • hasRole:結合 antMatchers 一起使用,設置請求允許訪問的角色權限或IP,如:
.antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")

方法名 | 用途
:-|:-|-:
access(String) | SpringEL表達式結果為true時可訪問
anonymous() | 匿名可訪問
denyAll() | 用戶不可以訪問
fullyAuthenticated() | 用戶完全認證訪問(非remember me下自動登錄)
hasAnyAuthority(String…) | 參數中任意權限可訪問
hasAnyRole(String…) | 參數中任意角色可訪問
hasAuthority(String) | 某一權限的用戶可訪問
hasRole(String) | 某一角色的用戶可訪問
permitAll() |所有用戶可訪問
rememberMe() | 允許通過remember me登錄的用戶訪問
authenticated() |用戶登錄后可訪問
hasIpAddress(String) | 用戶來自參數中的IP可訪問

注解與Spring EL

  • @EnableWebSecurity:開啟 Spring Security 注解
  • @EnableGlobalMethodSecurity(prePostEnabled=true):開啟security方法注解
  • @PreAuthorize:在方法調用前,通過SpringEL表達式限制方法訪問
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void addUser(User user){
    //如果具有權限 ROLE_ADMIN 訪問該方法
    ....
}
  • @PostAuthorize:允許方法調用,但時如果表達式結果為false拋出異常
//returnObject可以獲取返回對象user,判斷user屬性username是否和訪問該方法的用戶對象的用戶名一樣。不一樣則拋出異常。
@PostAuthorize("returnObject.user.username==principal.username")
public User getUser(int userId){
   //允許進入
...
    return user;
}
  • @PostFilter:允許方法調用,但必須按表達式過濾方法結果
//將結果過濾,即選出性別為男的用戶
@PostFilter("returnObject.user.sex=='男' ")
public List<User> getUserList(){
    //允許進入
    ...
    return user;
}
  • @PreFilter:允許方法調用,但必須在進入方法前過濾輸入值

Spring EL 表達式

表達式 描述
hasRole ([role]) 當前用戶是否擁有指定角色
hasAnyRole([role1,role2]) 多個角色是一個以逗號進行分隔的字符串。如果當前用戶擁有指定角色中的任意一個則返回true
hasAuthority ([auth]) 等同于hasRole
hasAnyAuthority([auth1,auth2]) 等同于 hasAnyRole
Principle 代表當前用戶的 principle對象
authentication 直接從 Security context獲取的當前 Authentication對象
permitAll 總是返回true,表示允許所有的訪問
denyAll 總是返回false,表示拒絕所有的訪問訪問
isAnonymous() 當前用戶是否是一個匿名用戶
isRememberMe 表示當前用戶是否是通過remember - me自動登錄的
isAuthenticated() 表示當前用戶是否已經登錄認證成功了
isFullAuthenticated() 如果當前用戶既不是一個匿名用戶,同時又不是通過Remember-Me自動登錄的,則返回true

密碼加密(PassWordEncoder)

  • Spring 提供的一個用于對密碼加密的接口,首選實現類為 BCryptPasswordEncoder
  • 通過@Bean注解將它注入到IOC容器:
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

過濾器鏈

SecurityContextPersistenceFilter

  • 過濾器鏈頭,是從 SecurityContextRepository 中取出用戶認證信息,默認實現為 HttpSessionSecurityContextRepository,它會從 Session 中取出已認證的用戶信息,提高效率,避免每次請求都要查詢用戶認證信息
  • 取出之后會放入 SecurityContextHolder 中,以便其它 filter 使用,SecurityContextHolder 使用 ThreadLocal 存儲用戶認證信息,保證線程之間信息隔離,最后再 finally 中清除該信息

WebAsyncManagerIntegrationFilter

  • 提供了對 SecurityContext 和 WebAsyncManager 的集成,會把 SecurityContext 設置到異步線程,使其也能獲取到用戶上下文認證信息

HanderWriterFilter

  • 會往請求的 Header 中添加相應的信息

CsrfFilter

  • 跨域請求偽造過濾器,通過客戶端穿來的 token 與服務端存儲的 token 進行對比來判斷請求

LogoutFilter

  • 匹配URL,默認為 /logout,匹配成功后則會用戶退出,清除認證信息,若有自己的退出邏輯,該過濾器可以關閉

UsernamePasswordAuthenticationFilter

  • 登錄認證過濾器,默認是對 /login 的 POST 請求進行認證,首先該方法會調用 attemptAuthentication 嘗試認證獲取一個 Authentication 認證對象,然后通過 sessionStrategy.onAuthentication 執行持久化,也就是保存認證信息,然后轉向下一個 Filter,最后調用 successfulAuthentication 執行認證后事件
  • attemptAuthentication 該方法是認證的主要方法,認證基本流程為 UserDeatilService 根據用戶名獲取到用戶信息,然后通過 UserDetailsChecker.check 對用戶狀態進行校驗,最后通過 additionalAuthenticationChecks 方法對用戶密碼進行校驗完后認證后,返回一個認證對象

DefaultLoginPageGeneratingFilter

  • 當請求為登錄請求時,生成簡單的登錄頁面,可以關閉

BasicAuthenticationFilter

  • Http Basci 認證的支持,該認證會把用戶名密碼使用 base64 編碼后放入 header 中傳輸,認證成功后會把用戶信息放入 SecurityContextHolder 中

RequestCacheAwareFilter

  • 恢復被打斷時的請求

SecurityContextHolderAwareRequestFilter

  • 針對 Servlet api 不同版本做一些包裝

AnonymousAuthenticationFIlter

  • SecurityContextHolder 中認證信息為空,則會創建一個匿名用戶到 SecurityContextHolder 中

SessionManagementFilter

  • 與登錄認證攔截時作用一樣,持久化用戶登錄信息,可以保存到 Session 中,也可以保存到 cookie 或 redis 中

ExceptionTranslationFilter

  • 異常攔截,處于 Filter 鏈條后部,只能攔截其后面的節點并著重處理 AuthenticationException 與 AccessDeniedException 異常
?著作權歸作者所有,轉載或內容合作請聯系作者
禁止轉載,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容