国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

Spring Security 6.0:深度剖析其核心實(shí)現(xiàn)與工作原理

開發(fā) 前端
Spring Security的架構(gòu)非常靈活,因此作者的建議是,不需要完全照搬整體框架,對(duì)于不同的應(yīng)用類型和場(chǎng)景,可以選擇性地引入部分功能。比如Admin應(yīng)用可以提供自定義的AuthenticationProvider?,而API服務(wù)完全可以自定義Securiy Filter,只要維護(hù)好Security Context的Authentication,就可以很好的集成到Spring Security

導(dǎo)言

Spring Security是一個(gè)功能強(qiáng)大且高度且可定制的身份驗(yàn)證和訪問控制框架,除了標(biāo)準(zhǔn)的身份認(rèn)證和授權(quán)之外,它還支持點(diǎn)擊劫持,CSRF,XSS,MITM(中間人)等常見攻擊手段的保護(hù),并提供密碼編碼,LDAP認(rèn)證,Session管理,Remember Me認(rèn)證,JWT,OAuth 2.0等功能特性。

由于安全領(lǐng)域本身的復(fù)雜性和豐富的安全特性支持,以及Spring Security高度的可定制性,使得它成為一個(gè)龐大且復(fù)雜的框架。每次升級(jí)可能帶來的破壞性更新,加上網(wǎng)絡(luò)上的陳舊教程,更是加重了Spring Security非常難用的印象。很多新手可能跟作者一樣,首次引入Spring Security框架之后,突然發(fā)現(xiàn)很多頁面無法訪問,感到無所適從。

為此,本文將基于Spring Boot 3.1.x依賴的Spring Security 6.1.x版本,深入探討Spring Security的架構(gòu)和實(shí)現(xiàn)原理。本文將著重解釋Spring Security的設(shè)計(jì)思想,而不會(huì)過多涉及具體的實(shí)現(xiàn)細(xì)節(jié)。

文章的目標(biāo)是讓讀者在閱讀完本文之后,能夠?qū)φ麄€(gè)Spring Security框架有個(gè)清晰的理解,并在面對(duì)問題時(shí)知道如何著手排查。另外,本文重點(diǎn)關(guān)注Spring Security的總體架構(gòu),以及身份認(rèn)證(Authentication)和鑒權(quán)控制(Authorization)的實(shí)現(xiàn)。

?

【版本兼容性】Spring Security 6引入了很多破壞性的更新,包括廢棄代碼的刪除,方法重命名,全新的配置DSL等,但是架構(gòu)和基本原理還是保持不變的。本文在講解過程中會(huì)盡量指出當(dāng)前版本跟老版本的差異,尤其是涉及到兼容性問題的時(shí)候。【閱讀提示】本文的篇幅較長(zhǎng),并且包含了部分源碼分析,時(shí)間有限的情況下,可以重點(diǎn)閱讀架構(gòu)圖部分。

Java Web應(yīng)用的Security實(shí)現(xiàn)基本思路

大家可以嘗試思考下,安全相關(guān)的校驗(yàn)和處理,應(yīng)該處于應(yīng)用的哪個(gè)部分呢?答案是,應(yīng)該放在所有請(qǐng)求的入口,因?yàn)樗歉唧w的業(yè)務(wù)邏輯無關(guān)的,在Spring MVC世界里就是@Controller之前。

在JakartaEE(JavaEE的新版)規(guī)范中,F(xiàn)ilter和Servlet都符合這個(gè)前置要求。然而,Spring的Web應(yīng)用基本上只包含一個(gè)DispatcherServelt,主要用于請(qǐng)求分發(fā),缺乏安全相關(guān)的支持和合適的擴(kuò)展機(jī)制。而Filter運(yùn)行在Servlet之前,而規(guī)范本身就支持配置多個(gè)Filter。因此,在請(qǐng)求到達(dá)Servlet之前,先通過Filter進(jìn)行安全驗(yàn)證就是一個(gè)非常合理的實(shí)現(xiàn)方式。這樣可以在請(qǐng)求進(jìn)入業(yè)務(wù)邏輯之前,對(duì)請(qǐng)求進(jìn)行攔擊,然后進(jìn)行必要的安全性檢查和處理。

這也是Spring Security的實(shí)現(xiàn)方式。本質(zhì)上,Spring Security的實(shí)現(xiàn)原理很簡(jiǎn)單,就是提供了一個(gè)用于安全驗(yàn)證的Filter。假如我們自己實(shí)現(xiàn)一個(gè)簡(jiǎn)化版的Filter,它的大概邏輯應(yīng)該是這樣的:

public class SimpleSecurityFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        UsernamePasswordToken token = extractUsernameAndPasswordFrom(request);  // (1)
        if (notAuthenticated(token)) {  // (2)
            // 用戶名密碼錯(cuò)誤
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // HTTP 401.
            return;
        }
        if (notAuthorized(token, request)) { // (3)
            // 當(dāng)前登錄用戶的權(quán)限不足
            response.setStatus(HttpServletResponse.SC_FORBIDDEN); // HTTP 403
            return;
        }
        // 通過了身份驗(yàn)證和權(quán)限校驗(yàn),繼續(xù)執(zhí)行其它Filter,最終到達(dá)Servlet
        chain.doFilter(request, response); // (4)
    }
}
  1. 從HTTP請(qǐng)求中獲取用戶名和密碼,來源包括標(biāo)準(zhǔn)的Basic Auth HTTP Header,表單字段或者cookie等等。
  2. 身份認(rèn)證,也就是校驗(yàn)用戶名和密碼。
  3. 認(rèn)證通過后,需要檢查當(dāng)前登錄的用戶有沒有訪問當(dāng)前HTTP請(qǐng)求的權(quán)限,也就是鑒權(quán)邏輯。
  4. 權(quán)限校驗(yàn)也通過后,就繼續(xù)執(zhí)行其它Filter,所有Filter都通過后,進(jìn)入Servlet,最終到達(dá)具體的Controller。

FilterChain

在安全領(lǐng)域,由于攻防手段的多樣性和認(rèn)證鑒權(quán)方式的復(fù)雜性,將所有功能都放在一個(gè)Filter中會(huì)導(dǎo)致該Filter迅速演變?yōu)橐粋€(gè)龐大而復(fù)雜的類。

因此,在實(shí)際應(yīng)用場(chǎng)景中,我們常常將這個(gè)龐大的Filter拆分成多個(gè)小Filter,并將它們鏈接在一起。每個(gè)Filter都只負(fù)責(zé)特定領(lǐng)域的功能,比如CsrfFilter,AuthenticationFilter,AuthorizationFilter等。

這種概念被稱為FilterChain,實(shí)際上JarkataEE規(guī)范也有相識(shí)的概念。通過使用FilterChain,你就可以以插拔的方式添加或移除特定功能的Filter,而無需改動(dòng)現(xiàn)有的代碼。

Spring Security框架的基本架構(gòu)和原理

上一節(jié)其實(shí)已經(jīng)說明了Spring Security框架的基本思路,下面我們深入分析其實(shí)現(xiàn)原理和架構(gòu)。

實(shí)現(xiàn)原理

一個(gè)應(yīng)用引入了Spring Security Starter包后,再啟動(dòng)應(yīng)用,你會(huì)發(fā)現(xiàn)控制臺(tái)多了下面這條日志,說明已經(jīng)開啟了Security特性。

