Spring認證-Spring 安全架構專題教程

TigerJin發表於2021-09-09

本指南是 Spring Security 的入門,提供對框架設計和基本構建塊的深入瞭解。我們只涵蓋應用程式安全的基礎知識。但是,透過這樣做,我們可以消除使用 Spring Security 的開發人員所遇到的一些困惑。為此,我們透過使用過濾器,更一般地說,透過使用方法註釋來檢視在 Web 應用程式中應用安全性的方式。當您需要對安全應用程式的工作原理、如何對其進行自定義,或者需要了解如何考慮應用程式安全性時,請使用本指南。

本指南並非旨在作為解決最基本問題以外的其他問題的手冊或方法(還有其他來源),但它可能對初學者和專家都有用。Spring Boot 也經常被引用,因為它為安全應用程式提供了一些預設行為,並且有助於理解它如何適應整體架構。

筆記所有原則同樣適用於不使用 Spring Boot 的應用程式。身份驗證和訪問控制

應用程式安全歸結為兩個或多或少獨立的問題:身份驗證(你是誰?)和授權(你被允許做什麼?)。有時人們會說“訪問控制”而不是“授權”,這可能會讓人感到困惑,但這樣想是有幫助的,因為“授權”在其他地方超載。Spring Security 的架構旨在將身份驗證與授權分開,併為兩者提供策略和擴充套件點。

驗證

認證的主要策略介面是AuthenticationManager,它只有一種方法:

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

AnAuthenticationManager可以在其authenticate()方法中執行以下三件事之一:

如果它可以驗證輸入代表有效的主體,則返回一個Authentication(通常帶有authenticated=true)。

AuthenticationException如果它認為輸入代表無效的主體,則丟擲一個。

null如果不能決定就返回。

AuthenticationException是執行時異常。它通常由應用程式以通用方式處理,具體取決於應用程式的樣式或目的。換句話說,使用者程式碼通常不會捕獲並處理它。例如,Web UI 可能會呈現一個表明身份驗證失敗的頁面,後端 HTTP 服務可能會傳送 401 響應,WWW-Authenticate根據上下文帶有或不帶有標頭。

最常用的實現AuthenticationManager是ProviderManager,它委託給一個AuthenticationProvider例項鏈。AnAuthenticationProvider有點像 an AuthenticationManager,但它有一個額外的方法來允許呼叫者查詢它是否支援給定Authentication型別:

public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class authentication); }

方法中的Class引數supports()是真的Class(只詢問它是否支援傳遞給authenticate()方法的東西)。AProviderManager可以透過委託給AuthenticationProviders. 如果 aProviderManager無法識別特定的Authentication例項型別,則會跳過它。

AProviderManager有一個可選的父級,如果所有提供者都返回,它可以諮詢null。如果父物件不可用,則null Authentication結果為AuthenticationException.

