Spring Security 是一個基于 Spring AOP 和 Servlet 過濾器的安全框架,它提供了安全性方面的解決方案,同時在Web請求和方法級處理身份確認和授權(quán)
Spring Boot 添加 Spring Security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring Security 模塊
- 核心模塊 - spring-security-core.jar:包含核心驗證和訪問控制類和接口,遠程支持的基本配置API,是基本模塊
- 遠程調(diào)用 - spring-security-remoting.jar:提供與 Spring Remoting 集成
- 網(wǎng)頁 - spring-security-web.jar:包括網(wǎng)站安全的模塊,提供網(wǎng)站認證服務(wù)和基于URL訪問控制
- 配置 - spring-security-config.jar:包含安全命令空間解析代碼,若使用XML進行配置則需要
- LDAP - spring-security-ldap.jar:LDAP 驗證和配置,若需要LDAP驗證和管理LDAP用戶實體
- ACL訪問控制表 - spring-security-acl.jar:ACL專門領(lǐng)域?qū)ο蟮膶崿F(xiàn)
- CAS - spring-security-cas.jar:CAS客戶端繼承,若想用CAS的SSO服務(wù)器網(wǎng)頁驗證
- OpenID - spring-security-openid.jar:OpenID網(wǎng)頁驗證支持
- Test - spring-security-test.jar:支持Spring Security的測試
認證流程
- 用戶使用用戶名和密碼登錄
- 用戶名密碼被過濾器(默認為 UsernamePasswordAuthenticationFilter)獲取到,封裝成 Authentication
- token(Authentication實現(xiàn)類)傳遞給 AuthenticationManager 進行認證
- AuthenticationManager 認證成功后返回一個封裝了用戶權(quán)限信息的 Authentication 對象
- 通過調(diào)用 SecurityContextHolder.getContext().setAuthentication(...) 將 Authentication 對象賦給當(dāng)前的 SecurityContext
默認執(zhí)行順序:
UsernamePasswordAuthenticationFilter :
- 用戶通過url:/login 登錄,該過濾器接收表單用戶名密碼
- 判斷用戶名密碼是否為空
- 生成 UsernamePasswordAuthenticationToken
- 將 Authentiction 傳給 AuthenticationManager接口的 authenticate 方法進行認證處理
- AuthenticationManager 默認是實現(xiàn)類為 ProviderManager ,ProviderManager 委托給 AuthenticationProvider 進行處理
- UsernamePasswordAuthenticationFilter 繼承了 AbstractAuthenticationProcessingFilter 抽象類,AbstractAuthenticationProcessingFilter 在 successfulAuthentication 方法中對登錄成功進行了處理,通過 SecurityContextHolder.getContext().setAuthentication() 方法將 Authentication 認證信息對象綁定到 SecurityContext
- 下次請求時,在過濾器鏈頭的 SecurityContextPersistenceFilter 會從 Session 中取出用戶信息并生成 Authentication(默認為 UsernamePasswordAuthenticationToken),并通過 SecurityContextHolder.getContext().setAuthentication() 方法將 Authentication 認證信息對象綁定到 SecurityContext
- 需要權(quán)限才能訪問的請求會從 SecurityContext 中獲取用戶的權(quán)限進行驗證
DaoAuthenticationProvider (實現(xiàn)了 AuthenticationProvider):
- 通過 UserDetailsService 獲取 UserDetails
- 將 UserDetails 和 UsernamePasswordAuthentionToken 進行認證匹配用戶名密碼是否正確
- 若正確則通過 UserDetails 中用戶的權(quán)限、用戶名等信息生成一個新的 Authentication 認證對象并返回
相關(guān)類
WebSecurityConfigurerAdapter
- 為創(chuàng)建 WebSecurityConfigurer 實例提供方便的基類,重寫它的 configure 方法來設(shè)置安全細節(jié)
- configure(HttpSecurity http):重寫該方法,通過 http 對象的 authorizeRequests()方法定義URL訪問權(quán)限,默認會為 formLogin() 提供一個簡單的測試HTML頁面
- _configureGlobal(AuthenticationManagerBuilder auth) _:通過 auth 對象的方法添加身份驗證
SecurityContextHolder
- SecurityContextHolder 用于存儲安全上下文信息(如操作用戶是誰、用戶是否被認證、用戶權(quán)限有哪些),它用 ThreadLocal 來保存 SecurityContext,者意味著 Spring Security 在用戶登錄時自動綁定到當(dāng)前現(xiàn)場,用戶退出時,自動清除當(dāng)前線程認證信息,SecurityContext 中含有正在訪問系統(tǒng)用戶的詳細信息
AuthenticationManagerBuilder
- 用于構(gòu)建認證 AuthenticationManager 認證,允許快速構(gòu)建內(nèi)存認證、LDAP身份認證、JDBC身份驗證,添加 userDetailsService(獲取認證信息數(shù)據(jù)) 和 AuthenticationProvider's(定義認證方式)
- 內(nèi)存驗證:
@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 獲取認證信息數(shù)據(jù)
- authenticationProvider(AuthenticationProvider authenticationProvider) :傳入自定義認證過程
UserDetailsService
- 該接口僅有一個方法 loadUserByUsername,Spring Security 通過該方法獲取
UserDetails
- 我們可以實現(xiàn)該接口來定義自己認證用戶的獲取方式(如數(shù)據(jù)庫中獲取),認證成功后會將 UserDetails 賦給 Authentication 的 principal 屬性,然后再把 Authentication 保存到 SecurityContext 中,之后需要實用用戶信息時通過 SecurityContextHolder 獲取存放在 SecurityContext 中的 Authentication 的 principal
Authentication
- Authentication 是一個接口,用來表示用戶認證信息,在用戶登錄認證之前相關(guān)信息(用戶傳過來的用戶名密碼)會封裝為一個 Authentication 具體實現(xiàn)類對象,默認情況下為 UsernamePasswordAuthenticationToken,登錄之后(通過AuthenticationManager認證)會生成一個更詳細的、包含權(quán)限的對象,然后把它保存在權(quán)限線程本地的 SecurityContext 中,供后續(xù)權(quán)限鑒定用
GrantedAuthority
- GrantedAuthority 是一個接口,它定義了一個 getAuthorities() 方法返回當(dāng)前 Authentication 對象的擁有權(quán)限字符串,用戶有權(quán)限是一個 GrantedAuthority 數(shù)組,每一個 GrantedAuthority 對象代表一種用戶
- 通常搭配 SimpleGrantedAuthority 類使用
AuthenticationManager
- AuthenticationManager 是用來處理認證請求的接口,它只有一個方法 authenticate(),該方法接收一個 Authentication 作為對象,如果認證成功則返回一個封裝了當(dāng)前用戶權(quán)限信息的 Authentication 對象進行返回
- 它默認的實現(xiàn)是 ProviderManager,但它不處理認證請求,而是將委托給 AuthenticationProvider 列表,然后依次使用 AuthenticationProvider 進行認證,如果有一個 AuthenticationProvider 認證的結(jié)果不為null,則表示成功(否則失敗,拋出 ProviderNotFoundException),之后不在進行其它 AuthenticationProvider 認證,并作為結(jié)果保存在 ProviderManager
- 認證校驗時最常用的方式就是通過用戶名加載 UserDetails,然后比較 UserDetails 密碼與請求認證是否一致,一致則通過,Security 內(nèi)部的 DaoAuthenticationProvider 就是實用這種方式
- 認證成功后加載 UserDetails 來封裝要返回的 Authentication 對象,加載的 UserDetails 對象是包含用戶權(quán)限等信息的。認證成功返回的 Authentication 對象將會保存在當(dāng)前的 SecurityContext 中
AuthenticationProvide
- AuthenticationProvider 是一個身份認證接口,實現(xiàn)該接口來定制自己的認證方式,可通過 UserDetailsSevice 對獲取數(shù)據(jù)庫中的數(shù)據(jù)
- 該接口中有兩個需要實現(xiàn)的方法:
- Authentication authenticate(Authentication authentication):認證處理,返回一個 Authentication 的實現(xiàn)類則代表成功,返回 null 則為認證失敗
- supports(Class<?> aClass):如果該 AuthenticationProvider 支持傳入的 Authentication 認證對象,則返回 true ,如:return aClass.equals(UsernamePasswordAuthenticationToken.class);
AuthorityUtils
- 是一個工具包,用于操作 GrantedAuthority 集合的實用方法:
- commaSeparatedStringToAuthorityList(String authorityString):從逗號分隔符中創(chuàng)建 GrantedAuthority 對象數(shù)組
AbstractAuthenticationProcessingFilter
- 該抽象類繼承了 GenericFilterBean,是處理 form 登錄的過濾器,與 form 登錄相關(guān)的所有操作都在該抽象類的子類中進行(UsernamePasswordAuthenticationFilter 為其子類),比如獲取表單中的用戶名、密碼,然后進行認證等操作
- 該類在 doFilter 方法中通過 attemptAuthentication() 方法進行用戶信息邏輯認證,認證成功會將用戶信息設(shè)置到 Session 中
UserDetails
- 代表了Spring Security的用戶實體類,帶有用戶名、密碼、權(quán)限特性等性質(zhì),可以自己實現(xiàn)該接口,供 Spring Security 安全認證使用,Spring Security 默認使用的是內(nèi)置的 User 類
- 將從數(shù)據(jù)庫獲取的 User 對象傳入實現(xiàn)該接口的類,并獲取 User 對象的值來讓類實現(xiàn)該接口的方法
- 通過 Authentication.getPrincipal() 的返回類型是 Object,但很多情況下其返回的其實是一個 UserDetails 的實例
HttpSecurity
- 用于配置全局 Http 請求的權(quán)限控制規(guī)則,對哪些請求進行驗證、不驗證等
- 常用方法:
- authorizeRequests():返回一個配置對象用于配置請求的訪問限制
- formLogin():返回表單配置對象,當(dāng)什么都不指定時會提供一個默認的,如配置登錄請求,還有登錄成功頁面
- logout():返回登出配置對象,可通過logoutUrl設(shè)置退出url
- antMatchers:匹配請求路徑或請求動作類型,如:.antMatchers("/admin/**")
- addFilterBefore: 在某過濾器之前添加 filter
- addFilterAfter:在某過濾器之后添加 filter
- addFilterAt:在某過濾器相同位置添加 filter,不會覆蓋相同位置的 filter
- hasRole:結(jié)合 antMatchers 一起使用,設(shè)置請求允許訪問的角色權(quán)限或IP,如:
.antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")方法名 用途 access(String) SpringEL表達式結(jié)果為true時可訪問 anonymous() 匿名可訪問 denyAll() 用戶不可以訪問 fullyAuthenticated() 用戶完全認證訪問(非remember me下自動登錄) hasAnyAuthority(String…) 參數(shù)中任意權(quán)限可訪問 hasAnyRole(String…) 參數(shù)中任意角色可訪問 hasAuthority(String) 某一權(quán)限的用戶可訪問 hasRole(String) 某一角色的用戶可訪問 permitAll() 所有用戶可訪問 rememberMe() 允許通過remember me登錄的用戶訪問 authenticated() 用戶登錄后可訪問 hasIpAddress(String) 用戶來自參數(shù)中的IP可訪問
注解與Spring EL
- @EnableWebSecurity:開啟 Spring Security 注解
- @EnableGlobalMethodSecurity(prePostEnabled=true):開啟security方法注解
- @PreAuthorize:在方法調(diào)用前,通過SpringEL表達式限制方法訪問
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void addUser(User user){
//如果具有權(quán)限 ROLE_ADMIN 訪問該方法
....
}
- @PostAuthorize:允許方法調(diào)用,但時如果表達式結(jié)果為false拋出異常
//returnObject可以獲取返回對象user,判斷user屬性username是否和訪問該方法的用戶對象的用戶名一樣。不一樣則拋出異常。
@PostAuthorize("returnObject.user.username==principal.username")
public User getUser(int userId){
//允許進入
...
return user;
}
- @PostFilter:允許方法調(diào)用,但必須按表達式過濾方法結(jié)果
//將結(jié)果過濾,即選出性別為男的用戶
@PostFilter("returnObject.user.sex=='男' ")
public List<User> getUserList(){
//允許進入
...
return user;
}
- @PreFilter:允許方法調(diào)用,但必須在進入方法前過濾輸入值
Spring EL 表達式:
| 表達式 | 描述 |
|---|---|
| hasRole ([role]) | 當(dāng)前用戶是否擁有指定角色 |
| hasAnyRole([role1,role2]) | 多個角色是一個以逗號進行分隔的字符串。如果當(dāng)前用戶擁有指定角色中的任意一個則返回true |
| hasAuthority ([auth]) | 等同于hasRole |
| hasAnyAuthority([auth1,auth2]) | 等同于 hasAnyRole |
| Principle | 代表當(dāng)前用戶的 principle對象 |
| authentication | 直接從 Security context獲取的當(dāng)前 Authentication對象 |
| permitAll | 總是返回true,表示允許所有的訪問 |
| denyAll | 總是返回false,表示拒絕所有的訪問訪問 |
| isAnonymous() | 當(dāng)前用戶是否是一個匿名用戶 |
| isRememberMe | 表示當(dāng)前用戶是否是通過remember - me自動登錄的 |
| isAuthenticated() | 表示當(dāng)前用戶是否已經(jīng)登錄認證成功了 |
| isFullAuthenticated() | 如果當(dāng)前用戶既不是一個匿名用戶,同時又不是通過Remember-Me自動登錄的,則返回true |
密碼加密(PassWordEncoder)
- Spring 提供的一個用于對密碼加密的接口,首選實現(xiàn)類為 BCryptPasswordEncoder
- 通過@Bean注解將它注入到IOC容器:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
過濾器鏈
SecurityContextPersistenceFilter
- 過濾器鏈頭,是從 SecurityContextRepository 中取出用戶認證信息,默認實現(xiàn)為 HttpSessionSecurityContextRepository,它會從 Session 中取出已認證的用戶信息,提高效率,避免每次請求都要查詢用戶認證信息
- 取出之后會放入 SecurityContextHolder 中,以便其它 filter 使用,SecurityContextHolder 使用 ThreadLocal 存儲用戶認證信息,保證線程之間信息隔離,最后再 finally 中清除該信息
WebAsyncManagerIntegrationFilter
- 提供了對 SecurityContext 和 WebAsyncManager 的集成,會把 SecurityContext 設(shè)置到異步線程,使其也能獲取到用戶上下文認證信息
HanderWriterFilter
- 會往請求的 Header 中添加相應(yīng)的信息
CsrfFilter
- 跨域請求偽造過濾器,通過客戶端穿來的 token 與服務(wù)端存儲的 token 進行對比來判斷請求
LogoutFilter
- 匹配URL,默認為 /logout,匹配成功后則會用戶退出,清除認證信息,若有自己的退出邏輯,該過濾器可以關(guān)閉
UsernamePasswordAuthenticationFilter
- 登錄認證過濾器,默認是對 /login 的 POST 請求進行認證,首先該方法會調(diào)用 attemptAuthentication 嘗試認證獲取一個 Authentication 認證對象,然后通過 sessionStrategy.onAuthentication 執(zhí)行持久化,也就是保存認證信息,然后轉(zhuǎn)向下一個 Filter,最后調(diào)用 successfulAuthentication 執(zhí)行認證后事件
- attemptAuthentication 該方法是認證的主要方法,認證基本流程為 UserDeatilService 根據(jù)用戶名獲取到用戶信息,然后通過 UserDetailsChecker.check 對用戶狀態(tài)進行校驗,最后通過 additionalAuthenticationChecks 方法對用戶密碼進行校驗完后認證后,返回一個認證對象
DefaultLoginPageGeneratingFilter
- 當(dāng)請求為登錄請求時,生成簡單的登錄頁面,可以關(guān)閉
BasicAuthenticationFilter
- Http Basci 認證的支持,該認證會把用戶名密碼使用 base64 編碼后放入 header 中傳輸,認證成功后會把用戶信息放入 SecurityContextHolder 中
RequestCacheAwareFilter
- 恢復(fù)被打斷時的請求
SecurityContextHolderAwareRequestFilter
- 針對 Servlet api 不同版本做一些包裝
AnonymousAuthenticationFIlter
- SecurityContextHolder 中認證信息為空,則會創(chuàng)建一個匿名用戶到 SecurityContextHolder 中
SessionManagementFilter
- 與登錄認證攔截時作用一樣,持久化用戶登錄信息,可以保存到 Session 中,也可以保存到 cookie 或 redis 中
ExceptionTranslationFilter
- 異常攔截,處于 Filter 鏈條后部,只能攔截其后面的節(jié)點并著重處理 AuthenticationException 與 AccessDeniedException 異常