2023-07-12T10:05:23.168+08:00  INFO 680540 --- [
main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@46e3559f, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@3b83459e, 
org.springframework.security.web.context.SecurityContextHolderFilter@26837057, org.springframework.security.web.header.HeaderWriterFilter@2d74c81b, org.springframework.security.web.csrf.CsrfFilter@3a17b2e3, 
org.springframework.security.web.authentication.logout.LogoutFilter@5f5827d0, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4ed5a1b0, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@3b332962, 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@32118208, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@67b355c8, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@991cbde, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@dd4aec3, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@414f87a9, org.springframework.security.web.access.ExceptionTranslationFilter@59939293, org.springframework.security.web.access.intercept.AuthorizationFilter@f438904]

從這條日志可以觀察到,Spring Security通過DefaultSecurityFilterChain類來完成安全相關(guān)的功能,而該類本身又由其它Filter組成。默認(rèn)情況下,Spring Security Starter引入了15個(gè)Filter,下面我們簡(jiǎn)要介紹下其中幾個(gè)重要的Filter:

  1. CsrfFilter:這個(gè)Filter用于防止跨站點(diǎn)請(qǐng)求偽造攻擊,這也是導(dǎo)致所有POST請(qǐng)求都失敗的原因。基于Token驗(yàn)證的API服務(wù)可以選擇關(guān)閉CsrfFilter,而一般Web頁面需要開啟。
  2. BasicAuthenticationFilter:支持HTTP的標(biāo)準(zhǔn)Basic Auth的身份驗(yàn)證模塊。
  3. UsernamePasswordAuthenticationFilter:支持Form表單形式的身份驗(yàn)證模塊。
  4. DefaultLoginPageGeneratingFilter和DefaultLogoutPageGeneratingFilter:用于自動(dòng)生成登錄頁面和注銷頁面。
  5. AuthorizationFilter: 這個(gè)Filter負(fù)責(zé)授權(quán)模塊。值得注意的是,在老版本中鑒權(quán)模塊是FilterSecurityInterceptor.

這些Filter構(gòu)成了Spring Security的核心功能,通過它們,我們可以實(shí)現(xiàn)身份驗(yàn)證、授權(quán)、防護(hù)等安全特性。根據(jù)應(yīng)用的需求,我們可以選擇啟用或禁用特定的Filter,以定制和優(yōu)化安全策略。

SecurityFilterChain

DefaultSecurityFilterChain類實(shí)現(xiàn)了SecurityFilterChain接口,我們打開這個(gè)接口的源碼,會(huì)發(fā)現(xiàn)它只有兩個(gè)方法,matches用于匹配特定的Http請(qǐng)求(比如特定規(guī)則的URL),getFilters 用于獲取可用的所有Security Filter。

public interface SecurityFilterChain {
    boolean matches(HttpServletRequest request); // 規(guī)則匹配
    List<Filter> getFilters(); // 該FilterChain下的所有Security Filter
}

從這段代碼可以得出兩個(gè)結(jié)論:

  1. 不同的Http請(qǐng)求可以對(duì)應(yīng)不同的SecurityFilterChain(通過matches方法)。
  2. SecurityFilterChain不是我們以為的JakartaEE的Servlet Filter實(shí)現(xiàn),它僅僅是一個(gè)包含多個(gè)Filter的容器,本身不負(fù)責(zé)調(diào)度和執(zhí)行。它只是一個(gè)配置項(xiàng),用于指定一組Filter,以實(shí)現(xiàn)特定的安全需求。

DelegatingFilterProxy

實(shí)際上,JakartaEE層面上的Filter實(shí)現(xiàn)是DelegatingFilterProxy類,它在Spring Security中起到了一個(gè)重要的橋梁作用,連接了Servlet容器和Spring容器。Servlet容器不了解Spring定義的Beans,而Spring Security的大部分組件及其依賴都是注冊(cè)到Spring容器中的Bean。

DelegatingFilterProxy核心代碼的主要工作就是從WebApplicationContext獲取指定名稱的Filter Bean,然后委托給這個(gè)Bean的doFilter方法。以下是簡(jiǎn)化后的偽代碼:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    Filter delegateToUse = this.delegate;
    if (delegateToUse == null) {
        synchronized (this.delegateMonitor) {
            delegateToUse = this.delegate;
            if (delegateToUse == null) {
                WebApplicationContext wac = findWebApplicationContext();
                // 獲取Filter Bean并初始化
                delegateToUse = initDelegate(wac);
            }
            this.delegate = delegateToUse;
        }
    }
    // 委托給的delegate對(duì)象完成實(shí)際的doFilter
    invokeDelegate(delegateToUse, request, response, filterChain);
}

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    // Bean名稱配置在SecurityFilterAutoConfiguration.DEFAULT_FILTER_NAME = "springSecurityFilterChain"
    String targetBeanName = getTargetBeanName();
    // 從容器中獲取指定名稱的Filter類型Bean
    Filter delegate = wac.getBean(targetBeanName, Filter.class);
    if (isTargetFilterLifecycle()) {
        delegate.init(getFilterConfig());
    }
    return delegate;
}

通過這種方式,DelegatingFilterProxy實(shí)現(xiàn)了將Servlet容器中的Filter請(qǐng)求委托給Spring容器中的具體Filter Bean處理,從而實(shí)現(xiàn)了Servlet容器和Spring容器之間的無縫連接。

FilterChainProxy

而這個(gè)被委托的Filter Bean的類型就是FilterChainProxy,是在WebSecurityConfiguration中配置的:

// name = "springSecurityFilterChain"
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
    // 配置SecurityFilterChain
    boolean hasFilterChain = !this.securityFilterChains.isEmpty();
    if (!hasFilterChain) {
        this.webSecurity.addSecurityFilterChainBuilder(() -> {
            this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
            this.httpSecurity.formLogin(Customizer.withDefaults());
            this.httpSecurity.httpBasic(Customizer.withDefaults());
            return this.httpSecurity.build();
        });
    }
    for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
        this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
    }
    // WebSecurity自定義配置
    for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
        customizer.customize(this.webSecurity);
    }
    // FilterChainProxy最終是由WebSecurity構(gòu)建出來的
    return this.webSecurity.build();
}

從上面代碼可以發(fā)現(xiàn),F(xiàn)ilterChainProxy對(duì)象最終是由WebSecurity根據(jù)SecurityFilterChain和其它一些配置構(gòu)建出來的。

FilterChainProxy主要作用就是查找匹配當(dāng)前Http請(qǐng)求規(guī)則的SecurityFilterChain,然后將工作委派給SecurityFilterChain的所有Filter。簡(jiǎn)化后的偽代碼如下所示:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    // 獲取匹配的所有Filter
    List<Filter> filters = getFilters(request); 
    // 按順序執(zhí)行Filter
    Filter nextFilter = this.filters.get(this.currentPosition - 1);
    nextFilter.doFilter(request, response, this);
}

private List<Filter> getFilters(HttpServletRequest request) {
    for (SecurityFilterChain chain : this.filterChains) {
        // 返回匹配規(guī)則的SecurityFilterChain的Filter列表
        if (chain.matches(request)) { 
            return chain.getFilters();
        }
    }
    return null;
}

?

