# 春季安全架構
本指南是Spring Security的入門,它提供了對該框架的設計和基本構建塊的見解。 我們僅介紹應用程序安全性的最基礎知識。 但是,這樣做可以消除使用Spring Security的開發人員遇到的一些困惑。 為此,我們看一下通過使用過濾器,更普遍地通過使用方法注釋,將安全性應用到Web應用程序中的方式。 當您需要對安全應用程序的工作原理,如何對其進行自定義或需要學習如何考慮應用程序安全性的高級了解時,請使用本指南。
本指南的目的不是用來解決最基本的問題(還有其他來源)的手冊或食譜,但對于初學者和專家都可能有用。 還經常引用Spring Boot,因為它為安全的應用程序提供了一些默認行為,并且有助于理解它與整個體系結構之間的關系。
筆記所有這些原則同樣適用于不使用Spring Boot的應用程序。
## 身份驗證和訪問控制
應用程序安全性可以歸結為或多或少的兩個獨立問題:身份驗證(您是誰?)和授權(您被允許做什么?)。 有時人們會說“訪問控制”而不是“授權”,這可能會造成混亂,但是以這種方式思考可能會有所幫助,因為“授權”在其他地方很繁重。 Spring Security的體系結構旨在將身份驗證與授權分開,并具有策略和擴展點。
### 驗證
身份驗證的主要策略界面是 `AuthenticationManager`,它只有一種方法:
~~~java
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
~~~
一個 `AuthenticationManager` 可以做三件事之一 `authenticate()` 方法:
* 返回一個 `Authentication` (通常與 `authenticated=true`)是否可以驗證輸入內容代表有效的委托人。
* 扔一個 `AuthenticationException` 如果它認為輸入代表無效的主體。
* 返回 `null` 如果無法決定。
`AuthenticationException`是運行時異常。 它通常由應用程序以通用方式處理,具體取決于應用程序的樣式或目的。 換句話說,通常不希望用戶代碼捕獲并處理它。 例如,Web UI可能會呈現一個頁面,指出認證失敗,而后端HTTP服務則可能會發送401響應(帶有或不帶有驗證碼) `WWW-Authenticate` 標頭取決于上下文。
最常用的實現 `AuthenticationManager` 是 `ProviderManager`,代表一系列 `AuthenticationProvider`實例。 一個 `AuthenticationProvider` 有點像 `AuthenticationManager`,但是它有一個額外的方法,允許調用者查詢它是否支持給定的 `Authentication` 類型:
~~~java
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
~~~
這 `Class<?>` 的論點 `supports()` 方法是真的 `Class<? extends Authentication>` (只會詢問它是否支持傳遞到 `authenticate()`方法)。 一個 `ProviderManager` 通過委托到一個應用程序鏈,可以在同一應用程序中支持多種不同的身份驗證機制 `AuthenticationProviders`。 如果一個 `ProviderManager` 無法識別特定的 `Authentication` 實例類型,將被跳過。
一個 `ProviderManager` 有一個可選的父級,如果所有提供程序都返回,則可以咨詢該父級 `null`。 如果父級不可用,則 `null` `Authentication` 導致 `AuthenticationException`.
有時,應用程序具有邏輯上受保護的資源組(例如,所有與路徑模式匹配的Web資源,例如 `/api/**`),每個組可以有自己專用的 `AuthenticationManager`。 通常,這些都是 `ProviderManager`,并且他們共享一個父母。 因此,父級是一種“全局”資源,充當所有提供者的后備。

