前言
上一篇我们对SpringSecurity登录流程几个重要组件进行了简单的分析,最后遗留下来了一点,这里我们将继续上篇内容讨论。有兴趣的朋友可以看看本专栏的文章。还请不吝赐教。
AbstractAuthenticationProcessingFilter
作为SpringSecurity过滤器链中的一环,AbstractAuthenticationProcessingFilter可以用来处理任何提交给它的身份你认证,我们来用图看看它的工作流程:
上图显示的流程是一个通用的架构。
AbstractAuthenticationProcessingFilter作为一个抽象类,如果使用用户名/密码的方式登录,那么它对应的实现类是UsernamePasswordAuthenticationFilter,构造出来的Authentication对象则是UsernamePasswordAuthenticationToken。至于AuthenticationManager,前面已经说过,一般情况下它的实现类就是ProviderManager,这里在ProviderManager中进行认证,认证成功就会进入认证成功的回调,否则进入认证失败的回调。因此我们可以对上面的流程图做进一步的细化,如下:
上一篇文章中所涉及到的认证基本流程就是这样,我们大致来梳理一下
(1)、当用户提交登录请求时,UsernamePasswordAuthenticationFilter会从当前请求的HttpServlertRequest中提取出用户名/密码,然后创建出一个UsernamePasswordAuthenticationToken对象。
(2)、UsernamePasswordAuthenticationToken对象被传入ProviderManager中进行具体的认证操作。
(3)、如果认证失败,则会进行登录信息存储,Session并发处理、登录成功事件发布以及登录成功方法回调等操作。
这是认证的一个大致流程。我们在结合AbstractAuthenticationProcessingFilter和UsernamePasswordAuthenticationFilter源码看一下。
AbstractAuthenticationProcessingFilter
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
setFilterProcessesUrl(defaultFilterProcessesUrl);
}
protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
}
protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl,
AuthenticationManager authenticationManager) {
setFilterProcessesUrl(defaultFilterProcessesUrl);
setAuthenticationManager(authenticationManager);
}
protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher,
AuthenticationManager authenticationManager) {
setRequiresAuthenticationRequestMatcher(requiresAuthenticationRequestMatcher);
setAuthenticationManager(authenticationManager);
}
@Override
public void afterPropertiesSet() {
Assert.notNull(this.authenticationManager, "authenticationManager must be specified");
}
@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 {
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
// return immediately as subclass has indicated that it hasn't completed
return;
}
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// Authentication failed
unsuccessfulAuthentication(request, response, ex);
}
}
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
if (this.requiresAuthenticationRequestMatcher.matches(request)) {
return true;
}
if (this.logger.isTraceEnabled()) {
this.logger
.trace(LogMessage.format("Did not match request to %s", this.requiresAuthenticationRequestMatcher));
}
return false;
}
public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException;
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.logger.trace("Cleared SecurityContextHolder");
this.logger.trace("Handling authentication failure");
this.rememberMeServices.loginFail(request, response);
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
protected AuthenticationManager getAuthenticationManager() {
return this.authenticationManager;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
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 this.rememberMeServices;
}
public void setRememberMeServices(RememberMeServices rememberMeServices) {
Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
this.rememberMeServices = rememberMeServices;
}
public void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
@Override
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
protected boolean getAllowSessionCreation() {
return this.allowSessionCreation;
}
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
}
public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionStrategy) {
this.sessionStrategy = sessionStrategy;
}
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 this.successHandler;
}
protected AuthenticationFailureHandler getFailureHandler() {
return this.failureHandler;
}
}
关于Filter我们当然从doFilter看:
(1)、首先通过requiresAuthentication方法来判断当前登录是不是登录认证请求,如果是认证请求,就执行下面代码,如果不是跳出此过滤器执行后面的过滤器。
(2)、调用attemptAuthentication方法来获取一个经过认证后的Authentication对象,attemptAuthentication方法是一个抽象方法,具体的实现在它的子类UsernamePasswordAuthenticationFilter中。
(3)、认证成功后,通过sessionStrategy.onAuthentication方法处理session并发问题。
(4)、continueChainBeforeSuccessfulAuthentication变量用来判断请求是否还需要向下走。默认情况下这个参数为false,即认证成功后,后续的过滤器将不在执行。
(5)、unsuccessfulAuthentication 方法用来处理认证失败事宜,主要做了三件事:1.从SecurityContextHolder中清除数据;2.清除Cookie等信息;3.调用失败认证的回调方法。
(6)、unsuccessfulAuthentication 方法主要用来处理认证成功事宜,主要做了四件事:1.向SecurityContextHolder中存入用户信息;2.处理Cookie;3.发布认证成功事件,这个事件类型是InteracticveAuthenticationSuccessEvent,表示通过一些自动交互的方式认证成功,例如通过RememberMe方式登录;4.调用认证成功回调方法。
这就是AbstractAuthenticationProcessingFilter 大致上所做的事情,还有一个抽象方法attemptAuthentication是在它的继承类UsernamePasswordAuthenticationFilter 中实现的,我们来看一下
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
"POST");
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return this.usernameParameter;
}
public final String getPasswordParameter() {
return this.passwordParameter;
}
}
(1)、首先声明了默认情况下的登录表单的用户名/密码字段,用户名的字段key默认就是username,密码字段key默认是password。这两个字段我们也可以自定义配置。
(2)、在UsernamePasswordAuthenticationFilter 过滤器构建的时候,指定了当前过滤器只用来处理登录请求,默认的登录请求是/login,当然我们也可以自己配置
(3)、接下来就是最重要的方法attemptAuthentication了,在该方法中,首先确认请求类型是POST类型;然后通过obtainUsername和obtainPassword得到用户名和密码(其实就是request.getParameter);拿到登录请求传来的用户名和密码之后,构造出一个authRequest,然后调用getAuthenticationManager().authenticate方法进行认证,这就进入到我们上一篇所说的providerManager中了。
以上就是整个认证流程