【Tips】FilterChainProxy可以認(rèn)為是整個(gè)Spring Security處理請(qǐng)求的一個(gè)起點(diǎn),如果你遇到Security相關(guān)問題,又不清楚是具體哪個(gè)Filter導(dǎo)致的,就可以從這里開始Debug。

基本架構(gòu)

從上一節(jié)的內(nèi)容,我們可以得出下面這一副架構(gòu)圖(圖中藍(lán)色和橘紅色的部分代表Security Security)。從圖中可以看出,Spring Security框架通過DelegatingFilterProxy建立起了Servlet容器和Spring容器的鏈接,F(xiàn)ilterChainProxy基于匹配規(guī)則(比如URL匹配),決定使用哪個(gè)SecurityFilterChain。而SecurityFilterChain又由零到多個(gè)Filter組成,這些Filter完成實(shí)際的功能。

圖片圖片

Security Filter和配置DSL

Spring Security是基于Jakarta EE的Filter實(shí)現(xiàn)的,而在此基礎(chǔ)上,它提供了一套自身的Filter機(jī)制,相當(dāng)于兩層的Filter嵌套。為了不混淆這兩種Filter,我們把Spring Security框架提供的Filter稱為Security Filter。在下文中,我們所提及的配置,擴(kuò)展和自定義的Filter都指的是Security Filter,如果沒有特別說明,都默認(rèn)指的是Security Filter。

通過一系列Security Filter,Spring Security提供了豐富的開箱即用的安全功能,包括身份認(rèn)證,鑒權(quán),Csrf等等。每個(gè)功能都是通過一個(gè)或者多個(gè)Security Filter實(shí)現(xiàn)的。有些復(fù)雜的Filter,例如身份認(rèn)證和鑒權(quán),擁有自己的特定架構(gòu),并且會(huì)依賴Filter的順序和執(zhí)行過程中的上下文信息,這也是導(dǎo)致Spring Security在使用上相對(duì)復(fù)雜的原因之一。

Spring Securiy的基本配置都是通過自定義SecurityFilterChain的Bean來實(shí)現(xiàn)的。下面是一個(gè)示例配置,它提供了自定義的登錄頁面,并且針對(duì)不同的URL配置了不同的角色權(quán)限,這些配置方法實(shí)際上就是配置不同的Security Filter,更詳細(xì)的解釋會(huì)在后面講解具體特性的時(shí)候時(shí)展開說明。

@Bean
static SecurityFilterChain mySecurityFilterChain(HttpSecurity http) throws Exception {
    // 鑒權(quán)相關(guān)配置
    http.authorizeHttpRequests((requests) ->
            request.requestMatchers("/admin").hasAuthority("ROLE_ADMIN") // "/admin"要求有“ROLE_ADMIN"角色權(quán)限
                    .requestMatchers("/hello").hasRole("USER") // "/hello"要求有"ROLE_USER"角色權(quán)限
                    .anyRequest().authenticated()); // 其它只需要身份認(rèn)證通過即可,不需要其它特殊權(quán)限
    // 登錄相關(guān)配置
    http.formLogin(formLogin -> formLogin
            .loginPage("/authentication") // 自定義登錄頁面,不再使用內(nèi)置的自動(dòng)生成頁面
            .permitAll() // 允許自定義頁面的匿名訪問,不需要認(rèn)證和鑒權(quán)
    );
    return http.build(); // 返回構(gòu)建的SecurityFilterChain實(shí)例
}

【版本兼容性】Spring Security 6.0在配置方面引入了許多改變。在之前的老版本中,可以選擇廢棄的WebSecurityConfigurerAdapter進(jìn)行配置,但從6.0版本開始,這個(gè)廢棄類已經(jīng)被刪除了。而目前很多老項(xiàng)目以及網(wǎng)上的教程仍在使用WebSecurityConfigurerAdapter。另外,配置DLS也發(fā)生了變化。Spring Security 6.0采用了基于Lambda表達(dá)式的DSL配置方式,取代了之前的純鏈?zhǔn)秸{(diào)用方式,使得配置更加靈活和直觀。一些方法名稱也進(jìn)行了修改,例如antMatchers替換為requestMatchers。

除了Spring Boot的專有配置,Spring Security自身也提供了默認(rèn)配置,這些默認(rèn)配置在HttpSecurityConfiguration#httpSecurity方法中,它默認(rèn)添加了很多Security Filter,核心代碼如下:

@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
    // ... //
    http
        .csrf(withDefaults())
        .addFilter(webAsyncManagerIntegrationFilter)
        .exceptionHandling(withDefaults())
        .headers(withDefaults())
        .sessionManagement(withDefaults())
        .securityContext(withDefaults())
        .requestCache(withDefaults())
        .anonymous(withDefaults())
        .servletApi(withDefaults())
        .apply(new DefaultLoginPageConfigurer<>());
    http.logout(withDefaults());
    // ... //
    return http;
}

以上解釋了Spring Security的實(shí)現(xiàn)原理和基本架構(gòu),而具體到特定的Security Filter,又有各種的框架,下面將展開說明認(rèn)證和鑒權(quán)兩個(gè)核心模塊。

Authentication身份認(rèn)證

身份認(rèn)證有很多種方式,大致可以分為以下4類:

  1. 標(biāo)準(zhǔn)的賬號(hào)密碼認(rèn)證:這是很多網(wǎng)站都支持的方式,也是大家最熟悉的認(rèn)證模式;
  2. 調(diào)用第三方服務(wù)或內(nèi)部其它API進(jìn)行認(rèn)證:當(dāng)服務(wù)自身無法直接獲取用戶的密碼時(shí),需要借助第三方服務(wù)或者內(nèi)部API進(jìn)行認(rèn)證;
  3. 基于Token的認(rèn)證:這是API服務(wù)一般使用的認(rèn)知方式,通過令牌來進(jìn)行身份驗(yàn)證;
  4. OAuth2或其它OpenID認(rèn)證:這種方式廣泛用于允許用戶使用其它平臺(tái)的身份信息進(jìn)行登錄,例如微信登錄,Google登錄等。

Spring Security支持大部分的認(rèn)證方式,但不同的認(rèn)證方式需要配置不同的Bean及其依賴Bean,否則很容易遇到各種異常和空指針。

本文重點(diǎn)討論標(biāo)準(zhǔn)的賬號(hào)密碼認(rèn)證方式。

實(shí)現(xiàn)原理

如果你使用的是Spring Boot,那么Spring Boot Starter Security默認(rèn)就配置了Form表單和Basic認(rèn)證方式,其配置代碼如下所示:

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnDefaultWebSecurity
    static class SecurityFilterChainConfiguration {
        @Bean
        @Order(SecurityProperties.BASIC_AUTH_ORDER)
        SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
            http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated()); // 所有URL都需要認(rèn)證用戶
            http.formLogin(withDefaults()); // 支持form表單認(rèn)證,默認(rèn)配置提供了自動(dòng)生成的登錄和注銷頁面
            http.httpBasic(withDefaults()); // 支持HTTP Basic Authentication
            return http.build();
        }

    }
    // ...其它配置...
}

為了討論方便,我們用下面的配置覆蓋Spring Boot默認(rèn)的配置,只支持Form表單認(rèn)證方式,討論它具體是如何實(shí)現(xiàn)的。

