Spring Security 身份认证源码解析

1. 认证信息的存储

本文参考:
Spring Security 架构与源码分析
spring security——基本介绍(一)

Spring Security requires a Java 8 or higher Runtime Environment.

当用户登录的时候,一般来说要校验用户名密码的正确性。还要查询此用户的权限信息。这些信息被保存在Authentication中。
UsernamePasswordAuthenticationFilter的校验过程为例,如下图所示
用户名密码被放在authRequest 中,作为入参递给AuthenticationManager的方法authenticate(authRequest)。

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
 }

在这里插入图片描述

1.1 Authentication 10.3

在这里插入图片描述
Authentication有两个作用
1.AuthenticationManager的输入,提供用户提供的用于身份验证的凭据。在此场景中使用时,isAuthenticated()返回false
2.表示当前已验证的用户。当前的身份验证可以从SecurityContext中获得。

包含信息:

  • principal -识别用户。当使用用户名/密码进行身份验证时,这通常是UserDetails的一个实例。.
  • credentials - 通常一个密码。在许多情况下,在对用户进行身份验证以确保其不泄漏之后,会清除此信息。.
  • authorities - GrantedAuthoritys是授予用户的高级权限。例如:角色或范围。

isAuthenticated() @return true if the token has been authenticated and the AbstractSecurityInterceptor does not need to present the token to the AuthenticationManager again for re-authentication.

if (authentication.isAuthenticated() && !alwaysReauthenticate) {
	return authentication;
}
// 否则
authentication = authenticationManager.authenticate(authentication);

1.2 SecurityContextHolder 10.1

我们从SecurityContextHolder中设置并获取身份认证信息Authentication。
在这里插入图片描述

Example 53. 手动设置身份验证信息

SecurityContext context = SecurityContextHolder.createEmptyContext(); 
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context); 

Example 54. 获取当前用户

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

在这里插入图片描述

org.springframework.security.core.context.SecurityContextHolder 是 SecurityContext的存放容器,默认使用ThreadLocal (MODE_THREADLOCAL)存储,意味SecurityContext在相同线程中的方法都可用。

private static void initialize() {
	if (!StringUtils.hasText(strategyName)) {
				// Set default
				strategyName = MODE_THREADLOCAL;
	}
	if (strategyName.equals(MODE_THREADLOCAL)) {
	    strategy = new ThreadLocalSecurityContextHolderStrategy();
	}
	……
}

成员变量 SecurityContextHolderStrategy strategy;
默认 ThreadLocalSecurityContextHolderStrategy
在这里插入图片描述

1.3 SecurityContext 10.2.

SecurityContextImpl实现接口SecurityContext 的两个方法,存放身份验证信息Authentication
在这里插入图片描述

2. 身份认证过程

一般在拦截器中会调用AuthenticationManager的authenticate方法。
在这里插入图片描述

2.1 AuthenticationManager 10.5

AuthenticationManager is the API that defines how Spring Security’s Filters perform authentication. The Authentication that is returned is then set on the SecurityContextHolder by the controller (i.e. Spring Security’s Filterss) that invoked the AuthenticationManager.

public interface AuthenticationManager {
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}

authenticate()方法主要做三件事:

  1. 如果验证通过,返回Authentication(通常带上authenticated=true)。
  2. 认证失败抛出AuthenticationException
  3. 如果无法确定,则返回null

AuthenticationManager的默认实现是ProviderManager,它委托一组AuthenticationProvider实例来实现认证 this.getProviders().iterator();
在这里插入图片描述

2.2 ProviderManager 10.6

在这里插入图片描述
在这里插入图片描述

ProviderManager包含一组AuthenticationProvider,执行authenticate时,遍历Providers,然后调用supports,如果支持,则执行遍历当前provider的authenticate方法,如果一个provider认证成功,则break,返回Authentication。

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
	for (AuthenticationProvider provider : getProviders()) {
		if (!provider.supports(toTest)) {
			continue;
		}
		result = provider.authenticate(authentication);
		if (result != null) {
			copyDetails(authentication, result);
			break;
		}
	}

	if (result == null && parent != null) {
		result = parentResult = parent.authenticate(authentication);
	}

	if (result != null) {
		if (eraseCredentialsAfterAuthentication
				&& (result instanceof CredentialsContainer)) {
			((CredentialsContainer) result).eraseCredentials();
		}
		if (parentResult == null) {
			eventPublisher.publishAuthenticationSuccess(result);
		}
		return result;
	}

}

从上面的代码可以看出, ProviderManager有一个可选parent,如果parent不为空,则调用parent.authenticate(authentication)

2.2.1 AuthenticationProvider 10.7

public interface AuthenticationProvider {
    Authentication authenticate(Authentication var1) throws    AuthenticationException;
    boolean supports(Class<?> var1);
}

Multiple AuthenticationProviders can be injected into ProviderManager.
Each AuthenticationProvider performs a specific type of authentication. For example, DaoAuthenticationProvider supports username/password based authentication while JwtAuthenticationProvider supports authenticating a JWT token.

AuthenticationProvider有多种实现,大家最关注的通常是DaoAuthenticationProvider,继承于abstract class AbstractUserDetailsAuthenticationProvider,核心是通过UserDetails来实现认证,DaoAuthenticationProvider默认会自动加载,不用手动配。
在这里插入图片描述

2.2.2 AbstractUserDetailsAuthenticationProvider

public abstract class AbstractUserDetailsAuthenticationProvider implements
		AuthenticationProvider, InitializingBean, MessageSourceAware

