Spring Security Web 5.1.2 原始碼解析 -- SessionManagementFilter
阿新 • • 發佈:2018-12-12
概述
該過濾器會檢測從當前請求處理開始到目前為止的過程中是否發生了使用者登入認證行為(比如這是一個使用者名稱/密碼錶單提交的請求處理過程),如果檢測到這一情況,執行相應的session
認證策略(一個SessionAuthenticationStrategy
),然後繼續繼續請求的處理。
針對Servlet 3.1+
,預設所使用的SessionAuthenticationStrategy
會是一個ChangeSessionIdAuthenticationStrategy
和CsrfAuthenticationStrategy
組合。ChangeSessionIdAuthenticationStrategy
session
,而CsrfAuthenticationStrategy
會建立新的csrf token
用於CSRF
保護。
如果當前過濾器鏈中啟用了UsernamePasswordAuthenticationFilter
,實際上本過濾器SessionManagementFilter
並不會真正被執行到上面所說的邏輯。因為在UsernamePasswordAuthenticationFilter
中,一旦使用者登入認證發生它會先執行上述的邏輯。因此到SessionManagementFilter
執行的時候,它會發現安全上下文儲存庫中已經有相應的安全上下文了,從而不再重複執行上面的邏輯。
另外需要注意的是,如果相應的session認證策略執行失敗的話,整個成功的使用者登入認證行為會被該過濾器否定,相應新建的SecurityContextHolder
中的安全上下文會被清除,所設定的AuthenticationFailureHandler
邏輯會被執行。
原始碼解析
package org.springframework.security.web.session;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
/**
* Detects that a user has been authenticated since the start of the request and, if they
* have, calls the configured SessionAuthenticationStrategy to perform any
* session-related activity such as activating session-fixation protection mechanisms or
* checking for multiple concurrent logins.
*
* 檢測從當前請求處理最初到現在整個處理過程中是否發生了使用者登入認證,如果確實發生了,呼叫所配置的
* SessionAuthenticationStrategy(session認證策略)執行與session相關的操作。比如針對session-fxation攻擊
* 保護機制對應的策略可能是為廢棄登入前的session為登入使用者生成一個新的session等。
*
* @author Martin Algesten
* @author Luke Taylor
* @since 2.0
*/
public class SessionManagementFilter extends GenericFilterBean {
// ~ Static fields/initializers
// =====================================================================================
static final String FILTER_APPLIED = "__spring_security_session_mgmt_filter_applied";
// ~ Instance fields
// ================================================================================================
// 安全上下文儲存庫,要和當前請求處理過程中其他filter配合,一般由配置階段使用統一配置設定進來
// 預設使用基於http session的安全上下文儲存庫:HttpSessionSecurityContextRepository
private final SecurityContextRepository securityContextRepository;
// session 認證策略
// 預設是一個 CompositeSessionAuthenticationStrategy 物件,應用了組合模式,組合一些其他的
// session 認證策略實現,比如針對Servlet 3.1+,預設會是 ChangeSessionIdAuthenticationStrategy跟
// CsrfAuthenticationStrategy組合
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
// 用於識別一個Authentication是哪種型別:anonymous ? remember ?
// 配置階段統一指定,預設使用 AuthenticationTrustResolverImpl
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
// 遇到無效session時的處理策略,預設值為null
private InvalidSessionStrategy invalidSessionStrategy = null;
// 認證失敗處理器,比如form使用者名稱/密碼登入如果失敗可能會引導使用者重新發起表單認證
// 預設使用SimpleUrlAuthenticationFailureHandler
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
public SessionManagementFilter(SecurityContextRepository securityContextRepository) {
this(securityContextRepository, new SessionFixationProtectionStrategy());
}
public SessionManagementFilter(SecurityContextRepository securityContextRepository,
SessionAuthenticationStrategy sessionStrategy) {
Assert.notNull(securityContextRepository,
"SecurityContextRepository cannot be null");
Assert.notNull(sessionStrategy, "SessionAuthenticationStrategy cannot be null");
this.securityContextRepository = securityContextRepository;
this.sessionAuthenticationStrategy = sessionStrategy;
}
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) {
// 如果在當前請求過程中該過濾器已經應用過,則不在二次應用,繼續filter chain的執行
chain.doFilter(request, response);
return;
}
// 該過濾器要執行了,在請求上設定該過濾器已經執行過的標記,避免在該請求的同一處理過程中
// 本過濾器執行二遍
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
// 檢測securityContextRepository是否已經儲存了針對當前請求的安全上下文物件 :
// 1. 未登入使用者訪問登入保護url的情況 : 否
// 2. 未登入使用者訪問登入頁面url的情況 : (不會走到這裡,已經被登入頁面生成Filter攔截)
// 3. 未登入使用者訪問公開url的情況 : 否
// 4. 登入使用者訪問公開url的情況 : 是
// 5. 登入使用者訪問登入保護url的情況 : 是
// 6. 登入使用者訪問公開url的情況 : 是
if (!securityContextRepository.containsContext(request)) {
// 如果securityContextRepository中沒有儲存安全上下文物件,
// 但是SecurityContextHolder中安全上下文物件的authentication屬性
// 不為null或者匿名,則說明從請求處理開始到現在出現了使用者登入認證成功的情況
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
// 認證登入成功的情況被檢測到,執行相應的session認證策略
try {
sessionAuthenticationStrategy.onAuthentication(authentication,
request, response);
}
catch (SessionAuthenticationException e) {
// 如果session認證策略執行出現異常,則拒絕該使用者登入認證,
// 清除已經登入成功的安全上下文,並呼叫相應的failureHandler認證失敗邏輯
// 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.
// 如果該請求處理過程中出現了使用者成功登入的情況,並且相應的session認證策略已經
// 執行成功,直接在securityContextRepository儲存新建的針對已經登入使用者的安全上下文,
// 這樣之後,在當前請求處理結束前,遇到任何可重入的請求,它們就可以利用該資訊了。
securityContextRepository.saveContext(SecurityContextHolder.getContext(),
request, response);
}
else {
// No security context or authentication present. Check for a session
// timeout
// authentication 為 null 或者 匿名 或者 RememberMe 的情況,
// 檢測是否遇到了session過期或者提供了無效的session id,如果遇到了,
// 並且設定了相應的無效session處理器invalidSessionStrategy,則最相應的處理
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);
}
/**
* Sets the strategy which will be invoked instead of allowing the filter chain to
* prceed, if the user agent requests an invalid session Id. If the property is not
* set, no action will be taken.
*
* @param invalidSessionStrategy the strategy to invoke. Typically a
* SimpleRedirectInvalidSessionStrategy.
*/
public void setInvalidSessionStrategy(InvalidSessionStrategy invalidSessionStrategy) {
this.invalidSessionStrategy = invalidSessionStrategy;
}
/**
* The handler which will be invoked if the AuthenticatedSessionStrategy
* raises a SessionAuthenticationException, indicating that the user is not
* allowed to be authenticated for this session (typically because they already have
* too many sessions open).
*
*/
public void setAuthenticationFailureHandler(
AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
/**
* Sets the AuthenticationTrustResolver to be used. The default is
* AuthenticationTrustResolverImpl.
*
* @param trustResolver the AuthenticationTrustResolver to use. Cannot be
* null.
*/
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
this.trustResolver = trustResolver;
}
}