@Configuration()  
public class MySecurityConfig {
    @Bean
    SecurityFilterChain mySecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated()); // (1)
        http.formLogin(withDefaults()); // (2)
        return http.build();
    }
}
  1. authorizeHttpRequests方法用于配置每個(gè)請(qǐng)求的權(quán)限控制,這里要求所有請(qǐng)求都要通過認(rèn)證后才能訪問。實(shí)際上,這個(gè)方法配置的更多是鑒權(quán)相關(guān)的內(nèi)容,跟身份認(rèn)證的關(guān)聯(lián)較小,它本質(zhì)上是增加了一個(gè)AuthorizationFilter用于鑒權(quán),具體細(xì)節(jié)在鑒權(quán)部分會(huì)詳細(xì)說明。
  2. http.formLogin方法提供了Form表單認(rèn)證的方式,withDefaults方法是Form表單認(rèn)證的默認(rèn)配置。這段配置的作用就是增加了用于賬號(hào)密碼認(rèn)證的UsernamePasswordAuthenticationFilter,以及自動(dòng)生成登錄頁面和注銷頁面的DefaultLogoutPageGeneratingFilter和DefaultLogoutPageGeneratingFilter共3個(gè)Security Filter。值得注意的是,登錄頁面和注銷頁面這兩個(gè)Filter是配合DefaultLoginPageConfigurer配置一起注冊(cè)的。如果你通過formLogin.loginPage提供了自定義的登錄頁面,那么這兩個(gè)Filter就不會(huì)被注冊(cè)。

在本節(jié)中,我們主要討論身份認(rèn)證的實(shí)現(xiàn),因此,接下來將詳細(xì)探究Form表單認(rèn)證方式中UsernamePasswordAuthenticationFilter的實(shí)現(xiàn)。

AbstractAuthenticationProcessingFilter

對(duì)于Filter,我們重點(diǎn)分析它的doFilter方法的源碼。實(shí)際上,它繼承了抽象類AbstractAuthenticationProcessingFilter,而這個(gè)抽象類的doFilter是一個(gè)模板方法,定義了整個(gè)認(rèn)證流程。其核心流程非常簡(jiǎn)單,偽代碼如下:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    // 首先判斷該請(qǐng)求是否是認(rèn)證請(qǐng)求或者登錄請(qǐng)求
    if (!requiresAuthentication(request, response)) { // (1)
        chain.doFilter(request, response);
        return;
    }
    try {
        Authentication authenticationResult = attemptAuthentication(request, response); // (2) 實(shí)際認(rèn)證邏輯
        // 認(rèn)證成功
        successfulAuthentication(request, response, chain, authenticationResult); // (3)
    }
    catch (AuthenticationException ex) {
        // 認(rèn)證失敗
        unsuccessfulAuthentication(request, response, ex); // (4)
    }
}
  1. 首先requiresAuthentication方法用于判斷當(dāng)前請(qǐng)求是否為認(rèn)證請(qǐng)求或者登錄請(qǐng)求,例如通常是POST /login。只有在登錄認(rèn)證的情況下,才需要通過這個(gè)Filter;
  2. attempAuthentication方法是實(shí)際的認(rèn)證邏輯,這是一個(gè)抽象方法,具體的邏輯由子類重寫實(shí)現(xiàn)。它的規(guī)范行為是,如果認(rèn)證成功,應(yīng)該返回認(rèn)證結(jié)果Authentication,否則以拋出異常AuthenticationException的方式表示認(rèn)證失敗;
  3. successfulAuthentication:認(rèn)證成功后,該方法會(huì)將Authentication對(duì)象放到Security Context中,這是非常關(guān)鍵的一步,后續(xù)需要認(rèn)證結(jié)果的時(shí)候都是從Security Context獲取的,比如鑒權(quán)Filter。此外,該方法還會(huì)處理其它一些相關(guān)功能,比如RememberMe,事件發(fā)布,最后再調(diào)用AuthenticationSuccessHandler;
  4. unsuccessfulAuthentication :在認(rèn)證失敗后,它會(huì)清空Security Context,調(diào)用RememberMe相關(guān)服務(wù)和AuthenticationFailureHandler來處理認(rèn)證失敗后的回調(diào)邏輯,比如跳轉(zhuǎn)到錯(cuò)誤頁面。

Authentication模型

在這里,我們涉及到了一個(gè)非常重要的數(shù)據(jù)模型——Authentication,它是一個(gè)接口類型,它既是對(duì)認(rèn)證結(jié)果的一個(gè)抽象表示,同時(shí)也是對(duì)認(rèn)證請(qǐng)求的一個(gè)抽象,通常也被稱為認(rèn)證Token。它的方法都比較抽象,定義如下:

public interface Authentication extends Principal, Serializable {
    // 當(dāng)前認(rèn)證用戶擁有的權(quán)限列表
    Collection<? extends GrantedAuthority> getAuthorities();
    // 用戶的一個(gè)身份標(biāo)識(shí),通常就是用戶名
    Object getPrincipal();
    // 可用于證明用戶身份的一個(gè)憑證,通常就是用戶密碼
    Object getCredentials();
    // 當(dāng)前用戶是否認(rèn)證通過
    boolean isAuthenticated();
    // 更新用戶的認(rèn)證狀態(tài)
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
    // 獲取附加的詳情信息,比如原始的Http請(qǐng)求體等。
    Object getDetails();
}

具體的Authentication實(shí)現(xiàn)一般都命名為XXXToken,大部分都繼承自抽象類AbstractAuthenticationToken,比如表示標(biāo)準(zhǔn)的用戶名密碼認(rèn)證結(jié)果的UsernamePasswordAuthenticationToken,表示匿名登錄用戶認(rèn)證結(jié)果的AnonymousAuthenticationToken等等,你也可以完全實(shí)現(xiàn)自己的Authentication。

attempAuthentication方法

接下來,我們看下UsernamePasswordAuthenticationFilter的認(rèn)證具體實(shí)現(xiàn)方法attempAuthentication,它的源碼如下:

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException {
    // 默認(rèn)只支持POST請(qǐng)求
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    // 從form表單獲取用戶名和密碼
    String username = obtainUsername(request);
    username = (username != null) ? username.trim() : "";
    String password = obtainPassword(request);
    password = (password != null) ? password : "";
    // 構(gòu)建一個(gè)用于認(rèn)證的請(qǐng)求
    UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
            password);
    // 附加詳細(xì)信息,比如請(qǐng)求體,有些認(rèn)證方式需要除了用戶名密碼外更多的信息
    setDetails(request, authRequest);
    // 委托給AuthenticationManager做具體的認(rèn)證
    return this.getAuthenticationManager().authenticate(authRequest);
}

這個(gè)方法非常簡(jiǎn)單,它主要進(jìn)行一些前置校驗(yàn)工作,從請(qǐng)求體中獲取用戶名和密碼,并構(gòu)建認(rèn)證請(qǐng)求對(duì)象。然后,剩余的認(rèn)證工作都是委托給AuthenticationManager接口來完成的,該接口的定義如下:

public interface AuthenticationManager {
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager和AuthenticationProvider

AuthenticationManager接口只有一個(gè)方法,它的入?yún)⒑统鰠⒍际茿uthentication對(duì)象。通常情況下,入?yún)⑻峁┝吮匾恼J(rèn)證信息,例如用戶名和密碼。而在認(rèn)證成功后,該方法會(huì)返回認(rèn)證結(jié)果,并附加認(rèn)證狀態(tài),用戶擁有的權(quán)限列表等信息。如果認(rèn)證失敗,它會(huì)拋出AuthenticationException異常類的子類,其中包括DisabledException,LockedException和BadCredentialsException等賬號(hào)相關(guān)的異常。

