UsernamePasswordAuthenticationFilter
用于表单登陆认证,从表单获取username和password,然后执行认证逻辑。
认证的主要逻辑位于attemptAuthentication
方法。如下所示:
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 如果只支持post请求,验证request是否是POST方式
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 从Request parameter的用户名和密码字段(字段名可自定义)获取获取username和password
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 构造authentication token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
// 设置认证详细信息,例如remote IP地址和session id等
setDetails(request, authRequest);
// 使用认证管理器,调用认证流程,后面专门介绍AuthenticationManager
return this.getAuthenticationManager().authenticate(authRequest);
}
要知道以上逻辑是如何同filter结合在一起的,我们需要分析下它的父类AbstractAuthenticationProcessingFilter
。
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter
是基于HTTP认证处理filter的抽象父类。
我们分析它的doFilter
方法:
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 使用RequestMatcher判断请求是否需要经过认证
// 如果不需要认证直接跳过
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
// 尝试进行认证操作
// 认证的逻辑在attemptAuthentication方法,是一个抽象方法,需要在子类中实现
Authentication authenticationResult = attemptAuthentication(request, response);
// 如果认证结果为null,说明认证未完成,直接返回
if (authenticationResult == null) {
// return immediately as subclass has indicated that it hasn't completed
return;
}
// SessionAuthenticationStrategy主要负责处理认证成功的时候session需要执行的逻辑
// 比如修改session id,用来防御session fixation攻击
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success
// 如果认证成功后需要执行后面的filter
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 执行认证成功后的相关逻辑,包含:
// 设置SecurityContext
// 通知rememberMeService登陆成功
// 广播登陆成功事件
// 通知successHandler
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);
}
}
unsuccessfulAuthentication
包含了认证失败的逻辑,代码如下:
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
// 清空SecurityContextHolder中的认证用户信息
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.logger.trace("Cleared SecurityContextHolder");
this.logger.trace("Handling authentication failure");
// 通知rememberMeServices登陆失败
this.rememberMeServices.loginFail(request, response);
// 通知failureHandler
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
SessionAuthenticationStrategy
主要负责处理认证成功的时候session需要执行的逻辑。
包含如下子类:
- SessionFixationProtectionStrategy:创建一个新的session,将老session的属性迁移到新的session中
- ChangeSessionIdAuthenticationStrategy:仍使用原来的session,仅修改下session id
- ConcurrentSessionControlAuthenticationStrategy:限制用户同时登陆的次数
- CompositeSessionAuthenticationStrategy:组合多个SessionAuthenticationStrategy
- CsrfAuthenticationStrategy:登陆成功之后,更换原来的csrf token
- NullAuthenticatedSessionStrategy:空实现,什么都不操作
- RegisterSessionAuthenticationStrategy:注册新session信息到SessionRegistry
这些子类涉及的内容较多,详细分析请查阅Spring Security 源码之 SessionAuthenticationStrategy。
AuthenticationSuccessHandler和AuthenticationFailureHandler
处理认证成功或失败之后的逻辑。
基本上每一种SuccessHandler
都有对应的FailureHandler
。
下面主要分析下SuccessHandler,它包含如下子类:
- ForwardAuthenticationSuccessHandler:重定向到特定的链接
- SavedRequestAwareAuthenticationSuccessHandler:用于从
requestCache
中获取redirectURL信息并重定向到指定URL,如果requestCache
没有,则使用默认targetUrl或者从request属性中获取,用于OAuth2认证环境 - SimpleUrlAuthenticationSuccessHandler:用于从request中获取redirectURL信息并重定向到指定URL
UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken
是用户认证信息的一个封装。保存了用户名,密码,拥有的权限和其他详细信息。
其中有一个布尔字段叫做authenticated
。为true表示已经认证通过。
AuthenticationManager
用于执行认证操作这些类的父类。它有一个最重要的实现:ProviderManager
。ProviderManager
包含了一个providers列表,为List<AuthenticationProvider>
类型。这些AuthenticationProvider
是多种认证方式的抽象,比如使用UserDetailsService
读取用户信息认证,RemeberMe方式认证等。简单来说ProviderManager
的认证过程是去遍历这些AuthenticationProvider
,挨个尝试认证,如果有一个认证成功则认证通过。
我们查看下它的代码:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取authentication的类型
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
// 挨个遍历所有的provider
for (AuthenticationProvider provider : getProviders()) {
// 如果provider不支持这种类型的authentication,跳过
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
// 执行认证过程
result = provider.authenticate(authentication);
// 如果认证通过
if (result != null) {
// 将其他认证详细信息写入到result中
copyDetails(authentication, result);
// 退出for循环
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
// 如果尝试完所有的认证方式都未认证成功
if (result == null && this.parent != null) {
// Allow the parent to try.
try {
// 使用parent AuthenticationManager进行认证
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (ProviderNotFoundException ex) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
// 如果认证成功
if (result != null) {
// 认证成功之后如果需要擦除密码凭证信息,在此处擦除
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful then it
// will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent
// AuthenticationManager already published it
// 发布认证成功事件
// 这个if检测是用于防止parent AuthenticationManager认证成功重复发布事件
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
// 返回认证结果
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
// 如果parent为null,认证未成功或者抛出异常
// 修改lastException为未找到provider异常
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed then it will
// publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
// parent AuthenticationManager already published it
// 如果parent尝试认证失败,发布认证失败事件
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
AuthenticationProvider
顾名思义AuthenticationProvider
即认证方式提供者。Spring支持多种认证方式,AuthenticationProvider
是这些认证方式统一实现的接口。
常用的AuthenticationProvider
子类如下:
- DaoAuthenticationProvider:从
UserDetailsService
中获取用户信息进行认证操作。 - RememberMeAuthenticationProvider:提供RemeberMe方式认证。
- JwtAuthenticationProvider:JWT方式认证。
- AnonymousAuthenticationProvider:匿名方式认证
此部分涉及的内容较多,详细分析请查阅Spring Security 源码之 AuthenticationProvider。
本文为原创内容,欢迎大家讨论、批评指正与转载。转载时请注明出处。
网友评论