Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to show custom 404 page when user enters invalid URL in spring boot application?

I am using java config in spring boot, spring security application. I have configured the error controller as below. But whenever, I enter an invalid URL it goes to error.jsp, which is configured to handle the errors in the application:

@Controller
public class AppErrorController implements ErrorController {

    private static final String PATH = "/error";

    @RequestMapping(value = "/pageNotFound", method = { RequestMethod.GET, RequestMethod.POST })
    public String pageNotFound() {
        return "pageNotFound";
    }

    @RequestMapping(value = "/accessDenied", method = { RequestMethod.GET, RequestMethod.POST })
    public String accessDenied() {
        return "accessDenied";
    }

    @RequestMapping(value = PATH)
    public String error() {
        return "error";
    }

    @Override
    public String getErrorPath() {
        return PATH;
    }
}

web.xml

<error-page>
    <error-code>404</error-code>
    <location>/pageNotFound</location>
</error-page>

<error-page>
    <error-code>500</error-code>
    <location>/error</location>
</error-page>

404 is never thrown when the invalid URL is entered.

Also, I am not using embedded tomcat. I am deploying war to the external tomcat. I have this in my application.yml file:

server:
    error:
        whitelabel:
            enabled: false

Below is the log in case if I enter a invalid URL. There is no error. It just redirects to /error in case of invalid URL:

2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.b.c.web.OrderedRequestContextFilter  : Bound request context to thread: org.apache.catalina.connector.RequestFacade@371ef2a3
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/donotexisturl'; against '/static/**'
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/donotexisturl'; against '/i18n/**'
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] w.c.HttpSessionSecurityContextRepository : Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@b8db0c9d: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'}'
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@6e929f31
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /donotexisturl' doesn't match 'POST /logout
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 6 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /donotexisturl' doesn't match 'POST /checklogin
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 7 of 13 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.AnonymousAuthenticationFilter  : SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'}'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /donotexisturl' doesn't match 'POST /logout
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/donotexisturl'; against '/login**'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/donotexisturl'; against '/error**'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/donotexisturl'; against '/checklogin**'
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /doNotExistURL; Attributes: [fullyAuthenticated]
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'}
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.access.vote.UnanimousBased  : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@6ad88564, returned: 1
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.access.vote.UnanimousBased  : Voter: org.springframework.security.access.vote.AuthenticatedVoter@19007bb6, returned: 0
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.access.vote.UnanimousBased  : Voter: com.sts.app.core.user.security.AccessDecisionVoterImpl@1465b821, returned: 1
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.i.FilterSecurityInterceptor    : Authorization successful
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.i.FilterSecurityInterceptor    : RunAsManager did not change Authentication object
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /doNotExistURL reached end of additional filter chain; proceeding with original chain
2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet        : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/gen/doNotExistURL]
2016-03-15 10:02:45.063 DEBUG 9997 --- [io-8080-exec-22] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /doNotExistURL
2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/doNotExistURL]
2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] o.s.w.s.handler.SimpleUrlHandlerMapping  : Matching patterns for request [/doNotExistURL] are [/**]
2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] o.s.w.s.handler.SimpleUrlHandlerMapping  : URI Template variables for request [/doNotExistURL] are {}
2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapping [/doNotExistURL] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[ServletContext resource [/], class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver@87dd2cb]]] and 1 interceptor
2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet        : Last-Modified value for [/gen/doNotExistURL] is: -1
2016-03-15 10:02:45.077 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet        : Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet        : Successfully completed request
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.ExceptionTranslationFilter     : Chain processed normally
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.b.c.web.OrderedRequestContextFilter  : Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@371ef2a3
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.b.c.web.OrderedRequestContextFilter  : Bound request context to thread: org.apache.catalina.core.ApplicationHttpRequest@20ae74b1
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/static/**'
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/i18n/**'
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] w.c.HttpSessionSecurityContextRepository : Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@b8db0c9d: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'}'
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /error' doesn't match 'POST /logout
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 6 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /error' doesn't match 'POST /checklogin
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 7 of 13 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.AnonymousAuthenticationFilter  : SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'}'
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy        : /error reached end of additional filter chain; proceeding with original chain
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet        : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/gen/error]
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /error
2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] s.w.s.m.m.a.RequestMappingHandlerMapping : Returning handler method [public java.lang.String com.sts.app.core.common.web.AppErrorController.error()]
like image 735
ashishjmeshram Avatar asked Mar 12 '16 11:03

ashishjmeshram


3 Answers

First off, define an exception for each especial HTTP error you're going to handle. Here i'm just defining one for handling 404 Not Found case:

public class NotFoundException extends RuntimeException {}

To replace the default Spring Boot's error handling mechanism completely, we can implement ErrorController. Instead of just implementing the ErrorController, here i'm extending AbstractErrorController, which implements the ErrorController and provides some auxiliary methods like getStatus().

Anyway, the basic idea is to handle all errors using an endpoint, say /error, and throw those pre-defined exceptions in case of their corresponding HTTP status codes:

@Controller
public class CustomErrorController extends AbstractErrorController {
    private static final String ERROR_PATH=  "/error";

    @Autowired
    public CustomErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }

    /**
     * Just catching the {@linkplain NotFoundException} exceptions and render
     * the 404.jsp error page.
     */
    @ExceptionHandler(NotFoundException.class)
    public String notFound() {
        return "404";
    }

    /**
     * Responsible for handling all errors and throw especial exceptions
     * for some HTTP status codes. Otherwise, it will return a map that
     * ultimately will be converted to a json error.
     */
    @RequestMapping(ERROR_PATH)
    public ResponseEntity<?> handleErrors(HttpServletRequest request) {
        HttpStatus status = getStatus(request);

        if (status.equals(HttpStatus.NOT_FOUND))
            throw new NotFoundException();

        return ResponseEntity.status(status).body(getErrorAttributes(request, false));
    }

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }
}

Of course, this solution is only suitable for Traditional Deployment. If you're planning to use an Embedded Servlet Container, you're better off defining an EmbeddedServletContainerCustomizer.

like image 164
Ali Dehghani Avatar answered Nov 03 '22 01:11

Ali Dehghani


Whould you like to add this @Bean to your spring application config, like this :

@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
    return new EmbeddedServletContainerCustomizer() {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {

            ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error404.html");
            container.addErrorPages(error404Page);

        }
    };
}

and put error404.html in your static folder.

Reference : http://www.sporcic.org/2014/05/custom-error-pages-with-spring-boot/

like image 31
sahmada Avatar answered Nov 03 '22 00:11

sahmada


Here you go

@RestController
public class RestErrorController implements ErrorController{

private static final String PATH = "/error";

@Autowired
private ErrorAttributes errorAttributes;

@RequestMapping(value=PATH,method=RequestMethod.GET)
public ApiErrorExtended error(HttpServletRequest request, HttpServletResponse response){
     return new ApiErrorExtended( response.getStatus(),getErrorAttributes(request, true));
}

private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
    RequestAttributes requestAttributes = new ServletRequestAttributes(request);
    return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
}  

public String getErrorPath() {
    return PATH;
} 
}

This is my rest error controller. You can modify it to return whatever u want.

like image 43
SeaBiscuit Avatar answered Nov 03 '22 02:11

SeaBiscuit