SpringSecurity


SpringSecurity 基礎

什么是安全框架

安全框架顧名思義,就是解決系統安全問題的框架。任何應用開發的計划階段都應該確定一組特定的安全需求,如身份驗證、授權和加密方式。不使用安全框架之前,我們需要手動處理每個資源的訪問控制,針對不同的項目都需要做不同對處理,此時就會顯得非常麻煩,並且低效率引起的額外開銷會延緩開發周期。使用安全框架,使開發團隊能夠選擇最適合這些需求的框架,可以通過配置的方式實現對資源的訪問限制,使得開發更加的高效。

常用的安全框架

Spring Security: Spring 家族一員,是一個能夠為基於 Spring 的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在 Spring 應用上下文中配置的 Bean,充分利用了 Spring IoC(控制反轉)、DI(依賴注入)和 AOP(面向切面編程)功能,為應用系統提供聲明式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重復代碼的工作。

Apache Shiro:一個功能強大且易於使用的Java安全框架,提供了認證、授權、加密和會話管理功能。使用 Shiro 的易於理解的API,您可以快速、輕松地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。

SpringSecurity 簡介

Spring Security 是一個高度自定義的安全框架,利用 Spring loC、DI 和 AOP 功能,為系統提供了聲明式安全訪問控制功能,減少了為系統安全而編寫大星重復代碼的工作。

使用 Spring Secruity 的原因有很多,但大部分都是發現了 javaEE 的 Servlet 規范或 EJ8 規范中的安全功能缺乏典型企業應用場景。同時認識到他們在WAR 或 EAR 級別無法移植,因此如果你更換服務器環境,還有大星工作去重新配置你的應用程序,使用 Spring Security 解決了這些問題,並且提供了可定制的安全功能,比如認證和授權。

SpringBoot 沒有發布之前,Shiro 應用更加廣泛,因為 Shiro 是一個強大且易用的 Java 安全框架,能夠非常清晰的處理身份驗證、授權、管理會話以及密碼加密。利用其易於理解的API,可以快速、輕松地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。但是 Shiro 只是一個框架而已,其中的內容需要自己的去構建,前后是自己的,中間是Shiro幫我們去搭建和配置好的。

SpringBoot 發布后,隨着其快速發展,Spring Security 重新進入人們的視野。SpringBoot 解決了 Spring Security 各種復雜的配置,Spring Security 在我們進行用戶認證以及授予權限的時候,通過各種各樣的攔截器來控制權限的訪問,從而實現安全,也就是說 Spring Security 除了不能脫離 Spring,Shiro 的功能它都有。

  • 在用戶認證方面,Spring Security 框架支持主流的認證方式,包括 HTTP 基本認證、HTTP 表單驗證、HTTP 摘要認證、OpenID 和 LDAP 等。

  • 在用戶授權方面,Spring Security 提供了基於角色的訪問控制和訪問控制列表(Access Control List,ACL),可以對應用中的領域對象進行細粒度的控制。

SpringSecurity 入門

引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

導入配置

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 禁用csrf(跨站請求偽造)
                .cors().and().csrf().disable()
                // 設置表單登陸以及登錄頁面,自動開啟登錄頁,如果沒有登錄,沒有權限就會來到登錄頁面
                .formLogin().loginPage("/login.html").and()
                // 過濾請求
                .authorizeRequests()
                // 訪問此地址不需要進行身份認證,允許直接訪問,防止重定向死循環
                .antMatchers("/login.html").permitAll()
                // 除上面外的所有請求全部需要鑒權認證,訪問任何資源都需要身份認證
                .anyRequest().authenticated();
    }
}

登錄測試

啟動服務之后,如果只實現一個 WebSecurityConfigurerAdapter 然后重寫一下 configure 方法,效果會默認使用Spring Security 的登錄頁 ,同時項目啟動時后台會打印出一個默認的密碼,然后使用任意賬號就可以進行登錄訪問指定的資源。


SpringSecurity 核心

基本原理

Spring Security 所解決的問題就是安全訪問控制,而安全訪問控制功能其實就是對所有進入系統的請求進行攔截,校驗每個請求是否能夠訪問它所期望的資源,並且采用的是責任鏈的設計模式。

Spring Security 對 Web 資源的保護是靠過濾器鏈(Filter Chain)實現的。當初始化 Spring Security 時,會創建一個名為 springSecurityFilterChain 的 Servlet 過濾器鏈,類型 FilterChainProxy,它實現 Filter,因此外部的請求會經過此類,下圖是 Spring Security 過慮器鏈結構圖:

image