核心的authenticate 伪代码:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName();
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException notFound) {
				logger.debug("User '" + username + "' not found");
				throw notFound;
			}
		}

		try {
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				cacheWasUsed = false;
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}

		postAuthenticationChecks.check(user);

		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);

  • preAuthenticationChecks.check(user); 查看用户isAccountNonLocked,isEnabled,isAccountNonExpired
  • postAuthenticationChecks.check(user) 查看用户isCredentialsNonExpired

抽象方法

  • retrieveUser(username,authentication) 获取用户信息
  • additionalAuthenticationChecks 验证用户信息
protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(),
				authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());
		return result;
	}

2.2.3 DaoAuthenticationProvider 10.10.9

在这里插入图片描述

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    this.prepareTimingAttackProtection();
    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    if (loadedUser == null) {
        throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
    } else {
        return loadedUser;
    }
   
}
protected void additionalAuthenticationChecks(UserDetails userDetails,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException {
   String presentedPassword = authentication.getCredentials().toString();

   if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
      logger.debug("Authentication failed: password does not match stored value");
   }
}

所我们是从UserDetailsService.loadUserByUsername(username)来获取用户,
然后比较密码是否相同

2.2.4 UserDetailsService 10.10.9

public interface UserDetailsService {
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

默认四种实现,获取当前用户名的用户信息
在这里插入图片描述
可参考: Spring Security5 四种添加用户的方式

本文都是通过userDetailsService这种方式来添加用户

2.2.4.1 InMemoryUserDetailsManager 10.10.4

在这里插入图片描述
在配置文件中创建用户user/abc,admin/abc

@Override
    protected void configure (AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(users()).passwordEncoder(passwordEncoder());
    }
    @Bean
    public UserDetailsService users() {
        UserDetails user = User.builder()
                .username("user")
                .password("$2a$10$6Ed5ZXTH9DNwKzeFU07Ks.jGr3beIjU3o6mi12Jgh4Rh2t0goLbqO")
                .roles("USER")
                .build();
        UserDetails admin = User.builder()
                .username("admin")
                .password("$2a$10$6Ed5ZXTH9DNwKzeFU07Ks.jGr3beIjU3o6mi12Jgh4Rh2t0goLbqO")
                .roles("USER", "ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }

用户信息被存放在final Map<String, MutableUserDetails> users 中

public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException {
		UserDetails user = users.get(username.toLowerCase());

		if (user == null) {
			throw new UsernameNotFoundException(username);
		}

		return new User(user.getUsername(), user.getPassword(), user.isEnabled(),
				user.isAccountNonExpired(), user.isCredentialsNonExpired(),
				user.isAccountNonLocked(), user.getAuthorities());
	}

然后用loadUserByUsername获取

2.2.4.2 JdbcUserDetailsManager 10.10.5

JdbcUserDetailsManager继承JdbcDaoImpl

 @Bean
 UserDetailsManager users() {
     UserDetails user = User.builder()
             .username("user")
             .password("$2a$10$6Ed5ZXTH9DNwKzeFU07Ks.jGr3beIjU3o6mi12Jgh4Rh2t0goLbqO")
             .roles("USER")
             .build();
     JdbcUserDetailsManager users = new JdbcUserDetailsManager(datasource);
     users.createUser(user);
     return users;
 }

users.createUser(user)会在数据库中创建这个用户和他的权限

public void createUser(final UserDetails user) {
		validateUserDetails(user);
        // 创建用户
		getJdbcTemplate().update(createUserSql, ps -> {
			ps.setString(1, user.getUsername());
			ps.setString(2, user.getPassword());
			ps.setBoolean(3, user.isEnabled());

			int paramCount = ps.getParameterMetaData().getParameterCount();
			if (paramCount > 3) {
				//NOTE: acc_locked, acc_expired and creds_expired are also to be inserted
				ps.setBoolean(4, !user.isAccountNonLocked());
				ps.setBoolean(5, !user.isAccountNonExpired());
				ps.setBoolean(6, !user.isCredentialsNonExpired());
			}
		});
      
		if (getEnableAuthorities()) {
		    // 创建权限
			insertUserAuthorities(user);
		}
	}
2.2.4.3 CustomUserDetailsService 10.10.7

创建CustomUserDetailsService继承UserDetailsService

@Override
protected void configure (AuthenticationManagerBuilder auth) throws Exception{
    auth.userDetailsService(customUserDetailsService()).passwordEncoder(passwordEncoder());
}
@Bean
CustomUserDetailsService customUserDetailsService() {
    return new CustomUserDetailsService();
}
2.2.4.4 LDAP Authentication 10.10.10.

3. 创建认证管理

3.1 AuthenticationManagerBuilder

在这里插入图片描述
从名字可以看出AuthenticationManagerBuilder就是用来创建AuthenticationManager的

@Override
protected void configure (AuthenticationManagerBuilder auth) throws Exception{
    auth.userDetailsService(customUserDetailsService()).passwordEncoder(passwordEncoder());
}
    public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(T userDetailsService) throws Exception {
        this.defaultUserDetailsService = userDetailsService;
        return (DaoAuthenticationConfigurer)this.apply(new DaoAuthenticationConfigurer(userDetailsService));
    }

使用userDetailsService()添加用户时(假设CustomUserDetailsService)会返回

DaoAuthenticationConfigurer<AuthenticationManagerBuilder, CustomUserDetailsService> 对象
在这里插入图片描述

3.2 ProviderManagerBuilder

public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>> extends SecurityBuilder<AuthenticationManager> {
    B authenticationProvider(AuthenticationProvider var1);
}

AuthenticationManagerBuilder的实现

public AuthenticationManagerBuilder authenticationProvider(AuthenticationProvider authenticationProvider) {
    this.authenticationProviders.add(authenticationProvider);
    return this;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章