有時,應用程式具有受保護資源的邏輯組(例如,匹配路徑模式的所有 Web 資源,例如/api/**),並且每個組都可以有自己的專用AuthenticationManager. 通常,這些中的每一個都是一個ProviderManager,並且它們共享一個父級。父級是一種“全域性”資源,充當所有提供者的後備。

圖片描述

圖 1.AuthenticationManager使用的層次結構ProviderManager自定義身份驗證管理器

Spring Security 提供了一些配置助手來快速獲取在您的應用程式中設定的常見身份驗證管理器功能。最常用的幫助器是AuthenticationManagerBuilder,它非常適合設定記憶體、JDBC 或 LDAP 使用者詳細資訊或新增自定義UserDetailsService. 以下示例顯示了配置全域性(父)的應用程式AuthenticationManager:

@Configuration public class ApplicationSecurity extends WebSecurityConfigurerAdapter {    ... // web stuff here   @Autowired   public void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {     builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")       .password("secret").roles("USER");   } }

此示例與 Web 應用程式有關,但 的用法AuthenticationManagerBuilder適用範圍更廣(有關如何實現 Web 應用程式安全性的更多詳細資訊,請參閱Web Security)。請注意,AuthenticationManagerBuilderis@Autowired進入 a 中的方法@Bean - 這就是它構建全域性(父)的原因AuthenticationManager。相反,請考慮以下示例:

@Configuration public class ApplicationSecurity extends WebSecurityConfigurerAdapter {   @Autowired   DataSource dataSource;    ... // web stuff here   @Override   public void configure(AuthenticationManagerBuilder builder) {     builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")       .password("secret").roles("USER");   } }

如果我們@Override在配置器中使用了of a 方法,AuthenticationManagerBuilder則將僅用於構建一個“本地”方法AuthenticationManager,它將是全域性方法的子項。在 Spring Boot 應用程式中,您可以@Autowired將全域性 bean 放入另一個 bean,但除非您自己顯式地公開它,否則不能對本地 bean 執行此操作。

Spring Boot 提供了一個預設的全域性變數AuthenticationManager(只有一個使用者),除非你透過提供你自己的型別 bean 來搶佔它AuthenticationManager。預設值本身就足夠安全,您不必擔心太多,除非您主動需要自定義全域性AuthenticationManager. 如果您執行任何構建AuthenticationManager.

授權或訪問控制

一旦認證成功,我們就可以進行授權,這裡的核心策略是AccessDecisionManager。有框架提供三種實現與所有三個委託鏈的AccessDecisionVoter情況下,有點像ProviderManager與會代表AuthenticationProviders。

AnAccessDecisionVoter考慮一個Authentication(代表一個主體)和一個安全的Object,它已經被裝飾了ConfigAttributes:

boolean supports(ConfigAttribute attribute); boolean supports(Class clazz); int vote(Authentication authentication, S object,         Collection attributes);

該Object是的簽名完全通用的AccessDecisionManager和AccessDecisionVoter。它代表使用者可能想要訪問的任何內容(Web 資源或 Java 類中的方法是兩種最常見的情況)。該ConfigAttributes也相當一般,較安全的裝修Object用一些確定的許可權級別後設資料來訪問它所需。ConfigAttribute是一個介面。它只有一個方法(非常通用並返回 a String),因此這些字串以某種方式編碼資源所有者的意圖,表達允許誰訪問它的規則。典型的ConfigAttribute是使用者角色的名稱(如ROLE_ADMIN或ROLE_AUDIT),它們通常具有特殊格式(如ROLE_ 字首)或表示需要計算的表示式。

大多數人使用預設值AccessDecisionManager,即AffirmativeBased(如果任何選民肯定返回,則授予訪問許可權)。透過新增新的或修改現有的工作方式,任何定製都傾向於在選民中發生。

使用ConfigAttributesSpring 表示式語言 (SpEL) 表示式是很常見的——例如,isFullyAuthenticated() && hasRole('user'). 這由AccessDecisionVoter可以處理表示式併為它們建立上下文的 支援。要擴充套件可以處理的表示式範圍,需要自定義實現 ,SecurityExpressionRoot有時還需要SecurityExpressionHandler.

網路安全

Web 層(用於 UI 和 HTTP 後端)中的 Spring Security 是基於 Servlet 的Filters,所以先看看Filters一般的作用是有幫助的。下圖顯示了單個 HTTP 請求的處理程式的典型分層。

圖片描述

客戶端嚮應用程式傳送請求,容器根據請求 URI 的路徑決定對其應用哪些過濾器和哪個 servlet。最多一個 servlet 可以處理單個請求,但過濾器形成一個鏈,因此它們是有序的。事實上,如果過濾器想要自己處理請求,它可以否決鏈的其餘部分。過濾器還可以修改下游過濾器和 servlet 中使用的請求或響應。過濾器鏈的順序很重要,Spring Boot 透過兩種機制來管理它:@Beans型別Filter可以有一個@Order或實現Ordered,它們可以是一個FilterRegistrationBean它本身有一個訂單作為其 API 的一部分。一些現成的過濾器定義了它們自己的常量來幫助表明它們相對於彼此的順序(例如,SessionRepositoryFilter來自 Spring Session 的 a DEFAULT_ORDERof Integer.MIN_VALUE + 50,它告訴我們它喜歡在鏈的早期,但是它不排除在它之前出現的其他過濾器)。

Spring SecurityFilter在鏈中作為單個安裝,其具體型別為FilterChainProxy,原因我們稍後會介紹。在 Spring Boot 應用程式中,安全過濾器位於@Bean中ApplicationContext,預設情況下會安裝它,以便將其應用於每個請求。它安裝在由 定義的位置SecurityProperties.DEFAULT_FILTER_ORDER,而該位置又由FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER(Spring Boot 應用程式在包裝請求、修改其行為時希望過濾器具有的最大順序)錨定。但是,還有更多:從容器的角度來看,Spring Security 是一個單一的過濾器,但是,在它內部,還有額外的過濾器,每個過濾器都扮演著特殊的角色。下圖顯示了這種關係:

圖片描述

圖 2. Spring Security 是單個物理,Filter但將處理委託給一系列內部過濾器

事實上,安全過濾器中甚至還有一層間接:它通常作為 安裝在容器中DelegatingFilterProxy,它不一定是 Spring @Bean。代理委託給 a FilterChainProxy,它始終是 a @Bean,通常具有固定名稱springSecurityFilterChain。它FilterChainProxy包含所有內部排列為過濾器鏈(或鏈)的安全邏輯。所有過濾器都具有相同的 API(它們都實現了FilterServlet 規範中的介面),並且它們都有機會否決鏈的其餘部分。

可以有多個過濾器鏈,所有過濾器鏈都由同一頂層的 Spring Security 管理,FilterChainProxy並且容器都不知道所有過濾器鏈。Spring Security 過濾器包含一個過濾器鏈列表,並將請求分派到匹配它的第一個鏈。下圖顯示了基於匹配請求路徑(/foo/**匹配之前/**)發生的排程。這是很常見的,但不是匹配請求的唯一方法。這個排程過程最重要的特點是隻有一個鏈處理一個請求。

圖片描述

圖 3. Spring SecurityFilterChainProxy將請求分派到第一個匹配的鏈。

沒有自定義安全配置的 vanilla Spring Boot 應用程式有多個(稱為 n 個)過濾器鏈,其中通常 n=6。第一個 (n-1) 鏈只是為了忽略靜態資源模式,比如/css/**和/images/**,以及錯誤檢視:/error。(路徑可以由使用者security.ignored從SecurityProperties配置 bean 中控制。)最後一個鏈匹配捕獲所有路徑 ( /**) 並且更活躍,包含用於身份驗證、授權、異常處理、會話處理、標題寫入等的邏輯在。預設情況下,該鏈中共有 11 個過濾器,但通常使用者無需關心使用哪些過濾器以及何時使用。

筆記Spring Security 內部的所有過濾器對容器來說都是未知的這一事實很重要,特別是在 Spring Boot 應用程式中,預設情況下,所有@Beans型別都會Filter自動註冊到容器中。因此,如果您想向安全鏈新增自定義過濾器,則需要不將其設為 a@Bean或將其包裝在FilterRegistrationBean顯式禁用容器註冊的 a 中。建立和自定義過濾器鏈

Spring Boot 應用程式(帶有/**請求匹配器的應用程式)中的預設回退過濾器鏈的預定義順序為SecurityProperties.BASIC_AUTH_ORDER. 您可以透過設定將其完全關閉security.basic.enabled=false,或者您可以將其用作後備並以較低的順序定義其他規則。要執行後者,請新增@Bean型別WebSecurityConfigurerAdapter(或WebSecurityConfigurer)並使用 裝飾類@Order,如下所示:

@Configuration @Order(SecurityProperties.BASIC_AUTH_ORDER - 10) public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {   @Override   protected void configure(HttpSecurity http) throws Exception {     http.antMatcher("/match1/**")      ...;   } }

這個 bean 導致 Spring Security 新增一個新的過濾器鏈並在回退之前對其進行排序。

與另一組資源相比,許多應用程式對一組資源具有完全不同的訪問規則。例如,託管 UI 和後備 API 的應用程式可能支援基於 cookie 的身份驗證,重定向到 UI 部分的登入頁面,以及基於令牌的身份驗證,對 API 部分的未經身份驗證的請求發出 401 響應。每組資源都有自己WebSecurityConfigurerAdapter的唯一順序和自己的請求匹配器。如果匹配規則重疊,則最早排序的過濾器鏈獲勝。

請求匹配排程和授權

安全過濾器鏈(或等效的 a WebSecurityConfigurerAdapter)有一個請求匹配器,用於決定是否將其應用於 HTTP 請求。一旦決定應用特定的過濾器鏈,就不會再應用其他過濾器鏈。但是,在過濾器鏈中,您可以透過在HttpSecurity配置器中設定額外的匹配器來對授權進行更細粒度的控制,如下所示:

@Configuration @Order(SecurityProperties.BASIC_AUTH_ORDER - 10) public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {   @Override   protected void configure(HttpSecurity http) throws Exception {     http.antMatcher("/match1/**")       .authorizeRequests()         .antMatchers("/match1/user").hasRole("USER")         .antMatchers("/match1/spam").hasRole("SPAM")         .anyRequest().isAuthenticated();   } }

配置 Spring Security 時最容易犯的錯誤之一是忘記了這些匹配器適用於不同的程式。一個是整個過濾器鏈的請求匹配器,另一個是隻選擇要應用的訪問規則。

將應用程式安全規則與執行器規則相結合

如果您將 Spring Boot Actuator 用於管理端點,您可能希望它們是安全的,並且預設情況下,它們是安全的。事實上,只要您將 Actuator 新增到安全應用程式中,您就會獲得一個僅適用於執行器端點的附加過濾器鏈。它使用僅匹配執行器端點的請求匹配器定義,它的順序為ManagementServerProperties.BASIC_AUTH_ORDER,比預設SecurityProperties回退過濾器少 5 ,因此在回退之前進行諮詢。

如果您希望您的應用程式安全規則應用於執行器端點,您可以新增一個過濾器鏈,該過濾器鏈的順序早於執行器,並且具有包含所有執行器端點的請求匹配器。如果您更喜歡執行器端點的預設安全設定,最簡單的方法是在執行器之後新增您自己的過濾器,但在回退之前(例如,ManagementServerProperties.BASIC_AUTH_ORDER + 1),如下所示:

@Configuration @Order(ManagementServerProperties.BASIC_AUTH_ORDER + 1) public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {   @Override   protected void configure(HttpSecurity http) throws Exception {     http.antMatcher("/foo/**")      ...;   } }

筆記Web 層中的 Spring Security 當前繫結到 Servlet API,因此它僅在 servlet 容器中執行應用程式時才真正適用,無論是嵌入式還是其他方式。但是,它不繫結到 Spring MVC 或 Spring Web 堆疊的其餘部分,因此它可以在任何 servlet 應用程式中使用——例如,一個使用 JAX-RS 的應用程式。方法安全

除了支援保護 Web 應用程式,Spring Security 還支援將訪問規則應用於 Java 方法執行。對於 Spring Security,這只是一種不同型別的“受保護資源”。對於使用者來說,這意味著訪問規則是使用相同格式的ConfigAttribute字串(例如,角色或表示式)宣告的,但在程式碼中的不同位置。第一步是啟用方法安全性——例如,在我們應用程式的頂級配置中:

@SpringBootApplication @EnableGlobalMethodSecurity(securedEnabled = true) public class SampleSecureApplication { }

然後我們可以直接裝飾方法資源:

@Service public class MyService {   @Secured("ROLE_USER")   public String secure() {     return "Hello Security";   } }

此示例是具有安全方法的服務。如果 Spring 建立了@Bean這種型別的 a ,它會被代理並且呼叫者必須在該方法實際執行之前透過一個安全攔截器。如果訪問被拒絕,呼叫者會得到一個AccessDeniedException而不是實際的方法結果。

您還可以在方法上使用其他註釋來強制實施安全約束,特別是@PreAuthorize和@PostAuthorize,它們分別允許您編寫包含對方法引數和返回值的引用的表示式。

提示將 Web 安全性和方法安全性結合起來的情況並不少見。過濾器鏈提供使用者體驗功能,例如身份驗證和重定向到登入頁面等,方法安全提供更細粒度的保護。使用執行緒

Spring Security 基本上是執行緒繫結的,因為它需要使當前經過身份驗證的主體可用於各種下游消費者。基本構建塊是SecurityContext,它可能包含一個Authentication(並且當使用者登入時,它Authentication是顯式的authenticated)。您始終可以SecurityContext透過 中的靜態便捷方法訪問和操作SecurityContextHolder,而後者又操作ThreadLocal. 以下示例顯示了這種安排:

SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); assert(authentication.isAuthenticated);

這是不是對使用者應用程式程式碼來執行這個共同的,但它可以是有用的,如果你,比如,需要寫一個自定義的驗證過濾器(雖然,即使如此,也有Spring Security的基類,您可以使用,讓你可以避免需要使用SecurityContextHolder)。

如果您需要訪問 Web 端點中當前經過身份驗證的使用者,您可以在 a 中使用方法引數@RequestMapping,如下所示:

@RequestMapping("/foo") public String foo(@AuthenticationPrincipal User user) {   ... // do stuff with user }

這個註解拉電流Authentication出來的SecurityContext,並呼叫getPrincipal()它的方法來產生方法的引數。Principalin an的型別Authentication取決於AuthenticationManager用於驗證身份驗證的 ,因此這是獲取對使用者資料的型別安全引用的有用小技巧。

如果正在使用 Spring Security,則PrincipalfromHttpServletRequest是 type Authentication,因此您也可以直接使用它:

@RequestMapping("/foo") public String foo(Principal principal) {   Authentication authentication = (Authentication) principal;   User = (User) authentication.getPrincipal();   ... // do stuff with user }

如果您需要編寫在不使用 Spring Security 時工作的程式碼(您需要對載入Authentication類更加防禦),這有時會很有用。

非同步處理安全方法

由於SecurityContext是執行緒繫結的,如果您想要進行任何呼叫安全方法的後臺處理(例如, with @Async),您需要確保傳播上下文。這歸結為SecurityContext用在後臺執行的任務(Runnable、Callable等)包裝。Spring Security 提供了一些幫助程式來使這更容易,例如包裝器Runnable和Callable. 要傳播SecurityContextto@Async方法,您需要提供一個AsyncConfigurer並確保Executoris 的型別正確:

@Configuration public class ApplicationConfiguration extends AsyncConfigurerSupport {   @Override   public Executor getAsyncExecutor() {     return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));   } }

內容來源:Spring中國教育管理中心


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2144/viewspace-2807001/,如需轉載,請註明出處,否則將追究法律責任。

相關文章