我曾經使用 Interceptor
實現了一個簡單網站Demo的登陸攔截和Session處理工做,雖然可以實現相應的功能,可是無疑Spring Security提供的配置方法更加簡單明確,可以更好的保護Web應用。java
這裏你們能夠參考Spring Security的官方介紹文檔:spring-security-architecture
簡單的來講:node
Filter
,其具體的類型是FilterChainProxy
,其是做爲@Bean
在ApplicationContext
中配置的。從容器的角度來看,Spring Security是一個單一的Filter,可是在其中有不少額外的Filter,每個都扮演着他們各自的角色,以下圖所示:
git
Spring Security的身份驗證,主要由AuthenticationManager
這個接口完成,其驗證的主要方法是authenticate()
web
public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }
Authentication
(一般包含 authenticated=true
)AuthenticationException
null
AuthicationManager
的實現是ProviderManager
,它將其委託給AuthticationProvider
這個實例,AuthenticationProvider
和AuthenticationManager
有一點像,可是含有一些額外的方法,來容許調用者來查詢是否支持該Authenticaion
形式。public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication); }
supports()
方法中的Class<?>
參數是Class<? extends Authentication>
,它只會詢問其是否支持傳遞給authenticate()
方法。spring
ProviderManager
經過委託一系列的AuthenticaitonProviders
,以此來支支持多個不一樣的認證機制,若是ProviderManager
沒法識別一個特定的Authentication
實例類型,則會跳過它。不少時候,一個程序含有多個資源保護邏輯組,每個組都有他們獨有的AuthenticationManager
,一般他們共享父級,那麼父級就成爲了了一個"global"資源
,做爲全部provider
的後背。
框架
Spring Security提供了一些配置幫助咱們快速的開啓驗證功能,最經常使用的就是AuthenticationManagerBuiler
,它在內存(in-memory)、JDBC、LDAP或者我的定製的UserDetailService
這些領域都很擅長。ssh
注意:本後續代碼以SpringBoot爲框架實現,其DEMO Git: Spring-Security-Demoide
方法 | 描述 |
---|---|
configure(WebSecurity) | 經過重載,配置Spring Security的Filter鏈 |
configure(HttpSecurity) | 經過重載,配置如何攔截器保護請求 |
configure(AuthenticationManagerBuilder) | 經過重載,配置user-detail服務 |
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/index").hasAnyAuthority("ROLE_USER","ROLE_ADMIN") .antMatchers("/oss").hasAuthority("ROLE_ADMIN") .antMatchers(HttpMethod.GET, "/login").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll()//.successHandler(successHandler) .and() .logout() .logoutSuccessUrl("/") .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("USER","ADMIN").and() .withUser("normal").password(new BCryptPasswordEncoder().encode("normal")).roles("USER"); //auth.authenticationProvider(userProvider); //auth.authenticationProvider(afterProvider); }
- 經過`antMatchers()`進行URL匹配,再進行相應的處理,好比見上代碼,咱們將**/index**和**/oss**兩個連接進行了攔截,並分別要求擁有`ROLE_USER`或`ROLE_ADMIN`、`ROLE_ADMIN`這兩個身份才能訪問。 - `anyRequest().authenticated()`指其餘請求都會須要驗證 - `formLogin()`使其有了登陸頁面,若是沒有後面的`loginPage()`,則會默認生成一個Spring Security的頁面,然後面註釋掉的`successHandler`則是後續會講到的。 - `permitAll()`則表示當前鏈接不須要認證。 - `logout()`會攔截因此的**\logout**請求,完成登出操做,`logoutSuccessUrl()`則是登出後的重定向地址。 - `and()`在其中起鏈接做用。
則此時咱們的root帳號既可以訪問index也可以訪問oss,而normal帳號只能訪問index,不能訪問oss,若是訪問oss會出現:
There was an unexpected error (type=Forbidden, status=403).網站
上面咱們經過重載configure(AuthenticationManagerBuilder auth)生成了兩個內存用戶root和normal,咱們也能夠經過jdbc等方法實現。ui
Token
的設置等等,好比我如今打印一條登陸信息,並將請求重定向到首頁@Component public class SuccessHandler implements AuthenticationSuccessHandler{ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println(authentication.getName()+" is loging , role is"+authentication.getAuthorities()); response.sendRedirect("/"); }
formLogin()
後,即:.formLogin() .loginPage("/login") .permitAll().successHandler(successHandler)
UserAuthProvider
,並讓其實現AuthenticationProvider
接口:@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { System.out.println("-----------------------------------------------------------------------"); System.out.println("This is UserAuthProvider"); System.out.println("starting authenticate ... ..."); System.out.println("Credentials:"+authentication.getCredentials()); System.out.println("Name:"+authentication.getName()); System.out.println("Class:"+authentication.getClass()); System.out.println("Details:"+authentication.getDetails()); System.out.println("Principal:"+authentication.getPrincipal()); System.out.println("-----------------------------------------------------------------------"); UsernamePasswordAuthenticationToken auth=new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials()); return auth; } @Override public boolean supports(Class<?> authentication) { System.out.println("This is UserAuthProvider"); System.out.println("starting supports"); System.out.println(authentication.getClass()); return false; }
auth.inMemoryAuthentication()
,將UserAuthProvider加入到AuthenticationManagerBuilder
中,即:@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) // .withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("USER","ADMIN").and() // .withUser("normal").password(new BCryptPasswordEncoder().encode("normal")).roles("USER"); auth.authenticationProvider(userProvider); auth.authenticationProvider(afterProvider); }
This is UserAuthProvider starting supports java.lang. Class
supports()
方法,永遠返回false,而返回false時,即不會再調用authenticate()
進行認證操做(正如上面所介紹的),咱們將supports()
的返回值變成true,再次登陸(username: root password: 1234),則控制檯會輸出This is UserAuthProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAuthProvider starting authenticate ... ... Credentials:1234 Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0 Principal:root ----------------------------------------------------------------------- root is loging , role is[]
即成功登陸了,由於咱們在authenticate()
方法中直接聲明瞭一個Authentication
的實例UsernamePasswordAuthenticationToken
,並返回了,正如上面所說,當返回Authentication
實例時,則默認爲受權成功,而若是咱們返回null
,則說明沒法判斷,不會登陸成功。
此時咱們再建立一個對象UserAfterProvider
,其也實現AuthenticationProvider
接口,並將UserAfterProvider
和UserAuthProvider
的authenticate()
返回值都設置爲null
,咱們再次使用上面的數據進行登陸,控制檯輸出以下:
This is UserAuthProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAuthProvider starting authenticate ... ... Credentials:1234 Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node01m47f3t6xq5a470fu07jaipzb0 Principal:root ----------------------------------------------------------------------- This is UserAfterProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAfterProvider starting authenticate ... ... Credentials:1234 Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node01m47f3t6xq5a470fu07jaipzb0 Principal:root -----------------------------------------------------------------------
AuthenticationManagerBuilder
的驗證都會進行一遍,那麼若是咱們將其中一個Provider的authenticate()
返回值還原爲Authentication
實例,再次登陸,則控制檯會輸出以下結果:This is UserAuthProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAuthProvider starting authenticate ... ... Credentials:1234 Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0 Principal:root ----------------------------------------------------------------------- root is loging , role is[] This is UserAuthProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAuthProvider starting authenticate ... ... Credentials:null Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0 Principal:root -----------------------------------------------------------------------
由於咱們重寫了AuthenticationSuccessHandler
,因此驗證成功後悔重定向到/,而我Controller裏對/又作了一次重定向到/index,因此發生了兩次驗證,而此次咱們發現由於UserAuthProvider
經過了,因此UserAfterProvider
並無進行驗證,因此咱們能夠知道,只要有一個Provider經過了驗證咱們就能夠認爲經過了驗證。
所以,咱們能夠經過實現AuthenticationProvider
來寫入本身的一些認證邏輯,甚至能夠@Autowire相關Service來輔助實現。
個人博客即將搬運同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=1353hw8jzy7ee