- Spring Security 源码分析
- 一 基本知识
- 1.1 核心概念
- 1.2 基本原理
- 二 源码分析
- 2.1 登录验证
- 2.1.1 基本流程
- 2.1.2 验证过滤器
- 2.1.3 尝试认证
- 2.1.4 封装密码认证令牌
- 2.1.5 认证与校验
- 2.1.6 认证失败与成功
- AuthenticationManager, 用户认证的管理类,所有的认证请求(比如login)都会通过提交一个token给AuthenticationManager的authenticate()方法来实现。当然事情肯定不是它来做,具体校验动作会由AuthenticationManager将请求转发给具体的实现类来做。根据实现反馈的结果再调用具体的Handler来给用户以反馈。这个类基本等同于shiro的SecurityManager。
- AuthenticationProvider, 认证的具体实现类,一个provider是一种认证方式的实现,比如提交的用户名密码我是通过和DB中查出的user记录做比对实现的,那就有一个DaoProvider;如果我是通过CAS请求单点登录系统实现,那就有一个CASProvider。这个是不是和shiro的Realm的定义很像?基本上你可以帮他们当成同一个东西。按照Spring一贯的作风,主流的认证方式它都已经提供了默认实现,比如DAO、LDAP、CAS、OAuth2等。
前面讲了AuthenticationManager只是一个代理接口,真正的认证就是由AuthenticationProvider来做的。一个AuthenticationManager可以包含多个Provider,每个provider通过实现一个support方法来表示自己支持那种Token的认证。AuthenticationManager默认的实现类是ProviderManager。 - UserDetailService, 用户认证通过Provider来做,所以Provider需要拿到系统已经保存的认证信息,获取用户信息的接口spring-security抽象成UserDetailService。虽然叫Service,但是我更愿意把它认为是我们系统里经常有的UserDao。
- AuthenticationToken, 所有提交给AuthenticationManager的认证请求都会被封装成一个Token的实现,比如最容易理解的UsernamePasswordAuthenticationToken。这个就不多讲了,连名字都跟Shiro中一样。
- SecurityContext,当用户通过认证之后,就会为这个用户生成一个唯一的SecurityContext,里面包含用户的认证信息Authentication。通过SecurityContext我们可以获取到用户的标识Principle和授权信息GrantedAuthrity。在系统的任何地方只要通过SecurityHolder.getSecruityContext()就可以获取到SecurityContext。在Shiro中通过SecurityUtils.getSubject()到达同样的目的。
- AbstractAuthenticationProcessingFilter
- 基于浏览器的基于 HTTP 的身份验证请求的抽象处理器。
- 认证流程,过滤器要求您设置authenticationManager属性。 需要AuthenticationManager来处理由实现类创建的身份验证请求令牌。
- 如果请求与setRequiresAuthenticationRequestMatcher(RequestMatcher)匹配,此过滤器将拦截请求并尝试从该请求执行身份验证。
- 身份验证由attemptAuthentication方法执行,该方法必须由子类实现。
- 认证成功,如果身份验证成功,则生成的Authentication对象将被放入当前线程的SecurityContext ,可供全局使用,该线程保证已由较早的过滤器创建。
- 然后将调用配置的AuthenticationSuccessHandler以在成功登录后重定向到适当的目的地。
- 默认行为在SavedRequestAwareAuthenticationSuccessHandler中实现,它将利用ExceptionTranslationFilter设置的任何DefaultSavedRequest并将用户重定向到其中包含的 URL。
- 验证失败,如果身份验证失败,它将委托给配置的AuthenticationFailureHandler以允许将失败信息传达给客户端。 默认实现是SimpleUrlAuthenticationFailureHandler,它向客户端发送 401 错误代码。 也可以使用失败 URL 进行配置作为替代。 同样,您可以在此处注入所需的任何行为。
AbstractAuthenticationProcessingFilter
- 当发起HTTP请求进行拦截
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
//HTTP请求,与响应
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//不需要认证,就放行
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
//封装验证之后的结果
Authentication authResult;
try {
//尝试认证
authResult = attemptAuthentication(request, response);
if (authResult == null) {
return;
}
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) {
//认证失败
unsuccessfulAuthentication(request, response, failed);
return;
}
// 认证成功,放行请求,并返回认证之后的结果
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
2.1.3 尝试认证
//这是一个接口,我们查看他的实现类,当然我们也可以自定义实现类,来重写里面的认证方法 public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException;
UsernamePasswordAuthenticationFilter
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
//默认匹配的URL路径,与提交方式,当然我们也可以更改其默认路径, formLogin().loginProcessingUrl(“XXXX”)
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
//尝试认证
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
//如果请求方法不是POST,就直接异常
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 = "";
}
username = username.trim();
//将前端传递的信息,封装成用户名密码认证令牌。方便AuthenticationManager,调用其他方法进行认证处理
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//获取身份验证器,进行验证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
2.1.4 封装密码认证令牌
UsernamePasswordAuthenticationToken
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
}
- 获取身份验证器,进行验证
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
//进行认证
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//遍历身份验证程序
for (AuthenticationProvider provider : getProviders()) {
//是否支持
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
//支持进行验证,并返回验证结果
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
}
}
2.1.5 认证与校验
AbstractUserDetailsAuthenticationProvider
- 一个基本的AuthenticationProvider ,它允许子类覆盖和使用UserDetails对象。
- 该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。
- 验证成功后,将创建UsernamePasswordAuthenticationToken并将其返回给调用者。
- 缓存是通过存储放置在UserCache的UserDetails对象来处理的。 这确保可以验证具有相同用户名的后续请求,而无需查询UserDetailsService 。
public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
//验证
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
try {
//检查用户情况,账户锁定,账户过期,凭证过期
preAuthenticationChecks.check(user);
//账号,密码检查
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//返回创建成功凭证
return createSuccessAuthentication(principalToReturn, authentication, user);
}
}
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
// Ensure we return the original credentials the user supplied,
// so subsequent attempts are successful even with encoded passwords.
// Also ensure we return the original getDetails(), so that future
// authentication events after cache expiry contain the details
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
//返回认证时候的结果
return result;
}
DaoAuthenticationProvider
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
//密码
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
//用户信息,数据库中
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
//认证检查,注意这个方法是废弃了
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
//前端传递的密码
String presentedPassword = authentication.getCredentials().toString();
//数据库是中的密码,与前端传递的密码进行比较
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
}
2.1.6 认证失败与成功
AbstractAuthenticationProcessingFilter
//拦截
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
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
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//认证成功
successfulAuthentication(request, response, chain, authResult);
}
//认证失败,当然我们也可以重写失败的提示
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 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);
}
- 以上就是认证的源码分析流程