FilterChainProxy 是一個代理,真正起作用的是 FilterChainProxy 中 SecurityFilterChain 所包含的各個 Filter,同時這些 Filter 作為 Bean 被 Spring 管理,它們是 Spring Security 核心,各有各的職責,但他們並不直接處理用戶的認證,也不直接處理用戶的授權,而是把它們交給了認證管理器(AuthenticationManager)和決策管理器 (AccessDecisionManager)進行處理。
Spring Security 功能的實現主要是由一系列過濾器鏈相互配合完成,如下圖:

image

下面介紹過濾器鏈中主要的幾個過濾器及其作用:

  • WebAsyncManagerIntegrationFilter:將 Security 上下文與 Spring Web 中用於處理異步請求映射的 WebAsyncManager 進行集成。

  • SecurityContextPersistenceFilter :每次請求處理之前將該請求相關的安全上下文信息加載到 SecurityContextHolder 中,然后在該次請求處理完成之后,將 SecurityContextHolder 中關於這次請求的信息存儲到一個“倉儲”中,然后將 SecurityContextHolder 中的信息清除。

  • UsernamePasswordAuthenticationFilter :用於處理基於表單的登錄請求,從表單中獲取用戶名和密碼。默認情況下處理來自 /login 的請求。從表單中獲取用戶名和密碼時,默認使用的表單 name 值為 username 和 password,這兩個值可以通過設置這個過濾器的usernameParameter 和 passwordParameter 兩個參數的值進行修改。其內部還有登錄成功或失敗后進行處理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,這些都可以根據需求做相關改變。

  • HeaderWriterFilter:用於將頭信息加入響應中。

  • CsrfFilter:用於處理跨站請求偽造。

  • LogoutFilter:用於處理退出登錄。

  • DefaultLoginPageGeneratingFilter:如果沒有配置登錄頁面,那系統初始化時就會配置這個過濾器,並且用於在需要進行登錄時生成一個登錄表單頁面。

  • BasicAuthenticationFilter:檢測和處理 http basic 認證。

  • RequestCacheAwareFilter:用來處理請求的緩存。

  • SecurityContextHolderAwareRequestFilter:主要是包裝請求對象 request。

  • AnonymousAuthenticationFilter:檢測 SecurityContextHolder 中是否存在 Authentication 對象,如果不存在為其提供一個匿名 Authentication。

  • SessionManagementFilter:管理 session 的過濾器

  • ExceptionTranslationFilter:處理 AccessDeniedException 和 AuthenticationException 異常。

  • FilterSecurityInterceptor: 是用於保護web資源的,使用 AccessDecisionManager 對當前用戶進行授權訪問。

  • RememberMeAuthenticationFilter:當用戶沒有登錄而直接訪問資源時, 從 cookie 里找出用戶的信息, 如果 Spring Security 能夠識別出用戶提供的 remember me cookie, 用戶將不必填寫用戶名和密碼, 而是直接登錄進入系統,該過濾器默認不開啟。

下面看一下 Spring Security 整個執行流程圖,只要把 Spring Security 的執行過程弄明白了,這個框架就會變得很簡單:

image

流程說明:

  1. 客戶端發起一個請求,進入 Security 過濾器鏈。

  2. 當到 LogoutFilter 的時候判斷是否是登出路徑,如果是登出路徑則到 logoutHandler ,如果登出成功則到 logoutSuccessHandler 登出成功處理,如果登出失敗則由 ExceptionTranslationFilter ;如果不是登出路徑則直接進入下一個過濾器。

  3. 當到 UsernamePasswordAuthenticationFilter 的時候判斷是否為登錄路徑,如果是,則進入該過濾器進行登錄操作,如果登錄失敗則到 AuthenticationFailureHandler 登錄失敗處理器處理,如果登錄成功則到 AuthenticationSuccessHandler 登錄成功處理器處理,如果不是登錄請求則不進入該過濾器。

  4. 當到 FilterSecurityInterceptor 的時候會拿到 uri ,根據 uri 去找對應的鑒權管理器,鑒權管理器做鑒權工作,鑒權成功則到 Controller 層,否則到 AccessDeniedHandler 鑒權失敗處理器處理。


核心配置

