Spring?Security?核心過濾器鏈講解
前言:
在熟悉Spring Security的使用和基本操作后,有時根據(jù)項目需求,我們需要在security原有的過濾器鏈中,添加符合我們自己的過濾器來實現(xiàn)功能時,我們就必須得先了解security的核心過濾鏈的流程和每個過濾器的各自功能,以此,我們才可以在特點的過濾器前后加入屬于我們項目需求的過濾器。
一、Filter Chain 圖解
在配置了spring security了之后,會在運行項目的時候,DefaultSecurityFilterChain會輸出相關log:
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters){
logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
this.requestMatcher = requestMatcher;
this.filters = new ArrayList<Filter>(filters);
}
輸出以下Log:
[main] o.s.s.web.DefaultSecurityFilterChain???? :
Creating filter chain:
org.springframework.security.web.util.matcher.AnyRequestMatcher@1,
[
??? org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@184de357,
??? org.springframework.security.web.context.SecurityContextPersistenceFilter@521ba38f,
??? org.springframework.security.web.header.HeaderWriterFilter@77bb916f,
??? org.springframework.security.web.csrf.CsrfFilter@76b305e1,
??? org.springframework.security.web.authentication.logout.LogoutFilter@17c53dfb,
??? org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2086d469,
??? org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@b1d19ff,
??? org.springframework.security.web.authentication.AnonymousAuthenticationFilter@efe49ab,
??? org.springframework.security.web.session.SessionManagementFilter@5a48d186,
??? org.springframework.security.web.access.ExceptionTranslationFilter@273aaab7
]
也可以從Debug進行查看:

