本指南是 Spring Security 的入門指南,提供對框架設計和基本構建塊的深入了解。我們僅涵蓋應用程序安全的基礎知識。但是,這樣做,我們可以清除使用 Spring Security 的開發人員遇到的一些困惑。為此,我們通過使用過濾器,更一般地,通過使用方法注解,來看看在 Web 應用程序中應用安全性的方式。當您需要深入了解安全應用程序的工作原理、如何對其進行自定義或需要學習如何考慮應用程序安全性時,請使用本指南。
本指南并非旨在作為解決最基本問題的手冊或秘訣(這些問題還有其他來源),但它對初學者和專家都可能有用。Spring Boot 也經常被引用,因為它為安全應用程序提供了一些默認行為,并且了解它如何與整體架構相適應會很有用。
所有原則同樣適用于不使用 Spring Boot 的應用程序。
身份驗證和訪問控制
應用程序安全性歸結為兩個或多或少獨立的問題:身份驗證(你是誰?)和授權(你可以做什么?)。有時人們會說“訪問控制”而不是“授權”,這可能會讓人感到困惑,但這樣想可能會有所幫助,因為“授權”在其他地方被重載了。Spring Security 的架構旨在將身份驗證與授權分開,并為兩者提供策略和擴展點。
驗證
認證的主要策略接口是AuthenticationManager,它只有一個方法:
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
An可以在其方法AuthenticationManager中做三件事之一: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 安全性)。請注意,它
AuthenticationManagerBuilder是@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 方法,那么
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簽名中是完全通用的。它代表用戶可能想要訪問的任何內容(Web 資源或 Java 類中的方法是最常見的兩種情況)。它們也是相當通用的,代表安全的裝飾,帶有一些決定訪問它所需的權限級別的元數據。是一個接口。它只有一個方法(非常通用并返回 a ),因此這些字符串以某種方式編碼了資源所有者的意圖,表達了關于允許誰訪問它的規則。典型的是用戶角色的名稱(如or ),它們通常具有特殊格式(如
AccessDecisionManagerAccessDecisionVoterConfigAttributesObjectConfigAttributeStringConfigAttributeROLE_ADMINROLE_AUDITROLE_前綴)或表示需要評估的表達式。
大多數人使用默認值AccessDecisionManager,即AffirmativeBased(如果任何選民肯定返回,則授予訪問權限)。通過添加新的或修改現有的工作方式,任何定制都傾向于發生在選民身上。
使用 Spring 表達式語言 (SpEL) 表達式非常常見ConfigAttributes——例如,isFullyAuthenticated() && hasRole('user'). 這由AccessDecisionVoter可以處理表達式并為它們創建上下文的 an 支持。要擴展可以處理的表達式范圍,SecurityExpressionRoot有時還需要自定義實現SecurityExpressionHandler.
網絡安全
Web 層中的 Spring Security(用于 UI 和 HTTP 后端)是基于 Servlet 的Filters,所以首先看一下Filters一般的作用是有幫助的。下圖顯示了單個 HTTP 請求的處理程序的典型分層。
客戶端向應用程序發送請求,容器根據請求 URI 的路徑決定應用哪些過濾器和哪個 servlet。最多一個 servlet 可以處理一個請求,但是過濾器形成一個鏈,所以它們是有序的。事實上,如果過濾器想要自己處理請求,它可以否決鏈的其余部分。過濾器還可以修改下游過濾器和 servlet 中使用的請求或響應。過濾器鏈的順序非常重要,Spring Boot 通過兩種機制來管理它:@Beans類型Filter可以有一個@Order或實現Ordered,它們可以是一個FilterRegistrationBean它本身有一個訂單作為其 API 的一部分。一些現成的過濾器定義了自己的常量來幫助表明他們喜歡的相對于彼此的順序(例如,SessionRepositoryFilter來自 Spring Session 有一個DEFAULT_ORDERof Integer.MIN_VALUE + 50,它告訴我們它喜歡在鏈中處于早期,但是它不排除在它之前出現其他過濾器)。
Spring Security 在鏈中作為單個安裝Filter,其具體類型是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將請求分派到匹配的第一個鏈。
沒有自定義安全配置的普通 Spring Boot 應用程序有幾個(稱為 n)過濾器鏈,其中通常 n=6。第一個 (n-1) 個鏈只是為了忽略靜態資源模式,例如/css/**and/images/**和錯誤視圖:/error. (路徑可以由用戶使用security.ignored配置SecurityPropertiesbean 控制。)最后一個鏈匹配包羅萬象的路徑 ( /**) 并且更加活躍,包含身份驗證、授權、異常處理、會話處理、標頭寫入等邏輯上。默認情況下,該鏈中共有 11 個過濾器,但通常用戶無需關心使用哪些過濾器以及何時使用。
容器不知道 Spring Security 內部的所有過濾器這一事實很重要,尤其是在 Spring Boot 應用程序中,默認情況下,所有@Beans類型Filter都自動注冊到容器中。因此,如果您想將自定義過濾器添加到安全鏈中,則需要不將其設為 a@Bean或將其包裝在FilterRegistrationBean明確禁用容器注冊的 a 中。
創建和自定義過濾器鏈
Spring Boot 應用程序(具有請求匹配器的應用程序)中的默認后備過濾器鏈/**具有預定義的
SecurityProperties.BASIC_AUTH_ORDER. 您可以通過設置將其完全關閉security.basic.enabled=false,也可以將其用作后備并以較低的順序定義其他規則。要執行后者,請添加一個(或)@Bean類型并用 裝飾類,如下所示:
WebSecurityConfigurerAdapterWebSecurityConfigurer@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 用于管理端點,您可能希望它們是安全的,并且默認情況下它們是安全的。事實上,只要將執行器添加到安全應用程序,您就會獲得一個僅適用于執行器端點的附加過濾器鏈。它是使用僅匹配執行器端點的請求匹配器定義的,它的順序為
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、等)包裝 。CallableSpring Security 提供了一些幫助程序來簡化此操作,例如 和 的包裝Runnable器Callable。要傳播SecurityContextto@Async方法,您需要提供AsyncConfigurer并確保其Executor類型正確:
@Configuration
public class ApplicationConfiguration extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));
}
}
審核編輯:符乾江
-
代碼
+關注
關注
30文章
4748瀏覽量
68355 -
spring
+關注
關注
0文章
338瀏覽量
14310
發布評論請先 登錄
相關推薦
評論