系統集成 Spring Security 后,通過創建配置類並繼承 WebSecurityConfigurerAdapter 類,這個類里面可以完成上述流程圖的所有配置,也就是認證及授權。接下來我們通過一個完整的配置類來進行詳細認識:

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 自定義用戶認證邏輯
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 認證失敗處理類
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出處理類
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * 跨域過濾器
     */
    @Autowired
    private CorsFilter corsFilter;

    /**
     * 資源請求配置
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // ...
    }

    /**
     * 全局安全性配置
     */
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(new String[]{"/v3/api-docs", "/swagger-resources/configuration/ui", 
            "/swagger-resources", "/swagger-ui.html", "/swagger-ui/*", "/modeler/**", "/**/doc.html",
             "/favicon.ico", "/definition/**", "/activiti/**", "/**/*.css", "/**/*.js", "/**/*.png", 
             "/**/*.gif", "/swagger-resources/**", "/**/*.ttf", "/upload/**", "/process/read-resource/**",
              "/ueditor/**", "/**/export/**", "/**importGdCache/**", "/**/sysGlobalConfig/**", "/OAuth/**", "/v1/**"});
    }

    /**
     * 身份認證接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

configure(AuthenticationManagerBuilder auth):身份認證接口

AuthenticationManager 的建造器,配置 AuthenticationManagerBuilder 會讓 Spring Security 自動構建一個 AuthenticationManager(該類的功能參考流程圖);

如果想要使用該功能你需要配置一個 UserDetailService 和 PasswordEncoder。UserDetailsService 用於在認證器中根據用戶傳過來的用戶名查找一個用戶, PasswordEncoder 用於密碼的加密與比對,我們存儲用戶密碼的時候用PasswordEncoder.encode() 加密存儲,在認證器里會調用 PasswordEncoder.matches() 方法進行密碼比對。

如果重寫了該方法,Spring Security 會啟用 DaoAuthenticationProvider 這個認證器,該認證就是先調用 UserDetailsService.loadUserByUsername 然后使用 PasswordEncoder.matches() 進行密碼比對,如果認證成功成功則返回一個 Authentication 對象。

configure(WebSecurity web):全局安全性配置

此方法用於配置影響全局安全性的配置,比如配置資源,設置調試模式,通過實現自定義防火牆定義拒絕請求,一般用於配置全局的某些通用事物,比如靜態資源等。

configure(HttpSecurity http):資源請求配置

這個方法是整個 Spring Security 的核心,也是最復雜的部分,通過案例簡單的說明一些常用配置,詳細說明會在權限控制中說明。

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .formLogin().loginPage("/login_page") // 自定義登錄頁
            .passwordParameter("username") // 用戶名屬性名
            .passwordParameter("password") // 密碼屬性名
            .loginProcessingUrl("/sign_in") // 登錄請求路徑
            .permitAll(); // 代表任意用戶可訪問
            .cors().and()).csrf().disable()) // 禁用csrf(跨站請求偽造)
            .authorizeRequests() // 過濾請求
            .antMatchers(new String[]{"/upload/**", "/definition/**", "/activiti/**"}).permitAll() // 放行匹配請求
            .anyRequest().authenticated().and() //除上面外的所有請求全部需要鑒權認證
            .exceptionHandling().accessDeniedHandler((AccessDeniedHandler) this.accessDeniedHandler); // 異常解析器
        http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); // 退出登錄
    }
}

權限控制

匹配規則

  • anyRequest():表示匹配所有的請求,一般情況都會使用此方法,設置全部內容都需要進行認證,比如anyRequest().authenticated();

  • antMatcher(String... antPatterns): 表示匹配指定的請求,參數是不定向參數,每個參數是一個 ant 表達式,? 表示匹配一個字符,* 表示匹配 0~N 個字符,** 表示匹配 0~N 個目錄。例如 antMatchers( "/**/*.js").permitAll()

  • regexMatchers(String... regexPatterns):使用正則表達式進行匹配,與 antMatchers() 主要的區別就是參數,antMatchers()參數是 ant 表達式,二 regexMatchers() 參數是正則表達式。例如 .regexMatchers( ".+[.]js").permitAll()

  • mvcMatchers():適用於配置了 servletPath 的情況。.servletPath() 是 mvcMatchers() 返回值特有的方法,例如 .mvcMatchers( "demo").servletPath( "/bjsxt").permitAll() 等價於 antMatchers( "/bjsxt/demo").permitAll()


訪問控制

  • permitAll():表示所匹配的 URL 任何人都允許訪問,也就是不需要認證,隨意訪問。

  • anonymous():表示可以匿名訪問匹配的 URL,只是設置為 anonymous() 的 URL 會執行 filter 鏈中,比如說瀏覽商城時。

  • authenticated():表示所匹配的 URL 都需要被認證才能訪問,也就是用戶登錄后可訪問。

  • denyAll():表示所匹配的 URL 都不允許被訪問。

  • rememberMe():只有被 remember me 的用戶才能訪問。

  • fullyAuthenticated():如果用戶不是被 remember me 的,才可以訪問。