二、過濾器逐一解析
在解析前,先說說兩個至關重要的類:OncePerRequestFilter和GenericFilterBean,在過濾器鏈的過濾器中,或多或少間接或直接繼承到
- OncePerRequestFilter顧名思義,能夠確保在一次請求只通過一次filter,而不需要重復執(zhí)行。
- GenericFilterBean是javax.servlet.Filter接口的一個基本的實現(xiàn)類
- GenericFilterBean將web.xml中filter標簽中的配置參數(shù)-init-param項作為bean的屬性
- GenericFilterBean可以簡單地成為任何類型的filter的父類
- GenericFilterBean的子類可以自定義一些自己需要的屬性
- GenericFilterBean,將實際的過濾工作留給他的子類來完成,這就導致了他的子類不得不實現(xiàn)doFilter方法
- GenericFilterBean不依賴于Spring的ApplicationContext,F(xiàn)ilters通常不會直接讀取他們的容器信息(ApplicationContext concept)而是通過訪問spring容器(Spring root application context)中的service beans來獲取,通常是通過調用filter里面的getServletContext() 方法來獲取
2.1.WebAsyncManagerIntegrationFilter
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {
......
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
if (securityProcessingInterceptor == null) {
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
new SecurityContextCallableProcessingInterceptor());
}
filterChain.doFilter(request, response);
}
}
從源碼中,我們可以分析出WebAsyncManagerIntegrationFilter相關功能:
- 根據(jù)請求封裝獲取WebAsyncManager
- 從WebAsyncManager獲取/注冊SecurityContextCallableProcessingInterceptor
2.2.SecurityContextPersistenceFilter
public class SecurityContextPersistenceFilter extends GenericFilterBean {
......
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything
// else.
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
......
}
從源碼中,我們可以分析出SecurityContextPersistenceFilter相關功能:
- 先實例SecurityContextHolder->HttpSessionSecurityContextRepository(下面以repo代替).作用:其會從Session中取出已認證用戶的信息,提高效率,避免每一次請求都要查詢用戶認證信息。
- 根據(jù)請求和響應構建HttpRequestResponseHolder
- repo根據(jù)HttpRequestResponseHolder加載context獲取SecurityContext
- SecurityContextHolder將獲得到的SecurityContext設置到Context中,然后繼續(xù)向下執(zhí)行其他過濾器
- finally-> SecurityContextHolder獲取SecurityContext,然后清除,并將其和請求信息保存到repo,從請求中移除FILTER_APPLIED屬性
2.3.HeaderWriterFilter
public class HeaderWriterFilter extends OncePerRequestFilter {
......
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
for (HeaderWriter headerWriter : headerWriters) {
headerWriter.writeHeaders(request, response);
}
filterChain.doFilter(request, response);
}
}
從源碼中,我們可以分析HeaderWriterFilter相關功能:
- 往該請求的Header中添加相應的信息,在http標簽內部使用security:headers來控制
2.4.CsrfFilter
public final class CsrfFilter extends OncePerRequestFilter {
......
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for "
+ UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
this.accessDeniedHandler.handle(request, response,
new MissingCsrfTokenException(actualToken));
}
else {
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}
filterChain.doFilter(request, response);
}
......
}
從源碼中,我們可以分析出CsrfFilter相關功能:
- csrf又稱跨域請求偽造,攻擊方通過偽造用戶請求訪問受信任站點。
- 對需要驗證的請求驗證是否包含csrf的token信息,如果不包含,則報錯。這樣攻擊網(wǎng)站無法獲取到token信息,則跨域提交的信息都無法通過過濾器的校驗。
2.5.LogoutFilter
public class LogoutFilter extends GenericFilterBean {
......
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (logger.isDebugEnabled()) {
logger.debug("Logging out user '" + auth
+ "' and transferring to logout destination");
}
this.handler.logout(request, response, auth);
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
......
}
從源碼中,我們可以分析出LogoutFilter相關功能:
- 匹配URL,默認為/logout
- 匹配成功后則用戶退出,清除認證信息
2.6.RequestCacheAwareFilter
public class RequestCacheAwareFilter extends GenericFilterBean {
......
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
(HttpServletRequest) request, (HttpServletResponse) response);
chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
response);
}
}
從源碼中,我們可以分析出RequestCacheAwareFilter相關功能:
通過HttpSessionRequestCache內部維護了一個RequestCache,用于緩存HttpServletRequest
2.7.SecurityContextHolderAwareRequestFilter
public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {
......
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
(HttpServletResponse) res), res);
}
......
}
從源碼中,我們可以分析出SecurityContextHolderAwareRequestFilter相關功能:
- 針對ServletRequest進行了一次包裝,使得request具有更加豐富的API
2.8.AnonymousAuthenticationFilter
public class AnonymousAuthenticationFilter extends GenericFilterBean implements InitializingBean {
......
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
if (logger.isDebugEnabled()) {
logger.debug("Populated SecurityContextHolder with anonymous token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
chain.doFilter(req, res);
}
......
}
從源碼中,我們可以分析出AnonymousAuthenticationFilter相關功能:
- 當SecurityContextHolder中認證信息為空,則會創(chuàng)建一個匿名用戶存入到SecurityContextHolder中。匿名身份過濾器,這個過濾器個人認為很重要,需要將它與UsernamePasswordAuthenticationFilter 放在一起比較理解,spring security為了兼容未登錄的訪問,也走了一套認證流程,只不過是一個匿名的身份。
- 匿名認證過濾器,可能有人會想:匿名了還有身份?個人對于Anonymous匿名身份的理解是Spirng Security為了整體邏輯的統(tǒng)一性,即使是未通過認證的用戶,也給予了一個匿名身份。而AnonymousAuthenticationFilter該過濾器的位置也是非常的科學的,它位于常用的身份認證過濾器(如UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、RememberMeAuthenticationFilter)之后,意味著只有在上述身份過濾器執(zhí)行完畢后,SecurityContext依舊沒有用戶信息,AnonymousAuthenticationFilter該過濾器才會有意義—-基于用戶一個匿名身份。
2.9.SessionManagementFilter
public class SessionManagementFilter extends GenericFilterBean {
......
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (!securityContextRepository.containsContext(request)) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication != null && !trustResolver.isAnonymous(authentication)) {
// The user has been authenticated during the current request, so call the
// session strategy
try {
sessionAuthenticationStrategy.onAuthentication(authentication,
request, response);
}
catch (SessionAuthenticationException e) {
// The session strategy can reject the authentication
logger.debug(
"SessionAuthenticationStrategy rejected the authentication object",
e);
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
// Eagerly save the security context to make it available for any possible
// re-entrant
// requests which may occur before the current request completes.
// SEC-1396.
securityContextRepository.saveContext(SecurityContextHolder.getContext(),
request, response);
}
else {
// No security context or authentication present. Check for a session
// timeout
if (request.getRequestedSessionId() != null
&& !request.isRequestedSessionIdValid()) {
if (logger.isDebugEnabled()) {
logger.debug("Requested session ID "
+ request.getRequestedSessionId() + " is invalid.");
}
if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
}
}
}
chain.doFilter(request, response);
}
......
}
從源碼中,我們可以分析出SessionManagementFilter相關功能:
- securityContextRepository限制同一用戶開啟多個會話的數(shù)量
- SessionAuthenticationStrategy防止session-fixation protection attack(保護非匿名用戶)
2.10.ExceptionTranslationFilter
public class ExceptionTranslationFilter extends GenericFilterBean {
......
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
chain.doFilter(request, response);
logger.debug("Chain processed normally");
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
}
if (ase != null) {
handleSpringSecurityException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
......
}
從源碼中,我們可以分析出ExceptionTranslationFilter相關功能:
- ExceptionTranslationFilter異常轉換過濾器位于整個springSecurityFilterChain的后方,用來轉換整個鏈路中出現(xiàn)的異常
- 此過濾器的作用是處理中FilterSecurityInterceptor拋出的異常,然后將請求重定向到對應頁面,或返回對應的響應錯誤代碼
2.11.FilterSecurityInterceptor
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
......
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
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) {
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);
}
}
......
}
從源碼中,我們可以分析出FilterSecurityInterceptor相關功能:
- 獲取到所配置資源訪問的授權信息
- 根據(jù)SecurityContextHolder中存儲的用戶信息來決定其是否有權限
- 主要一些實現(xiàn)功能在其父類AbstractSecurityInterceptor中
2.12.UsernamePasswordAuthenticationFilter
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
......
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
......
}
從源碼中,我們可以分析出UsernamePasswordAuthenticationFilter相關功能:
- 表單認證是最常用的一個認證方式,一個最直觀的業(yè)務場景便是允許用戶在表單中輸入用戶名和密碼進行登錄,而這背后的UsernamePasswordAuthenticationFilter,在整個Spring Security的認證體系中則扮演著至關重要的角色
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
IDEA利用jclasslib 修改class文件的實現(xiàn)
這篇文章主要介紹了IDEA利用jclasslib 修改class文件的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02
從零開始使用IDEA創(chuàng)建SpringBoot項目(圖文)
這篇文章主要介紹了從零開始使用IDEA創(chuàng)建SpringBoot項目(圖文),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
springboot?整合?clickhouse的實現(xiàn)示例
本文主要介紹了springboot?整合?clickhouse的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
Java?+?Selenium?+?OpenCV解決自動化測試中的滑塊驗證問題
OpenCV是一個基于Apache2.0許可(開源)發(fā)行的跨平臺計算機視覺和機器學習軟件庫,可以運行在Linux、Windows、Android和Mac?OS操作系統(tǒng)上,這篇文章主要介紹了Java?+?Selenium?+?OpenCV解決自動化測試中的滑塊驗證,需要的朋友可以參考下2022-07-07

