SpringSecurity簡介:
許可權管理中的相關概念
主體 principal:
使用系統的使用者或裝置或從其他系統遠端登入的使用者等等,簡單說就是誰使用系統誰就是主體。
認證 authentication:
許可權管理系統確認一個主體的身份,允許主體進入系統。簡單說就是“主體”證明自己是誰。
授權 authorization:
將作業系統的“權力”“授予”“主體”,這樣主體就具備了作業系統中特定功能的能力。 所以簡單來說,授權就是給使用者分配許可權。
SpringSecurity本質是過濾器鏈:
客戶端發起一個請求,在請求到達Controller前Security通過一系列的過濾處理,完成對使用者的認證授權等相關處理。需要注意的是在傳統業務系統開發中,我們一般會在UserController中實現一個login介面來處理使用者登入,但在使用Security時不需要在Controller實現login介面,它幫助我們在過濾器中實現了使用者密碼登入。預設通過UsernamePasswordAuthenticationFilter過濾器實現,從表單中讀取使用者名稱密碼進行認證登入。
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
主要過濾器
ExceptionTranslationFilter:異常處理過濾器,凡是在過濾器環節出現的錯誤都或轉到該過濾器進行統一處理
UsernamePasswordAuthenticationFilter:預設的認證過濾器,從請求中讀取表單使用者名稱密碼資料進行校驗,如果是JSON提交需要對其進行重寫
FilterSecurityInterceptor:是一個方法級的許可權過濾器, 基本位於過濾鏈的最底部
自定義新增過濾器
除了SpringSecurity預設的過濾器,我們還可以新增自己的過濾器來進行自定義認證或授權。在SpringBoot開發中,SpringSecurity自動配置會向容器中自動注入相關過濾器,因此如果自己也有過濾器的情況下回導致過濾器順序混亂,建議通過SpringSecurity統一管理過濾器。
如下,我們在自己的配置類中重寫configure(HttpSecurity http)配置方法,自定義新增自己的過濾器
1 @Configuration //告訴SpringBoot該類是一個配置類,自動裝配到IOC容器 2 @EnableWebSecurity //全域性開啟SpringSecurity 3 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) //開啟許可權註解,之後會介紹 4 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 5 @Override 6 protected void configure(HttpSecurity http) throws Exception { 7 //新增自定義過濾器在某過濾器前執行 8 http.addFilterBefore(wrapperFilter, UsernamePasswordAuthenticationFilter.class); 9 //新增自定義過濾器在某過濾器之後 10 http.addFilterAfter(filter,UsernamePasswordAuthenticationFilter.class); 11 //新增過濾器在最後 12 http.addFilter(filter); 13 } 14 }
配置常用元件:
WebSecurityConfigurerAdapter(Security配置介面卡)
UsernamePasswordAuthentcationFilter(使用者名稱密碼認證過濾器)
UserDetailService(使用者許可權資料查詢服務)
TokenRepository(記住我Token Dao)
PasswordEncoder(NoOpPasswordEncoder、BCryptPasswordEncoder加密方式)
HttpSecutity常用配置:
Remember(記住我):
1 .rememberMe() 2 .tokenRepository(tokenRepository) //記住我的token管理Dao,運算元據庫的記住我快取 3 .tokenValiditySeconds(60) //token的有效時長
formLogin(表單登入):
1 .formLogin() 2 .loginPage("/login_page") //登陸頁面 3 .loginProcessingUrl("/login") //登陸請求處理介面,Spring security預設的處理登入介面是/login這個自帶的介面 4 .usernameParameter("name") //指定使用者名稱引數名稱 5 .passwordParameter("passwd") //指定密碼引數名稱 6 .permitAll() //將登入操作url放行
authorizeRequests(url請求許可權):
.authorzeRequests() .antMatchers("/admin/**") .hasRole("ADMIN") //具備特定角色可訪問 .antMatchers("/user/**") .access("hasAnyRole('ADMIN','USER')") //引數以表示式方式書寫,多個以 and 連線 //除了.permitAll()的url,其餘所有URL需要認證後訪問 .anyRequest() .authenticated()
logout(登出):
1 .logout() //開啟登出登陸 2 .logoutUrl("/logout") //登出登陸請求url 3 .clearAuthentication(true) //清除身份資訊 4 .invalidateHttpSession(true) //session失效 5 .addLogoutHandler(new LogoutHandler() { //登出處理 6 @Override 7 public void logout(HttpServletRequest req, 8 HttpServletResponse resp, 9 Authentication auth) { 10 11 } 12 }) 13 .logoutSuccessHandler(new LogoutSuccessHandler() { //登出成功處理 14 @Override 15 public void onLogoutSuccess(HttpServletRequest req, 16 HttpServletResponse resp, 17 Authentication auth) 18 throws IOException { 19 resp.sendRedirect("/login_page"); //跳轉到自定義登陸頁面 20 } 21 })
csrf(跨站指令碼偽造):
1 .csrf().disabl(); //前後端分離時一般關閉
RemenberMe記住我(自動登入)原理:
總結配置流程
- 認證使用者實現UserDetails介面
- 使用者來源的Service實現UserDetailsService介面,實現loadUserByUsername()方法,從資料庫中獲取資料
- 實現自己的過濾器繼承UsernamePasswordAuthenticationFilter,重寫attemptAuthentication()和successfulAuthentication()方法實現自己的邏輯
- Spring Security的配置類繼承自WebSecurityConfigurerAdapter,重寫裡面的兩個config()方法
- 如果使用RSA非對稱加密,就準備好RSA的配置類,然後在啟動類中加入註解將其加入IOC容器
CSRF跨站請求偽造
攻擊者利用已經登入(認證)某網站的瀏覽器,向網站發起惡意請求
原理:攻擊者利用某種方式讓受害者瀏覽器發起請求訪問正常登入過的網站地址,此時由於瀏覽器儲存著網站Cookie,網站接收請求並處理操作。攻擊者可以利用這種方式發起任何正常網站使用者可以進行的操作。
要完成一次CSRF攻擊,受害者必須依次完成兩個步驟:
1.登入受信任網站A,並在本地生成Cookie。
2.在不登出A的情況下,訪問危險網站B。
從Spring Security 4.0開始,預設情況下會啟用CSRF保護,以防止CSRF 攻擊應用程式,Spring Security CSRF會針對PATCH,POST,PUT和DELETE方法進行防護。
註解說明
許可權校驗
@EnableGlobalMethodSecurity:開啟方法註解許可權校驗
引數:
- securedEnable=true 開啟@Securd註解
- prePostEnable=true 開啟@Preauthorize@PostAuthorize註解
@Securd("ROLE_xxx"):使用者具有某個許可權(角色)才能訪問
@Preauthorize("hashAnyRole('ROLE_ADMIN')"):進入方法之前進行許可權嚴重
1 @Service 2 public class MethodService { 3 @Secured("ROLE_ADMIN") //訪問此方法需要ADMIN角色 4 public String admin() { 5 return "hello admin"; 6 } 7 @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')") //訪問此方法需要ADMIN且DBA 8 public String dba() { 9 return "hello dba"; 10 } 11 @PreAuthorize("hasAnyRole('ADMIN','DBA','USER')") //三個都行 12 public String user() { 13 return "user"; 14 } 15 }
引數過濾
@PostFilter:對返回值進行過濾
@PreFilter:對方法引數進行過濾
用法:
@PostFilter("filterObject.屬性==xx"):過濾物件屬性,當表示式為true時允許資料通過,為false的資料會被過濾掉
開起security
註解 @EnableWebSecurity在 Spring boot 應用中使用 Spring Security,用到了 @EnableWebSecurity註解,官方說明為,該註解和 @Configuration 註解一起使用, 註解 WebSecurityConfigurer 型別的類,或者利用@EnableWebSecurity 註解繼承 WebSecurityConfigurerAdapter的類,這樣就構成了 Spring Security 的配置。
單點登入相關
SpringSecurity總結理解:
SpringSecurity的配置非常靈活可擴充性很強,因此使用該安全元件可以完成很多自定義配置。首先分析專案中的幾個要素:認證路徑(登入處理)、需要放行的路徑,需要授權的路徑、是否使用session、是否禁用csrf、配置跨域過濾器、是否自定義認證成功後失敗的邏輯處理、使用者認證失敗或許可權不足的響應邏輯。
就以我現在的專案分析:
對於前後端分離的專案首先要考慮到禁用session和csrf因為我們用jwt token來解決http的無狀態性,需要注意的是禁用session後SecurityContextHolder會失效,因為它預設依賴session來進行上下文處理。
然後對於靜態資源的url放行處理,和對公開訪問介面的放行處理,例如登入處理介面、註冊介面、驗證碼獲取介面等。做完這些放行處理之後,再對剩下所有的url進行認證攔截處理。
再然後就需要做對於token認證授權的一些適配配置問題進行自定義處理,比如登入成功後我們需要將使用者的token存入redis,那麼這個邏輯可以通過實現UsernamePasswordAuthenticationFilter然後重寫successfulAuthentication和unsuccessfulAuthentication方法,在successfulAuthentication方法中我們可以自定義登入成功後的處理邏輯,如token處理和security上下文處理等。unsuccessfulAuthentication一般就返回失敗就行了。注意我們自己實現的UsernamePasswordAuthenticationFilter想讓他生效需要在配置中讓他替換原來的通過http.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
還有一種方法通過http.formLogin().successHandler().failureHandler();來設定認證成功和失敗的處理器;
注意,如果我們使用token來維護狀態,那麼每個請求都是需要通過token來鑑權的,所以我們還需要寫一個過濾器來維護token校驗邏輯,寫一個過濾器獲取請求中的token做校驗邏輯,校驗處理成功後我們還可以將使用者資料新增到SecurityContextHolder中,以便在後續處理中獲取當前請求使用者資料資訊。
還有一個需要注意的地方,關於使用者的認證校驗邏輯,一般我們通過實現UsernamePasswordAuthenticationFilter並重寫attemptAuthentication方法進行認證校驗。還有一種方法是不實現UsernamePasswordAuthenticationFilter,而是提供一個我們的身份提供者,一個實現了AuthenticationProvider介面的類,然後將認證校驗邏輯解除安裝authenticate()方法中,並且通過authenticationProvider()方式進行配置。
由過濾器鏈組成,通過配置可以開啟或關閉特定過濾器,其中認證過濾器最終都會建立一個對應的XXXToken,例如UsernamePasswordAuthentication會建立一個UsernamePasswordAuthenticationToken。建立好了之後通過實現了authenticationManager介面的ProviderManager遍歷所有實現了AuthenticationProvider身份提供者進行身份校驗邏輯。注意每個ProviderManager都只處理自己關聯的Token,如RememberMeAuthenticationProvider只處理RememberMeAuthenticationToken。當遍歷到其中一個ProviderManager能夠成功驗證時使用者就認證成功了。