Spring Security在内部维护一个过滤器链,其中每个过滤器都有特定的责任;

比如:

  • ChannelProcessingFilter,因为它可能需要重定向到不同的协议

  • SecurityContextPersistenceFilter,因此可以在web请求开头的SecurityContextHolder中设置SecurityContext,并且SecurityContext的任何更改都可以复制到HttpSession当web请求结束时(准备好与下一个web请求一起使用)

    等等……….

登录流程解析

UsernamePasswordAuthenticationFilter

登录认证流程解析认证的是通过一个对应的过滤器UsernamePasswordAuthenticationFilter

此类是一个过滤器继承 AbstractAuthenticationProcessingFilter

既然是过滤器首先看 doFilter 逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
//核心代码获取 Authentication
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);
}
}


attemptAuthentication 是一个抽象方法,由 UsernamePasswordAuthenticationFilter实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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());
}
//获取用户名 其实就是request.getParam 获取
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
//获取密码 其实就是request.getParam 获取
String password = obtainPassword(request);
password = (password != null) ? password : "";
//用户名和密码组装成 UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}

this.getAuthenticationManager() 获取 权限管理器 AuthenticationManager

AuthenticationManager

这里的实现是 ProviderManager ,管理了多个 AuthenticationProvider 针对不同的认证方式有不同的实现,会根据开启了那些功能有多个实现;

执行 authenticate 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
//有多个provider权限校验
int size = this.providers.size();
for (AuthenticationProvider provider : getProviders()) {
//是否支持当前的校验
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
//执行校验 这里看下 DaoAuthenticationProvider
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
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;
}
}


这里的代码省略。。。。。。

}

DaoAuthenticationProvider

DaoAuthenticationProvider 使用 userdetailservice 查询dao的用户信息等;

调用 DaoAuthenticationProvider#authenticate

执行到 DaoAuthenticationProvider 的抽象父类 AbstractUserDetailsAuthenticationProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
   @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));

// authentication 就是之前封装的usernamepasswordtoken 从里面获取username
String username = determineUsername(authentication);
boolean cacheWasUsed = true;
//这里用到了缓存
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//缓存中获取不到执行此逻辑
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException ex) {
this.logger.debug("Failed to find user '" + username + "'");
if (!this.hideUserNotFoundExceptions) {
throw ex;
}
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
//校验账户是否过期,索引等信息
this.preAuthenticationChecks.check(user);
//进行密码比对的逻辑
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException ex) {
if (!cacheWasUsed) {
throw ex;
}
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
this.preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
//本地缓存添加
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//创建一个成功的 Authentication
return createSuccessAuthentication(principalToReturn, authentication, user);
}



本地缓存中获取不到后,执行此方法,开始获取我们配置的 UserDetailsService 调用我们定义的查询方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14

@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}

passwordEncoder

根据用户名查询到用户信息后调用 additionalAuthenticationChecks 执行密码匹配逻辑,会使用passwordEncoder 也就是一般需要我们配置的那个密码编码器
如果不匹配就抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}

//最后一步创建成功的认证,重新创建了 UsernamePasswordAuthenticationToken 并且可以通过 authoritiesMapper 再次从数据库中查询权限信息;

1
2
3
4
5
6
7
8
9
10
11
12
13
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(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
this.logger.debug("Authenticated user");
return result;
}

当认证成功后,调用 AbstractAuthenticationProcessingFilter#successfulAuthentication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
//将当前的认证信息放入 SecurityContextHolder 中
SecurityContextHolder.getContext().setAuthentication(authResult);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
//rememberMe 的相关逻辑
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
//可以 成功的处理器,可以自定义的,有时候会根据需求自定义
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}


同样失败后也会调用对应的方法 AbstractAuthenticationProcessingFilter#unsuccessfulAuthentication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
//清空contextholder
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.logger.trace("Cleared SecurityContextHolder");
this.logger.trace("Handling authentication failure");
//清空rememberMe信息
this.rememberMeServices.loginFail(request, response);
//失败的处理器,也是可以配置自定义的
this.failureHandler.onAuthenticationFailure(request, response, failed);
}


总结下基本流程:

  1. UsernamePasswordAuthenticationFilter 会拦截提交到loginurl 的请求
  2. 当前如果没有登录就从请求中获取用户名和密码 根据 用户名 调用自定义的userdetailservice 查询用户信息
  3. 查询到用户信息后校验用户的状态 并且使用passwordEncoder 进行密码比对
  4. 登录成功后将用户认证信息放入 SecurityContextHolder 中。同时执行登录成功的handler
  5. 登录失败后将用户认证信息 从 SecurityContextHolder 清空,同时执行登录失败的handler

SecurityContextHolder 解析

SecurityContextHolder 是用来存储用户的认证的信息的,从中可以静态的获取认证信息;

可以获取到 SecurityContext

内部维护了一个 SecurityContextHolderStrategy 不同的实现;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private static SecurityContextHolderStrategy strategy;

private static int initializeCount = 0;

static {
initialize();
}

private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
// Set default
strategyName = MODE_THREADLOCAL;
}
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
}
else {
// Try to load a custom strategy
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
initializeCount++;
}


GlobalSecurityContextHolderStrategy 共享的认证信息;
ThreadLocalSecurityContextHolderStrategy ThreadLocal存储(子线程不能获取)
InheritableThreadLocalSecurityContextHolderStrategy InheritableThreadLocal存储(子线程也可获取)

那么对于已经登录完成的用户下次请求是怎么放到 SecurityContextHolder 中的呢? 是通过一个过滤器 SecurityContextPersistenceFilter

SecurityContextPersistenceFilter

此过滤器的主要作用就是在请求前从 SecurityContextRepository 中 ,获取到认证信息放到 SecurityContextHolder 中,请求的最后同时将
SecurityContextHolder 中的认证信息清空。