SpringSecurity - 启动流程分析(七)


活动地址:CSDN21天学习挑战赛

前言

在上篇文章 SpringSecurity - 启动流程分析(六) 中,我们知道了 UsernamePasswordAuthenticationFilter 会通过 DaoAuthenticationProvider 中的 authenticate() 方法完成认证,其实是通过父类 AbstractUserDetailsAuthenticationProviderauthenticate() 方法

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		...

		if (user == null) {
			cacheWasUsed = false;

			try {
				// 核心流程,由子类实现
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			...
		}
}

// 由子类 DaoAuthenticationProvider 实现
protected abstract UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException;

其中又调用了 DaoAuthenticationProvider 实现的 retrieveUser() 方法。

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;
	}
	catch (UsernameNotFoundException ex) {
		mitigateAgainstTimingAttack(authentication);
		throw ex;
	}
	catch (InternalAuthenticationServiceException ex) {
		throw ex;
	}
	catch (Exception ex) {
		throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
	}
}

UserDetailsService

至于是什么时候初始化的这个 UserDetailsService,在上篇文章中有提到这么一个配置类 InitializeUserDetailsBeanManagerConfigurer,其中有一个内部类用于处理 UserDetailsService 的赋值工作:

class InitializeUserDetailsManagerConfigurer
			extends GlobalAuthenticationConfigurerAdapter {
	@Override
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		if (auth.isConfigured()) {
			return;
		}
		// 从容器中获取 UserDetailsService
		UserDetailsService userDetailsService = getBeanOrNull(
				UserDetailsService.class);
		if (userDetailsService == null) {
			return;
		}

		PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
		UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);

		// 初始化 DaoAuthenticationProvider
		DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
		provider.setUserDetailsService(userDetailsService);
		if (passwordEncoder != null) {
			provider.setPasswordEncoder(passwordEncoder);
		}
		if (passwordManager != null) {
			provider.setUserDetailsPasswordService(passwordManager);
		}
		provider.afterPropertiesSet();

		auth.authenticationProvider(provider);
	}

	...
}

查看 spring.factories 中的自动配置类

所以要实现自定义认证功能,我们只需要实现 UserDetailsService 接口即可,并且 SpringSecurity 默认给我们初始化了一个 UserDetailsService 的实现类在 IoC 容器 中。我们来看一下 spring-boot-autoconfigure 包中的 spring.factories 中关于 SpringSecurity 的自动配置类:

在这里插入图片描述

  • SecurityAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
// 开启配置参数 SecurityProperties
@EnableConfigurationProperties(SecurityProperties.class)
// 条件注入 WebSecurityConfigurerAdapter
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
		SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
		return new DefaultAuthenticationEventPublisher(publisher);
	}

}

查看 SecurityProperties

@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {

	...

	private User user = new User();

	public User getUser() {
		return this.user;
	}

	...

	public static class User {

		/**
		 * Default user name.
		 */
		private String name = "user";

		/**
		 * Password for the default user name.
		 */
		private String password = UUID.randomUUID().toString();

		/**
		 * Granted roles for the default user name.
		 */
		private List<String> roles = new ArrayList<>();

		...

	}

}

可以看到,如果配置文件中没有配置 spring.security 相关的值,会默认初始化一个 user,用户名为 user,密码自动生成。

  • UserDetailsServiceAutoConfiguration

从命名上就可以看出,UserDetailsServiceAutoConfigurationSpringBoot 给我们提供的默认的 UserDetailsService 的自动配置类:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
		value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class },
		type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
				"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" })
public class UserDetailsServiceAutoConfiguration {

	private static final String NOOP_PASSWORD_PREFIX = "{noop}";

	// 正则表达式预编译 
	private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");

	private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);

	@Bean
	@ConditionalOnMissingBean(
			type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
	// 同时注意,这里是 懒加载 的,如果我们自定义了,这个就不会初始化到 IoC 容器中了
	@Lazy
	public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
			ObjectProvider<PasswordEncoder> passwordEncoder) {
		SecurityProperties.User user = properties.getUser();
		List<String> roles = user.getRoles();
		// 这里会初始化一个 InMemoryUserDetailsManager 到 IoC 容器中
		return new InMemoryUserDetailsManager(
				User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
						.roles(StringUtils.toStringArray(roles)).build());
	}

	// 密码加密
	private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
		String password = user.getPassword();
		if (user.isPasswordGenerated()) {
			logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
		}
		if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
			return password;
		}
		return NOOP_PASSWORD_PREFIX + password;
	}

}
  • SecurityFilterAutoConfiguration

至于这个自动配置类,是为了指定 springSecurityFilterChain 这个过滤器的顺序和分发类型的,详细的可以查看 DelegatingFilterProxyRegistrationBean

AbstractUserDetailsAuthenticationProvider

通过上面的分析,我们知道了 UserDetailsService 的加载过程,接下来我们返回 DaoAuthenticationProviderauthenticate() 方法继续分析:

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
	...
	// 这个 user 就是通过 UserDetailsService 的 loadUserByUsername() 方法返回的 UserDetails 对象
	Object principalToReturn = user;

	// 可以通过设置这个值使 pricipal 中设置用户名或用户对象
	if (forcePrincipalAsString) {
		principalToReturn = user.getUsername();
	}

	// 创建认证成功的返回对象
	return createSuccessAuthentication(principalToReturn, authentication, user);
}

认证成功会返回一个 Authentication

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;
}

注意: 认证方法的入参也是一个 Authentication !!!,Authentication 贯穿全文啊

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值