Spring Security是一款基於Spring的安全框架,主要包含認證和授權兩大安全模塊,和另外一款流行的安全框架Apache Shiro相比,它擁有更為強大的功能。Spring Security也可以輕松的自定義擴展以滿足各種需求,並且對常見的Web安全攻擊提供了防護支持。如果你的Web框架選擇的是Spring,那么在安全方面Spring Security會是一個不錯的選擇。
這里我們使用Spring Boot來集成Spring Security,Spring Boot版本為1.5.14.RELEASE,Spring Security版本為4.2.7RELEASE。
開啟Spring Security
創建一個Spring Boot項目,然后引入spring-boot-starter-security:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
接下來我們創建一個TestController,對外提供一個/hello服務:
@RestController
public class TestController {
@GetMapping("hello")
public String hello() {
return "hello spring security";
}
}
這時候我們直接啟動項目,訪問http://localhost:8080/hello,可看到頁面彈出了個HTTP Basic認證框:

當Spring項目中引入了Spring Security依賴的時候,項目會默認開啟如下配置:
security:
basic:
enabled: true
這個配置開啟了一個HTTP basic類型的認證,所有服務的訪問都必須先過這個認證,默認的用戶名為user,密碼由Sping Security自動生成,回到IDE的控制台,可以找到密碼信息:
Using default security password: e9ed391c-93de-4611-ac87-d871d9e749ac
輸入用戶名user,密碼e9ed391c-93de-4611-ac87-d871d9e749ac后,我們便可以成功訪問/hello接口。
基於表單認證
我們可以通過一些配置將HTTP Basic認證修改為基於表單的認證方式。
創建一個配置類BrowserSecurityConfig繼承org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter這個抽象類並重寫configure(HttpSecurity http)方法。WebSecurityConfigurerAdapter是由Spring Security提供的Web應用安全配置的適配器:
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 表單方式
.and()
.authorizeRequests() // 授權配置
.anyRequest() // 所有請求
.authenticated(); // 都需要認證
}
}
Spring Security提供了這種鏈式的方法調用。上面配置指定了認證方式為表單登錄,並且所有請求都需要進行認證。這時候我們重啟項目,再次訪問http://localhost:8080/hello,可以看到認證方式已經是form表單的方式了:

用戶名依舊是user,密碼由Spring Security自動生成。當輸入憑證錯誤時,頁面上將顯示錯誤信息:

如果需要換回HTTP Basic的認證方式,我們只需要簡單修改configure方法中的配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.formLogin() // 表單方式
http.httpBasic() // HTTP Basic方式
.and()
.authorizeRequests() // 授權配置
.anyRequest() // 所有請求
.authenticated(); // 都需要認證
}
基本原理
上面我們開啟了一個最簡單的Spring Security安全配置,下面我們來了解下Spring Security的基本原理。通過上面的的配置,代碼的執行過程可以簡化為下圖表示:

如上圖所示,Spring Security包含了眾多的過濾器,這些過濾器形成了一條鏈,所有請求都必須通過這些過濾器后才能成功訪問到資源。其中UsernamePasswordAuthenticationFilter過濾器用於處理基於表單方式的登錄認證,而BasicAuthenticationFilter用於處理基於HTTP Basic方式的登錄驗證,后面還可能包含一系列別的過濾器(可以通過相應配置開啟)。在過濾器鏈的末尾是一個名為FilterSecurityInterceptor的攔截器,用於判斷當前請求身份認證是否成功,是否有相應的權限,當身份認證失敗或者權限不足的時候便會拋出相應的異常。ExceptionTranslateFilter捕獲並處理,所以我們在ExceptionTranslateFilter過濾器用於處理了FilterSecurityInterceptor拋出的異常並進行處理,比如需要身份認證時將請求重定向到相應的認證頁面,當認證失敗或者權限不足時返回相應的提示信息。
下面我們通過debug來驗證這個過程(登錄方式改回表單的方式)。
我們在/hello服務上打個斷點:

在FilterSecurityInterceptor的invoke方法的super.beforeInvocation上打個斷點:

當這行代碼執行通過后,便可以調用下一行的doFilter方法來真正調用/hello服務,否則將拋出相應的異常。
當FilterSecurityInterceptor拋出異常時,異常將由ExceptionTranslateFilter捕獲並處理,所以我們在ExceptionTranslateFilter的doFilter方法catch代碼塊第一行打個斷點:

我們待會模擬的是用戶未登錄直接訪問/hello,所以應該是拋出用戶未認證的異常,所以接下來應該跳轉到UsernamePasswordAuthenticationFilter處理表單方式的用戶認證。在UsernamePasswordAuthenticationFilter的attemptAuthentication方法上打個斷點:

准備完畢后,我們啟動項目,然后訪問http://localhost:8080/hello,代碼直接跳轉到FilterSecurityInteceptor的斷點上:

往下執行,因為當前請求沒有經過身份認證,所以將拋出異常並被ExceptionTranslateFilter捕獲:

捕獲異常后重定向到登錄表單登錄頁面,當我們在表單登錄頁面輸入信息點login后,代碼跳轉到UsernamePasswordAuthenticationFilter過濾器的attemptAuthentication方法上:

判斷用戶名和密碼是否正確之后,代碼又跳回FilterSecurityInterceptor的beforeInvocation方法執行上:

當認證通過時,FilterSecurityInterceptor代碼往下執行doFilter,然后代碼最終跳轉到/hello上:

瀏覽器頁面將顯示hello spring security
