Spring Security學習(翻譯官方文檔)

本文主要翻譯Spring的官方文檔,主要介紹SS的基本概念和基本接口和原理。java

Spring Security中的兩個核心的概念:Authentication(驗證);Authorization(受權); Spring Security能夠用來實現諸如,OAuth、OpenID、LDAP、CAS、JAAS等系統間的認證很受權。如咱們可使用Spring Security配合OAuth實現單點登陸。web

Authentication驗證

Authentication主要的接口是AuthenticationManagerspring

public interface AuthenticationManager {
  Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
複製代碼

authenticate()方法調用有三種狀況:安全

  1. 驗證經過,正常返回一個Authentication實例
  2. 驗證不經過,拋出AuthenticationException
  3. 沒法決定,返回null 最經常使用的AuthenticationManager接口實現類是ProviderManager,ProviderManager內部將驗證委託給一系列AuthenticationProvider。AuthenticationProvider接口有點像AuthenticationManager接口,可是他有一個額外的方法,容許調用者來判斷是否支持驗證時給定的Authentication實例類型,該方法就是下面的supports方法。
public interface AuthenticationProvider {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
	boolean supports(Class<?> authentication);
}
複製代碼

ProviderManager將驗證委託給一系列AuthenticationProvider,實際是一個AuthenticationProvider列表,因此他能夠在一個應用中支持多種驗證策略。在驗證的時候會遍歷全部的AuthenticationProvider逐個驗證,調用supports方法判斷是否驗證該類型的Authentication,是則驗證不然跳過。若是全部的AuthenticationProvider都沒法決定返回了null,那就調用父級的AuthenticationManager驗證(若是父級不爲空)。 有時在應用中有驗證的邏輯分組,每一個分組有本身的AuthenticationManager(一般就是ProviderManager),這些分組共享一個父級AuthenticationManager。 網絡

自定義AuthenticationManager

SpringSecurity提供了一些配置幫助類來快去獲取AuthenticationManager的功能,最經常使用的就是AuthenticationManagerBuilder,它能夠以in-memory、JDBC、LDAP方式配置user details,或者添加自定義UserDetailService。eg.app

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

??? spring官方給的代碼實例有問題吧,configure方法沒有返回類型框架

Authorization受權

Authorization受權的主要接口是AccessDecisionManager,框架提供了3個實現類:UnanimousBased(所有經過)、ConsensusBased(少數服從多數)、AffirmativeBased(無人反對則經過),全部這些實現類都是把受權委託給一系列的AccessDecisionVoter,這有點像ProviderManager樣驗證委託給AuthenticationProvider。此處AccessDecisionVoter就是抽象的投票人。ide

public interface AccessDecisionVoter<S> {
    boolean supports(ConfigAttribute attribute);    
    boolean supports(Class<?> clazz);
    int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
}
複製代碼

一個AccessDecisionVoter會考慮驗證主體Authentication和一個被ConfigAttributes修飾的安全對象Object。 Object在AccessDecisionManager和AccessDecisionVoter的簽名中是徹底通用的,Object表明一切用戶可能要訪問的東西(一般狀況是網絡資源或者Java類的方法調用這兩種狀況)。ConfigAttributes也是通用的,用一些元數據來表示對安全對象的修飾,這些元數據決定訪問安全對象所須要的權限級別。ConfigAttribute是一個接口,它只有一個很是通用的方法,並返回String,所以這些字符串以某種方式編碼了資源全部者的意圖,表達關於誰被容許訪問安全對象的規則。一個典型的ConfigAttribute就是用戶的角色名(如 ROLE_ADMIN 或者 ROLE_AUDIT),而且它們一般會有特殊的格式(如 ROLE_ 前綴)或者表示須要評估的表達式。 大部分人只是用AccessDecisionManager的默認實現類AffirmativeBased(無人反對則容許經過),任何定製都會發生在投票者身上,要麼添加新的,要麼修改現有的工做方式。 使用EL表達式的ConfigAttribute是很是常見的,例如 isFullyAuthenticated() && hasRole('FOO'), 這是有一個能夠處理表達式併爲他們建立上下文的AccessDecisionVoter支持的。要擴展可處理的表達式的範圍,須要自定義SecurityExpressionRoot來實現,有時還須要SecurityExpressionHandler。ui

Web Security

Spring Security在web層的使用是基於Servlet過濾器的,下圖是單個HTTP請求處理程序的典型分層。 編碼

當客戶端嚮應用發送一個請求,應用容器會根據請求的URI路徑來以爲應用那個過濾器和Servlet。最多隻能有一個Servlet處理請求,可是過濾器是鏈式的,因此過濾器處理是有順序的,實際上一個過濾器若是想要本身處理請求能夠斷開過濾鏈。過濾器也能夠修改下流過濾器使用的Request和Response。過濾器鏈的順序很是重要,Spring boot爲其提供了2中機制:第一個是使用@Order註解或着實現Ordered接口,另外一個是做爲FilterRegistrationBean的一部分,FilterRegistrationBean自己的API是有順序的。一些現成的過濾器經過定義常數,來表示他們系統彼此之間的相對順序(例如,SessionRepositoryFilter中的常數DEFAULT_ORDER = Integer.MIN_VALUE + 50,這告訴咱們它喜歡在鏈的早期,可是它不排除其餘過濾器在它以前)。 Spring Security是過濾器鏈上一個節點做爲Spring Security接入的入口,該過濾器就是FilterChainProxy,以下圖:
Spring Security在請求處理過濾器鏈中插入了一個過濾器節點FilterChainProxy,看名字就知道是個代理過濾器鏈,它過處理過程委託給內部的過濾器(這裏就進入Spring Security的內部,下文稱安全過濾器)。事實上,在安全過濾器中甚至還有一層DelegatingFilterProxy,它一般在標準顧慮器和安全過濾器之間作代理,它將委託給FilterChainProxy,它沒有必要必須是Spring的bean,而FilterChainProxy是Spring的bean,也就是說通常FilterChainProxy的生命週期交給Spring容器來管理。 FilterChainProxy中包含了一個過濾器鏈列表,會將請求分發到第一個匹配的過濾器鏈中去,下圖顯示了基於匹配請求路徑的調度,這很常見,但不是匹配請求的惟一方式。這個調度過程最重要的特徵是隻有一個鏈處理一個請求。

建立自定義過濾器鏈

Spring Boot應用中的默認的回退過濾器鏈(匹配/**)預先定義了順序(SecurityProperties.BASIC_AUTH_ORDER),你能夠經過security.basic.enabled=false來關閉它,或者你可使用它做爲回退過濾器你只定義較低順序的匹配其餘規則的過濾器。爲此,只須要添加一個WebSecurityConfigurerAdapter(或者WebSecurityConfigurer)類型的@Bean,而且使用@Order註解修飾這個Class。例如:

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

調度和受權的請求匹配

安全過濾器鏈(或等同於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("/foo/**")
        .authorizeRequests()
        .antMatchers("/foo/bar").hasRole("BAR")
        .antMatchers("/foo/spam").hasRole("SPAM")
       .anyRequest().isAuthenticated();
  }
}
複製代碼

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

將應用安全規則與執行器規則相結合 涉及到Spring Boot Actuator內容,暫且略過

方法安全

除了支持web應用,Spring Security還支持將訪問規則應用到Java方法執行。對於Sping Security來講只是不通的類型的資源保護。對於用戶來講,這意味着使用相同格式的ConfigAttribute字符串(例如角色或表達式)聲明訪問規則,可是在代碼中的不一樣位置。第一步是讓方法安全生效,例如:

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SampleSecureApplication {
}
複製代碼

而後咱們能夠直接修飾方法資源,例如:

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

此示例是一個具備安全方法的服務。若是Spring建立了這種類型的@Bean,那麼它將被代理,調用方將必須在方法實際執行以前經過安全攔截器。若是訪問被拒絕,調用方將得到AccessDeniedException異常,而不是實際的方法結果。 還有其餘註釋能夠用於強制安全約束的方法,特別是@PreAuthorize和@PostAuthorize,它們容許您分別編寫包含對方法參數和返回值的引用的表達式。

Working with Threads

Spring Security從根本上說是線程綁定的,由於它須要使當前通過身份驗證的主體對各類各樣的下游消費者可用,基本的構建模塊是SecurityContext,它可能包含Authentication(當用戶登陸時,它將是一個驗證經過的Authentication)。您始終能夠經過SecurityContextHolder裏的靜態方便方法訪問和操做SecurityContext。例如:

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
assert(authentication.isAuthenticated);
複製代碼

若是您須要訪問網絡端點中當前通過身份驗證的用戶,能夠在@RequestMapping中使用方法參數,例如:

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

此註釋將當Authentication從SecurityContext中拉出,並在其上調用getPrincipal()方法以產生方法參數。Authentication中Principal的類型取決於用於驗證身份驗證的AuthenticationManager,所以這對於獲取對用戶數據的類型安全引用多是一個有用的小技巧。 若是Spring Security正在使用來自HttpServletRequest的Principal,將會是Authentication類型,所以你能夠直接使用它:

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

Processing Secure Methods Asynchronously

因爲SecurityContext是線程綁定的,若是您想要執行任何調用安全方法的後臺處理,例如使用@Async,您須要確保上下文獲得傳播。這能夠歸結爲用任務(Runnable、Callable等)包裝SecurityContext,在後臺執行。Spring Security提供了一些助手來簡化這一過程,好比Runnable和Callable的包裝器。要將SecurityContext傳播到@Async方法,您須要提供一個AsyncConfigurer,並確Executor的類型正確:

@Configuration
public class ApplicationConfiguration extends AsyncConfigurerSupport {
  @Override
  public Executor getAsyncExecutor() {
    return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));
  }
}
複製代碼
相關文章
相關標籤/搜索