Spring 引导 + Tomcat 错误处理

Spring Boot + Tomcat Error Handling

我们有一个使用 spring 引导构建的 Web 应用程序,我们正在使用 spring 安全来管理身份验证。身份验证现在正在使用基本模式进行。

我们正在使用 UserDetailsService to handle authentication. When we are running the application directly from STS using the embedded tomcat, when the user is not enabled,我们收到以下错误,状态代码为 401。

{"timestamp":1485512173312,"status":401,"error":"Unauthorized","message":"User is disabled","path":"/trex/user"}

但是,当我将应用程序打包到 war 并将其部署到 Tomcat 时,我们不再收到 json 错误。相反,我们会收到类似于以下内容的 html 错误。

<html><head><title>Apache Tomcat/7.0.75 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 401 - User is disabled</h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>User is disabled</u></p><p><b>description</b> <u>This request requires HTTP authentication.</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/7.0.75</h3></body></html>

是否可以停止处理标准错误代码并让 json 数据原样传回?

已编辑: Spring 安全配置

/**
 * Security configuration. Defines configuration methods and security
 * constraints. Autowired bean definitions can be found in {@link AuthAppConfig}
 * 

 *
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    Logger logger = LoggerFactory.getLogger(getClass());

    /*userDetailsService
     * @Autowired TRexUserDetailsService customAuthenticationEntryPoint;
     * 
     */
    @Autowired
    TRexUserDetailsService userDetailsService;

    @Autowired
    private AmazonProperties amazonProperties;

    /**
     * Configuration related to authentication and authorization
     * 
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        AuthenticationEntryPoint entryPoint = new CustomAuthenticationEntryPoint();
        LogoutHandler myLogoutHandler = new MyLogoutSuccessHandler();

        logger.info("Configuring the security of the application");

        http

                // Basic Authentication
                .httpBasic().authenticationEntryPoint(entryPoint)
                .and().authorizeRequests()
                // Allowing index.html, login.html and / and
                // resetforgotpassword.html and all forgot user mappings since
                // we are using 2 way handshake for this purpose
                .antMatchers("/activate/user","/activate/token/**"
                        ,"/validate","/validateEmail","/user/resetpassword/**","/user/forgotpassword"
                        ,"/admin/masters/*.js","/admin/modeller/*.js", "/login.html", "/index.html","/activateuser.html","/savepassword.html","/forgotpassword.html","/","/lib/**"
                        ,"/directives/paginated-control.js"
                        , "/expense/js/summary.js"
                        ,"/travel/js/directive/uploadDirecetive.js"
                        ,"/travel/js/travelData.js"
                        ,"/travel/js/summary.js"
                        ,"/directives/paginated-control.js"
                        ,"/loginform.html"
                        ,"/infoDirective.html"
                        )
                .permitAll()
                // Admin ADMIN,
                .antMatchers("/admin/**").access("hasRole('ADMIN')")
                // Allow SA
                .antMatchers("/SA/**").access("hasRole('SUPER')")
                //Allow REPORTS
                .antMatchers("/reports/**").access("hasRole('REPORT')")
                // All other requests are to be authenticated.
                .anyRequest().authenticated().and()

                // Filter to prevent CSRF attacks.
                .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class).csrf()
                // Used to check which requests are to be checked for csrf
                // protection
                .requireCsrfProtectionMatcher(new CsrfRequestMatcher())
                // Add the renamed CSRF token header repository
                .csrfTokenRepository(csrfTokenRepository())
                .and()
                // Configuration for /logout
                .logout()
                // Logout requires form submit. Bypassing the same.
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/#/login")
                .addLogoutHandler(myLogoutHandler)
                //Session management to store session registry
                .and().sessionManagement()
                .maximumSessions(100)
                .maxSessionsPreventsLogin(false)
                .expiredUrl("/#/login")
                .sessionRegistry(sessionRegistry());

    }

    /**
     * Session Registry to store sessions
     * @return
     */
    @Bean
    SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    /**
     * Session event publisher.
     * @return
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    public static ServletListenerRegistrationBean httpSessionEventPublisher() {        
        return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
    }
    /**
     * This method specifies that the header name to be used is XSRF token
     * instead of the default CSRF cause Angular names the token as XSRF instead
     * of CSRF
     * 
     * @return
     */
    private CsrfTokenRepository csrfTokenRepository() {
        logger.info("Configuring the CSRF token name as XSRF-TOKEN");
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    }

    /**
     * Configuration for Custom Authentication
     * 
     * @param auth
     * @throws Exception
     */
    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth, BCryptPasswordEncoder passwordEncoder)
            throws Exception {
        logger.info("Configuring the authentication to custom User Details Service");

        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);

        logger.info("Configuring the authentication to custom User Details Service");
    }

    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        // TODO Auto-generated method stub
        return super.authenticationManagerBean();
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() throws Exception {
        BasicAuthenticationEntryPoint entryPoint = new CustomAuthenticationEntryPoint();
        entryPoint.setRealmName("Spring");

        return entryPoint;

    }
}

您应该能够在 CustomAuthenticationEntryPoint 中解决这个问题。请参阅此 answer,了解如何避免 Tomcat 的默认错误页面。

假设您的自定义 EntryPoint 是 BasicAuthenticationEntryPoint 的扩展(正如 bean 所建议的那样),请参见此处: org/springframework/security/web/authentication/www/BasicAuthenticationEntryPoint.java/#59

您需要更换:

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());

与:

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);