AuthenticationManager接口定義了Spring Security的認(rèn)證行為。你可以提供自定義的實(shí)現(xiàn),Spring Security也提供了一個(gè)通用的實(shí)現(xiàn)類ProviderManager。ProviderManager將具體的認(rèn)證工作委托給一系列的AuthenticationProvider。

每個(gè)AuthenticationProvider對(duì)應(yīng)不同的認(rèn)證方式。比如最常見的用戶名密碼的認(rèn)證實(shí)現(xiàn)是DaoAuthenticationProvider,而JwtAuthenticationProvider提供了JWT Token的認(rèn)證。你可以通過添加不同的AuthenticationProvider的方式,在同一個(gè)服務(wù)內(nèi)支持多種類型的認(rèn)證方式,比如需要調(diào)用其它API檢驗(yàn)密碼的情況,就需要自定義AuthenticationProvider。

此外,ProviderManager還可以配置父級(jí)AuthenticationManager,當(dāng)這個(gè)ProviderManager的所有AuthenticationProvider都不支持所需的認(rèn)證方式時(shí),它會(huì)繼續(xù)委托給父級(jí)的AuthenticationManager,而該父級(jí)通常也是一個(gè)ProviderManager類型。

UserDetailsService和PasswordEncoder

DaoAuthenticationProvider是最常用的認(rèn)證實(shí)現(xiàn)之一,它通過UserDetailsService和PasswordEncoder來驗(yàn)證用戶名和密碼。

UserDetailsService的作用是查找用戶信息UserDetails,這些信息包括用戶密碼,狀態(tài),權(quán)限列表等。用戶信息可以存儲(chǔ)在內(nèi)存,數(shù)據(jù)庫或者其它任何地方。Spring Security默認(rèn)的配置是內(nèi)存存儲(chǔ),對(duì)應(yīng)的UserDetailsService實(shí)現(xiàn)是InMemoryUserDetailsManager,而數(shù)據(jù)庫存儲(chǔ)則對(duì)應(yīng)JdbcUserDetailsManager。

從UserDetailsService獲取到用戶密碼后,需要通過PasswordEncoder來驗(yàn)證密碼的正確性。因?yàn)槊艽a一般都不應(yīng)該以明文形式存儲(chǔ),實(shí)際存儲(chǔ)的是按一定規(guī)則編碼后的文本,Spring Security支持多種編碼方式,例如bcrypt,argon2,scrypt,pbkdf2等。你可以配置PasswordEncoder Bean來選擇不同的編碼方式。都是請(qǐng)注意,內(nèi)置的編碼方式默認(rèn)對(duì)編碼后的文本有一個(gè)格式要求,就是必須有類似{bcrypt}的前綴來表示編碼方式。

基本架構(gòu)

架構(gòu)圖

上一節(jié)中,我們講述了用戶名密碼認(rèn)證的實(shí)現(xiàn)細(xì)節(jié),現(xiàn)在,讓我們以用戶名密碼認(rèn)證方式為例,從整體上來看下身份認(rèn)證的架構(gòu)和流程。它的整體架構(gòu)如下:

  1. 當(dāng)一個(gè)HTTP請(qǐng)求進(jìn)來后,UsernamePasswordAuthenticationFilter會(huì)從HTTP請(qǐng)求體中獲取用戶名和密碼,然后使用這些信息創(chuàng)建一個(gè)UsernamePasswordAuthenticationToken對(duì)象作為認(rèn)證請(qǐng)求的參數(shù)。
  2. 接下來,AuthenticationManager(其實(shí)現(xiàn)類是ProviderManager)負(fù)責(zé)對(duì)接受到的UsernamePasswordAuthenticationToken進(jìn)行認(rèn)證。
  3. ProviderManager會(huì)遍歷配置的所有AuthenticationProvider,查找支持UsernamePasswordAuthenticationToken類型的AuthenticationProvider,然后委托其進(jìn)行實(shí)際的認(rèn)證工作,而在這里,匹配的就是DaoAuthenticationProvider。
  4. DaoAuthenticationProvider首先調(diào)用UserDetailService獲取用戶信息,然后將獲取到的密碼(通常是編碼后的密碼)委托給PasswordEncoder進(jìn)行驗(yàn)證。如果認(rèn)證失敗,DaoAuthenticationProvider會(huì)拋出AuthenticationException的子類表示認(rèn)證失敗。
  5. 當(dāng)認(rèn)證成功時(shí),AuthenticationManager會(huì)返回一個(gè)UsernamePasswordAuthenticationToken對(duì)象作為認(rèn)證結(jié)果,這個(gè)對(duì)象除了包含用戶的基本信息外,最重要的是認(rèn)證通過狀態(tài)以及該用戶擁有的權(quán)限列表,這些信息在后續(xù)的鑒權(quán)模塊會(huì)用到。
  6. 認(rèn)證結(jié)果會(huì)被放入SecurityContext,這樣后續(xù)的模塊(包括鑒權(quán)和用戶業(yè)務(wù)模塊等)如果需要這個(gè)結(jié)果(包括用戶信息和權(quán)限列表),就可以通過以下方法獲取:SecurityContextHolder.getContext().getAuthentication()。

組件替換

這個(gè)架構(gòu)非常靈活,大部分組件都是可配置和可替換的,自底向上,我們分別可以替換以下組件來滿足特定需求:

  1. UserDetailsService:根據(jù)用戶名查找用戶信息的組件,默認(rèn)配置的是內(nèi)存存儲(chǔ)InMemoryUserDetailsManager,你也可以配置為內(nèi)置的數(shù)據(jù)庫存儲(chǔ)JdbcUserDetailsManager,但是它有很多默認(rèn)的約定要遵守,對(duì)未來的擴(kuò)展也不夠靈活。通常會(huì)根據(jù)公司的規(guī)范要求或數(shù)據(jù)庫存儲(chǔ)的方式提供自定義的實(shí)現(xiàn)。
  2. PasswordEncoder:對(duì)密碼進(jìn)行編碼的組件,建議根據(jù)公司的編碼要求或當(dāng)前數(shù)據(jù)庫中已使用的編碼來配置。如果沒有特殊要求,建議采用默認(rèn)的BCryptPasswordEncoder。
  3. AuthenticationProvider: 為了安全需要,公司內(nèi)部很多應(yīng)用是不允許直接訪問用戶的密碼的,而通常會(huì)提供一個(gè)認(rèn)證的API。此時(shí),就需要自定義AuthenticationProvider,它的核心邏輯就是調(diào)用API做認(rèn)證,然后把結(jié)果再包裝成Authentication返回給AuthenticationManager。
  4. AuthenticationManager:它的默認(rèn)實(shí)現(xiàn)ProviderManager適用于大部分場(chǎng)景,通常不需要替換,除非你不想引入太多的概念。
  5. UsernamePasswordAuthenticationFilter:如果你不想引入過多的概念和復(fù)雜度,可以提供自己的Security Filter,從而完全脫離該框架。但是需要確保認(rèn)證結(jié)果模型Authentication仍然被正確處理,并且將結(jié)果通過方法SecurityContextHolder.getContext().setAuthentication放入Security Context中。

