本指南是 Spring Security 的入門,,提供對框架設(shè)計和基本構(gòu)建塊的深入了解。我們只涵蓋應(yīng)用程序安全的基礎(chǔ)知識,。但是,,通過這樣做,,我們可以消除使用 Spring Security 的開發(fā)人員所遇到的一些困惑。為此,,我們通過使用過濾器,更一般地說,,通過使用方法注釋來查看在 Web 應(yīng)用程序中應(yīng)用安全性的方式,。當(dāng)您需要對安全應(yīng)用程序的工作原理、如何對其進(jìn)行自定義,,或者需要了解如何考慮應(yīng)用程序安全性時,,請使用本指南。 本指南并非旨在作為解決最基本問題以外的其他問題的手冊或方法(還有其他來源),,但它可能對初學(xué)者和專家都有用,。Spring Boot 也經(jīng)常被引用,因?yàn)樗鼮榘踩珣?yīng)用程序提供了一些默認(rèn)行為,,并且有助于理解它如何適應(yīng)整體架構(gòu),。 筆記所有原則同樣適用于不使用 Spring Boot 的應(yīng)用程序。身份驗(yàn)證和訪問控制 應(yīng)用程序安全歸結(jié)為兩個或多或少獨(dú)立的問題:身份驗(yàn)證(你是誰,?)和授權(quán)(你被允許做什么,?)。有時人們會說“訪問控制”而不是“授權(quán)”,,這可能會讓人感到困惑,,但這樣想是有幫助的,因?yàn)椤笆跈?quán)”在其他地方超載,。Spring Security 的架構(gòu)旨在將身份驗(yàn)證與授權(quán)分開,,并為兩者提供策略和擴(kuò)展點(diǎn)。 驗(yàn)證 認(rèn)證的主要策略接口是AuthenticationManager,,它只有一種方法: public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; } AnAuthenticationManager可以在其authenticate()方法中執(zhí)行以下三件事之一: 如果它可以驗(yàn)證輸入代表有效的主體,,則返回一個Authentication(通常帶有authenticated=true)。 AuthenticationException如果它認(rèn)為輸入代表無效的主體,,則拋出一個,。 null如果不能決定就返回。 AuthenticationException是運(yùn)行時異常,。它通常由應(yīng)用程序以通用方式處理,,具體取決于應(yīng)用程序的樣式或目的。換句話說,,用戶代碼通常不會捕獲并處理它,。例如,Web UI 可能會呈現(xiàn)一個表明身份驗(yàn)證失敗的頁面,,后端 HTTP 服務(wù)可能會發(fā)送 401 響應(yīng),,WWW-Authenticate根據(jù)上下文帶有或不帶有標(biāo)頭,。 最常用的實(shí)現(xiàn)AuthenticationManager是ProviderManager,它委托給一個AuthenticationProvider實(shí)例鏈,。AnAuthenticationProvider有點(diǎn)像 an AuthenticationManager,,但它有一個額外的方法來允許調(diào)用者查詢它是否支持給定Authentication類型: public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class authentication); } 方法中的Class參數(shù)supports()是真的Class(只詢問它是否支持傳遞給authenticate()方法的東西)。AProviderManager可以通過委托給AuthenticationProviders. 如果 aProviderManager無法識別特定的Authentication實(shí)例類型,,則會跳過它,。 AProviderManager有一個可選的父級,如果所有提供者都返回,,它可以咨詢null,。如果父對象不可用,則null Authentication結(jié)果為AuthenticationException. 有時,,應(yīng)用程序具有受保護(hù)資源的邏輯組(例如,,匹配路徑模式的所有 Web 資源,例如/api/**),,并且每個組都可以有自己的專用AuthenticationManager. 通常,,這些中的每一個都是一個ProviderManager,并且它們共享一個父級,。父級是一種“全局”資源,,充當(dāng)所有提供者的后備。 圖 1.AuthenticationManager使用的層次結(jié)構(gòu)ProviderManager自定義身份驗(yàn)證管理器 Spring Security 提供了一些配置助手來快速獲取在您的應(yīng)用程序中設(shè)置的常見身份驗(yàn)證管理器功能,。最常用的幫助器是AuthenticationManagerBuilder,,它非常適合設(shè)置內(nèi)存、JDBC 或 LDAP 用戶詳細(xì)信息或添加自定義UserDetailsService. 以下示例顯示了配置全局(父)的應(yīng)用程序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 應(yīng)用程序有關(guān),,但 的用法AuthenticationManagerBuilder適用范圍更廣(有關(guān)如何實(shí)現(xiàn) Web 應(yīng)用程序安全性的更多詳細(xì)信息,,請參閱Web Security)。請注意,,AuthenticationManagerBuilderis@Autowired進(jìn)入 a 中的方法@Bean?- 這就是它構(gòu)建全局(父)的原因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則將僅用于構(gòu)建一個“本地”方法AuthenticationManager,,它將是全局方法的子項。在 Spring Boot 應(yīng)用程序中,,您可以@Autowired將全局 bean 放入另一個 bean,,但除非您自己顯式地公開它,否則不能對本地 bean 執(zhí)行此操作,。 Spring Boot 提供了一個默認(rèn)的全局變量AuthenticationManager(只有一個用戶),,除非你通過提供你自己的類型 bean 來搶占它AuthenticationManager。默認(rèn)值本身就足夠安全,,您不必?fù)?dān)心太多,,除非您主動需要自定義全局AuthenticationManager. 如果您執(zhí)行任何構(gòu)建AuthenticationManager. 授權(quán)或訪問控制 一旦認(rèn)證成功,,我們就可以進(jìn)行授權(quán),這里的核心策略是AccessDecisionManager,。有框架提供三種實(shí)現(xiàn)與所有三個委托鏈的AccessDecisionVoter情況下,,有點(diǎn)像ProviderManager與會代表AuthenticationProviders。 AnAccessDecisionVoter考慮一個Authentication(代表一個主體)和一個安全的Object,,它已經(jīng)被裝飾了ConfigAttributes: boolean supports(ConfigAttribute attribute); boolean supports(Class clazz); int vote(Authentication authentication, S object, Collection attributes); 該Object是的簽名完全通用的AccessDecisionManager和AccessDecisionVoter,。它代表用戶可能想要訪問的任何內(nèi)容(Web 資源或 Java 類中的方法是兩種最常見的情況)。該ConfigAttributes也相當(dāng)一般,,較安全的裝修Object用一些確定的權(quán)限級別元數(shù)據(jù)來訪問它所需。ConfigAttribute是一個接口,。它只有一個方法(非常通用并返回 a String),,因此這些字符串以某種方式編碼資源所有者的意圖,表達(dá)允許誰訪問它的規(guī)則,。典型的ConfigAttribute是用戶角色的名稱(如ROLE_ADMIN或ROLE_AUDIT),,它們通常具有特殊格式(如ROLE_ 前綴)或表示需要計算的表達(dá)式。 大多數(shù)人使用默認(rèn)值A(chǔ)ccessDecisionManager,,即AffirmativeBased(如果任何選民肯定返回,,則授予訪問權(quán)限)。通過添加新的或修改現(xiàn)有的工作方式,,任何定制都傾向于在選民中發(fā)生,。 使用ConfigAttributesSpring 表達(dá)式語言 (SpEL) 表達(dá)式是很常見的——例如,isFullyAuthenticated() && hasRole('user'). 這由AccessDecisionVoter可以處理表達(dá)式并為它們創(chuàng)建上下文的 支持,。要擴(kuò)展可以處理的表達(dá)式范圍,,需要自定義實(shí)現(xiàn) ,SecurityExpressionRoot有時還需要SecurityExpressionHandler. 網(wǎng)絡(luò)安全 Web 層(用于 UI 和 HTTP 后端)中的 Spring Security 是基于 Servlet 的Filters,,所以先看看Filters一般的作用是有幫助的,。下圖顯示了單個 HTTP 請求的處理程序的典型分層。 客戶端向應(yīng)用程序發(fā)送請求,,容器根據(jù)請求 URI 的路徑?jīng)Q定對其應(yīng)用哪些過濾器和哪個 servlet,。最多一個 servlet 可以處理單個請求,但過濾器形成一個鏈,,因此它們是有序的,。事實(shí)上,如果過濾器想要自己處理請求,,它可以否決鏈的其余部分,。過濾器還可以修改下游過濾器和 servlet 中使用的請求或響應(yīng)。過濾器鏈的順序很重要,,Spring Boot 通過兩種機(jī)制來管理它:@Beans類型Filter可以有一個@Order或?qū)崿F(xiàn)Ordered,,它們可以是一個FilterRegistrationBean它本身有一個訂單作為其 API 的一部分,。一些現(xiàn)成的過濾器定義了它們自己的常量來幫助表明它們相對于彼此的順序(例如,SessionRepositoryFilter來自 Spring Session 的 a DEFAULT_ORDERof Integer.MIN_VALUE + 50,,它告訴我們它喜歡在鏈的早期,,但是它不排除在它之前出現(xiàn)的其他過濾器)。 Spring SecurityFilter在鏈中作為單個安裝,,其具體類型為FilterChainProxy,,原因我們稍后會介紹。在 Spring Boot 應(yīng)用程序中,,安全過濾器位于@Bean中ApplicationContext,,默認(rèn)情況下會安裝它,以便將其應(yīng)用于每個請求,。它安裝在由 定義的位置SecurityProperties.DEFAULT_FILTER_ORDER,,而該位置又由FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER(Spring Boot 應(yīng)用程序在包裝請求、修改其行為時希望過濾器具有的最大順序)錨定,。但是,,還有更多:從容器的角度來看,Spring Security 是一個單一的過濾器,,但是,,在它內(nèi)部,還有額外的過濾器,,每個過濾器都扮演著特殊的角色,。下圖顯示了這種關(guān)系: 圖 2. Spring Security 是單個物理,F(xiàn)ilter但將處理委托給一系列內(nèi)部過濾器 事實(shí)上,,安全過濾器中甚至還有一層間接:它通常作為 安裝在容器中DelegatingFilterProxy,,它不一定是 Spring @Bean。代理委托給 a FilterChainProxy,,它始終是 a @Bean,,通常具有固定名稱springSecurityFilterChain。它FilterChainProxy包含所有內(nèi)部排列為過濾器鏈(或鏈)的安全邏輯,。所有過濾器都具有相同的 API(它們都實(shí)現(xiàn)了FilterServlet 規(guī)范中的接口),,并且它們都有機(jī)會否決鏈的其余部分。 可以有多個過濾器鏈,,所有過濾器鏈都由同一頂層的 Spring Security 管理,,F(xiàn)ilterChainProxy并且容器都不知道所有過濾器鏈。Spring Security 過濾器包含一個過濾器鏈列表,,并將請求分派到匹配它的第一個鏈,。下圖顯示了基于匹配請求路徑(/foo/**匹配之前/**)發(fā)生的調(diào)度。這是很常見的,,但不是匹配請求的唯一方法,。這個調(diào)度過程最重要的特點(diǎn)是只有一個鏈處理一個請求,。 圖 3. Spring SecurityFilterChainProxy將請求分派到第一個匹配的鏈。 沒有自定義安全配置的 vanilla Spring Boot 應(yīng)用程序有多個(稱為 n 個)過濾器鏈,,其中通常 n=6,。第一個 (n-1) 鏈只是為了忽略靜態(tài)資源模式,比如/css/**和/images/**,,以及錯誤視圖:/error,。(路徑可以由用戶security.ignored從SecurityProperties配置 bean 中控制。)最后一個鏈匹配捕獲所有路徑 ( /**) 并且更活躍,,包含用于身份驗(yàn)證,、授權(quán)、異常處理,、會話處理,、標(biāo)題寫入等的邏輯在。默認(rèn)情況下,,該鏈中共有 11 個過濾器,但通常用戶無需關(guān)心使用哪些過濾器以及何時使用,。 筆記Spring Security 內(nèi)部的所有過濾器對容器來說都是未知的這一事實(shí)很重要,,特別是在 Spring Boot 應(yīng)用程序中,默認(rèn)情況下,,所有@Beans類型都會Filter自動注冊到容器中,。因此,如果您想向安全鏈添加自定義過濾器,,則需要不將其設(shè)為 a@Bean或?qū)⑵浒b在FilterRegistrationBean顯式禁用容器注冊的 a 中,。創(chuàng)建和自定義過濾器鏈 Spring Boot 應(yīng)用程序(帶有/**請求匹配器的應(yīng)用程序)中的默認(rèn)回退過濾器鏈的預(yù)定義順序?yàn)镾ecurityProperties.BASIC_AUTH_ORDER. 您可以通過設(shè)置將其完全關(guān)閉security.basic.enabled=false,或者您可以將其用作后備并以較低的順序定義其他規(guī)則,。要執(zhí)行后者,,請?zhí)砑覢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 導(dǎo)致 Spring Security 添加一個新的過濾器鏈并在回退之前對其進(jìn)行排序,。 與另一組資源相比,,許多應(yīng)用程序?qū)σ唤M資源具有完全不同的訪問規(guī)則。例如,,托管 UI 和后備 API 的應(yīng)用程序可能支持基于 cookie 的身份驗(yàn)證,,重定向到 UI 部分的登錄頁面,以及基于令牌的身份驗(yàn)證,,對 API 部分的未經(jīng)身份驗(yàn)證的請求發(fā)出 401 響應(yīng),。每組資源都有自己WebSecurityConfigurerAdapter的唯一順序和自己的請求匹配器。如果匹配規(guī)則重疊,,則最早排序的過濾器鏈獲勝,。 請求匹配調(diào)度和授權(quán) 安全過濾器鏈(或等效的 a WebSecurityConfigurerAdapter)有一個請求匹配器,,用于決定是否將其應(yīng)用于 HTTP 請求。一旦決定應(yīng)用特定的過濾器鏈,,就不會再應(yīng)用其他過濾器鏈,。但是,在過濾器鏈中,,您可以通過在HttpSecurity配置器中設(shè)置額外的匹配器來對授權(quán)進(jìn)行更細(xì)粒度的控制,,如下所示: @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 時最容易犯的錯誤之一是忘記了這些匹配器適用于不同的進(jìn)程。一個是整個過濾器鏈的請求匹配器,,另一個是只選擇要應(yīng)用的訪問規(guī)則,。 將應(yīng)用程序安全規(guī)則與執(zhí)行器規(guī)則相結(jié)合 如果您將 Spring Boot Actuator 用于管理端點(diǎn),您可能希望它們是安全的,,并且默認(rèn)情況下,,它們是安全的。事實(shí)上,,只要您將 Actuator 添加到安全應(yīng)用程序中,,您就會獲得一個僅適用于執(zhí)行器端點(diǎn)的附加過濾器鏈。它使用僅匹配執(zhí)行器端點(diǎn)的請求匹配器定義,,它的順序?yàn)镸anagementServerProperties.BASIC_AUTH_ORDER,,比默認(rèn)SecurityProperties回退過濾器少 5 ,因此在回退之前進(jìn)行咨詢,。 如果您希望您的應(yīng)用程序安全規(guī)則應(yīng)用于執(zhí)行器端點(diǎn),,您可以添加一個過濾器鏈,該過濾器鏈的順序早于執(zhí)行器,,并且具有包含所有執(zhí)行器端點(diǎn)的請求匹配器,。如果您更喜歡執(zhí)行器端點(diǎn)的默認(rèn)安全設(shè)置,最簡單的方法是在執(zhí)行器之后添加您自己的過濾器,,但在回退之前(例如,,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 當(dāng)前綁定到 Servlet API,,因此它僅在 servlet 容器中運(yùn)行應(yīng)用程序時才真正適用,,無論是嵌入式還是其他方式。但是,,它不綁定到 Spring MVC 或 Spring Web 堆棧的其余部分,,因此它可以在任何 servlet 應(yīng)用程序中使用——例如,一個使用 JAX-RS 的應(yīng)用程序,。方法安全 除了支持保護(hù) Web 應(yīng)用程序,,Spring Security 還支持將訪問規(guī)則應(yīng)用于 Java 方法執(zhí)行。對于 Spring Security,這只是一種不同類型的“受保護(hù)資源”,。對于用戶來說,,這意味著訪問規(guī)則是使用相同格式的ConfigAttribute字符串(例如,角色或表達(dá)式)聲明的,,但在代碼中的不同位置,。第一步是啟用方法安全性——例如,在我們應(yīng)用程序的頂級配置中: @SpringBootApplication @EnableGlobalMethodSecurity(securedEnabled = true) public class SampleSecureApplication { } 然后我們可以直接裝飾方法資源: @Service public class MyService { @Secured("ROLE_USER") public String secure() { return "Hello Security"; } } 此示例是具有安全方法的服務(wù),。如果 Spring 創(chuàng)建了@Bean這種類型的 a ,,它會被代理并且調(diào)用者必須在該方法實(shí)際執(zhí)行之前通過一個安全攔截器。如果訪問被拒絕,,調(diào)用者會得到一個AccessDeniedException而不是實(shí)際的方法結(jié)果,。 您還可以在方法上使用其他注釋來強(qiáng)制實(shí)施安全約束,特別是@PreAuthorize和@PostAuthorize,,它們分別允許您編寫包含對方法參數(shù)和返回值的引用的表達(dá)式,。 提示將 Web 安全性和方法安全性結(jié)合起來的情況并不少見。過濾器鏈提供用戶體驗(yàn)功能,,例如身份驗(yàn)證和重定向到登錄頁面等,,方法安全提供更細(xì)粒度的保護(hù)。使用線程 Spring Security 基本上是線程綁定的,,因?yàn)樗枰巩?dāng)前經(jīng)過身份驗(yàn)證的主體可用于各種下游消費(fèi)者,。基本構(gòu)建塊是SecurityContext,,它可能包含一個Authentication(并且當(dāng)用戶登錄時,它Authentication是顯式的authenticated),。您始終可以SecurityContext通過 中的靜態(tài)便捷方法訪問和操作SecurityContextHolder,,而后者又操作ThreadLocal. 以下示例顯示了這種安排: SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); assert(authentication.isAuthenticated); 這是不是對用戶應(yīng)用程序代碼來執(zhí)行這個共同的,但它可以是有用的,,如果你,,比如,需要寫一個自定義的驗(yàn)證過濾器(雖然,,即使如此,,也有Spring Security的基類,您可以使用,,讓你可以避免需要使用SecurityContextHolder),。 如果您需要訪問 Web 端點(diǎn)中當(dāng)前經(jīng)過身份驗(yàn)證的用戶,您可以在 a 中使用方法參數(shù)@RequestMapping,,如下所示: @RequestMapping("/foo") public String foo(@AuthenticationPrincipal User user) { ... // do stuff with user } 這個注解拉電流Authentication出來的SecurityContext,,并調(diào)用getPrincipal()它的方法來產(chǎn)生方法的參數(shù)。Principalin an的類型Authentication取決于AuthenticationManager用于驗(yàn)證身份驗(yàn)證的 ,因此這是獲取對用戶數(shù)據(jù)的類型安全引用的有用小技巧,。 如果正在使用 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是線程綁定的,如果您想要進(jìn)行任何調(diào)用安全方法的后臺處理(例如,, with @Async),,您需要確保傳播上下文。這歸結(jié)為SecurityContext用在后臺執(zhí)行的任務(wù)(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)); } } 內(nèi)容來源:Spring中國教育管理中心 |
|