圖1. An `AuthenticationManager` 層次結構使用 `ProviderManager`
### 自定義身份驗證管理器
Spring Security提供了一些配置助手,可以快速獲取在應用程序中設置的通用身份驗證管理器功能。 最常用的助手是 `AuthenticationManagerBuilder`,這對于設置內存,JDBC或LDAP用戶詳細信息或添加自定義非常有用 `UserDetailsService`。 以下示例顯示了配置全局(父)的應用程序 `AuthenticationManager`:
~~~java
@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安全](https://spring.io/guides/topicals/spring-security-architecture/#web-security) 有關如何實現Web應用程序安全性的更多詳細信息, 性)。 請注意 `AuthenticationManagerBuilder` 是 `@Autowired` 變成一個方法 `@Bean`\-這就是它構建全球(父)的原因 `AuthenticationManager`。 相比之下,請考慮以下示例:
~~~java
@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` 配置程序中的方法 `AuthenticationManagerBuilder` 僅用于構建“本地” `AuthenticationManager`,這將是全球兒童的子代。 在Spring Boot應用程序中,您可以 `@Autowired` 將全局的一個放到另一個bean中,但是除非您自己顯式公開它,否則您不能對本地的那個使用它。
Spring Boot提供了一個默認的全局 `AuthenticationManager` (只有一個用戶),除非您通過提供自己的type Bean來搶占它 `AuthenticationManager`。 缺省值本身具有足夠的安全性,除非您積極需要自定義全局變量,否則不必擔心太多 `AuthenticationManager`。 如果您進行了任何配置來構建 `AuthenticationManager`,您通常可以在本地對要保護的資源執行此操作,而不必擔心全局默認值。
### 授權或訪問控制
身份驗證成功后,我們可以繼續進行授權,這里的核心策略是 `AccessDecisionManager`。 該框架提供了三種實現,所有這三個實現都委托給一個 `AccessDecisionVoter` 實例,有點像 `ProviderManager` 代表參加 `AuthenticationProviders`.
一個 `AccessDecisionVoter` 認為 `Authentication` (代表委托人)和擔保人 `Object`,其中已裝飾有 `ConfigAttributes`:
~~~java
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
~~~
這 `Object` 是完全通用的簽名 `AccessDecisionManager` 和 `AccessDecisionVoter`。 它代表用戶可能想要訪問的任何內容(Web資源或Java類中的方法是兩種最常見的情況)。 這 `ConfigAttributes` 也很通用,代表安全的裝飾 `Object` 帶有確定訪問它所需權限級別的一些元數據。 `ConfigAttribute`是一個接口。 它只有一個方法(非常通用,并返回一個 `String`),因此這些字符串以某種方式編碼了資源所有者的意圖,表達了有關允許誰訪問資源的規則。 典型的 `ConfigAttribute` 是用戶角色的名稱(例如 `ROLE_ADMIN` 或者 `ROLE_AUDIT`),并且它們通常具有特殊的格式(例如 `ROLE_` 前綴)或表示需要求值的表達式。
大多數人使用默認 `AccessDecisionManager`,這是 `AffirmativeBased`(如果有任何選民肯定地返回,則將授予訪問權限)。 通過添加新的選項或修改現有選項的工作方式,任何定制都傾向于在投票者中發生。
這是很常見的使用 `ConfigAttributes` 是Spring Expression Language(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` 從春季會議有一個 `DEFAULT_ORDER` 的 `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`。 代理人委托給 `FilterChainProxy`,這始終是 `@Bean`,通常使用固定名稱 `springSecurityFilterChain`。 它是 `FilterChainProxy`包含所有安全邏輯,這些安全邏輯在內部排列為一個或多個過濾器鏈。 所有過濾器都具有相同的API(它們都實現了 `Filter` Servlet規范中的接口),它們都有機會否決該鏈的其余部分。
在同一頂層可以有多個由Spring Security管理的過濾器鏈 `FilterChainProxy`容器不知道所有這些。 Spring Security過濾器包含一個過濾器鏈列表,并向與其匹配的第一個鏈調度一個請求。 下圖顯示了基于匹配請求路徑的調度( `/foo/**` 之前有比賽 `/**`)。 這是很常見的,但不是匹配請求的唯一方法。 此調度過程的最重要特征是,只有一個鏈可以處理請求。

圖3. Spring安全性 `FilterChainProxy` 將請求分派到匹配的第一個鏈。
沒有自定義安全配置的普通Spring Boot應用程序具有多個(稱為n)過濾器鏈,其中通常n = 6。 前(n-1)個鏈僅用于忽略靜態資源模式,例如 `/css/**` 和 `/images/**`,以及錯誤視圖: `/error`。 (路徑可以由用戶使用 `security.ignored` 來自 `SecurityProperties` 配置Bean。)最后一個鏈匹配所有路徑( `/**`),并且更加活躍,其中包含用于身份驗證,授權,異常處理,會話處理,標頭寫入等的邏輯。 默認情況下,該鏈中總共有11個過濾器,但通常用戶不必擔心使用哪種過濾器以及何時使用。
筆記容器不知道Spring Security內部的所有過濾器這一事實非常重要,尤其是在Spring Boot應用程序中,默認情況下,在該應用程序中,所有 @Beans 類型的 Filter已自動向容器注冊。 因此,如果您想向安全鏈添加自定義過濾器,則無需將其設為 @Bean 或包裹在一個 FilterRegistrationBean 明確禁用容器注冊。
### 創建和自定義過濾器鏈
Spring Boot應用程序中的默認后備過濾器鏈(帶有 `/**` 請求匹配器)的預定義順序為 `SecurityProperties.BASIC_AUTH_ORDER`。 您可以通過設置將其完全關閉 `security.basic.enabled=false`,也可以將其用作后備并以較低的順序定義其他規則。 要執行后者,請添加一個 `@Bean` 類型的 `WebSecurityConfigurerAdapter` (或者 `WebSecurityConfigurer`)并用 `@Order`, 如下:
~~~java
@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`具有唯一的訂單和自己的請求匹配器。 如果匹配規則重疊,則最早的有序過濾器鏈將獲勝。
### 請求匹配以進行派遣和授權
安全過濾器鏈(或等效地, `WebSecurityConfigurerAdapter`)具有一個請求匹配器,該請求匹配器用于決定是否將其應用于HTTP請求。 一旦決定應用特定的過濾器鏈,就不再應用其他過濾器鏈。 但是,在過濾器鏈中,您可以通過在過濾器鏈中設置其他匹配器來對授權進行更細粒度的控制。 `HttpSecurity` 配置器,如下所示:
~~~java
@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`,比默認值少5 `SecurityProperties` 回退過濾器,因此在回退之前請先咨詢該過濾器。
如果您希望將應用程序安全規則應用于執行器端點,則可以添加一個過濾器鏈,該過濾器鏈的訂購要早于執行器一,并且其請求匹配器包括所有執行器端點。 如果您希望執行器端點使用默認的安全性設置,最簡單的方法是在執行器端點之后,但在回退時刻之前添加自己的過濾器(例如, `ManagementServerProperties.BASIC_AUTH_ORDER + 1`), 如下:
~~~java
@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的servlet應用程序。
## 方法安全性
除了支持Web應用程序安全之外,Spring Security還提供了將訪問規則應用于Java方法執行的支持。 對于Spring Security,這只是另一種類型的“受保護資源”。 對于用戶,這意味著使用相同的格式聲明訪問規則 `ConfigAttribute`字符串(例如,角色或表達式),但在代碼中的其他位置。 第一步是啟用方法安全性-例如,在我們應用程序的頂級配置中:
~~~java
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SampleSecureApplication {
}
~~~
然后,我們可以直接裝飾方法資源:
~~~java
@Service
public class MyService {
@Secured("ROLE_USER")
public String secure() {
return "Hello Security";
}
}
~~~
此示例是具有安全方法的服務。 如果Spring創建一個 `@Bean`這種類型的代理將被代理,并且在方法實際執行之前,調用者必須經過安全攔截器。 如果訪問被拒絕,則呼叫者會得到一個 `AccessDeniedException` 而不是實際的方法結果。
您還可以在方法上使用其他注釋來強制執行安全性約束,特別是 `@PreAuthorize` 和 `@PostAuthorize`,可讓您編寫分別包含對方法參數和返回值的引用的表達式。
提示結合使用Web安全性和方法安全性并不少見。 篩選器鏈提供了用戶體驗功能,例如身份驗證和重定向到登錄頁面等,并且方法安全性在更精細的級別上提供了保護。
## 使用線程
Spring Security從根本上講是線程綁定的,因為它需要使當前經過身份驗證的主體可供各種下游使用者使用。 基本的構建塊是 `SecurityContext`,其中可能包含一個 `Authentication` (當用戶登錄時, `Authentication` 那是明確的 `authenticated`)。 您可以隨時訪問和操作 `SecurityContext` 通過靜態便利方法 `SecurityContextHolder`,進而操縱 `ThreadLocal`。 以下示例顯示了這種安排:
~~~java
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
assert(authentication.isAuthenticated);
~~~
這是 **不是** 對用戶應用程序代碼來執行這個共同的,但它可以是有用的,如果你,比如,需要寫一個自定義的驗證過濾器(雖然,即使如此,也有Spring Security的基類,您可以使用,讓你可以避免使用 `SecurityContextHolder`).
如果您需要訪問Web端點中當前經過身份驗證的用戶,則可以在 `@RequestMapping`, 如下:
~~~java
@RequestMapping("/foo")
public String foo(@AuthenticationPrincipal User user) {
... // do stuff with user
}
~~~
該注釋拉動當前 `Authentication` 出于 `SecurityContext` 并致電 `getPrincipal()`在其上生成method參數的method。 的類型 `Principal` 在一個 `Authentication` 取決于 `AuthenticationManager` 用于驗證身份驗證,因此這對于獲得對用戶數據的類型安全的引用可能是一個有用的小技巧。
如果使用Spring Security,則 `Principal` 來自 `HttpServletRequest` 是類型 `Authentication`,因此您也可以直接使用它:
~~~java
@RequestMapping("/foo")
public String foo(Principal principal) {
Authentication authentication = (Authentication) principal;
User = (User) authentication.getPrincipal();
... // do stuff with user
}
~~~
如果您需要編寫在不使用Spring Security時可以工作的代碼,這有時會很有用(您需要在加載代碼時更加防御 `Authentication` 班級)。
### 異步處理安全方法
自從 `SecurityContext` 如果您要進行任何調用安全方法的后臺處理(例如,使用 `@Async`),則需要確保傳播上下文。 這歸結為包裝 `SecurityContext` 與任務( `Runnable`, `Callable`,依此類推)在后臺執行。 Spring Security提供了一些幫助程序來簡化此工作,例如包裝 `Runnable` 和 `Callable`。 傳播 `SecurityContext` 到 `@Async` 方法,您需要提供一個 `AsyncConfigurer` 并確保 `Executor` 是正確的類型:
~~~java
@Configuration
public class ApplicationConfiguration extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));
}
}
~~~
- springboot概述
- springboot構建restful服務
- spring構建一個RESTful Web服務
- spring定時任務
- 消費RESTful Web服務
- gradle構建項目
- maven構建項目
- springboot使用jdbc
- springboot應用上傳文件
- 使用LDNA驗證用戶
- 使用 spring data redis
- 使用 spring RabbitTemplate消息隊列
- 用no4j訪問nosql數據庫
- springboot驗證web表單
- Spring Boot Actuator構j建服務
- 使用jms傳遞消息
- springboot創建批處理服務
- spring security保護web 安全
- 在Pivotal GemFire中訪問數據
- 使用Spring Integration
- 使用springboot jpa進行數據庫操作
- 數據庫事務操作
- 操作mongodb
- springmvc+tymleaf創建web應用
- 將Spring Boot JAR應用程序轉換為WAR
- 創建異步服務
- spring提交表單
- 使用WebSocket構建交互式Web應用程序
- 使用REST訪問Neo4j數據
- jquery消費restful
- springboot跨域請求
- 消費SOAP Web服務
- springboot使用緩存
- 使用Vaadin創建CRUD UI
- 使用REST訪問JPA數據
- 使用REST訪問Pivotal GemFire中的數據
- 構建soap服務
- 使用rest訪問mongodb數據
- 構建springboot應用docker鏡像
- 從STS部署到Cloud Foundry
- springboot測試web應用
- springboot訪問mysql
- springboot編寫自定義模塊并使用
- 使用Google Cloud Pub / Sub進行消息傳遞
- 構建反應式RESTful Web服務
- 使用Redis主動訪問數據
- Spring Boot 部署到Kubernetes
- 使用反應式協議R2DBC訪問數據
- Spring Security架構
- spring構建Docker鏡像詳解
- Spring Boot和OAuth2
- springboot應用部署到k8s
- spring構建rest服務詳解