【Tips】從整個(gè)Security框架的角度來看,認(rèn)證模塊的核心概念只有兩個(gè),分別是認(rèn)證結(jié)果Authentication和Security Context。其它概念都可以認(rèn)為是認(rèn)證模塊的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。

鑒權(quán)模塊Authorization

認(rèn)證模塊證明了用戶的身份,但顯然普通用戶不應(yīng)該可以隨意訪問管理頁面或敏感資源,因此還需要有個(gè)模塊來確保只有授權(quán)的用戶才能執(zhí)行特定的操作,這個(gè)模塊稱之為鑒權(quán)或者授權(quán)(Authorization)。

當(dāng)你通過HttpSecurity.authorizeHttpRequests方法來配置請(qǐng)求的訪問權(quán)限控制時(shí),就會(huì)自動(dòng)添加鑒權(quán)的Security Filter:AuthorizationFilter,它是整個(gè)SecurityFilterChain的最后一個(gè)Filter。

【版本兼容性】在Spring Security 6.0版本中,鑒權(quán)模塊發(fā)生了很大變化。以前的版本中,鑒權(quán)模塊使用FilterSecurityInterceptor,而6.0版本之后,這個(gè)被廢棄了,取而代之的是AuthorizationFilter。同時(shí),還有一些相關(guān)的依賴組件,如AccessDecisionManager和AccessDecisionVoter也被AuthorizationManger替換了。因此,本節(jié)的內(nèi)容只限于6.0以及之后的版本。

實(shí)現(xiàn)原理

我們先看下鑒權(quán)模塊的入口,也就是AuthorizationFilter的doFilter方法:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
        throws ServletException, IOException {
    // ...其它非核心邏輯... //
    try {
        AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request); // (1)
        this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
        if (decision != null && !decision.isGranted()) { // (2)
            throw new AccessDeniedException("Access Denied");
        }
        chain.doFilter(request, response);
    }
    finally {
        request.removeAttribute(alreadyFilteredAttributeName);
    }
}

private Authentication getAuthentication() {
    Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); // (3)
    if (authentication == null) {
        throw new AuthenticationCredentialsNotFoundException(
                "An Authentication object was not found in the SecurityContext");
    }
    return authentication;
}
  1. 這個(gè)方法本身很簡(jiǎn)單,核心邏輯都委托給了AuthorizationManager。AuthorizationManager會(huì)校驗(yàn)Authentication的權(quán)限,并返回鑒權(quán)的結(jié)果AuthorizationDecision。
  2. 如果當(dāng)前認(rèn)證用戶沒有訪問權(quán)限,就會(huì)拋出AccessDeniedException異常,表示拒絕訪問。
  3. 待校驗(yàn)的Authentication是從Security Context獲取的,通常是在前面的認(rèn)證階段設(shè)置的。在這里,實(shí)際上傳給AuthorizationManager的是一個(gè)獲取Authentication的方法,而不是Authentication本身,這樣就把實(shí)際的獲取操作延后到了真正進(jìn)行授權(quán)的時(shí)候,這在某些場(chǎng)景下可以提高性能,比如permitAll,實(shí)際上它根本用不到Authentication。

AuthorizationManager

AuthorizationManager才是真正執(zhí)行鑒權(quán)邏輯的類,最常用的實(shí)現(xiàn)類是AuthorityAuthorizationManager,它的實(shí)現(xiàn)邏輯很簡(jiǎn)單,它會(huì)調(diào)用Authentication的getAuthorities方法,獲取當(dāng)前登錄用戶的權(quán)限列表,然后將這些權(quán)限與請(qǐng)求需要的權(quán)限進(jìn)行匹配。

實(shí)際上,選擇使用哪個(gè)AuthorizationManager是開發(fā)手動(dòng)設(shè)置的。我們來分析一個(gè)常用的權(quán)限配置代碼片段:

@Bean
static SecurityFilterChain mySecurityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests((requests) -> // (1)
            requests
                    .requestMatchers("/admin").hasAuthority("ROLE_ADMIN") // (2)
                    .requestMatchers("/hello").hasRole("USER") // (3)
                    .anyRequest().authenticated()); // (4)
    // ... 其它配置 ... //
    return http.build();
}
  1. 調(diào)用authorizeHttpRequests方法就相當(dāng)于打開了鑒權(quán)模塊,它會(huì)注冊(cè)AuthorizationFilter到SecurityFilterChain的最后。
  2. 對(duì)于匹配/admin的請(qǐng)求,要求有ROLE_ADMIN權(quán)限。hasAuthority的底層就是配置了一個(gè)要求ROLE_ADMIN權(quán)限的AuthorityAuthorizationManager對(duì)象。
  3. 對(duì)于匹配/hello的請(qǐng)求,要求有USER角色,等價(jià)于ROLE_USER權(quán)限。hasRole會(huì)自動(dòng)在角色名稱前面加上前綴ROLE_。hasRole的底層就是配置了一個(gè)要求ROLE_USER權(quán)限的AuthorityAuthorizationManager對(duì)象。
  4. 對(duì)于其它的請(qǐng)求,只要通過身份認(rèn)證就可以訪問,不需要特定的權(quán)限。類似的,authenticated方法的底層配置了一個(gè)AuthenticatedAuthorizationManager對(duì)象。

在Spring Security中,很多初學(xué)者都容易混淆Role和Authority的區(qū)別,實(shí)際上在技術(shù)實(shí)現(xiàn)層面上,這兩者沒有本質(zhì)區(qū)別,底層都僅僅是一個(gè)表示權(quán)限的字符串標(biāo)識(shí)符。更多的區(qū)別在于權(quán)限管理的概念上,一般情況下,Authority表示細(xì)粒度的操作權(quán)限,比如ADD_USER,DELETE_USER等,通常是動(dòng)詞;而Role則會(huì)與實(shí)際業(yè)務(wù)角色想對(duì)應(yīng),比如管理員ADMIN,普通員工STAFF等,通常是名稱。此外,一般一個(gè)Role會(huì)對(duì)應(yīng)多個(gè)Authority,同時(shí)角色之間可以存在繼承關(guān)系,比如ADMIN可以繼承STAFF的所有權(quán)限。

我們來看下hasAuthority的源碼,以分析它是如何配置AuthorizeManager的:

public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
    return access( // (3)
      withRoleHierarchy( //(2)
          AuthorityAuthorizationManager.hasAuthority(authority) // (1)
      )
    );
}

public static <T> AuthorityAuthorizationManager<T> hasAuthority(String authority) {
    Assert.notNull(authority, "authority cannot be null");
    return new AuthorityAuthorizationManager<>(authority);
}

public AuthorizationManagerRequestMatcherRegistry access(
        AuthorizationManager<RequestAuthorizationContext> manager) {
    Assert.notNull(manager, "manager cannot be null");
    return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
}

private AuthorityAuthorizationManager<RequestAuthorizationContext> withRoleHierarchy(           
AuthorityAuthorizationManager<RequestAuthorizationContext> manager) {
    manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
    return manager;
}
  1. AuthorityAuthorizationManager.hasAuthority方法簡(jiǎn)單地創(chuàng)建了一個(gè)要求特定authority權(quán)限的AuthorityAuthorizationManager實(shí)例。
  2. withRoleHierarchy是一個(gè)裝飾器方法,它打開了角色繼承的功能。角色繼承允許一個(gè)角色繼承另一個(gè)角色的所有權(quán)限,從而簡(jiǎn)化權(quán)限配置。
  3. 最后,access方法將這個(gè)AuthorityAuthorizationManager實(shí)例注冊(cè)到權(quán)限控制中。

