相關(guān)文章:
- OAuth2的定義和運行流程
- Spring Security OAuth實現(xiàn)Gitee快捷登錄
- Spring Security OAuth實現(xiàn)GitHub快捷登錄
- Spring Security的過濾器鏈機(jī)制
- Spring Security OAuth Client配置加載源碼分析
前言
根據(jù)前面的示例,我們已經(jīng)知道啟動時會加載18個過濾器,并且已經(jīng)知道了請求會匹配到DefaultSecurityFilterChain并依次通過這18個過濾器。
DisableEncodeUrlFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2LoginAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
我們從OAuth2AuthorizationRequestRedirectFilter開始看,OAuth2開頭這非常明顯。
OAuth2AuthorizationRequestRedirectFilter
OAuth2 客戶端認(rèn)證核心過濾器,通過重定向到authorization_uri來獲取code
該過濾器并沒有doFilter()方法,只有doFilterInternal,而doFilter()存在于其父類OncePerRequestFilter過濾器中
如下是核心代碼:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
//從request、ClientRegistration中構(gòu)造出OAuth2的授權(quán)請求對象
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
if (authorizationRequest != null) {
//重定向到authorizationUri
this.sendRedirectForAuthorization(request, response, authorizationRequest);
return;
}
}
catch (Exception ex) {
this.unsuccessfulRedirectForAuthorization(request, response, ex);
return;
}
try {
//執(zhí)行下一個過濾器
filterChain.doFilter(request, response);
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
//判斷是否有ClientAuthorizationRequiredException,如果有單獨處理
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
ClientAuthorizationRequiredException authzEx = (ClientAuthorizationRequiredException) this.throwableAnalyzer
.getFirstThrowableOfType(ClientAuthorizationRequiredException.class, causeChain);
if (authzEx != null) {
try {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request,
authzEx.getClientRegistrationId());
if (authorizationRequest == null) {
throw authzEx;
}
this.sendRedirectForAuthorization(request, response, authorizationRequest);
this.requestCache.saveRequest(request, response);
}
catch (Exception failed) {
this.unsuccessfulRedirectForAuthorization(request, response, failed);
}
return;
}
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new RuntimeException(ex);
}
}
OAuth2LoginAuthenticationFilter
說明:核心OAuth登錄過濾器,首先從URL中提取code,然后使用code獲取access_token,接著借助access_token獲取用戶信息,最終構(gòu)建出OAuth2AuthenticationToken認(rèn) 證對象,表明認(rèn)證成功。
該過濾器會在授權(quán)服務(wù)器調(diào)用回調(diào)接口的時候起作用,本例中回調(diào)的URL為/login/oauth2/code/gitee?code=c52e1e1f8ab954d680f4bb78e33ce303b15ed3cb1040bd47089067e9a8ed9ea5&state=k2Ci-0eyt0To1Me_ThgInRX2CPv2EtKSiu5xTrWyN3Y%3D
doFilter代碼位于父類AbstractAuthenticationProcessingFilter中,核心代碼如下:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//通過requestMatcher判斷request請求是否需要處理
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
//獲取身份認(rèn)證結(jié)果,并創(chuàng)建認(rèn)證對象,由子類實現(xiàn),OAuth2 使用的是OAuth2LoginAuthenticationFilter
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
// return immediately as subclass has indicated that it hasn't completed
return;
}
//對會話進(jìn)行處理,防止會話固定攻擊(session-fixation詳情可網(wǎng)上查詢)
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
//認(rèn)證成功后,是否繼續(xù)執(zhí)行后面的過濾器
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//處理認(rèn)證成功后的處理邏輯,委托給AuthenticationSuccessHandler,此處為SavedRequestAwareAuthenticationSuccessHandler
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
//認(rèn)證失敗處理
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// Authentication failed
//認(rèn)證失敗處理
unsuccessfulAuthentication(request, response, ex);
}
}
其中attemptAuthentication處的代碼量很大,作用是:獲取身份認(rèn)證結(jié)果,并創(chuàng)建認(rèn)證對象,由子類實現(xiàn),OAuth2 使用的是OAuth2LoginAuthenticationFilter,我們來看看這部分
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
//從授權(quán)服務(wù)器回調(diào)的URL請求中,將請求參數(shù)轉(zhuǎn)換為map格式,key=參數(shù)名,value=參數(shù)值
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
//根據(jù)code、status、error字段判斷是否為返回請求
if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
//返回OAuth2AuthorizationRequest,該對象在OAuth2LoginAuthenticationFilter中構(gòu)建
//removeAuthorizationRequest官方的話是,但不太理解為什么要remove
//Removes and returns the OAuth2AuthorizationRequest associated to the provided
//HttpServletRequest and HttpServletResponse or if not available returns null.
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository
.removeAuthorizationRequest(request, response);
if (authorizationRequest == null) {
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
if (clientRegistration == null) {
OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
"Client Registration not found with Id: " + registrationId, null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
// @formatter:off
String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replaceQuery(null)
.build()
.toUriString();
// @formatter:on
//構(gòu)建OAuth2授權(quán)響應(yīng)對象
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,
redirectUri);
//從request構(gòu)建一個完整的身份認(rèn)證信息
Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
//構(gòu)建OAuth2認(rèn)證Token
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration,
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authenticationRequest.setDetails(authenticationDetails);
//請求授權(quán)服務(wù)器的token-uri,進(jìn)行身份認(rèn)證,返回完整的OAuth2認(rèn)證Token
//請求user-info-uri,獲取資源所有者主體信息
//會輪詢所有的provider,直到遇到支持的provider,該處為OAuth2LoginAuthenticationProvider
OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
.getAuthenticationManager().authenticate(authenticationRequest);
//轉(zhuǎn)換成需要返回的OAuth2AuthenticationToken
OAuth2AuthenticationToken oauth2Authentication = this.authenticationResultConverter
.convert(authenticationResult);
Assert.notNull(oauth2Authentication, "authentication result cannot be null");
oauth2Authentication.setDetails(authenticationDetails);
//構(gòu)建最終的OAuth2 授權(quán)客戶端
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(), oauth2Authentication.getName(),
authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
//保存授權(quán)相關(guān)信息,此處保存到本地
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
return oauth2Authentication;
}
DefaultLoginPageGeneratingFilter
說明:默認(rèn)的登錄頁面
當(dāng)沒有配置自定義登錄頁時,將使用該默認(rèn)登錄頁
如下的doFilter核心代碼:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//當(dāng)前請求是否是failureUrl指定的地址
boolean loginError = isErrorPage(request);
//當(dāng)前請求是否是logoutSuccessUrl指定的地址
boolean logoutSuccess = isLogoutSuccess(request);
//是否是登錄請求,是否是重定向的錯誤請求,是否是登出地址
if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
//生成一個默認(rèn)的登錄頁
String loginPageHtml = generateLoginPageHtml(request, loginError, logoutSuccess);
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
response.getWriter().write(loginPageHtml);
return;
}
//否則執(zhí)行下一個過濾器
chain.doFilter(request, response);
}
DefaultLogoutPageGeneratingFilter
說明:默認(rèn)的登出頁面
當(dāng)請求地址為GET請求,/logout時,生成一個默認(rèn)的登出頁面
核心代碼如下:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//地址是/logout
if (this.matcher.matches(request)) {
//生成一個登出頁面
renderLogout(request, response);
}
else {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Did not render default logout page since request did not match [%s]",
this.matcher));
}
filterChain.doFilter(request, response);
}
}
界面如下:
RequestCacheAwareFilter
說明:
從session中獲取SavedRequest,如果當(dāng)前請求信息和SaveRequest信息一致(一般是登錄成功后重定向),則返回SavedRequestAwareWrapper的HttpServletRequest包裝類
SecurityContextHolderAwareRequestFilter
說明:
通過HttpServletRequestFactory將HttpServletRequest請求包裝成SecurityContextHolderAwareRequestWrapper,它實現(xiàn)了HttpServletRequest,并進(jìn)行了擴(kuò)展,添加一些額外的方法,比如:getPrincipal()方法等。這樣就可以那些需要Principal等參數(shù)的Controller就可以接收到對應(yīng)參數(shù)了。除了這個地方的應(yīng)用,在其他地方,也可以直接調(diào)用request#getUserPrincipal()獲取對應(yīng)信息。
AnonymousAuthenticationFilter
說明:
匿名過濾器,如果執(zhí)行到該過濾器時還沒有主體,則創(chuàng)建一個匿名主體
OAuth2AuthorizationCodeGrantFilter
說明:
OAuth2授權(quán)碼授權(quán)過濾器,跟OAuth2LoginAuthenticationFilter很像,不知道在這里的作用是什么?
是為了兜底?
SessionManagementFilter
Session管理過濾器
ExceptionTranslationFilter
處理過濾器鏈中拋出的AccessDeniedException和AuthenticationException 異常
FilterSecurityInterceptor
對資源的過濾處理
核心代碼如下:
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
//該請求已經(jīng)運行過該過濾,跳過,執(zhí)行下一個過濾器
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// first time this request being called, so perform security checking
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//獲取當(dāng)前的權(quán)限配置
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
}
finally {
//設(shè)置SecurityContextHolderStrategy context
super.finallyInvocation(token);
}
//最后的處理
super.afterInvocation(token, null);
}
獲取當(dāng)前 request 對應(yīng)的權(quán)限配置,首先是調(diào)用基類的 beforeInvocation 方法 。
看一下基類的 beforeInvocation 方法,從配置好的 SecurityMetadataSource 中獲取當(dāng)前 request 所對應(yīng)的 ConfigAttribute,即權(quán)限信息。
protected InterceptorStatusToken beforeInvocation(Object object) {
......
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
......
}
}
這里需要注意一下 rejectPublicInvocations 屬性,默認(rèn)為 false。此屬性含義為拒絕公共請求。如果從配置好的 SecurityMetadataSource 中獲取不到當(dāng)前 request 所對應(yīng)的 ConfigAttribute 時,即認(rèn)為當(dāng)前請求為公共請求。如配置 rejectPublicInvocations 屬性為 true,則系統(tǒng)會拋出 IllegalArgumentException 異常,即當(dāng)前請求需要配置權(quán)限信息。
接下來,就要判斷是否需要進(jìn)行身份認(rèn)證了,即調(diào)用 authenticateIfRequired 方法。
protected InterceptorStatusToken beforeInvocation(Object object) {
......
Authentication authenticated = authenticateIfRequired();
......
}
而判斷及身份認(rèn)證邏輯也并不復(fù)雜,首先會判斷當(dāng)前用戶是否已通過身份認(rèn)證,如果已通過身份認(rèn)證,則直接返回;如果尚未通過身份認(rèn)證,則調(diào)用身份認(rèn)證管理器 AuthenticationManager 進(jìn)行認(rèn)證,就如同登錄時一樣。認(rèn)證通過后,同樣會在當(dāng)前的安全上下文中存儲一份認(rèn)證后的 authentication。
private Authentication authenticateIfRequired() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication.isAuthenticated() && !alwaysReauthenticate) {
if (logger.isDebugEnabled()) {
logger.debug("Previously Authenticated: " + authentication);
}
return authentication;
}
authentication = authenticationManager.authenticate(authentication);
// We don't authenticated.setAuthentication(true), because each provider should do
// that
if (logger.isDebugEnabled()) {
logger.debug("Successfully Authenticated: " + authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
然后,使用獲取到的 ConfigAttribute ,繼續(xù)調(diào)用訪問控制器 AccessDecisionManager 對當(dāng)前請求進(jìn)行鑒權(quán)。
protected InterceptorStatusToken beforeInvocation(Object object) {
......
// Attempt authorization
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
}
注意,無論鑒權(quán)通過或是不通后,Spring Security 框架均使用了觀察者模式,來通知其它Bean,當(dāng)前請求的鑒權(quán)結(jié)果。
如果鑒權(quán)不通過,則會拋出 AccessDeniedException 異常,即訪問受限,然后會被 ExceptionTranslationFilter 捕獲,最終解析后調(diào)轉(zhuǎn)到對應(yīng)的鑒權(quán)失敗頁面。
如果鑒權(quán)通過,AbstractSecurityInterceptor 通常會繼續(xù)請求。但是,在極少數(shù)情況下,用戶可能希望使用不同的 Authentication 來替換 SecurityContext 中的 Authentication。該身份認(rèn)證就會由 RunAsManager 來處理。這在某些業(yè)務(wù)場景下可能很有用,錄入服務(wù)層方法需要調(diào)用遠(yuǎn)程系統(tǒng)并呈現(xiàn)不同的身份。因為 Spring Security 會自動將安全標(biāo)識從一個服務(wù)器傳播到另一個服務(wù)器(假設(shè)使用的是正確配置的 RMI 或 HttpInvoker 遠(yuǎn)程協(xié)議客戶端),這就可能很有用。
在 AccessDecisionManager 鑒權(quán)成功后,將通過 RunAsManager 在現(xiàn)有 Authentication 基礎(chǔ)上構(gòu)建一個新的Authentication,如果新的 Authentication 不為空則將產(chǎn)生一個新的 SecurityContext,并把新產(chǎn)生的Authentication 存放在其中。這樣在請求受保護(hù)資源時從 SecurityContext中 獲取到的 Authentication 就是新產(chǎn)生的 Authentication。
protected InterceptorStatusToken beforeInvocation(Object object) {
......
// 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);
}
}
注意,AbstractSecurityInterceptor 默認(rèn)持有的是 RunAsManager 的空實現(xiàn) NullRunAsManager。
public abstract class AbstractSecurityInterceptor implements InitializingBean,
ApplicationEventPublisherAware, MessageSourceAware {
......
private RunAsManager runAsManager = new NullRunAsManager();
......
}
待請求完成后會在 finallyInvocation() 中將原來的 SecurityContext 重新設(shè)置給SecurityContextHolder。
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());
}
}
然而,無論正常調(diào)用,亦或是請求異常等,都會觸發(fā) finallyInvocation()。
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
......
}
else {
......
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
// 無論是否成功、拋異常與否,均會執(zhí)行
super.finallyInvocation(token);
}
// 正常請求結(jié)束,最后也會執(zhí)行(afterInvocation 內(nèi)部會調(diào)用finallyInvocation )
super.afterInvocation(token, null);
}
}
即便是正常執(zhí)行結(jié)束,依然會執(zhí)行 finallyInvocation()(afterInvocation 內(nèi)部會調(diào)用finallyInvocation )。
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
......
finallyInvocation(token); // continue to clean in this method for passivity
......
}
此外,Spring Security 對 RunAsManager 有一個還有一個非空實現(xiàn)類 RunAsManagerImpl,其構(gòu)造新 Authentication 的邏輯如下:
如果受保護(hù)對象對應(yīng)的 ConfigAttribute 中擁有以“RUN_AS_”開頭的配置屬性,則在該屬性前加上“ROLE_”,然后再把它作為一個 SimpleGrantedAuthority 賦給將要創(chuàng)建的 Authentication(如ConfigAttribute 中擁有一個“RUN_AS_ADMIN”的屬性,則將構(gòu)建一個“ROLE_RUN_AS_ADMIN”的SimpleGrantedAuthority),最后再利用原 Authentication 的 principal、權(quán)限等信息構(gòu)建一個新的 Authentication 并返回;如果不存在任何以“RUN_AS_”開頭的 ConfigAttribute,則直接返回null。
public Authentication buildRunAs(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
List<GrantedAuthority> newAuthorities = new ArrayList<>();
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
GrantedAuthority extraAuthority = new SimpleGrantedAuthority(
getRolePrefix() + attribute.getAttribute());
newAuthorities.add(extraAuthority);
}
}
if (newAuthorities.size() == 0) {
return null;
}
// Add existing authorities
newAuthorities.addAll(authentication.getAuthorities());
return new RunAsUserToken(this.key, authentication.getPrincipal(),
authentication.getCredentials(), newAuthorities,
authentication.getClass());
}
AccessDecisionManager 是在訪問受保護(hù)的對象之前判斷用戶是否擁有該對象的訪問權(quán)限。然而,有時候我們可能會希望在請求執(zhí)行完成后對返回值做一些修改或者權(quán)限校驗,當(dāng)然,也可以簡單的通過AOP來實現(xiàn)這一功能。
同樣的,Spring Security 提供了 AfterInvocationManager 接口,它允許我們在受保護(hù)對象訪問完成后對返回值進(jìn)行修改或者進(jìn)行權(quán)限校驗,權(quán)限校驗不通過時拋出 AccessDeniedException,并使用觀察者模式通知其它Bean。
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
......
if (afterInvocationManager != null) {
......
catch (AccessDeniedException accessDeniedException) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(
token.getSecureObject(), token.getAttributes(), token
.getSecurityContext().getAuthentication(),
accessDeniedException);
publishEvent(event);
throw accessDeniedException;
}
}
......
}
其將由 AbstractSecurityInterceptor 的子類進(jìn)行調(diào)用,如默認(rèn)子類 FilterSecurityInterceptor 。
需要特別注意的是,AfterInvocationManager 需要在受保護(hù)對象成功被訪問后才能執(zhí)行。
類似于AuthenticationManager,AfterInvocationManager 同樣也有一個默認(rèn)的實現(xiàn)類AfterInvocationProviderManager,其中有一個由 AfterInvocationProvider 組成的集合屬性。
public class AfterInvocationProviderManager implements AfterInvocationManager,
InitializingBean {
......
private List<AfterInvocationProvider> providers;
......
}
非常有趣的是,AfterInvocationProvider 與 AfterInvocationManager 具有相同的方法定義。此一來,在調(diào)用AfterInvocationProviderManager 中的方法時,實際上就是依次調(diào)用其中成員屬性 providers 中的AfterInvocationProvider 接口對應(yīng)的方法。
public Object decide(Authentication authentication, Object object,
Collection<ConfigAttribute> config, Object returnedObject)
throws AccessDeniedException {
Object result = returnedObject;
for (AfterInvocationProvider provider : providers) {
result = provider.decide(authentication, object, config, result);
}
return result;
}
而 AfterInvocationProvider 的默認(rèn)實現(xiàn)類 PostInvocationAdviceProvider 中的 PostInvocationAuthorizationAdvice,其默認(rèn)實現(xiàn)類 ExpressionBasedPostInvocationAdvice,不正是對應(yīng)著后置權(quán)限注解 @PostAuthorize 嗎?
最后,關(guān)于 FILTER_APPLIED 常量,在 FilterSecurityInterceptor 中是這么使用的:
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);
}
......
}
}
其主要作用,是用于阻止請求的重復(fù)安全檢查。文章來源:http://www.zghlxwxcb.cn/news/detail-452387.html
原理也簡單,第一次執(zhí)行時,檢查 request 中 FILTER_APPLIED 屬性值為空,則放入值;后續(xù)該 request 再次請求時,F(xiàn)ILTER_APPLIED 屬性值不為空,代表已經(jīng)進(jìn)行過安全檢查,則該請求直接通過,不再重復(fù)進(jìn)行安全檢查。文章來源地址http://www.zghlxwxcb.cn/news/detail-452387.html
到了這里,關(guān)于Spring Security內(nèi)置過濾器詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!