Spring Security Как получить username из PasswordEncoder?

Во время добавления нового юзера, я сохраняю его пароль в базе, в виде SHA256(cleartext_password + salt), значение salt сохраняю рядом в отдельную колонку для каждого юзера. Соответственно, проверка пароля должна выполняться следующим образом: вытаскиваем клеартекст пароля из BasicAuth заголовка, достаем из БД значение salt для этого логина, применяем SHA256(cleartext_password + salt), проверяем, что получившаяся строка и строка, хранящаяся в БД совпали. Для того чтобы это реализовать, я хочу создать костомный PasswordEncoder, в котором буду выполнять проверку, но в него Не приходит username, по которому я мог бы вытащить salt для этого юзера, которая нужна для проверки. Как можно выйти из этой ситуации?

Вот текущая реализации Security:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final UserDetailsService customUserDetailsService;

    public SecurityConfig(UserDetailsService customUserDetailsService) {
        this.customUserDetailsService = customUserDetailsService;
    }

    @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(new CustomPasswordEncoder());
    }

}

@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) {
            return org.springframework.security.core.userdetails.
                    User.withUsername(username)
                    .password(new CustomPasswordEncoder().encode(user.getPassword()))
                    .roles("USER")
                    .build();
        }
        throw new IllegalArgumentException();
    }
}

public class CustomPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        String sha256hex = DigestUtils.sha256Hex(rawPassword.toString()); // Здеь должно быть DigestUtils.sha256Hex(rawPassword.toString() + salt)
        return sha256hex.equals(encodedPassword);
    }

}

Ответы (1 шт):

Автор решения: Gleb Kuznetsov

Можно написать свой AuthenticationProvider

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
        
        private final UserDetailsService customUserDetailsService;

        public AuthenticationProvider(UserDetailsService customUserDetailsService) {
            this.customUserDetailsService = customUserDetailsService;
        }

        @Override
        public Authentication authenticate(Authentication authentication) 
          throws AuthenticationException {
     
            String name = authentication.getName();
            String password = authentication.getCredentials().toString();
            
            // тут идет вся логика с обращением в customUserDetailsService,
            // проверка пароля и т.д.
            ...

            // Для примера, если авторизация не удалась - вернуть null
            // name и password тут не обязательно передавать. 
            // Токен нужен для дальнейших проверок, если они конечно есть. 
            // Смотри javadoc этого конструктора.
            return new UsernamePasswordAuthenticationToken(
                  name, password, new ArrayList<>());
            
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return authentication.equals(UsernamePasswordAuthenticationToken.class);
        }
    }

Далее нужно передать его в AuthenticationManagerBuilder:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomAuthenticationProvider authProvider;
    
    public SecurityConfig(CustomAuthenticationProvider authProvider) {
        this.authProvider = authProvider;
    }
    
    ...
  
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider);
    }

}
→ Ссылка