access方法是公開的,你可以自己實(shí)現(xiàn)一個(gè)AuthorizationManager,然后通過這個(gè)方法進(jìn)行注冊(cè)。例如,我們可以提供一個(gè)拒絕所有請(qǐng)求的實(shí)現(xiàn):

http.authorizeHttpRequests((requests) ->
    requests.anyRequest().access((authentication, object) -> null));

【Tips】通過自定義AuthorizationManager,我們可以完全接管鑒權(quán)的邏輯,實(shí)現(xiàn)更加靈活和復(fù)雜的權(quán)限控制。

基本架構(gòu)

相比認(rèn)證模塊,鑒權(quán)模塊不需要太多的靈活性和擴(kuò)展性需求,因此它的架構(gòu)相對(duì)簡(jiǎn)單。

同樣,我們以一個(gè)標(biāo)準(zhǔn)的鑒權(quán)流程為例,來看整體的架構(gòu)和流程圖。

  1. 一個(gè)HTTP請(qǐng)求進(jìn)來,經(jīng)過了一系列Security Filter后,最終來到AuthorizationFilter,進(jìn)而調(diào)用AuthorizationManager#check方法進(jìn)行權(quán)限校驗(yàn)。
  2. 實(shí)際的校驗(yàn)工作繼續(xù)委托給AuthoritiesAuthorizationManager。
  3. AuthoritiesAuthorizationManager先從Security Context中獲取到Authentication對(duì)象(這個(gè)對(duì)象一般是前面的某個(gè)認(rèn)證Filter設(shè)置的),然后基于其Authorites權(quán)限列表構(gòu)建GrantedAuthority列表,用于權(quán)限項(xiàng)的匹配。
  4. 最終會(huì)返回一個(gè)AuthorizationDecision表示權(quán)限校驗(yàn)結(jié)果。

總結(jié)

本文重點(diǎn)分析了Spring Security的源碼和架構(gòu),幫助讀者理解其實(shí)現(xiàn)原理。由于篇幅有限,本文只覆蓋了身份認(rèn)證和鑒權(quán)模塊的核心邏輯,很多特性沒有涉及,包括Session管理,Remember Me服務(wù),異常分支和錯(cuò)誤處理等等,不過有了上述的基礎(chǔ)知識(shí),讀者完全可以自己分析源碼并深入理解這些特性。

FAQ

認(rèn)證和鑒權(quán)失敗拋出的異常是如何處理的?

當(dāng)發(fā)生認(rèn)證或鑒權(quán)失敗時(shí),Spring Security有專門的Security Filter ExceptionTranslationFilter來捕獲并處理這些異常。如果是認(rèn)證異常錯(cuò)誤AuthenticationException及其子類,會(huì)觸發(fā)AuthenticationEntryPoint#commence方法,而如果是鑒權(quán)錯(cuò)誤AccessDeniedException及其子類,則會(huì)觸發(fā)AccessDeniedHandler#handle方法。

一個(gè)請(qǐng)求被Security拒絕了,應(yīng)該如何Debug排查?

如果遇到身份認(rèn)證錯(cuò)誤,建議直接Debug相關(guān)Filter的doFilter方法,比如Form表單登錄的Filter就是UsernamePasswordAuthenticationFilter;而如果是鑒權(quán)錯(cuò)誤,可以從AuthorizationFilter開始Debug。

但需要注意的是,出于安全考慮,Security相關(guān)的錯(cuò)誤通常不會(huì)提供明確的錯(cuò)誤信息,甚至不會(huì)顯示錯(cuò)誤信息,而是直接跳轉(zhuǎn)到登錄頁面,比如CsrfFilter可能會(huì)導(dǎo)致這種情況。在這種情況下,可以從第一個(gè)Filter開始Debug,啟動(dòng)日志搜索Will secure any request with,就可以找到所有Security Filter列表。或者直接從入口FilterChainProxy#doFilter開始Debug。

SecurityFilterChain的配置方法底層是如何實(shí)現(xiàn)的?

SecurityFilterChain是通過HttpSecurity提供的一套DSL進(jìn)行配置的。諸如formLogin,csrf,authorizeHttpRequests等方法的邏輯都類似,參數(shù)都是一個(gè)lambda表達(dá)式,用于做各種自定義配置。而每個(gè)方法都會(huì)對(duì)應(yīng)一個(gè)特定的配置類,比如FormLoginConfigurer,CsrfConfigurer等,在執(zhí)行HttpSecurity#build方法的時(shí)候,會(huì)調(diào)用這些配置類的configure方法,該方法的作用就是根據(jù)用戶的自定義配置,創(chuàng)建一個(gè)或者多個(gè)Security Filter,并將其注冊(cè)到SecurityFilterChain。

此外,開發(fā)者還可以通過HttpSecurity.addFilter方法直接添加自定義的Security Filter。而對(duì)于復(fù)雜且有許多配置選項(xiàng)的Filter,也可以自定義SecurityConfigurerAdapter類,并通過HttpSecurity#apply方法來配置和注冊(cè)Filter。

Spring Security Starter有哪些默認(rèn)配置?

Spring Security Starter默認(rèn)配置在spring-boot-autoconfigure-x.x.x包下的文件META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports中可以找到。而具體的核心配置類有HttpSecurityConfiguration和SpringBootWebSecurityConfiguration。

Spring Security版本跟本文的不一樣,遇到問題如何排查?

每次Spring Security升級(jí),尤其是大版本升級(jí),都可能引入破壞性或者不兼容的更新。不過,基于Filter和SecuiryFilterChain的框架和架構(gòu)通常是不會(huì)改變的。但是,通常會(huì)廢棄掉老的配置方法,引入新的配置,某些特定模塊的實(shí)現(xiàn)也有可能完全替換,比如6.0的鑒權(quán)模塊AuthorizationFilter就完全替換了老的鑒權(quán)模塊。

你可以先從Security Filter列表開始排查,也可以通過入口FilterChainProxy#doFilter來Debug。

Spring Security整體太復(fù)雜了,能不能不使用它,而完全自己實(shí)現(xiàn)?

Security是個(gè)一個(gè)非常復(fù)雜的領(lǐng)域,很多開發(fā)者對(duì)其了解不深。使用Spring Security不僅提供了大部分的安全特性,還包含了很多安全領(lǐng)域的最佳實(shí)踐。自己從頭實(shí)現(xiàn)安全功能成本很高,并可能缺乏一些重要的安全特性。不過Spring Security的復(fù)雜設(shè)計(jì)以及頻繁的破壞性更新,的確給開發(fā)帶來了很大的學(xué)習(xí)成本和維護(hù)成本。

Spring Security的架構(gòu)非常靈活,因此作者的建議是,不需要完全照搬整體框架,對(duì)于不同的應(yīng)用類型和場(chǎng)景,可以選擇性地引入部分功能。比如Admin應(yīng)用可以提供自定義的AuthenticationProvider,而API服務(wù)完全可以自定義Securiy Filter,只要維護(hù)好Security Context的Authentication,就可以很好的集成到Spring Security框架里,同時(shí)開發(fā)的學(xué)習(xí)和維護(hù)成本也能降到最低。

