You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security中如何对Basic Auth传入密码执行SHA256加盐加密并与数据库存储密码比对

Solution for Custom SHA256 + Per-User Salt Validation with Spring Security Basic Auth

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 SecureRandom for this), then call passwordEncoder().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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 22:17:32