本文主要翻譯Spring的官方文檔,主要介紹SS的基本概念和基本接口和原理。java
Spring Security中的兩個核心的概念:Authentication(驗證);Authorization(受權); Spring Security能夠用來實現諸如,OAuth、OpenID、LDAP、CAS、JAAS等系統間的認證很受權。如咱們可使用Spring Security配合OAuth實現單點登陸。web
Authentication主要的接口是AuthenticationManager
spring
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
複製代碼
authenticate()方法調用有三種狀況:安全
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。 網絡
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受權的主要接口是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
Spring Security在web層的使用是基於Servlet過濾器的,下圖是單個HTTP請求處理程序的典型分層。 編碼
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,它們容許您分別編寫包含對方法參數和返回值的引用的表達式。
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
}
複製代碼
因爲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));
}
}
複製代碼