責(zé)任編輯:武曉燕 來源: 一安未來
相關(guān)推薦

2025-06-30 02:22:00

2025-03-26 11:30:40

2024-12-24 14:01:10

2010-03-18 11:16:24

全光交換機(jī)

2025-03-25 10:29:52

2020-09-16 10:31:58

SMTP網(wǎng)絡(luò)電子郵件

2023-05-12 08:02:43

分布式事務(wù)應(yīng)用

2009-06-18 13:31:03

Spring工作原理

2012-08-08 10:04:41

IBM但W

2024-03-12 12:57:07

Redis主從架構(gòu)

2021-12-20 00:03:38

Webpack運(yùn)行機(jī)制

2023-04-11 08:00:56

Redis類型編碼

2009-06-15 15:57:21

Spring工作原理

2010-09-17 15:32:52

JVM工作原理

2025-03-18 10:28:32

LLMAI算法大語言模型

2024-12-17 00:00:00

Spring線程

2012-02-08 10:37:42

Java反射

2010-03-17 14:04:05

核心交換機(jī)

2024-01-29 08:00:00

架構(gòu)微服務(wù)開發(fā)

2012-09-27 09:47:43

SpringJava面向?qū)ο?/a>
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

天堂中文最新版在线中文| 久在线观看视频| 丝袜美女在线观看| 亚洲欧美日韩系列| 日韩av影视| 你懂的网址国产 欧美| 欧美激情精品久久久久久大尺度| 欧美人与动牲性行为| 欧美亚洲丝袜传媒另类| 特黄特黄的视频| 久久久亚洲欧洲日产国码αv| 强伦女教师2:伦理在线观看| 欧美亚洲在线| 精品国产乱码久久久久久88av | 国产精品视频精品视频| 成人av资源网址| 久久99国产综合精品女同| 成人亚洲网站| 日韩精品黄色网| 激情黄产视频在线免费观看| 日韩欧美视频在线| 日韩特级毛片| 亚洲国产精品热久久| 欧美精品videossex少妇| 欧美大片顶级少妇| 欧美人与禽猛交乱配| 精品国产成人在线影院| 污视频网站在线免费| 精品国产sm最大网站| 中文不卡1区2区3区| 在线视频欧美日韩| 精品中文视频| 热re91久久精品国99热蜜臀| 欧洲视频一区| ts人妖另类在线| 亚洲欧美清纯在线制服| 亚洲 日韩 国产第一区| 精品在线视频一区| 欧美午夜性视频| 中文字幕成人在线观看| 天堂av免费观看| 欧美日韩一区二区三区在线看| 免费观看在线午夜影视| 日韩精品中文字幕久久臀| 裤袜国产欧美精品一区| 久久人人爽人人爽爽久久| av成人app永久免费| 国产精品久久久久久久久久三级 | 成年人在线免费观看视频网站| 依依成人综合视频| 黄色在线播放| 日韩h在线观看| 日韩精品一区二区三区中文字幕 | 国产精品av在线| 欧美日韩精品一本二本三本 | 亚洲一区高清| 97精品超碰一区二区三区| 91亚洲免费视频| 日本道免费精品一区二区三区| hd国产人妖ts另类视频| 欧美猛少妇色xxxxx| 99久久综合| 亚洲一一在线| 国产精品久久夜| 888av在线| 日韩在线视频导航| 日韩大片在线| 国产a级片免费看| 日韩毛片视频在线看| 欧美精品videos另类| 最近2019年手机中文字幕| 一道本一区二区三区| 成人资源视频网站免费| 亚洲图色一区二区三区| 国产成人免费电影| 91免费在线看| 国产中文字幕在线视频| 久久九九亚洲综合| 国产精品mm| 99精品人妻少妇一区二区| 欧美日韩激情视频8区| 午夜日韩成人影院| 国产欧美一区二区三区久久人妖| 激情图区综合网| 在线播放中文字幕| 伊人男人综合视频网| 99久久婷婷| 久久久免费视频网站| 欧美日韩激情一区二区三区| 日韩三级不卡| 一本一道久久a久久综合精品| 一区二区三区丝袜| ww久久综合久中文字幕| 精品国产一二| 1区2区3区国产精品| 在线成人av观看| 国内精品久久久久久久果冻传媒| 久久毛片高清国产| 超碰在线资源| 成人自拍视频网站| 亚洲欧美一区二区视频| 偷拍精品精品一区二区三区| 国产精品日韩高清| 亚洲免费在线看| 色999久久久精品人人澡69 | 日本一本中文字幕| 欧美美女直播网站| 国产一区二区区别| 男人的天堂狠狠干| 亚洲成人三级在线| 亚洲高清二区| 中文在线二区| 秋霞av国产精品一区| 久久一区二区三区四区| 国产精欧美一区二区三区蓝颜男同| 国产精品我不卡| 色综合视频在线观看| 天堂俺去俺来也www久久婷婷| 日本成人在线不卡| 欧美精品一区二区高清在线观看| 女人香蕉久久**毛片精品| 美女视频黄a视频全免费观看| 久久精品亚洲热| 成人午夜激情片| 日韩毛片一区| 屁屁影院ccyy国产第一页| 亚洲国产黄色片| 欧美aaaaa成人免费观看视频| 日韩美女网站| 国产美女精品在线观看| 欧美日韩午夜激情| 91视频一区| 免费在线视频你懂得| 国产欧美日韩中文字幕在线| 亚洲免费av观看| 欧美性生活一级片| v888av成人| 欧洲亚洲妇女av| 一区二区三区资源| 欧美系列电影免费观看| 中文在线中文字幕| 亚洲最大成人在线| 欧美精选一区二区| 开心九九激情九九欧美日韩精美视频电影 | 99国产精品视频免费观看一公开 | 天天影视网天天综合色在线播放| 日韩欧美1区| www.91在线| 神马欧美一区二区| 亚洲欧美国产一本综合首页| 成人在线视频一区二区| 久久精品免视看国产成人| 中文av一区二区三区| 国产精品日韩一区| 在线免费观看日韩欧美| 亚洲一区二区动漫| 亚洲黄色网址| 午夜视频你懂的| 久久久在线观看| 亚洲人被黑人高潮完整版| 神马电影久久| 黄色成人在线| 国产免费裸体视频| 精品国产一区av| 国产精品卡一卡二卡三| 成人免费直播在线| 三级国产在线观看| 91免费观看| 7777精品伊人久久久大香线蕉| 中文国产一区| 三级成人在线| 精品视频一区二区在线| 91国在线精品国内播放| 亚洲乱码国产乱码精品精的特点 | 色偷偷av亚洲男人的天堂| 国产一区二区三区免费看| 综合伊人久久| 超碰在线免费看| www.成人三级视频| 欧美老肥妇做.爰bbww| 国产xxx精品视频大全| 伊人久久大香| 天天操天天摸天天爽| 国产精品27p| 欧美日韩日日骚| 国产精品亚洲成人| 精品日本12videosex| 成年人视频在线观看免费| 午夜一区二区三区| 欧美国产激情18| 777午夜精品免费视频| 国产精品自在欧美一区| 精品五月天堂| 亚洲高清成人影院| 国产精品igao激情视频| 91国内在线视频| 欧美日韩一级二级三级| 国产精品 欧美精品| 精品国产一区二区三区四区| 日韩美女网站|