spring security 兩個(gè)核心
- authentication 認(rèn)證
- authorization(access-control) 授權(quán)(或者說是訪問控制)
認(rèn)證是聲明主體的過程纹笼,授權(quán)是指確定一個(gè)主體是否允許在你的應(yīng)用程序執(zhí)行一個(gè)動(dòng)作的過程。
spring security 核心流程
這一套核心流程具體每條的實(shí)現(xiàn)由AbstractSecurityInterceptor 實(shí)現(xiàn),也就是說AbstractSecurityInterceptor 只是定義了一些行為荒适,然后這些行為的安排算途,也就是執(zhí)行流程則是由具體的子類所實(shí)現(xiàn)稳懒,AbstractSecurityInterceptor 雖然也叫Interceptor ,但是并沒有繼承和實(shí)現(xiàn)任何和過濾器相關(guān)的類坤塞,具體和過濾器有關(guān)的部分是由子類所定義舌菜。每一種受保護(hù)對(duì)象都擁有繼承自AbstrachSecurityInterceptor的攔截器類萌壳。spring security 提供了兩個(gè)具體實(shí)現(xiàn)類,MethodSecurityInterceptor 將用于受保護(hù)的方法日月,F(xiàn)ilterSecurityInterceptor 用于受保護(hù)的web 請(qǐng)求袱瓮。
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
...
}
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements
MethodInterceptor {
...
}
- 查找當(dāng)前請(qǐng)求里分配的"配置屬性"。
- 把安全對(duì)象爱咬,當(dāng)前的Authentication和配置屬性,提交給AccessDecisionManager來進(jìn)行以此認(rèn)證決定尺借。
- 有可能在調(diào)用的過程中,對(duì)Authentication進(jìn)行修改。
- 允許安全對(duì)象進(jìn)行處理(假設(shè)訪問被允許了)精拟。
- 在調(diào)用返回的時(shí)候執(zhí)行配置的AfterInvocationManager燎斩。如果調(diào)用引發(fā)異常,AfterInvocationManager將不會(huì)被調(diào)用。
AbstractSecurityInterceptor 的兩個(gè)實(shí)現(xiàn)都具有一致的邏輯
- 先將正在請(qǐng)求調(diào)用的受保護(hù)對(duì)象傳遞給beforeInvocation()方法進(jìn)行權(quán)限鑒定串前。
- 權(quán)限鑒定失敗就直接拋出異常了瘫里。
- 鑒定成功將嘗試調(diào)用受保護(hù)對(duì)象,調(diào)用完成后荡碾,不管是成功調(diào)用谨读,還是拋出異常,都將執(zhí)行finallyInvocation()坛吁。
- 如果在調(diào)用受保護(hù)對(duì)象后沒有拋出異常劳殖,則調(diào)用afterInvocation()铐尚。
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
// ~ Static fields/initializers
// =====================================================================================
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
// ~ Instance fields
// ================================================================================================
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;
// ~ Methods
// ========================================================================================================
...
/**
* Method that is actually called by the filter chain. Simply delegates to the
* {@link #invoke(FilterInvocation)} method.
*
* @param request the servlet request
* @param response the servlet response
* @param chain the filter chain
*
* @throws IOException if the filter chain fails
* @throws ServletException if the filter chain fails
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
...
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
...
}
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements
MethodInterceptor {
// ~ Instance fields
// ================================================================================================
private MethodSecurityMetadataSource securityMetadataSource;
...
/**
* This method should be used to enforce security on a <code>MethodInvocation</code>.
*
* @param mi The method being invoked which requires a security decision
*
* @return The returned value from the method invocation (possibly modified by the
* {@code AfterInvocationManager}).
*
* @throws Throwable if any error occurs
*/
public Object invoke(MethodInvocation mi) throws Throwable {
InterceptorStatusToken token = super.beforeInvocation(mi);
Object result;
try {
result = mi.proceed();
}
finally {
super.finallyInvocation(token);
}
return super.afterInvocation(token, result);
}
...
}
spring security 默認(rèn)的過濾器是FilterSecurityInterceptor。spring security 的方法安全是需要配置啟用的哆姻。
<global-method-security secured-annotations="enabled" />
添加一個(gè)注解到類或者接口的方法中可以限制對(duì)相應(yīng)方法的訪問宣增。spring security 的原生注解支持定義了一套用于該方法的屬性。這些將被傳遞到AccessDecisionManager 用來做實(shí)際的決定:
public interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();
@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
啟用 JSR-250 注解使用
<global-method-security jsr250-annotations="enabled" />
這些都是基于標(biāo)準(zhǔn)的矛缨,并允許應(yīng)用簡單的基于角色的約束爹脾,但是沒有Spring Security的原生注解強(qiáng)大。要使用新的基于表達(dá)式的語法箕昭,你可以使用
<global-method-security pre-post-annotations="enabled" />
public interface BankService {
@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);
@PreAuthorize("isAnonymous()")
public Account[] findAccounts();
@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}
基于表達(dá)式的注解是一個(gè)很好的選擇灵妨,如果你需要定義超過一個(gè)檢查當(dāng)前用戶列表中的角色名稱的簡單的規(guī)則。
也可以使用注解啟用
EnableGlobalMethodSecurity
我們可以在任何使用@Configuration的實(shí)例上落竹,使用@EnableGlobalMethodSecurity注解來啟用基于注解的安全性泌霍。例如下面會(huì)啟用Spring的@Secured注解。
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
// ...
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
AbstractSecurityInterceptor
spring security 的核心是 AbstractSecurityInterceptor這個(gè)過濾器基本上控制著spring security 的整個(gè)流程述召。
spring security 會(huì)用到一些spring framework 提供的基礎(chǔ)功能
- spring 事件發(fā)布機(jī)制(ApplicationEventPublisher)
- spring AOP advice 思想
- spring messageSource 本地消息
spring security 的權(quán)限鑒定是由AccessDecisionManager 接口中的decide() 方法負(fù)責(zé)的
void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException,InsufficientAuthenticationException;
authentication 就是主體對(duì)象朱转,configAttributes 是
主體(是受保護(hù)對(duì)象)的配置屬性,至于第二個(gè)對(duì)象就是表示請(qǐng)求的受保護(hù)對(duì)象积暖,基本上來說MethodInvocation(使用AOP)藤为、JoinPoint(使用Aspectj) 和 FilterInvocation(web 請(qǐng)求)三種類型。
AbstractSecurityInterceptor 是一個(gè)實(shí)現(xiàn)了對(duì)受保護(hù)對(duì)象的訪問進(jìn)行攔截的抽象類呀酸。
ConfigAttribute
public interface ConfigAttribute extends Serializable {
String getAttribute();
}
AccessDecisionManager 的decide() 方法是需要接收一個(gè)受保護(hù)對(duì)象對(duì)應(yīng)的configAttribute集合的凉蜂。一個(gè)configAttribute可能只是一個(gè)簡單的角色名稱琼梆,具體將視AccessDecisionManager的實(shí)現(xiàn)者而定性誉。
一個(gè)"配置屬性"可以看做是一個(gè)字符串,它對(duì)于AbstractSecurityInterceptor使用的類是有特殊含義的。它們由框架內(nèi)接口ConfigAttribute表示茎杂。它們可能是簡單的角色名稱或擁有更復(fù)雜的含義,這就與AccessDecisionManager實(shí)現(xiàn)的先進(jìn)程度有關(guān)了错览。AbstractSecurityInterceptor和配置在一起的 SecurityMetadataSource 用來為一個(gè)安全對(duì)象搜索屬性。通常這個(gè)屬性對(duì)用戶是不可見的煌往。配置屬性將以注解的方式設(shè)置在受保護(hù)方法上倾哺,或者作為受保護(hù)URLs的訪問屬性。例如,當(dāng)我們看到像<intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>命名空間中的介紹,這是說配置屬性ROLE_A和ROLE_B適用于匹配Web請(qǐng)求的特定模式刽脖。在實(shí)踐中,使用默認(rèn)的AccessDecisionManager配置, 這意味著,任何人誰擁有GrantedAuthority只要符合這兩個(gè)屬性將被允許訪問羞海。嚴(yán)格來說,它們只是依賴于AccessDecisionManager實(shí)施的屬性和解釋。使用前綴ROLE_是一個(gè)標(biāo)記,以表明這些屬性是角色,應(yīng)該由Spring Security的RoleVoter前綴被消耗掉曲管。這只是使用AccessDecisionManager的選擇基礎(chǔ)却邓。
RunAsManager
RunAsManagerImpl構(gòu)建新的Authentication的核心代碼如下所示。
public Authentication buildRunAs(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
List<GrantedAuthority> newAuthorities = new ArrayList<GrantedAuthority>();
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
GrantedAuthority extraAuthority = newSimpleGrantedAuthority(getRolePrefix() + attribute.getAttribute());
newAuthorities.add(extraAuthority);
}
}
if (newAuthorities.size() == 0) {
returnnull;
}
// Add existing authorities
newAuthorities.addAll(authentication.getAuthorities());
returnnew RunAsUserToken(this.key, authentication.getPrincipal(), authentication.getCredentials(),
newAuthorities, authentication.getClass());
}
在某些情況下你可能會(huì)想替換保存在SecurityContext中的Authentication院水。這可以通過RunAsManager來實(shí)現(xiàn)的腊徙。在AbstractSecurityInterceptor的beforeInvocation()方法體中简十,在AccessDecisionManager鑒權(quán)成功后,將通過RunAsManager在現(xiàn)有Authentication基礎(chǔ)上構(gòu)建一個(gè)新的Authentication撬腾,如果新的Authentication不為空則將產(chǎn)生一個(gè)新的SecurityContext螟蝙,并把新產(chǎn)生的Authentication存放在其中。這樣在請(qǐng)求受保護(hù)資源時(shí)從SecurityContext中獲取到的Authentication就是新產(chǎn)生的Authentication民傻。待請(qǐng)求完成后會(huì)在finallyInvocation()中將原來的SecurityContext重新設(shè)置給SecurityContextHolder胰默。AbstractSecurityInterceptor默認(rèn)持有的是一個(gè)對(duì)RunAsManager進(jìn)行空實(shí)現(xiàn)的NullRunAsManager。此外漓踢,Spring Security對(duì)RunAsManager有一個(gè)還有一個(gè)非空實(shí)現(xiàn)類RunAsManagerImpl初坠,其在構(gòu)造新的Authentication時(shí)是這樣的邏輯:如果受保護(hù)對(duì)象對(duì)應(yīng)的ConfigAttribute中擁有以“RUN_AS_”開頭的配置屬性,則在該屬性前加上“ROLE_”彭雾,然后再把它作為一個(gè)GrantedAuthority賦給將要?jiǎng)?chuàng)建的Authentication(如ConfigAttribute中擁有一個(gè)“RUN_AS_ADMIN”的屬性碟刺,則將構(gòu)建一個(gè)“ROLE_RUN_AS_ADMIN”的GrantedAuthority),最后再利用原Authentication的principal薯酝、權(quán)限等信息構(gòu)建一個(gè)新的Authentication進(jìn)行返回半沽;如果不存在任何以“RUN_AS_”開頭的ConfigAttribute,則直接返回null吴菠。
AfterInvocationManager
按照下面安全對(duì)象執(zhí)行和返回的方式-可能意味著完全的方法調(diào)用或過濾器鏈的執(zhí)行-在AbstractSecurityInterceptor得到一個(gè)最后的機(jī)會(huì)來處理調(diào)用者填。這種狀態(tài)下AbstractSecurityInterceptor對(duì)有可能修改返回對(duì)象感興趣。你可能想讓它發(fā)生做葵,因?yàn)轵?yàn)證決定不能“關(guān)于如何在”一個(gè)安全對(duì)象調(diào)用占哟。高可插拔性,AbstractSecurityInterceptor通過控制AfterInvocationManager在實(shí)際需要的時(shí)候修改對(duì)象。這里類實(shí)際上可能替換對(duì)象酿矢,或者拋出異常榨乎,或者什么也不做。如果調(diào)用成功后瘫筐,檢查調(diào)用才會(huì)執(zhí)行蜜暑。如果出現(xiàn)異常,額外的檢查將被跳過策肝。
AbstractSecurityInterceptor 中的一些方法
- afterPropertiesSet()
public void afterPropertiesSet() throws Exception {
Assert.notNull(getSecureObjectClass(),
"Subclass must provide a non-null response to getSecureObjectClass()");
Assert.notNull(this.messages, "A message source must be set");
Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
Assert.notNull(this.accessDecisionManager, "An AccessDecisionManager is required");
Assert.notNull(this.runAsManager, "A RunAsManager is required");
Assert.notNull(this.obtainSecurityMetadataSource(),
"An SecurityMetadataSource is required");
...
}
主要是對(duì)類的屬性進(jìn)行校驗(yàn)肛捍。
- beforeInvocation()
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
...
// 獲取Object 配置屬性
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
...
// authentication 必須存在
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
...
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
/**
* Checks the current authentication token and passes it to the AuthenticationManager
*/
private Authentication authenticateIfRequired() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
// 如果authentication已經(jīng)被驗(yàn)證過了并且總是重新驗(yàn)證為false
if (authentication.isAuthenticated() && !alwaysReauthenticate) {
if (logger.isDebugEnabled()) {
logger.debug("Previously Authenticated: " + authentication);
}
// 則跳過驗(yàn)證
return authentication;
}
// 否則 authenticationManager 執(zhí)行驗(yàn)證
authentication = authenticationManager.authenticate(authentication);
// We don't authenticated.setAuthentication(true), because each provider should do
// that
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
這個(gè)方法實(shí)現(xiàn)了對(duì)訪問對(duì)象的權(quán)限校驗(yàn),內(nèi)部使用了AccessDecisionManager 和 AuthenticationManager
- finallyInvocation()
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
if (token == null) {
// public object
return returnedObject;
}
finallyInvocation(token); // continue to clean in this method for passivity
if (afterInvocationManager != null) {
// Attempt after invocation handling
try {
returnedObject = afterInvocationManager.decide(token.getSecurityContext()
.getAuthentication(), token.getSecureObject(), token
.getAttributes(), returnedObject);
}
catch (AccessDeniedException accessDeniedException) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(
token.getSecureObject(), token.getAttributes(), token
.getSecurityContext().getAuthentication(),
accessDeniedException);
publishEvent(event);
throw accessDeniedException;
}
}
return returnedObject;
}
這個(gè)方法實(shí)現(xiàn)了對(duì)返回結(jié)果的處理之众,在注入了AfterInvocationManager的情況下默認(rèn)會(huì)調(diào)用其decide()的方法
- finallyInvocation()
/**
* Cleans up the work of the <tt>AbstractSecurityInterceptor</tt> after the secure
* object invocation has been completed. This method should be invoked after the
* secure object invocation and before afterInvocation regardless of the secure object
* invocation returning successfully (i.e. it should be done in a finally block).
*
* @param token as returned by the {@link #beforeInvocation(Object)} method
*/
protected void finallyInvocation(InterceptorStatusToken token) {
if (token != null && token.isContextHolderRefreshRequired()) {
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: "
+ token.getSecurityContext().getAuthentication());
}
SecurityContextHolder.setContext(token.getSecurityContext());
}
}
方法用于實(shí)現(xiàn)受保護(hù)對(duì)象請(qǐng)求完畢后的一些清理工作拙毫,主要是如果在beforeInvocation()中改變了SecurityContext,則在finallyInvocation()中需要將其恢復(fù)為原來的SecurityContext棺禾,該方法的調(diào)用應(yīng)當(dāng)包含在子類請(qǐng)求受保護(hù)資源時(shí)的finally語句塊中
AbstractSecurityInterceptor 和它的相關(guān)對(duì)象