该过滤器会拦截用户请求,看它是否是一个来自用户名/密码表单登录页面提交的用户登录认证请求,缺省使用的匹配模式是:POST /login
。缺省情况下,如果是用户登录认证请求,该请求就不会在整个filter chain
中继续传递了,而是会被当前过滤器处理并进入响应用户阶段。
具体用户登录认证处理逻辑是这样的,它会调用所指定的AuthenticationManager
认证所提交的用户名/密码。
如果认证成功,则会 :
调用所设置的SessionAuthenticationStrategy
会话认证策略;
针对
Servlet 3.1+
,缺省所使用的SessionAuthenticationStrategy
是一个ChangeSessionIdAuthenticationStrategy
和CsrfAuthenticationStrategy
组合。ChangeSessionIdAuthenticationStrategy
会为登录的用户创建一个新的session
,而CsrfAuthenticationStrategy
会创建新的csrf token
用于CSRF
保护。
经过完全认证的Authentication
对象设置到SecurityContextHolder
中的SecurityContext
上;
如果请求要求了Remember Me
,进行相应记录;
发布事件InteractiveAuthenticationSuccessEvent
;
获取并跳转到目标跳转页面;
缺省情况下,该跳转策略是
SavedRequestAwareAuthenticationSuccessHandler
。
一般在未登录用户直接访问受保护页面时会出现该情况:先被跳转到登录页面,登录完成过后再被跳转到原始请求页面
alwaysUseDefaultTargetUrl
为true
则总是会跳到指定的defaultTargetUrl
;
注意:
defaultTargetUrl
也是可以设置的,如果不设置,其值缺省为/
alwaysUseDefaultTargetUrl
为false
则
targetUrlParameter
值的参数,如果有,跳转到它定义的地址;useReferer
,尝试使用请求头部Referer
作为目标跳转地址;defaultTargetUrl
作为目标跳转地址;package org.springframework.security.web.authentication;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
// 用户名/密码登录表单中用户名字段缺省使用的名称
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
// 用户名/密码登录表单中密码字段缺省使用的名称
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
// ~ Constructors
// ===================================================================================================
public UsernamePasswordAuthenticationFilter() {
// 缺省匹配用户请求 POST /login,认为该请求是用户名/密码表单登录验证请求
super(new AntPathRequestMatcher("/login", "POST"));
}
// ~ Methods
// ========================================================================================================
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 = "";
}
// 注意,这里对用户名做了trim操作,一般理解,就是去除了前后的空格
username = username.trim();
// 根据用户提供的用户名/密码信息构建一个认证token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
// 交给 authenticationManager执行真正的用户身份认证
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* 从请求参数中获取密码
*
* Enables subclasses to override the composition of the password, such as by
* including additional values and a separator.
*
* This might be used for example if a postcode/zipcode was required in addition to
* the password. A delimiter such as a pipe (|) should be used to separate the
* password and extended value(s). The AuthenticationDao will need to
* generate the expected password in a corresponding manner.
*
*
* @param request so that request attributes can be retrieved
*
* @return the password that will be presented in the Authentication
* request token to the AuthenticationManager
*/
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
/**
* 从请求参数中获取用户名
*
* Enables subclasses to override the composition of the username, such as by
* including additional values and a separator.
*
* @param request so that request attributes can be retrieved
*
* @return the username that will be presented in the Authentication
* request token to the AuthenticationManager
*/
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
/**
* Provided so that subclasses may configure what is put into the authentication
* request's details property.
*
* @param request that an authentication request is being created for
* @param authRequest the authentication request object that should have its details
* set
*/
protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
/**
* Sets the parameter name which will be used to obtain the username from the login
* request.
* 指定从用户名/密码登录认证表单中获取用户名时使用的属性名称,缺省为 username
* @param usernameParameter the parameter name. Defaults to "username".
*/
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
/**
* Sets the parameter name which will be used to obtain the password from the login
* request..
* 指定从用户名/密码登录认证表单中获取用户名时使用的属性名称,缺省为 password
* @param passwordParameter the parameter name. Defaults to "password".
*/
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
/**
* Defines whether only HTTP POST requests will be allowed by this filter. If set to
* true, and an authentication request is received which is not a POST request, an
* exception will be raised immediately and authentication will not be attempted. The
* unsuccessfulAuthentication() method will be called as if handling a failed
* authentication.
*
* 设置是否仅仅接受HTTP POST用户名/密码登录验证表单请求,缺省为true,表示必须使用HTTP POST。
*
* Defaults to true but may be overridden by subclasses.
*/
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return usernameParameter;
}
public final String getPasswordParameter() {
return passwordParameter;
}
}
UsernamePasswordAuthenticationFilter
继承自AbstractAuthenticationProcessingFilter
:
package org.springframework.security.web.authentication;
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.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
/**
* Abstract processor of browser-based HTTP-based authentication requests.
* 基于浏览器和HTTP的认证请求的处理抽象。
*
* Authentication Process 认证过程
*
* The filter requires that you set the authenticationManager property. An
* AuthenticationManager is required to process the authentication request tokens
* created by implementing classes.
* 该过滤器执行认证需要一个authenticationManager,这个AuthenticationManager用来针对
* 所创建的认证请求token做真正的用户身份认证动作。
*
* This filter will intercept a request and attempt to perform authentication from that
* request if the request matches the
* #setRequiresAuthenticationRequestMatcher(RequestMatcher).
* 该过滤器拦截请求识别当前请求是否一个用户名/密码表单认证请求的匹配方法是通过方法
* #setRequiresAuthenticationRequestMatcher(RequestMatcher)指定的一个RequestMatcher。
*
* Authentication is performed by the
* #attemptAuthentication(HttpServletRequest, HttpServletResponse)
* method, which must be implemented by subclasses.
* 认证动作由方法#attemptAuthentication(HttpServletRequest, HttpServletResponse)完成,
* 该方法由子类实现。上面的UsernamePasswordAuthenticationFilter就提供了该方法的实现。
*
* Authentication Success 认证成功
*
* If authentication is successful, the resulting Authentication object will be
* placed into the SecurityContext for the current thread, which is
* guaranteed to have already been created by an earlier filter.
* 认证成功时,结果认证对象Authentication会被放到一个SecurityContext对象中,然后保存在处理
* 当前请求的线程中。
*
* The configured #setAuthenticationSuccessHandler(AuthenticationSuccessHandler)
* will then be called to take the redirect to the
* appropriate destination after a successful login. The default behaviour is implemented
* in a SavedRequestAwareAuthenticationSuccessHandler which will make use of any
* DefaultSavedRequest set by the ExceptionTranslationFilter and
* redirect the user to the URL contained therein. Otherwise it will redirect to the
* webapp root "/". You can customize this behaviour by injecting a differently configured
* instance of this class, or by using a different implementation.
* 然后通过#setAuthenticationSuccessHandler(AuthenticationSuccessHandler)所设置的
* AuthenticationSuccessHandler会被调用,从而页面被跳转到所配置的成功登录页面。
*
* Authentication Failure 认证失败
*
* If authentication fails, it will delegate to the configured
* AuthenticationFailureHandler to allow the failure information to be conveyed to
* the client. The default implementation is SimpleUrlAuthenticationFailureHandler
* , which sends a 401 error code to the client. It may also be configured with a failure
* URL as an alternative. Again you can inject whatever behaviour you require here.
* 如果认证失败,该过滤器会代理给AuthenticationFailureHandler将失败信息传回给客户。缺省实现是
* 一个SimpleUrlAuthenticationFailureHandler,它会发送一个HTTP 401代码给客户端。
*
* Event Publication 事件发布
*
* If authentication is successful, an InteractiveAuthenticationSuccessEvent will
* be published via the application context. No events will be published if authentication
* was unsuccessful, because this would generally be recorded via an
* AuthenticationManager-specific application event.
* 认证成功时一个InteractiveAuthenticationSuccessEvent事件会发布到应用上下文。认证不成功
* 则不会发布任何事件。
*
* Session Authentication
*
* The class has an optional SessionAuthenticationStrategy which will be invoked
* immediately after a successful call to attemptAuthentication(). Different
* implementations
* #setSessionAuthenticationStrategy(SessionAuthenticationStrategy) can be
* injected to enable things like session-fixation attack prevention or to control the
* number of simultaneous sessions a principal may have.
*
* @author Ben Alex
* @author Luke Taylor
*/
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
// ~ Static fields/initializers
// =====================================================================================
// ~ Instance fields
// ================================================================================================
protected ApplicationEventPublisher eventPublisher;
protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
new WebAuthenticationDetailsSource();
// 真正执行认证的认真管理器
private AuthenticationManager authenticationManager;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private RememberMeServices rememberMeServices = new NullRememberMeServices();
private RequestMatcher requiresAuthenticationRequestMatcher;
// 登录认证成功时是否继续执行filter chain,缺省为false,该属性安全配置阶段会重新指定,
// 但安全配置阶段缺省使用的值也是false,表示登录认证成功时不继续执行filter chain,而是
// 由该页面直接进入响应用户阶段
private boolean continueChainBeforeSuccessfulAuthentication = false;
// session 认证策略
// 安全配置中缺省是一个 CompositeSessionAuthenticationStrategy 对象,应用了组合模式,组合一些其他的
// session 认证策略实现,比如针对Servlet 3.1+,缺省会是 ChangeSessionIdAuthenticationStrategy跟
// CsrfAuthenticationStrategy组合
// 这里虽然初始化为NullAuthenticatedSessionStrategy,但它会被安全配置中的值覆盖
private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
private boolean allowSessionCreation = true;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
// ~ Constructors
// ===================================================================================================
/**
* @param defaultFilterProcessesUrl the default value for filterProcessesUrl.
*/
protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
setFilterProcessesUrl(defaultFilterProcessesUrl);
}
/**
* Creates a new instance
*
* @param requiresAuthenticationRequestMatcher the {@link RequestMatcher} used to
* determine if authentication is required. Cannot be null.
*/
protected AbstractAuthenticationProcessingFilter(
RequestMatcher requiresAuthenticationRequestMatcher) {
Assert.notNull(requiresAuthenticationRequestMatcher,
"requiresAuthenticationRequestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
}
// ~ Methods
// ========================================================================================================
@Override
public void afterPropertiesSet() {
Assert.notNull(authenticationManager, "authenticationManager must be specified");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
// 检查当前请求是否是一个用户名/密码登录表单请求,如果不是,则继续执行filter chain
// 的其他部分
chain.doFilter(request, response);
return;
}
// 下面是检测到这是一个用户名/密码登录表单请求时的处理逻辑
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
// 交给 AuthenticationManger 执行相应的认证
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
//针对Servlet 3.1+,缺省所使用的SessionAuthenticationStrategy会是一个
//ChangeSessionIdAuthenticationStrategy和CsrfAuthenticationStrategy组合。
//ChangeSessionIdAuthenticationStrategy会为登录的用户创建一个新的session,
//而CsrfAuthenticationStrategy会创建新的csrf token用于CSRF保护。
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
// 认证失败
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
// 认证失败
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
// 认证成功,如果continueChainBeforeSuccessfulAuthentication为true,
// (continueChainBeforeSuccessfulAuthentication缺省为false)
// 继续执行filter chain的其他部分
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 认证成功后的处理逻辑
successfulAuthentication(request, response, chain, authResult);
}
/**
* 检测当前请求是否是登录认证请求
* Indicates whether this filter should attempt to process a login request for the
* current invocation.
*
* It strips any parameters from the "path" section of the request URL (such as the
* jsessionid parameter in http://host/myapp/index.html;jsessionid=blah)
* before matching against the filterProcessesUrl property.
*
* Subclasses may override for special requirements, such as Tapestry integration.
*
* @return true if the filter should attempt authentication,
* false otherwise.
*/
protected boolean requiresAuthentication(HttpServletRequest request,
HttpServletResponse response) {
return requiresAuthenticationRequestMatcher.matches(request);
}
/**
* Performs actual authentication. 执行真正的认证
*
* The implementation should do one of the following:
* 会是以下三种情况之一:
*
* 1.Return a populated authentication token for the authenticated user, indicating
* successful authentication 认证成功,填充认证了的用户的其他信息到authentication token并返回之
* 2.Return null, indicating that the authentication process is still in progress.
* Before returning, the implementation should perform any additional work required to
* complete the process.返回null表示认证仍在进行中。
* 3.Throw an AuthenticationException if the authentication process fails。抛出一个
* 异常AuthenticationException表示认证失败。
*
*
* @param request from which to extract parameters and perform the authentication
* @param response the response, which may be needed if the implementation has to do a
* redirect as part of a multi-stage authentication process (such as OpenID).
*
* @return the authenticated user token, or null if authentication is incomplete.
*
* @throws AuthenticationException if authentication fails.
*/
public abstract Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException,
ServletException;
/**
* Default behaviour for successful authentication.认证成功时的缺省行为。
*
* 1. Sets the successful Authentication object on the SecurityContextHolder
* 2. Informs the configured RememberMeServices of the successful login
* 3.Fires an InteractiveAuthenticationSuccessEvent via the configured
* ApplicationEventPublisher
* 4.Delegates additional behaviour to the AuthenticationSuccessHandler.
*
*
* Subclasses can override this method to continue the FilterChain after
* successful authentication.
* @param request
* @param response
* @param chain
* @param authResult the object returned from the attemptAuthentication
* 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);
}
/**
* Default behaviour for unsuccessful authentication.
* 认证失败时的缺省行为
*
* 1.Clears the SecurityContextHolder
* 2.Stores the exception in the session (if it exists or
* allowSesssionCreation is set to true)
* 3.Informs the configured RememberMeServices of the failed login
* 4.Delegates additional behaviour to the AuthenticationFailureHandler.
*
*/
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (logger.isDebugEnabled()) {
logger.debug("Authentication request failed: " + failed.toString(), failed);
logger.debug("Updated SecurityContextHolder to contain null Authentication");
logger.debug("Delegating to authentication failure handler " + failureHandler);
}
rememberMeServices.loginFail(request, response);
failureHandler.onAuthenticationFailure(request, response, failed);
}
protected AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* Sets the URL that determines if authentication is required
*
* @param filterProcessesUrl
*/
public void setFilterProcessesUrl(String filterProcessesUrl) {
setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(
filterProcessesUrl));
}
public final void setRequiresAuthenticationRequestMatcher(
RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requestMatcher;
}
public RememberMeServices getRememberMeServices() {
return rememberMeServices;
}
public void setRememberMeServices(RememberMeServices rememberMeServices) {
Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
this.rememberMeServices = rememberMeServices;
}
/**
* Indicates if the filter chain should be continued prior to delegation to
* #successfulAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, Authentication)
* , which may be useful in certain environment (such as Tapestry applications).
* Defaults to false.
*/
public void setContinueChainBeforeSuccessfulAuthentication(
boolean continueChainBeforeSuccessfulAuthentication) {
this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource,
"AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
protected boolean getAllowSessionCreation() {
return allowSessionCreation;
}
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
}
/**
* The session handling strategy which will be invoked immediately after an
* authentication request is successfully processed by the
* AuthenticationManager. Used, for example, to handle changing of the
* session identifier to prevent session fixation attacks.
*
* @param sessionStrategy the implementation to use. If not set a null implementation
* is used.
*/
public void setSessionAuthenticationStrategy(
SessionAuthenticationStrategy sessionStrategy) {
this.sessionStrategy = sessionStrategy;
}
/**
* Sets the strategy used to handle a successful authentication. By default a
* SavedRequestAwareAuthenticationSuccessHandler is used.
*/
public void setAuthenticationSuccessHandler(
AuthenticationSuccessHandler successHandler) {
Assert.notNull(successHandler, "successHandler cannot be null");
this.successHandler = successHandler;
}
public void setAuthenticationFailureHandler(
AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
protected AuthenticationSuccessHandler getSuccessHandler() {
return successHandler;
}
protected AuthenticationFailureHandler getFailureHandler() {
return failureHandler;
}
}