spring-security Spring安全性中的自定义消息,包含UserDetailsService实现和异常

jpfvwuh4  于 2022-11-11  发布在  Spring
关注(0)|答案(2)|浏览(180)

我正在使用Spring Security 2.0.7。它已经用preAuthenticatedUserDetailsService bean实现了UserDetailsService
它运行的很好。现在我想添加一个新的自定义错误消息。在方法loadUserByUsername中,我想添加一些自定义的业务逻辑。例如,基于某个属性,我不想让用户登录,所以我抛出了一个带有自定义消息的UsernameNotFoundException
Spring负责处理异常并将其设置为会话,但当我使用“SPRING_SECURITY_LAST_EXCEPTION”键从会话中检索异常时,我收到了“Bad credentials”消息。
此刻之所以定着一个龌龊的变通办法,主要是我想弄明白究竟发生了什么事!
想法?
Ps.我在SO中读到了很多这个问题,但大部分都是关于Spring Security 3. 0的

kx1ctssn

kx1ctssn1#

只是为了关闭它.正如@M.代努姆在注解中提到的. Spring处理这种方式是出于安全目的.实现可以在ExceptionTranslationFilter类中看到.

byqmnocz

byqmnocz2#

这个问题困扰了我一段时间,才找到一个有效而简单的解决方案
通过使用自定义AuthenticationFailureHandler(已添加到HttpSecurity配置中)从UserDetailsService获取自定义错误消息,然后将其作为错误参数发送回登录页,解决了此问题
自定义AuthenticationFailureHandler的定义如下:

@Component("CustomAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        String encodedErrorMessage = null;
        // Get AuthenticationException that was thrown in UserDetailsService, retrieve the error message and attach it as encoded error parameter of the error login page
        if (exception != null) {
            String errorMessage = exception.getMessage();
            encodedErrorMessage = Base64.getUrlEncoder().encodeToString(errorMessage.getBytes());
        }
        redirectStrategy.sendRedirect(request, response, "/login?error=" + encodedErrorMessage);
    }
}

然后,在WebSecurityConfigurerAdapter中配置自定义验证失败处理程序(同时更新configureGlobal),如下所示:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and().formLogin().loginPage("/login").permitAll()
                .failureHandler(customAuthenticationFailureHandler) // Add failure handler class
        // ...
        // ...
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider());
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    public AuthenticationProvider authProvider() {
        DaoAuthenticationProvider impl = new DaoAuthenticationProvider();
        impl.setUserDetailsService(userDetailsService);
        impl.setPasswordEncoder(bCryptPasswordEncoder());
        // setHideUserNotFoundExceptions is set to false in order to get the exceptions CustomAuthenticationFailureHandler  
        impl.setHideUserNotFoundExceptions(false);
        return impl;
    }
}

与用户帐户相关的异常(UsernameNotFoundException)总是在UserDetailsService接口中抛出:

@Service
public class UserDetailsServiceImpl implements UserDetailsService{
    private UserDetails getUserByUsername(String username) {
        UserDTO userDTO = userService.findByUsername(username);
        if (userDTO == null) {
            // Custom error message when no account was found by the given username 
            throw new UsernameNotFoundException("No user account was found by the username [" + username + "]");
        }
        Date expirationDate = userDTO.getExpirationDate();
        if(expirationDate != null) {
            if(expirationDate.before(new Date())) {
                // Custom error message when the account is expired 
                throw new UsernameNotFoundException("The user account [" + username + "] is expired");
            }
        }
        // Can add more UsernameNotFoundException with custom messages based on functional requirements
        // ...
        List<GrantedAuthority> grantedAuthorities = getGrantedAutorities(userDTO);
        return new org.springframework.security.core.userdetails.User(userDTO.getUsername(), userDTO.getPassword(), grantedAuthorities);
    }
}

异常在CustomAuthenticationFailureHandler中处理,以便发送如下所示的自定义错误消息(使用Base64编码):

@Component("CustomAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        String encodedErrorMessage = null;
        // Get AuthenticationException that was thrown in UserDetailsService, retrieve the error message and attach it as encoded error parameter of the error login page
        if (exception != null) {
            String errorMessage = exception.getMessage();
            encodedErrorMessage = Base64.getUrlEncoder().encodeToString(errorMessage.getBytes());
        }
        redirectStrategy.sendRedirect(request, response, "/login?error=" + encodedErrorMessage);
    }
}

然后在控制器中检索错误消息,如下所示:

@GetMapping(value = {"/login", "/"})
public String login(Model model, @RequestParam(name="error", required = false) String error) {
    if (error != null) {
        byte[] decodedBytes = Base64.getDecoder().decode(error);
        String decodedString = new String(decodedBytes);
        model.addAttribute("error", decodedString);
    }

    return "login";
}

希望这对任何有同样问题的人有帮助

相关问题