Spring Boot 2.7.3:命令行传参密码哈希后认证失败求助
问题描述
需求是通过命令行传入明文密码,在Spring Boot应用中完成哈希处理后用于用户认证,但使用admin/admin登录时抛出BadCredentialsException。
操作步骤与配置
- 命令行启动参数:
mvn spring-boot:run -Dspring-boot.run.arguments="--user.password=admin"
application.yaml安全配置片段:
security: user: name: admin password: ${user.password}
- 自定义密码编码器
MyPasswordEncoder:
class MyPasswordEncoder : PasswordEncoder { private val logger = LoggerFactory.getLogger(javaClass) // 需确保以下常量已正确定义 private val argon2PasswordEncoder = Argon2PasswordEncoder(SALT_LENGTH, HASH_LENGTH, PARALLELISM, MEMORY_USE, ITERATIONS) override fun encode(decryptedPassword: CharSequence?): String { val hashedPassword : String = argon2PasswordEncoder.encode(decryptedPassword) logger.debug("Password Encoding Successful!") return hashedPassword } override fun matches(rawPassword: CharSequence?, encodedPassword: String?): Boolean { return argon2PasswordEncoder.matches(rawPassword, encodedPassword) } }
- 未使用的委托编码器类
MyPasswordDelegation:
class MyPasswordDelegation { fun createDelegatingPasswordEncoder(): PasswordEncoder { val idForEncode = "myEncoder" val encoders: MutableMap<String, PasswordEncoder> = mutableMapOf() encoders[idForEncode] = NCaaSPasswordEncoder() // 此处疑似笔误,应为MyPasswordEncoder() return DelegatingPasswordEncoder(idForEncode, encoders) } }
- 安全配置类
SecurityConfig:
@Configuration @EnableWebSecurity class SecurityConfig { @Value("\${spring.security.user.name}") private val userName: String? = null @Value("\${spring.security.user.password}") private val password: String? = null @Autowired lateinit var appAuthenticationEntryPoint: AppAuthenticationEntryPoint @Bean fun passwordEncoder(): MyPasswordEncoder { return MyPasswordEncoder() } @Bean @Throws(Exception::class) fun userDetailsService(): InMemoryUserDetailsManager? { val userDetails : UserDetails = User.withUsername(userName).password(passwordEncoder().encode(password)).roles("USER").build() return InMemoryUserDetailsManager(userDetails) } @Throws(Exception::class) @Bean fun filterChain(httpSecurity : HttpSecurity): SecurityFilterChain { httpSecurity.csrf().disable() // Allow only HTTPS Requests httpSecurity.requiresChannel { channel -> channel.anyRequest().requiresSecure() }.authorizeRequests { authorize -> authorize.anyRequest().fullyAuthenticated().and().httpBasic().and().exceptionHandling().authenticationEntryPoint(appAuthenticationEntryPoint) } return httpSecurity.build() } }
错误日志
Failed to process authentication request org.springframework.security.authentication.BadCredentialsException: Bad credentials at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:79) at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:147) at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201) at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:172)
问题分析与解决
1. 过滤器链配置写法错误
原filterChain方法中,authorizeRequests的Lambda链式写法逻辑错误,导致httpBasic认证未被正确添加到过滤器链中,认证流程无法正常触发。
修复代码:
@Throws(Exception::class) @Bean fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain { httpSecurity.csrf().disable() // 仅允许HTTPS请求 .requiresChannel { channel -> channel.anyRequest().requiresSecure() } // 配置权限规则 .authorizeRequests { authorize -> authorize.anyRequest().fullyAuthenticated() } // 启用HTTP Basic认证 .httpBasic() // 配置认证异常处理 .and() .exceptionHandling() .authenticationEntryPoint(appAuthenticationEntryPoint) return httpSecurity.build() }
2. 避免重复创建PasswordEncoder实例
原userDetailsService方法中直接调用passwordEncoder()方法,虽@Bean默认是单例,但在配置类代理失效的场景下可能创建新实例,导致编码与匹配使用的编码器实例不一致(虽参数相同,但仍存在未知风险)。
修复代码:
通过注入方式获取PasswordEncoder实例,而非直接调用@Bean方法:
@Configuration @EnableWebSecurity class SecurityConfig { @Value("\${spring.security.user.name}") private val userName: String? = null @Value("\${spring.security.user.password}") private val password: String? = null @Autowired lateinit var appAuthenticationEntryPoint: AppAuthenticationEntryPoint @Autowired private lateinit var passwordEncoder: PasswordEncoder @Bean fun passwordEncoder(): MyPasswordEncoder { return MyPasswordEncoder() } @Bean @Throws(Exception::class) fun userDetailsService(): InMemoryUserDetailsManager? { val encodedPassword = passwordEncoder.encode(password) // 添加日志验证编码结果 LoggerFactory.getLogger(javaClass).info("Encoded password for user {}: {}", userName, encodedPassword) val userDetails: UserDetails = User.withUsername(userName) .password(encodedPassword) .roles("USER") .build() return InMemoryUserDetailsManager(userDetails) } // 修复后的filterChain方法同上 }
3. 验证Argon2参数合法性
确保MyPasswordEncoder中的Argon2参数为合法值,示例配置如下:
class MyPasswordEncoder : PasswordEncoder { private val logger = LoggerFactory.getLogger(javaClass) private const val SALT_LENGTH = 16 private const val HASH_LENGTH = 32 private const val PARALLELISM = 4 private const val MEMORY_USE = 65536 // 64MB private const val ITERATIONS = 3 private val argon2PasswordEncoder = Argon2PasswordEncoder(SALT_LENGTH, HASH_LENGTH, PARALLELISM, MEMORY_USE, ITERATIONS) // encode和matches方法不变 }
4. 清理无用代码
MyPasswordDelegation类未被使用,且存在疑似笔误(NCaaSPasswordEncoder应为MyPasswordEncoder),可直接删除该类,避免混淆。
验证步骤
- 启动应用后,查看日志中输出的
Encoded password for user admin: xxxx,确认是标准的Argon2格式字符串(如$argon2id$v=19$m=65536,t=3,p=4$xxxx$xxxx)。 - 使用
admin/admin发起HTTP Basic认证请求,验证是否能正常通过认证。
内容的提问来源于stack exchange,提问作者hell_storm2004
相关产品推荐
相关产品推荐

