【Spring Security】 密码编码流程
【Spring Security】 密码编码流程
Metadata
title: 【Spring Security】 密码编码流程
date: 2023-02-02 14:01
tags:
- 行动阶段/完成
- 主题场景/组件
- 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
- 细化主题/Module/SpringSecurity
categories:
- SpringSecurity
keywords:
- SpringSecurity
description: 【Spring Security】 密码编码流程
【Spring Security】 密码编码流程
流程分析
1. 注入密码解析器
/**
* 注入密码解析器到IOC中
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
2. DaoAuthenticationProvider
DaoAuthenticationProvider 有一个属性 PasswordEncoder,就是用来校验数据库密码的。创建 DaoAuthenticationProvider 的时候,会设置 PasswordEncoder 委派对象。
public DaoAuthenticationProvider() {
this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
使用 PasswordEncoderFactories 工厂创建委派的 DelegatingPasswordEncoder,首先会将所有 security 支持的加密解析器放入一个 Map 中。
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new LdapShaPasswordEncoder());
encoders.put("MD4", new Md4PasswordEncoder());
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new StandardPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
3. DelegatingPasswordEncoder
接下来使用 DelegatingPasswordEncoder 的构造方法创建对象。该对象维护了所有了密码解析器。
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
if (idForEncode == null) {
throw new IllegalArgumentException("idForEncode cannot be null");
}
if (!idToPasswordEncoder.containsKey(idForEncode)) {
throw new IllegalArgumentException(
"idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
}
for (String id : idToPasswordEncoder.keySet()) {
if (id == null) {
continue;
}
if (id.contains(PREFIX)) {
throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX);
}
if (id.contains(SUFFIX)) {
throw new IllegalArgumentException("id " + id + " cannot contain " + SUFFIX);
}
}
// 密码解析器的ID bcrypt
this.idForEncode = idForEncode;
// 密码解析器
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
// 所有的密码解析器
this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);
}
5. InitializeUserDetailsBeanManagerConfigurer
之后进入 InitializeUserDetailsBeanManagerConfigurer 类的 configure 方法进行相关配置。这里主要获取数据库查询认证时需要的一些 bean 对象,比如 UserDetailsService、PasswordEncoder、UserDetailsPasswordService,并把这个对象赋值给 DaoAuthenticationProvider,此时我们注入的密码解析器就和验证管理器相关联了。
@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;
private final ApplicationContext context;
/**
* @param context
*/
InitializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
this.context = context;
}
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.apply(new InitializeUserDetailsManagerConfigurer());
}
class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
// 获取UserDetailsService Bean
UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);
if (userDetailsService == null) {
return;
}
// 获取PasswordEncoder Bean
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
}
/**
* @return a bean of the requested class if there's just a single registered
* component, null otherwise.
* 获取Bean
*/
private <T> T getBeanOrNull(Class<T> type) {
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
if (beanNames.length != 1) {
return null;
}
return InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
}
}
6. 密码校验
加载完成后,用户输入账号密码进行登录,最后密码校验调用的是 DaoAuthenticationProvider 的 additionalAuthenticationChecks 方法。第五步中我们获取到了注入的密码解析器,并交给了 DaoAuthenticationProvider,那么这些就可以调用注入的密码解析器,并使用 matches 方法校验数据库查询出来的和输入的是否匹配,密码错误时,抛出异常,正确则返回认证信息。流程结束。
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"));
} else {
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"));
}
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 蝶梦庄生!
评论