3

I've got a Spring REST application that is using Spring Security. So far, I've been doing error handling using a @ControllerAdvice class which is great to handle all errors thrown during the MVC process.

However, all exceptions thrown by Spring Security (ex: AccessDeniedException) are not handled by MVC error handling as they never actually make it to the MVC components. So if I want to handle Spring Security exceptions, I have to either add an AccessDeniedHandler or use a custom filter placed after the EXCEPTION_TRANSLATION_FILTER (see Spring Docs and Filter Ordering).

My main goal in handling my exceptions is that I want to return a consistent response to the client no matter the error being throw with pertinent information embedded in a JSON object (ex: Timestamp, error code, message, etc).

Is there a preferred technique for this kind of setup? Or a better way of being able to centralize everything? I love the simplicity of MVC error handling with @ExceptionHandlers but everything seems to push me towards using a custom filter and handle everything in one location.

On a related note, it seems to me that the default FilterChain that is generated by using namespace configuration is overkill for a REST application. Is there a more appropriate configuration for a REST application? (Ex: no need for the FORM_LOGIN_FILTER, REMEMBER_ME_FILTER, CONCURRENT_SESSION_FILTER, etc...). Does anyone have a list of the filters that should be used in a REST application? I cannot find anything in the docs.

1 Answer 1

2

I'm not sure if I understand you right. I also had an issue to send a proper error message with json back to the client. I have written all (filter & provider) custom, because I authenticate via HTTPHeaderFields. The error handling and sending proper messages back to the client has been the biggest issue. Short overview will follow:

At first I created custom delegatingAuthenticationEntryPoint and AccesssDeniedHandler.
Snipp of secure config

... @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // IMPORTANT: Add Filter after "ExceptionTranslation". // If not AuthenticationException from Custom Filter or Custom Provider // will not be catched by AuthenticationEntryPoint. .addFilterAfter(httpClientFilter(), ExceptionTranslationFilter.class) .exceptionHandling() // catch AuthenticationExeption and SecureToken with authenticated=false .authenticationEntryPoint(delegatingAuthenticationEntryPoint()) // catch PermissionDenied Exeption e.g. missing in authorizeRequests() .accessDeniedHandler(new ClientRestAccessDeniedHandler()) .and() ... 

The ClientRestAccessDeniedHandler looks like this

public class ClientRestAccessDeniedHandler implements AccessDeniedHandler{ @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { final Logger logger = Logger.getLogger(ClientRestAccessDeniedHandler.class); if(logger.isDebugEnabled()) logger.debug("Requered Role for this request is missing!"); HTTPAuthenticationErrorSender.sendResponse(request, response, SecurityContextHolder.getContext().getAuthentication()); } } 

The Custom DelegationEntriyPoint looks very similar. Both call the HTTPAuthenticationErrorSender. It will send the HttpResponse generated by low level stuff :-).

public final class HTTPAuthenticationErrorSender { public static void sendResponse(HttpServletRequest request, HttpServletResponse response, Authentication token) throws JsonGenerationException, JsonMappingException, IOException{ final Logger logger = Logger.getLogger(HTTPAuthenticationErrorSender.class); if(!(token instanceof HTTPRestSecureToken)){ if (token != null){ response.sendError(403, "No valide AuthenticationToken found. Token instance of: "+token.getClass().toString()); if(logger.isDebugEnabled()) logger.debug("Send default HTTP Response 403. No HTTPRestSecureToken found. " + "Token is instance of: "+token.getClass().getName()); } else { response.sendError(403, "No valide AuthenticationToken found. Token is null"); if(logger.isDebugEnabled()) logger.debug("Send default HTTP Response 403. No HTTPRestSecureToken found. " + "Token is null"); } return; } HTTPRestSecureToken restToken = (HTTPRestSecureToken) token; ObjectMapper mapper = new ObjectMapper(); AuthenticationErrorResponse authErrorResponse = new AuthenticationErrorResponse(restToken.getAuthStatus().getErrorCode(),restToken.getAuthStatus().getDescription()); String content = mapper.writeValueAsString(authErrorResponse); HTTPRestPrincipal principal = (HTTPRestPrincipal) token.getPrincipal(); if(logger.isDebugEnabled()){ logger.debug("AccessDenied for request: ["+principal.getFullURI()+"] clientID: ["+principal.getClientID() + "] loginMail: ["+principal.getLoginMail()+"]"); logger.debug("Send following json response: "+content); } response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().print(content); } } 

To send proper Information I track status information in my custom SecureToken. The token will run throw secure chain filter and provider and will be filled up with information.

And yes I would say it is not very small for a common issue like this. But I couldn't find another way. The default Impl. are not detailed enough for my case. I hope it will help a little or gives you a push in the right direction.

Sign up to request clarification or add additional context in comments.

1 Comment

What do you have in your httpClientFilter()?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.