序
本文主要研究下SecurityContextPersistenceFilter的作用。
filter顺序
spring security内置的各种filter:
Alias | Filter Class | Namespace Element or Attribute |
---|---|---|
CHANNEL_FILTER | ChannelProcessingFilter | http/intercept-url@requires-channel |
SECURITY_CONTEXT_FILTER | SecurityContextPersistenceFilter | http |
CONCURRENT_SESSION_FILTER | ConcurrentSessionFilter | session-management/concurrency-control |
HEADERS_FILTER | HeaderWriterFilter | http/headers |
CSRF_FILTER | CsrfFilter | http/csrf |
LOGOUT_FILTER | LogoutFilter | http/logout |
X509_FILTER | X509AuthenticationFilter | http/x509 |
PRE_AUTH_FILTER | AbstractPreAuthenticatedProcessingFilter Subclasses | N/A |
CAS_FILTER | CasAuthenticationFilter | N/A |
FORM_LOGIN_FILTER | UsernamePasswordAuthenticationFilter | http/form-login |
BASIC_AUTH_FILTER | BasicAuthenticationFilter | http/http-basic |
SERVLET_API_SUPPORT_FILTER | SecurityContextHolderAwareRequestFilter | http/@servlet-api-provision |
JAAS_API_SUPPORT_FILTER | JaasApiIntegrationFilter | http/@jaas-api-provision |
REMEMBER_ME_FILTER | RememberMeAuthenticationFilter | http/remember-me |
ANONYMOUS_FILTER | AnonymousAuthenticationFilter | http/anonymous |
SESSION_MANAGEMENT_FILTER | SessionManagementFilter | session-management |
EXCEPTION_TRANSLATION_FILTER | ExceptionTranslationFilter | http |
FILTER_SECURITY_INTERCEPTOR | FilterSecurityInterceptor | http |
SWITCH_USER_FILTER | SwitchUserFilter | N/A |
可以看到SecurityContextPersistenceFilter优先级仅次于ChannelProcessingFilter
SecurityContextPersistenceFilter
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/context/SecurityContextPersistenceFilter.java
/**
* Populates the {@link SecurityContextHolder} with information obtained from the
* configured {@link SecurityContextRepository} prior to the request and stores it back in
* the repository once the request has completed and clearing the context holder. By
* default it uses an {@link HttpSessionSecurityContextRepository}. See this class for
* information <tt>HttpSession</tt> related configuration options.
* <p>
* This filter will only execute once per request, to resolve servlet container
* (specifically Weblogic) incompatibilities.
* <p>
* This filter MUST be executed BEFORE any authentication processing mechanisms.
* Authentication processing mechanisms (e.g. BASIC, CAS processing filters etc) expect
* the <code>SecurityContextHolder</code> to contain a valid <code>SecurityContext</code>
* by the time they execute.
* <p>
* This is essentially a refactoring of the old
* <tt>HttpSessionContextIntegrationFilter</tt> to delegate the storage issues to a
* separate strategy, allowing for more customization in the way the security context is
* maintained between requests.
* <p>
* The <tt>forceEagerSessionCreation</tt> property can be used to ensure that a session is
* always available before the filter chain executes (the default is <code>false</code>,
* as this is resource intensive and not recommended).
*
* @author Luke Taylor
* @since 3.0
*/
public class SecurityContextPersistenceFilter extends GenericFilterBean {
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
private SecurityContextRepository repo;
private boolean forceEagerSessionCreation = false;
public SecurityContextPersistenceFilter() {
this(new HttpSessionSecurityContextRepository());
}
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
this.repo = repo;
}
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");
}
}
}
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
this.forceEagerSessionCreation = forceEagerSessionCreation;
}
}
SecurityContextPersistenceFilter是承接容器的session与spring security的重要filter,主要工作是从session中获取SecurityContext,然后放到上下文中,之后的filter大多依赖这个来获取登录态。其主要是通过HttpSessionSecurityContextRepository来存取的。
HttpSessionSecurityContextRepository
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
/**
* A {@code SecurityContextRepository} implementation which stores the security context in
* the {@code HttpSession} between requests.
* <p>
* The {@code HttpSession} will be queried to retrieve the {@code SecurityContext} in the
* <tt>loadContext</tt> method (using the key {@link #SPRING_SECURITY_CONTEXT_KEY} by
* default). If a valid {@code SecurityContext} cannot be obtained from the
* {@code HttpSession} for whatever reason, a fresh {@code SecurityContext} will be
* created by calling by {@link SecurityContextHolder#createEmptyContext()} and this
* instance will be returned instead.
* <p>
* When <tt>saveContext</tt> is called, the context will be stored under the same key,
* provided
* <ol>
* <li>The value has changed</li>
* <li>The configured <tt>AuthenticationTrustResolver</tt> does not report that the
* contents represent an anonymous user</li>
* </ol>
* <p>
* With the standard configuration, no {@code HttpSession} will be created during
* <tt>loadContext</tt> if one does not already exist. When <tt>saveContext</tt> is called
* at the end of the web request, and no session exists, a new {@code HttpSession} will
* <b>only</b> be created if the supplied {@code SecurityContext} is not equal to an empty
* {@code SecurityContext} instance. This avoids needless <code>HttpSession</code>
* creation, but automates the storage of changes made to the context during the request.
* Note that if {@link SecurityContextPersistenceFilter} is configured to eagerly create
* sessions, then the session-minimisation logic applied here will not make any
* difference. If you are using eager session creation, then you should ensure that the
* <tt>allowSessionCreation</tt> property of this class is set to <tt>true</tt> (the
* default).
* <p>
* If for whatever reason no {@code HttpSession} should <b>ever</b> be created (for
* example, if Basic authentication is being used or similar clients that will never
* present the same {@code jsessionid}), then {@link #setAllowSessionCreation(boolean)
* allowSessionCreation} should be set to <code>false</code>. Only do this if you really
* need to conserve server memory and ensure all classes using the
* {@code SecurityContextHolder} are designed to have no persistence of the
* {@code SecurityContext} between web requests.
*
* @author Luke Taylor
* @since 3.0
*/
public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
/**
* The default key under which the security context will be stored in the session.
*/
public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
/**
* Gets the security context for the current request (if available) and returns it.
* <p>
* If the session is null, the context object is null or the context object stored in
* the session is not an instance of {@code SecurityContext}, a new context object
* will be generated and returned.
*/
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
if (logger.isDebugEnabled()) {
logger.debug("No SecurityContext was available from the HttpSession: "
+ httpSession + ". " + "A new one will be created.");
}
context = generateNewContext();
}
SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(
response, request, httpSession != null, context);
requestResponseHolder.setResponse(wrappedResponse);
if (isServlet3) {
requestResponseHolder.setRequest(new Servlet3SaveToSessionRequestWrapper(
request, wrappedResponse));
}
return context;
}
//......
}
readSecurityContextFromSession
/**
*
* @param httpSession the session obtained from the request.
*/
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
final boolean debug = logger.isDebugEnabled();
if (httpSession == null) {
if (debug) {
logger.debug("No HttpSession currently exists");
}
return null;
}
// Session exists, so try to obtain a context from it.
Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);
if (contextFromSession == null) {
if (debug) {
logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
}
return null;
}
// We now have the security context object from the session.
if (!(contextFromSession instanceof SecurityContext)) {
if (logger.isWarnEnabled()) {
logger.warn(springSecurityContextKey
+ " did not contain a SecurityContext but contained: '"
+ contextFromSession
+ "'; are you improperly modifying the HttpSession directly "
+ "(you should always use SecurityContextHolder) or using the HttpSession attribute "
+ "reserved for this class?");
}
return null;
}
if (debug) {
logger.debug("Obtained a valid SecurityContext from "
+ springSecurityContextKey + ": '" + contextFromSession + "'");
}
// Everything OK. The only non-null return from this method.
return (SecurityContext) contextFromSession;
}
可以看到是从session中取出名为SPRING_SECURITY_CONTEXT的attribute
取出来是SecurityContextImpl,实例如下
org.springframework.security.core.context.SecurityContextImpl@4198818b: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4198818b: Principal: org.springframework.security.core.userdetails.User@586034f: Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@380f4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 5B9AFCC0537B1202116A23CEC6B41142; Granted Authorities: ROLE_USER
可以看到,如果是之前登录过的,则session会关联上登录用户信息,包含用户的AuthenticationT信息,比如Principal,Granted Authorities等,这些都是重要的鉴权依据。
generateNewContext
如果取出来没有的话,则new一个
/**
* By default, calls {@link SecurityContextHolder#createEmptyContext()} to obtain a
* new context (there should be no context present in the holder when this method is
* called). Using this approach the context creation strategy is decided by the
* {@link SecurityContextHolderStrategy} in use. The default implementations will
* return a new <tt>SecurityContextImpl</tt>.
*
* @return a new SecurityContext instance. Never null.
*/
protected SecurityContext generateNewContext() {
return SecurityContextHolder.createEmptyContext();
}
通常这种情况是用户没有登陆,那么通常会经过AnonymousAuthenticationFilter(
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/AnonymousAuthenticationFilter.java
),其Authentication的值就是AnonymousAuthenticationToken
登录态如何写入session
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java
/**
* Default behaviour for successful authentication.
* <ol>
* <li>Sets the successful <tt>Authentication</tt> object on the
* {@link SecurityContextHolder}</li>
* <li>Informs the configured <tt>RememberMeServices</tt> of the successful login</li>
* <li>Fires an {@link InteractiveAuthenticationSuccessEvent} via the configured
* <tt>ApplicationEventPublisher</tt></li>
* <li>Delegates additional behaviour to the {@link AuthenticationSuccessHandler}.</li>
* </ol>
*
* Subclasses can override this method to continue the {@link FilterChain} after
* successful authentication.
* @param request
* @param response
* @param chain
* @param authResult the object returned from the <tt>attemptAuthentication</tt>
* method.
* @throws IOException
* @throws ServletException
*/
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
鉴权成功之后,会将authentication写入到SecurityContextHolder的context中。
SecurityContextPersistenceFilter#saveContext
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/context/SecurityContextPersistenceFilter.java
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在filter执行完之后,finally的时候,调用了saveContext来保存context的东西到session中。
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
/**
* Stores the supplied security context in the session (if available) and if it
* has changed since it was set at the start of the request. If the
* AuthenticationTrustResolver identifies the current user as anonymous, then the
* context will not be stored.
*
* @param context the context object obtained from the SecurityContextHolder after
* the request has been processed by the filter chain.
* SecurityContextHolder.getContext() cannot be used to obtain the context as it
* has already been cleared by the time this method is called.
*
*/
@Override
protected void saveContext(SecurityContext context) {
final Authentication authentication = context.getAuthentication();
HttpSession httpSession = request.getSession(false);
// See SEC-776
if (authentication == null || trustResolver.isAnonymous(authentication)) {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.");
}
if (httpSession != null && authBeforeExecution != null) {
// SEC-1587 A non-anonymous context may still be in the session
// SEC-1735 remove if the contextBeforeExecution was not anonymous
httpSession.removeAttribute(springSecurityContextKey);
}
return;
}
if (httpSession == null) {
httpSession = createNewSessionIfAllowed(context);
}
// If HttpSession exists, store current SecurityContext but only if it has
// actually changed in this thread (see SEC-37, SEC-1307, SEC-1528)
if (httpSession != null) {
// We may have a new session, so check also whether the context attribute
// is set SEC-1561
if (contextChanged(context)
|| httpSession.getAttribute(springSecurityContextKey) == null) {
httpSession.setAttribute(springSecurityContextKey, context);
if (logger.isDebugEnabled()) {
logger.debug("SecurityContext '" + context
+ "' stored to HttpSession: '" + httpSession);
}
}
}
}
可以看到这里把context设置进入session当中,这样就关联起来了。
小结
可以看到SecurityContextPersistenceFilter通过往session存取名为SPRING_SECURITY_CONTEXT,值为SecurityContext的attribute,来为后续filter建立所需的上下文,包括登录态。其中:
- before filter取出或创建新的context
- AbstractAuthenticationProcessingFilter鉴权完,见authentication写入context
- after filter保存context到session
因此它的filter顺序排在前面就不足为奇了。
网友评论