在我們前面的文章Spring Security 初識(一)中,我們看到了一個最簡單的
Spring Security 配置,會要求所有的請求都要經過認證.但是,這並不是我們想要的,我們通常想自定義應用的安全性.因爲有些路徑我們想要誰都可以訪問.
Spring Security對此的實現也很簡單.關鍵在於重載 WebSecurityConfigurerAdapter 的 configure() 方法.
我們使用最簡單的基於內訓的用戶存儲來演示Spring Security 的請求攔截,首先 就是 SecurotyConfigure 的實現.如下:
/**
* @author itguang
* @create 2017-12-28 9:19
**/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//基於內存的用戶存儲
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("itguang").password("123456").roles("USER").and()
.withUser("admin").password("123456").roles("ADMIN");
}
//請求攔截
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/shop/hello").authenticated()
.antMatchers(HttpMethod.POST,"/shop/order").authenticated()
.anyRequest().permitAll();
}
}
先來 解釋一下 configure(HttpSecurity http) 方法,在解釋之前我們可以先看 WebSecurityConfigurerAdapter 裏面的默認實現:
/**
* Override this method to configure the {@link HttpSecurity}. Typically subclasses
* should not invoke this method by calling super as it may override their
* configuration. The default configuration is:
*
* <pre>
* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
* </pre>
*
* @param http the {@link HttpSecurity} to modify
* @throws Exception if an error occurs
*/
// @formatter:off
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();
}
默認的實現是什麼意思呢? 通過之前我們的測試和查看源碼,不難理解. HttpSecurity 實現了一個 HttpSecurityBuilder 接口,這是一個構造器接口.
因此可以使用構造器典型的鏈式調用風格.通過調用authorizeRequests()和 anyRequest().authenticated()就會要求所有進入應用的
HTTP請求都要進行認證。它也配置Spring Security支持基於表單的登錄以及HTTP Basic方式的認證。
接下來再看看我們的實現: 我們只是配置了 “/shop/hello” 和 “/shop/order” 這兩個路徑必須進過認證,並且 “/shop/order” 必須是 post 請求的方式.對於其他的請求,
我們都是 .anyRequest().permitAll() ;都放行.
具體的controller請查看源碼,文章最後會給出.
最後我們進行測試,會發現: 訪問 “/shop/hello” 和”/shop/order” 瀏覽器會返回給我們一個空白頁,如下圖:
這是因爲我們並沒有配置未授權時的登錄頁面.
antMatchers()方法中設定的路徑支持Ant風格的通配符。在這裏我們並沒有這樣使用,但是也可以使用通配符來指定路徑,如下所示:
.antMatchers("/shop/**").authenticated();
我們也可以在一個對antMatchers()方法的調用中指定多個路徑:
.antMatchers("/shop/hello","/shop/order").authenticated();
antMatchers()方法所使用的路徑可能會包括Ant風格的通配符,而regexMatchers()方法則能夠接受正則表達式來定義請求路徑。
例如,如下代碼片段所使用的正則表達式與“/spitters/**”(Ant風格)功能是相同的:
.regexMatchers().authenticated();
除了路徑選擇,我們還通過authenticated()和permitAll()來定義該如何保護路徑。authenticated()要求在執行該請求時,
必須已經登錄了應用。如果用戶沒有認證的話,Spring Security的Filter將會捕獲該請求,並將用戶重定向到應用的登錄頁面。
同時,permitAll()方法允許請求沒有任何的安全限制。
除了authenticated()方法和permitAll()方法外,還有一些其他方法用來定義該如何保護請求.
- access(String) 如果給定的SpEL表達式計算結果爲true,就允許訪問
- anonymous() 允許匿名用戶訪問
- authenticated() 允許認證的用戶進行訪問
- denyAll() 無條件拒絕所有訪問
- fullyAuthenticated() 如果用戶是完整認證的話(不是通過Remember-me功能認證的),就允許訪問
- hasAuthority(String) 如果用戶具備給定權限的話就允許訪問
- hasAnyAuthority(String…)如果用戶具備給定權限中的某一個的話,就允許訪問
- hasRole(String) 如果用戶具備給定角色(用戶組)的話,就允許訪問/
- hasAnyRole(String…) 如果用戶具有給定角色(用戶組)中的一個的話,允許訪問.
- hasIpAddress(String 如果請求來自給定ip地址的話,就允許訪問.
- not() 對其他訪問結果求反.
- permitAll() 無條件允許訪問
- rememberMe() 如果用戶是通過Remember-me功能認證的,就允許訪問
通過上面的方法,我們可以修改 configure 方法,要求用戶不僅需要認證,還需要具備相應的權限
/**
* 請求攔截
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello").hasAnyAuthority("ROLE_DELETE")
.antMatchers(HttpMethod.POST,"/order").hasAnyAuthority("ROLE_UPDATE")
.anyRequest().permitAll();
}
作爲代替方案,我們還可以使用 hasRole() ,它會自動使用 “ROLE_” 前綴.
/**
* 請求攔截
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello").hasRole("DELETE")
.antMatchers(HttpMethod.POST,"/order").hasRole("UPDATE")
.anyRequest().permitAll();
}
注意:這些規則會按照給定的順序發揮作用。所以,很重要的一點就是將最爲具體的請求路徑放在前面,
而最不具體的路徑(如anyRequest())放在最後面。如果不這樣做的話,那不具體的路徑配置將會覆蓋掉更爲具體的路徑配置。
使用Spring表達式進行安全保護
上面的方法中,我們看到一個 access() 方法,此方法可以接收一個Spel表達式讓我們對請求進行攔截.
如下就是使用SpEL表達式來聲明具有“HELLO”角色才能訪問“/shop/hello”URL:
.antMatchers("/shop/hello").access("hasRole('HELLO')");
這個對“/shop/hello”的安全限制與開始時的效果是等價的,只不過這裏使用了SpEL來描述安全規則。
如果當前用戶被授予了給定角色的話,那hasRole()表達式的計算結果就爲true。
下面列出了Spring Security 支持的所有SPEL表達式:
- authentication 用戶的認證對象
- denyAll 結果始終爲false
- hasAnyRole(list of roles) 如果用戶被授予了列表中任意的指定角色,結果爲true
- hasRole(role) 如果用戶被授予了指定的角色,結果爲true
- hasIpAddress(IPAddress) 如果請求來自指定IP的話,結果爲true
- isAnonymous() 如果當前用戶爲匿名用戶,結果爲true
- isAuthenticated() 如果當前用戶進行了認證的話,結果爲true
- isFullyAuthenticated() 如果當前用戶進行了完整認證的話(不是通過Remember-me功能進行的認證),結果爲true
- isRememberMe() 如果當前用戶是通過Remember-me自動認證的,結果爲true
- permitAll() 結果始終爲true
- principal() 用戶的principal對象
小結
這節我們講了如何使用 HttpSecurity 進行細緻的請求攔截,
此外,Spring Security 還支持防止跨站請求僞造(cross-site request forgery,CSRF,
和視圖保護的功能,
這裏就不再細講,感興趣的可以參考: spring in action 一書的 9.3.3 和 9.5 節.
源碼地址:https://github.com/itguang/security/blob/master/spring-security-demo4/README.md