完成了基本配置后,我們需要自定義Spring Security的一些進階配置,才能應用於我們的項目。
和SpringBoot項目中其他插件一樣,我們一般也是寫一個配置類來存放對Spring Security的配置。
@EnableWebSecurity
我們自己定義的配置類 WebSecurityConfig 加上了 @EnableWebSecurity 注解,同時繼承了 WebSecurityConfigurerAdapter。你可能會在想誰的作用大一點,毫無疑問 @EnableWebSecurity 起到決定性的配置作用,它其實是個組合注解。
@Import({ WebSecurityConfiguration.class, // <2>
SpringWebMvcImportSelector.class }) // <1>
@EnableGlobalAuthentication // <3>
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
@Import
是 springboot 提供的用於引入外部的配置的注解,可以理解為:@EnableWebSecurity
注解激活了 @Import
注解中包含的配置類。
<1> SpringWebMvcImportSelector
的作用是判斷當前的環境是否包含 springmvc,因為 spring security 可以在非 spring 環境下使用,為了避免 DispatcherServlet 的重復配置,所以使用了這個注解來區分。
<2> WebSecurityConfiguration
顧名思義,是用來配置 web 安全的,下面的小節會詳細介紹。
<3> @EnableGlobalAuthentication
注解的源碼如下:
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
注意點同樣在 @Import
之中,它實際上激活了 AuthenticationConfiguration
這樣的一個配置類,用來配置認證相關的核心類。
也就是說:@EnableWebSecurity
完成的工作便是加載了 WebSecurityConfiguration
,AuthenticationConfiguration
這兩個核心配置類,也就此將 spring security 的職責划分為了配置安全信息,配置認證信息兩部分。
WebSecurityConfigurerAdapter
適配器模式在 spring 中被廣泛的使用,在配置中使用 Adapter 的好處便是,我們可以選擇性的配置想要修改的那一部分配置,而不用覆蓋其他不相關的配置。WebSecurityConfigurerAdapter 中我們可以選擇自己想要修改的內容,來進行重寫,而其提供了三個 configure 重載方法,是我們主要關心的:
由參數就可以知道,分別是對 AuthenticationManagerBuilder,WebSecurity,HttpSecurity 進行個性化的配置。
配置SecurityConfig
在項目源代碼目錄下建立config包,創建 SecurityConfig 配置類,繼承 WebSecurityConfigurerAdapter 抽象類,實現 Spring Security 在 Web 場景下的自定義配置。
重寫configure(AuthenticationManagerBuilder auth)
想要在 WebSecurityConfigurerAdapter
中進行認證相關的配置,可以使用 configure(AuthenticationManagerBuilder auth)
暴露一個 AuthenticationManager
的建造器:AuthenticationManagerBuilder
如下所示:
// SecurityConfig.java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.
// <X> 使用內存中的 InMemoryUserDetailsManager
inMemoryAuthentication()
// <Y> 不使用 PasswordEncoder 密碼編碼器
.passwordEncoder(NoOpPasswordEncoder.getInstance())
// <Z> 配置 admin 用戶
.withUser("admin").password("admin").roles("ADMIN")
// <Z> 配置 normal 用戶
.and().withUser("normal").password("normal").roles("NORMAL");
}
說明
AuthenticationManagerBuilder#inMemoryAuthentication()
方法,使用內存級別的
InMemoryUserDetailsManager Bean 對象,提供認證的用戶信息。
Spring 內置了兩種 UserDetailsManager 實現:
- InMemoryUserDetailsManager
- JdbcUserDetailsManager ,基於 JDBC的 JdbcUserDetailsManager 。
實際項目中,我們更多采用調用AuthenticationManagerBuilder#userDetailsService(userDetailsService)
方法,使用自定義實現的 UserDetailsService 實現類,更加靈活、自由地實現認證的用戶信息的讀取。
AbstractDaoAuthenticationConfigurer#passwordEncoder(passwordEncoder)
方法,設置 PasswordEncoder 密碼編碼器。在這里,為了方便,我們使用
NoOpPasswordEncoder 。實際上,等於不使用 PasswordEncoder 。
NoOpPasswordEncoder的
encode方法就只是簡單地把字符序列轉成字符串,也就是說,你輸入的密碼”123456”存儲在數據庫里仍然是”123456”,這樣如果數據庫被攻破的話,密碼就直接泄露了,十分不安全。
不過,正因其十分簡單,所以在Spring Security 5.0 之前NoOpPasswordEncoder是作為默認的密碼編碼器而存在的,它可以是你沒有主動加密時的一個默認選擇。
生產環境下,推薦使用 BCryptPasswordEncoder
。更多關於 PasswordEncoder 的內容,推薦閱讀《該如何設計你的 PasswordEncoder?》文章。
重寫configure(HttpSecurity http)
然后,我們重寫 configure(HttpSecurity http)
方法,主要配置 URL 的權限控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("test/demo").permitAll()
// 配置請求地址的權限
.antMatchers("test/admin").hasRole("ADMIN")
.antMatchers("test/normal").hasRole("NORMAL")
.anyRequest().authenticated()
// 設置 Form 表單登陸
.and().formLogin()
//loginPage("/login")
.permitAll()
.and().logout()
//logoutUrl("/logout")
.permitAll();
}
上述是一個使用 Java Configuration 配置 HttpSecurity 的典型配置,其中 http 作為根開始配置,每一個 and()對應了一個模塊的配置(等同於 xml 配置中的結束標簽),並且 and() 返回了 HttpSecurity 本身,於是可以連續進行配置。(鏈式編程調用)
他們配置的含義也非常容易通過變量本身來推測,
- authorizeRequests() 配置路徑攔截,表明路徑訪問所對應的權限,角色,認證信息。
- formLogin() 對應表單認證相關的配置
- logout() 對應了注銷相關的配置
- httpBasic() 可以配置 basic 登錄
- etc
他們分別代表了 http 請求相關的安全配置,這些配置項無一例外的返回了 Configurer 類,而所有的 http 相關配置可以通過查看 HttpSecurity 的主要方法得知:
這里要特別注意:如果配置了loginPage和logoutUrl這兩項,就必須提供html文件來構成 登陸頁面,否則就會無法登錄。所以這里不配置這兩項,還是使用系統默認給的登陸頁面。
需要對 http 協議有一定的了解才能完全掌握所有的配置,不過,springboot 和 spring security 的自動配置已經足夠使用了。其中每一項 Configurer(e.g.FormLoginConfigurer,CsrfConfigurer)都是 HttpConfigurer 的細化配置項。
調用 HttpSecurity.authorizeRequests()
方法,開始配置 URL 的權限控制。
下面,是配置權限控制會使用到的方法:
#String... antPatterns)
方法,配置匹配的 URL 地址,基於 Ant 風格路徑表達式 ,可傳入多個。- 【常用】
permitAll()
方法,所有用戶可訪問。 - 【常用】
denyAll()
方法,所有用戶不可訪問。 - 【常用】
authenticated()
方法,登錄用戶可訪問。 anonymous()
方法,無需登錄,即匿名用戶可訪問。rememberMe()
方法,通過 remember me 登錄的用戶可訪問。fullyAuthenticated()
方法,非 remember me 登錄的用戶可訪問。hasIpAddress(String ipaddressExpression)
方法,來自指定 IP 表達式的用戶可訪問。- 【常用】
hasRole(String role)
方法, 擁有指定角色的用戶可訪問。 - 【常用】
hasAnyRole(String... roles)
方法,擁有指定任一角色的用戶可訪問。 - 【常用】
hasAuthority(String authority)
方法,擁有指定權限(authority
)的用戶可訪問。 - 【常用】
hasAuthority(String... authorities)
方法,擁有指定任一權限(authority
)的用戶可訪問。 - 【最牛】
access(String attribute)
方法,當 Spring EL 表達式的執行結果為true
時,可以訪問。
@PermitAll
注解,等價於 permitAll()
方法,所有用戶可訪問。
重要!!!因為在SecurityConfig中,配置了
.anyRequest().authenticated()
,任何請求,訪問的用戶都需要經過認證。所以這里@PermitAll
注解實際是不生效的。也就是說,Java Config 配置的權限,和注解配置的權限,兩者是疊加的。
@PreAuthorize
注解,等價於 access(String attribute)
方法,,當 Spring EL 表達式的執行結果為 true 時,可以訪問。
重寫configure(WebSecurity web)
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
}
這個方法看起來不是太常用……