首页 > 解决方案 > 带有 CustomAuthenticationProvider 和 CustomPasswordEncoder 的 Spring-Boot

问题描述

我不清楚如何将我的 CustomPasswordEncoder 粘贴到 Spring Boot 的身份验证过程中。我在配置中定义 spring boot 应该使用我的 CustomAuthenticationProvider 和我的 UserDetailsS​​ervice 和我的 CustomPasswordEncoder

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;


    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {

        builder.authenticationProvider(customAuthenticationProvider)
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        PasswordEncoder encoder = new CustomPasswordEncoder();
        return encoder;
    }       
}

我的 CustomPasswordEncoder 将编码为 md5 值(我知道它不安全,但它是一个遗留数据库)

@Component
public class CustomPasswordEncoder implements PasswordEncoder{

    @Override
    public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());

    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {

        return rawPassword.toString().equals(encodedPassword);
    }
}

在 CustomAuthtenticationProvider 中将完成身份验证检查。传递的密码将使用 passwordEncoder.encode() 进行编码。用户将从数据库中获取,然后我再次使用 passwordEncoder 进行匹配。如果匹配成功,则将生成身份验证对象。

    @Component
    public class CustomAuthenticationProvider implements AuthenticationProvider {

        @Autowired
        private UserServiceImpl userService;
        @Autowired
        private CustomPasswordEncoder passwordEncoder;



        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            System.out.println("authentication = [" + authentication + "]");
            String name = authentication.getName();
            Object credentials = authentication.getCredentials();

            String password = credentials.toString();
            //why is this necessary isnt it called automatically?
            String passwordEncoded = passwordEncoder.encode((CharSequence) credentials);
            Optional<UserEntity> userOptional = userService.findByUsername(name);

            if (userOptional.isPresent() && passwordEncoder.matches(passwordEncoded, userOptional.get().getPassword())) {
                List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
                grantedAuthorities.add(new SimpleGrantedAuthority(userOptional.get().getRoles().toString()));
                Authentication auth = new
                        UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
                return auth;
            }
            else{
                throw new BadCredentialsException("Authentication failed for " + name);
            }
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return authentication.equals(UsernamePasswordAuthenticationToken.class);
        }
    }

这是正确的方法吗?我认为 CustomPasswordEncoder 将“自动”使用,或者仅当您使用提供的 authenticationProviders 之一(如 jdbcAuthenticationProvider)时才会使用这种情况。也许有人可以解释身份验证过程的事件顺序。我在网上做了一些研究,但我仍然无法详细了解这一点。

标签: javaspringspring-bootspring-security

解决方案


首先,您可以从该matches方法中看到,它使用编码密码验证原始密码(因此由用户输入)。所以编码的代码属于matches方法而不是你现在拥有的。

public class CustomPasswordEncoder implements PasswordEncoder{

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

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        String rawEncoded = encode(rawPassword);
        return Objects.equals(rawEncoded, encodedPassword);
    }
}

现在您可以从代码中删除编码行/步骤。

但是,您实际上并不需要自定义AuthenticationProvider,因为通常只有在添加其他身份验证机制(如 LDAP 或 OAuth)时才需要自定义。

您需要的是一个适配器,供您UserService使用UserDetailsService并使用它。我认为UserDetailsServiceImpl正是这样做的。如果没有,您可以使用类似下面的代码。

public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserService delegate;

    public UserDetailsServiceAdapter(UserService delegate) {
        this.delegate=delegate;
    }

    public UserDetails loadUserByUsername(String username) {
       reutrn userService.findByUsername(name)
                 .map(this::toUserDetails).orElseThrow(() -> new UsernameNotFoundException("Unknown user " + username);
    }

    private UserDetails toUserDetails(User user) {
        Set<GrantedAuthority> authorities = new HashSet<>();
        user.getRoles().forEach(r -> authorities.add(new SimpleGrantedAuthority(r));
         return new UserDetails(user.getUsername(), user.getPassword(), authorities);
    }
}

现在您可以PasswordEncoder在配置中使用您的和此适配器,并且不需要您的自定义AuthenticationProvider.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {

        builder.userDetailsService(userDetailsService)
               .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        PasswordEncoder encoder = new CustomPasswordEncoder();
        return encoder;
    }       
}

推荐阅读