Spring Security中如何对Basic Auth传入密码执行SHA256加盐加密并与数据库存储密码比对
Got it, let's break down how to make this work. Your core challenge is that Spring Security's default password encoders don't support per-user salts stored in a separate database column. We need to build three key components: a custom UserDetails to carry the user's salt, a modified UserDetailsService to fetch that salt, and a custom PasswordEncoder that uses the salt to validate the incoming plaintext password.
Step 1: Create a Custom UserDetails Class
The default User class from Spring Security doesn't include a salt field, so we'll extend it to hold the user's salt fetched from the database:
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import java.util.Collection; public class CustomUserDetails extends User { private final String salt; public CustomUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities, String salt) { super(username, password, authorities); this.salt = salt; } public String getSalt() { return salt; } }
Step 2: Fix Your CustomUserDetailsService
Your current code is re-encoding the already-hashed password from the database—this will break validation! Instead, we'll return our custom CustomUserDetails with the raw stored hash and the user's salt:
@Component public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUserName(username); if (user == null) { throw new UsernameNotFoundException("User not found: " + username); } // Return our custom UserDetails with the stored hash and salt return new CustomUserDetails( username, user.getPassword(), // Directly use the SHA256 hash from your database List.of(() -> "ROLE_USER"), // Adjust roles to match your actual setup user.getSalt() // Pass the user's unique salt ); } }
Step 3: Build a Custom PasswordEncoder
This is the core logic where we take the incoming plaintext password, append the user's salt, hash it with SHA256, and compare it to the stored hash:
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.nio.charset.StandardCharsets; public class SaltedSha256PasswordEncoder implements PasswordEncoder { // Use this method when registering users to generate the hashed password public String encode(CharSequence rawPassword, String salt) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); String combinedInput = rawPassword.toString() + salt; byte[] hashBytes = digest.digest(combinedInput.getBytes(StandardCharsets.UTF_8)); // Convert byte array to hex string StringBuilder hexBuilder = new StringBuilder(); for (byte b : hashBytes) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) hexBuilder.append('0'); hexBuilder.append(hex); } return hexBuilder.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("SHA-256 algorithm unavailable", e); } } // Default encode method (unused for validation, throws error to prevent misuse) @Override public String encode(CharSequence rawPassword) { throw new UnsupportedOperationException("Use encode(rawPassword, salt) for user registration instead"); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { // Fetch the current user's salt from the SecurityContext Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (!(principal instanceof CustomUserDetails)) { return false; } CustomUserDetails userDetails = (CustomUserDetails) principal; String userSalt = userDetails.getSalt(); // Compute the hash of the incoming password + user's salt String computedHash = encode(rawPassword, userSalt); // Compare with the stored hash from the database return computedHash.equals(encodedPassword); } }
Step 4: Update SecurityConfig to Use the Custom Encoder
Replace the default DelegatingPasswordEncoder with our custom one:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { private final UserDetailsService customUserDetailsService; public SecurityConfig(UserDetailsService customUserDetailsService) { this.customUserDetailsService = customUserDetailsService; } @Bean public PasswordEncoder passwordEncoder() { return new SaltedSha256PasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll() .anyRequest().authenticated() .and().httpBasic(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService) .passwordEncoder(passwordEncoder()); // Wire up our custom encoder } }
Key Notes
- User Registration: When creating a new user, generate a unique, random salt (use
SecureRandomfor this), then callpasswordEncoder().encode(plaintextPassword, salt)to get the hash. Store both the hash and salt in your database. - Security Improvement: SHA256 is fine for basic use cases, but for stronger security, consider using an adaptive hashing algorithm like PBKDF2 or Argon2, which adds iteration counts to slow down brute-force attacks.
- Avoid Re-encoding: Never re-encode the stored hash from your database—this was a critical mistake in your original code that would have prevented valid passwords from being matched.
内容的提问来源于stack exchange,提问作者SorryForAsking