角色控制

  • hasRole():用戶具備某個角色即可訪問資源,此方法會自動給傳入的字符串加上 ROLE_ 前綴,例如 hasRole("list"),表示擁有 ROLE_list 權限即可訪問。

  • hasAnyRole():用戶具備多個角色中的任意一個即可訪問資源,例如 hasAnyRole("admin", "save"),只要具備其中一個角色,即可訪問資源。

  • hasAuthority():類似於 hasRole,但是不會添加 ROLE_ 前綴,也就是說,使用 hasAuthority 更具有一致性,你不用考慮要不要加 ROLE_ 前綴,數據庫什么樣這里就是什么樣!

  • hasAnyAuthority():類似於 hasAnyRole,只是沒有前綴。


注解控制

當我們使用注解之前,必須通過 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) 開啟注解。

  • @PreAuthorize:方法執行前進行權限檢查,允許使用 SpEL(pring表達式語言)。

  • @PostAuthorize:方法執行后進行權限檢查,基本不用。

  • @Secured:類似於 @PreAuthorize,但是不允許使用 SpEL(pring表達式語言)。

例如:

@Controller
public class HelloController {

    // 只有當前登錄用戶名為 javaboy 的用戶才可以訪問該方法。
    @PreAuthorize("principal.username.equals('javaboy')")
    public String hello() {
        return "hello";
    }

    // 表示訪問該方法的用戶必須具備 admin 角色
    @PreAuthorize("hasRole('admin')")
    public String admin() {
        return "admin";
    }

    // 表示訪問該方法的 age 參數必須大於 98,否則請求不予通過。
    @PreAuthorize("#age>98")
    public String getAge(Integer age) {
        return String.valueOf(age);
    }

    // 表示該方法的用戶必須具備 user 角色,但是注意 user 角色需要加上 ROLE_ 前綴。
    @Secured({"ROLE_user"})
    public String user() {
        return "user";
    }
}

請求認證

image

讓我們仔細分析認證過程:

  1. 當用戶發送登錄請求的時候,首先進入到 UsernamePasswordAuthenticationFilter 中進行校驗。

  2. UsernamePasswordAuthenticationFilter 通過 attemptAuthentication 方法會獲取用戶的username以及password參數的信息,封裝為 UsernamePasswordAuthenticationToken 對象,最后會進入 AuthenticationManager 接口的實現類 ProviderManager 中。AuthenticationManager(認證管理器) 本身不包含驗證的邏輯,它的作用是用來管理 AuthenticationProvider。

  3. 進入 ProviderManager 類調用 authenticate() 方法,通過循環遍歷判斷它是否支持這種登錄方式,具體的登錄方式有表單登錄,qq登錄,微信登錄等。如果支持則會進入A uthenticationProvider 接口的抽象實現類 AbstractUserDetailsAuthenticationProvider 中調用 authenticate() 方法對用戶的身份進入校驗。

  4. 進入 AbstractUserDetailsAuthenticationProvider 的 authenticate方法之后,UserDetail 的 user 對象是否為空,如果為空,表示還沒有認證,就需要調用 DaoAuthenticationProvider 類的 retrieveUser 方法去獲取用戶的信息。

  5. 該擴展類的 retrieveUser 方法中調用 UserDetailsService 這個接口的實現類的 loadUserByUsername 方法去獲取用戶信息。如果需要自定義實現,則可以實現 UserDetails 接口,編寫自己的邏輯,從數據庫中獲取用戶密碼等權限信息返回。

  6. 獲取到用戶信息之后,返回到 AbstractUserDetailsAuthenticationProvider 類中調用 createSuccessAuthentication() 方法,通過 PasswordEncoder 對比用戶信息是否與 AuthenticationManager 一直,如果一直,則認證通過(setAuthenticated(true))。

  7. 認證成功后, AuthenticationManager 身份管理器返回一個被填充滿了信息的(包括上面提到的權限信息, 身份信息,細節信息,但密碼通常會被移除) Authentication 實例。


請求授權

image

讓我們仔細分析授權過程:

  1. 已認證用戶訪問受保護的 web 資源將被 SecurityFilterChain 中的 FilterSecurityInterceptor 的子類攔截。

  2. FilterSecurityInterceptor 會從 SecurityMetadataSource 的 getAttributes() 方法,獲取要訪問當前資源所需要的權限。其實就是讀取訪問策略的抽象,而讀取的內容,其實就是我們配置的訪問規則。

  3. FilterSecurityInterceptor 會調用 AccessDecisionManager(授權決策器)的 decide() 方法進行授權決策,若決策通過,則允許訪問資源,否則將禁止訪問。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM