是否可以覆盖弹簧安全认证错误消息

Posted

技术标签:

【中文标题】是否可以覆盖弹簧安全认证错误消息【英文标题】:Is it possible to override the spring security authentication error message 【发布时间】:2020-11-06 23:51:14 【问题描述】:

我们使用的是 Spring Boot 2.2.2。

当用户输入错误凭据时,我想在 json 响应中返回自定义错误消息以及 401 状态代码,因为我执行了以下步骤。

WebSecurityConfig.java:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

    private final UserDetailsService userDetailsService;

    private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    public WebSecurityConfig(RepositoryUserDetailsService userDetailsService RestAuthenticationEntryPoint restAuthenticationEntryPoint) 
        this.userDetailsService = userDetailsService;
        this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
    

    @Override
    public void configure(final WebSecurity web) 
        web.ignoring()
                .antMatchers(HttpMethod.OPTIONS, "/**")
                .antMatchers("/proj-name/**/*.js,html")
                .antMatchers("/test/**");

    

    @Override
    protected void configure(HttpSecurity http) throws Exception 

        http
                .csrf()
                .disable()
                .exceptionHandling()
                .authenticationEntryPoint(restAuthenticationEntryPoint)
                .and()
                .headers()
                .frameOptions()
                .disable()
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/login").permitAll()
                .antMatchers("/error").permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    

 @Bean
    RestAuthenticationEntryPoint authenticationEntryPoint() 
        return new RestAuthenticationEntryPoint();
    

    @Bean
    AuthenticationEntryPoint forbiddenEntryPoint() 
        return new HttpStatusEntryPoint(FORBIDDEN);
    

    @Bean
    public CustomAuthenticationProvider customDaoAuthenticationProvider() throws Exception 
        CustomAuthenticationProvider customDaoAuthenticationProvider = new CustomAuthenticationProvider();
        customDaoAuthenticationProvider.setPasswordEncoder(new PasswordEncoderConfig().encoder());
        customDaoAuthenticationProvider.setUserDetailsService(userDetailsService);
        return customDaoAuthenticationProvider;
    

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.authenticationProvider(customDaoAuthenticationProvider());
    

RestAuthenticationEntryPoint.java

@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint 
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException 
        httpServletResponse.addHeader("WWW-Authenticate", "Basic realm=" + "");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = httpServletResponse.getWriter();
        writer.println("HTTP Status 401 - " + e.getMessage());
    

CustomAuthenticationProvider.java

public class CustomAuthenticationProvider extends DaoAuthenticationProvider 

    @Autowired
    private PasswordEncoder passwordEncoder;

@Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication) throws AuthenticationException 
        if (authentication.getCredentials() == null) 
            logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "test bad credentials"));
        

        String presentedPassword = authentication.getCredentials().toString();
        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) 
            throw new BadCredentialsException(
                    messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Invalid credentials"));
        
        
        
        

当我点击/login api 时返回以下响应:Full authentication is required to access this resource

如果我添加 .antMatchers("/error").permitAll() 将返回以下响应。


    "timestamp": 1594970514264,
    "status": 401,
    "error": "Unauthorized",
    "message": "Unauthorized",
    "path": "/login"

我尝试在 CustomAuthenticationProvider 上添加 @Component 注释,然后它在服务器启动时抛出以下错误:java.lang.IllegalArgumentException: A UserDetailsService must be set

我尝试按照url 添加CustomAuthenticationFailureHandler,但没有成功。我看过几篇文章,但这些例子也没有奏效。我还尝试使用 @ControllerAdvice 注释创建异常处理程序类,但仍然无效。是否有任何其他方法可以覆盖默认错误消息,或者我是否缺少任何注释或配置?提前感谢您的帮助!

【问题讨论】:

尝试将antMatchers(HttpMethod.GET, "/login").permitAll()更改为antMatchers("/login").permitAll() 感谢您的建议。我试过它仍然返回"message": "Unauthorized", 【参考方案1】:

您可以使用控制器建议来处理服务引发的错误:

@ControllerAdvice
@Order(HIGHEST_PRECEDENCE)
public class AppointmentErrorController 

    @ExceptionHandler(BadCredentialsException.class)
    public ResponseEntity<ApiError> handleError(HttpServletRequest request, BadCredentialsException ex) 
    ApiError apiError = new ApiError(...); //retrieve info from BadCredentialsException
    return ResponseEntity.status(HttpStatus.UNAUTHORIZED, apiError);

【讨论】:

以上是关于是否可以覆盖弹簧安全认证错误消息的主要内容,如果未能解决你的问题,请参考以下文章

弹簧 websocket 没有弹簧安全

如何配置弹簧安全性

弹簧视图安全

Grails:弹簧安全插件 - 错误 springsecurity.GormPersistentTokenRepository

提供配置弹簧安全的方法?

使用弹簧安全与自定义过滤器的优势?