SpringSecurity是一個安全框架,主要用於授權和認證,在普通項目中,我們使用過濾器和攔截器也可以實現,但是使用SpringSecurity更加簡單。
一、spring security 簡介
spring security 的核心功能主要包括:
- 認證 (你是誰)
- 授權 (你能干什么)
- 攻擊防護 (防止偽造身份)
其核心就是一組過濾器鏈,項目啟動后將會自動配置。最核心的就是 Basic Authentication Filter 用來認證用戶的身份。在spring security中一種過濾器處理一種認證方式。
比如,對於username password認證過濾器來說,會檢查是否是一個登錄請求;是否包含username 和 password (也就是該過濾器需要的一些認證信息),如果不滿足則放行給下一個。
下一個按照自身職責判定是否是自身需要的信息,basic的特征就是在請求頭中有 Authorization:Basic eHh4Onh4 的信息。
中間可能還有更多的認證過濾器。最后一環是 FilterSecurityInterceptor,這里會判定該請求是否能進行訪問rest服務,判斷的依據是 BrowserSecurityConfig 中的配置,如果被拒絕了就會拋出不同的異常(根據具體的原因)。
Exception Translation Filter 會捕獲拋出的錯誤,然后根據不同的認證方式進行信息的返回提示。
注意:綠色的過濾器可以配置是否生效,其他的都不能控制。
二、整合SpringSecurity
1、在pom文件中導入依賴
<dependencies> ... <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency> ... </dependencies>
2、新建配置類
package com.opengauss.exam.security; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 授權 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/test").permitAll() .anyRequest().authenticated().and() .formLogin(); } // 認證 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("gwf").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1"); } }
該配置類的意思是:直接訪問 /test 可以訪問,但是若訪問 /hello 則需要先登錄。
WebSecurityConfig類使用了@EnableWebSecurity注解 ,以啟用SpringSecurity的Web安全支持,並提供Spring MVC集成。它還擴展了WebSecurityConfigurerAdapter,並覆蓋了一些方法來設置Web安全配置的一些細節。
configure(HttpSecurity)方法定義了哪些URL路徑應該被保護,哪些不應該。具體來說,“/test”路徑被配置為不需要任何身份驗證,所有其他路徑必須經過身份驗證。
當用戶成功登錄時,它們將被重定向到先前請求的需要身份認證的頁面。有一個由 loginPage()指定的自定義“/登錄”頁面,每個人都可以查看它。
對於configure(AuthenticationManagerBuilder) 方法,它將單個用戶設置在內存中。該用戶的用戶名為“gwf”,密碼為“123456”,角色為“vip1”。
3、下面我們看下其他詳細攔截規則:
// 案例1
http.authorizeRequests() //請求路徑“/test”容許訪問
.antMatchers("/test").permitAll() //其它請求都需要校驗才能訪問
.anyRequest().authenticated() .and() // 定義登錄的頁面為“/login”,容許訪問
.formLogin().loginPage("/login").permitAll() .and() //默認的“/logout”,容許訪問
.logout().permitAll(); // 案例2
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").permitAll() //必須有“USER”角色的才能訪問
.antMatchers("/user/**").hasAuthority("USER") .and() //登陸成功以后默認訪問路徑/user
.formLogin().loginPage("/login").defaultSuccessUrl("/user") .and() //注銷以后默認訪問路徑/login
.logout().logoutUrl("/logout").logoutSuccessUrl("/login"); http.addFilterAt(customFromLoginFilter(), UsernamePasswordAuthenticationFilter.class); } // 案例3
@Override public void configure(WebSecurity web) throws Exception {
// 設置下面為忽略地址 web.ignoring().antMatchers("/js/**","/css/**","/img/**","/webjars/**"); }
4、自定義頁面
前面我們的登錄頁面都是使用的SpringSecurity默認的,我們可以在配置類中修改成我們自定義的登錄頁面
(1)自定義登錄頁面:resources/templates/login.html
SpringSecurity的name
屬性默認是username
和password
,這里我們采用自定義的方式,改成:user 與 pwd,/login
是SpringSecurity默認的處理登錄的Controller
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="/login" method="post"> 用戶名:<input type="text" name="user"><br> 密碼:<input type="password" name="pwd"><br>
<input type="radio" name="remember">記住我 <button type="submit">提交</button>
</form>
</body>
</html>
(2)修改SecurityConfig配置類
在configure(HttpSecurity http)
方法中添加如下內容,(注意這里我們要禁止csrf,否則登錄會被攔截):
// 開啟登錄頁面,即沒有權限的話跳轉到登錄頁面,對應地址:/login
http.formLogin() // 登錄頁面
.loginPage("/toLogin") // 用戶名的name
.usernameParameter("user") // 密碼的name
.passwordParameter("pwd") // 處理登錄的Controller
.loginProcessingUrl("/login"); http.csrf().disable(); // 開啟記住我功能,默認保存兩周
http.rememberMe() // name屬性
.rememberMeParameter("remember");
(3)編寫Controller
@GetMapping("/toLogin") public String toLogin(){ return "login"; }
此時,我們就可以使用自定義登錄頁面了。
三、參數詳解
1、注解 @EnableWebSecurity
在 SpringBoot 應用中使用 Spring Security,用到了 @EnableWebSecurity 注解,官方說明為,該注解和 @Configuration 注解一起使用,注解 WebSecurityConfigurer 類型的類,或者利用@EnableWebSecurity 注解繼承 WebSecurityConfigurerAdapter的類,這樣就構成了 Spring Security 的配置。
2、抽象類 WebSecurityConfigurerAdapter
一般情況,會選擇繼承 WebSecurityConfigurerAdapter 類,其官方說明為:WebSecurityConfigurerAdapter 提供了一種便利的方式去創建 WebSecurityConfigurer的實例,只需要重寫 WebSecurityConfigurerAdapter 的方法,即可配置攔截什么URL、設置什么權限等安全控制。
3、方法 configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http)
Demo 中重寫了 WebSecurityConfigurerAdapter 的兩個方法:
/** * 通過 {@link #authenticationManager()} 方法的默認實現嘗試獲取一個 {@link AuthenticationManager}. * 如果被復寫, 應該使用{@link AuthenticationManagerBuilder} 來指定 {@link AuthenticationManager}. * * 例如, 可以使用以下配置在內存中進行注冊公開內存的身份驗證{@link UserDetailsService}: * * // 在內存中添加 user 和 admin 用戶 * @Override * protected void configure(AuthenticationManagerBuilder auth) { * auth * .inMemoryAuthentication().withUser("user").password("password").roles("USER").and() * .withUser("admin").password("password").roles("USER", "ADMIN"); * } * * // 將 UserDetailsService 顯示為 Bean * @Bean * @Override * public UserDetailsService userDetailsServiceBean() throws Exception { * return super.userDetailsServiceBean(); * } * */
protected void configure(AuthenticationManagerBuilder auth) throws Exception { this.disableLocalConfigureAuthenticationBldr = true; } /** * 復寫這個方法來配置 {@link HttpSecurity}. * 通常,子類不能通過調用 super 來調用此方法,因為它可能會覆蓋其配置。 默認配置為: * * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); * */
protected void configure(HttpSecurity http) throws Exception { logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); }
4、final 類 HttpSecurity
HttpSecurity 常用方法及說明:
openidLogin() // 用於基於 OpenId 的驗證
headers() // 將安全標頭添加到響應
cors() // 配置跨域資源共享( CORS )
sessionManagement() // 允許配置會話管理
portMapper() // 允許配置一個PortMapper(HttpSecurity#(getSharedObject(class))),
其他提供SecurityConfigurer的對象使用 PortMapper 從 HTTP 重定向到 HTTPS 或者從 HTTPS 重定向到 HTTP。默認情況下,
Spring Security使用一個PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee() // 配置基於容器的預認證。 在這種情況下,認證由Servlet容器管理
x509() // 配置基於x509的認證
rememberMe // 允許配置“記住我”的驗證
authorizeRequests() // 允許基於使用HttpServletRequest限制訪問
requestCache() // 允許配置請求緩存
exceptionHandling() // 允許配置錯誤處理
securityContext() // 在HttpServletRequests之間的SecurityContextHolder上設置SecurityContext的管理。
當使用WebSecurityConfigurerAdapter時,這將自動應用
servletApi() // 將HttpServletRequest方法與在其上找到的值集成到SecurityContext中。
當使用WebSecurityConfigurerAdapter時,這將自動應用
csrf() // 添加 CSRF 支持,使用WebSecurityConfigurerAdapter時,默認啟用
logout() // 添加退出登錄支持。當使用WebSecurityConfigurerAdapter時,這將自動應用。
默認情況是,訪問URL”/ logout”,使HTTP Session無效來清除用戶,清除已配置的任何#rememberMe()身份驗證,
清除SecurityContextHolder,然后重定向到”/login?success”
anonymous() // 允許配置匿名用戶的表示方法。 當與WebSecurityConfigurerAdapter結合使用時,這將自動應用。
默認情況下,匿名用戶將使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,
並包含角色 “ROLE_ANONYMOUS”
formLogin() // 指定支持基於表單的身份驗證。如果未指定FormLoginConfigurer#loginPage(String),則將生成默認登錄頁面
oauth2Login() // 根據外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份驗證
requiresChannel() // 配置通道安全。為了使該配置有用,必須提供至少一個到所需信道的映射
httpBasic() // 配置 Http Basic 驗證
addFilterAt() // 在指定的Filter類的位置添加過濾器
5、類 AuthenticationManagerBuilder
/** * {@link SecurityBuilder} used to create an {@link AuthenticationManager}. Allows for * easily building in memory authentication, LDAP authentication, JDBC based * authentication, adding {@link UserDetailsService}, and adding * {@link AuthenticationProvider}'s. */
意思是,AuthenticationManagerBuilder 用於創建一個 AuthenticationManager,讓我能夠輕松的實現內存驗證、LADP驗證、基於JDBC的驗證、添加UserDetailsService、添加AuthenticationProvider。
四、校驗流程圖
五、源碼解析
上面的流程圖就是spring security的整體驗證流程!
1、首先所有的請求都會走AbstractAuthenticationProcessingFilter.doFilter(req, res, chain)方法
2、判斷請求是否需要驗證
3、authResult = attemptAuthentication(request, response) 進行用戶驗證(request中會帶有用戶的信息),該方法也是驗證過程中最重要的方法
(1)返回一個 Authentication 對象,說明驗證成功
(2)驗證時發生 AuthenticationException。
(3)返回Null,表示身份驗證不完整。假設子類做了一些必要的工作(如重定向)來繼續處理驗證,方法將立即返回。假設后一個請求將被這種方法接收,其中返回的Authentication對象不為空。
接下來我們來看一下AbstractAuthenticationProcessingFilter到底是怎么搞得?
首先AbstractAuthenticationProcessingFilter抽象類,咱們看一下繼承它的子類
從上圖我們可以看出UsernamePasswordAuthenticationFilter類是它的子類,那么我們看一下UsernamePasswordAuthenticationFilter類attemptAuthentication(request, response)方法是怎么搞得
從代碼可以看出:首先用用戶名和密碼生成個token,然后去驗證token;
那么問題來了,是誰去驗證的token?別急我們繼續跟代碼。通過跟代碼我們可以得出,原來AbstractAuthenticationProcessingFilter類中有一個private AuthenticationManager authenticationManager成員變量,也就是通過它去驗證token;
下面我們看一下AuthenticationManager 類:
通過查看原來AuthenticationManager是一個驗證管理器接口,既然是接口那一定有實現它的實現類!我們繼續跟!!!
通過查看代碼,就ProviderManager類像正常點的,那我們繼續看看ProviderManager是到底在搞什么
以上就是ProviderManager的authenticate(Authentication authentication),它是實現AuthenticationManager接口的,主要看一下紅線指向兩處:
(1)有個這個東西【getProviders()】,然后遍歷它,
(2)AuthenticationProvider對象去進行驗證token!!【result = provider.authenticate(authentication)】;
通過查看代碼原來ProviderManager類里面有這個屬性private List providers = Collections.emptyList(); 也就是getProviders();
既然干活的是AuthenticationProvider對象,那就再看看它是怎么搞得!
AuthenticationProvider接口:

AbstractUserDetailsAuthenticationProvider通過名字,你有沒有什么想法?抽象的用戶詳情驗證提供者!!!那么我們看一下authenticate(Authentication authentication)方法!
retrieveUser方法
查看源碼,他是一個抽象的方法;那接下來我們看一下它的實現類!DaoAuthenticationProvider
看箭頭,有沒有什么茅塞頓開!!!this.getUserDetailsService().loadUserByUsername(username);就是我們自己實現的UserDetailsService接口的實現類,我們通過實現loadUserByUsername(username)方法,獲取userDetail對象;然后通過additionalAuthenticationChecks方法檢驗!!!
看到這里,如果之前沒有用過spring security 的人一定會一頭霧水~沒事兒,多看看就好了,再結合網上的Demo自己感覺感覺!!!
總結:其實看着有點雲里霧里~但是中心思想,就是把驗證用戶信息的一整套流程預先已經定義好了,封裝在一個方法中(模板模式),然后各種暴露接口,抽象類,讓子類去實現具體的業務邏輯!
源碼部分文章:https://www.jianshu.com/p/dd42cb2b46dc