142

I'm using Spring MVC's @ControllerAdvice and @ExceptionHandler to handle all the exception of a REST Api. It works fine for exceptions thrown by web mvc controllers but it does not work for exceptions thrown by spring security custom filters because they run before the controller methods are invoked.

I have a custom spring security filter that does a token based auth:

public class AegisAuthenticationFilter extends GenericFilterBean {

...

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        try {

            ...         
        } catch(AuthenticationException authenticationException) {

            SecurityContextHolder.clearContext();
            authenticationEntryPoint.commence(request, response, authenticationException);

        }

    }

}

With this custom entry point:

@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{

    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
    }

}

And with this class to handle exceptions globally:

@ControllerAdvice
public class RestEntityResponseExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({ InvalidTokenException.class, AuthenticationException.class })
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
    @ResponseBody
    public RestError handleAuthenticationException(Exception ex) {

        int errorCode = AegisErrorCode.GenericAuthenticationError;
        if(ex instanceof AegisException) {
            errorCode = ((AegisException)ex).getCode();
        }

        RestError re = new RestError(
            HttpStatus.UNAUTHORIZED,
            errorCode, 
            "...",
            ex.getMessage());

        return re;
    }
}

What I need to do is to return a detailed JSON body even for spring security AuthenticationException. Is there a way make spring security AuthenticationEntryPoint and spring mvc @ExceptionHandler work together?

I'm using spring security 3.1.4 and spring mvc 3.2.4.

Victor Wong
  • 2,486
  • 23
  • 32
Nicola
  • 2,876
  • 2
  • 18
  • 26
  • 12
    You cannot... The `(@)ExceptionHandler` will only work if the request is handled by the `DispatcherServlet`. However this exception occurs before that as it is thrown by a `Filter`. So you will never be able to handle this exception with an `(@)ExceptionHandler`. – M. Deinum Nov 04 '13 at 12:04
  • Ok, you are right. Is there a way to return a json body along with the response.sendError of the EntryPoint? – Nicola Nov 04 '13 at 12:32
  • Sounds like you need to insert a custom filter earlier in the chain to catch the Exception and return accordingly. The documentation lists the filters, their aliases and the order they are applied: http://docs.spring.io/spring-security/site/docs/3.1.4.RELEASE/reference/ns-config.html#ns-custom-filters – Romski Nov 04 '13 at 12:41
  • 2
    If the only location you need the JSON then simply construct/write it inside the `EntryPoint`. You might want to construct the object there, and inject a `MappingJackson2HttpMessageConverter` in there. – M. Deinum Nov 04 '13 at 12:54
  • @M.Deinum I will try to build the json inside the entry point. – Nicola Nov 05 '13 at 11:40
  • In my case, even I am not able to get call on AuthenticationEntryPoint class, Why? I am using , I am using JWT for Authentication, If I enter bad credentials, AuthenticationEntryPoint not executing pls help me.. Also for Bad credentials @ExceptionHandler executing before AegisAuthenticationFilter ..please please help – PAA Sep 24 '15 at 09:24
  • So you work@Aegis. – Akanksha Gaur Aug 02 '18 at 16:22

14 Answers14

90

Ok, I tried as suggested writing the json myself from the AuthenticationEntryPoint and it works.

Just for testing I changed the AutenticationEntryPoint by removing response.sendError

@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{

    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
    
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getOutputStream().println("{ \"error\": \"" + authenticationException.getMessage() + "\" }");

    }
}

In this way you can send custom json data along with the 401 unauthorized even if you are using Spring Security AuthenticationEntryPoint.

Obviously you would not build the json as I did for testing purposes but you would serialize some class instance.

In Spring Boot, you should add it to http.authenticationEntryPoint() part of SecurityConfiguration file.

kolege
  • 3
  • 3
Nicola
  • 2,876
  • 2
  • 18
  • 26
  • 4
    Example using Jackson: ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(response.getOutputStream(), new FailResponse(401, authException.getLocalizedMessage(), "Access denied", "")); – Cyrusmith Mar 14 '15 at 05:16
  • 1
    I know the question is a little bit old, but did you register your AuthenticationEntryPoint to SecurityConfig? – leventunver Dec 21 '16 at 18:52
  • 2
    @leventunver Here you can find how to register the entry point: http://stackoverflow.com/questions/24684806/how-to-define-a-custom-authenticationentrypoint-without-xml-configuration. – Nicola Dec 22 '16 at 14:19
  • could you help me plz https://stackoverflow.com/questions/66688115/how-to-remove-a-variable-from-unauthorized-response-in-springboot – Catalina Mar 18 '21 at 09:33
  • This is the best solution i found for this problem, and it works perfectly! Thanks @Nicola – WalterVi Mar 23 '21 at 21:32
  • I also tried `response.sendError()`, but the response body stays empty. But using a `writer` / `outputStream` worked. Any explenation? – nonNumericalFloat Apr 13 '23 at 14:07
47

The best way I've found is to delegate the exception to the HandlerExceptionResolver

@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Autowired
    private HandlerExceptionResolver resolver;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        resolver.resolveException(request, response, null, exception);
    }
}

then you can use @ExceptionHandler to format the response the way you want.

Christophe Bornet
  • 1,062
  • 8
  • 8
  • 21
    Works like a charm. If Spring throw an error saying there 2 bean definition for autowirering, you must add qualifier annotation : @Autowired @Qualifier("handlerExceptionResolver") private HandlerExceptionResolver resolver; – Daividh Jul 18 '18 at 07:49
  • 2
    Be aware that by passing a null handler, your `@ControllerAdvice` will not work if you have specified basePackages on the annotation. I had to remove this entirely to allow the handler to be called. – Jarmex May 29 '19 at 10:46
  • Why did you give `@Component("restAuthenticationEntryPoint")`? Why the need of a name like restAuthenticationEntryPoint? Is it to avoid some Spring name collisions? – theprogrammer Feb 01 '20 at 00:16
  • @Jarmex So in place of null, what did you pass? its some kind of handler right? Should I just pass a class which has been annotated with @ControllerAdvice? Thanks – theprogrammer Feb 01 '20 at 00:26
  • @theprogrammer, I had to restructure the application slightly to remove the basePackages annotation parameter to get around it - not ideal! – Jarmex Feb 03 '20 at 15:13
  • I also use this approach, but it intercepts all the exceptions which are raised even the Internal Server Error 500, which results in the same response for a wide variety of errors. What can be done in such a case? AuthenticationException is thrown in commence method for any error which occurs. – ayush Jun 21 '23 at 02:53
40

This is a very interesting problem that Spring Security and Spring Web framework is not quite consistent in the way they handle the response. I believe it has to natively support error message handling with MessageConverter in a handy way.

I tried to find an elegant way to inject MessageConverter into Spring Security so that they could catch the exception and return them in a right format according to content negotiation. Still, my solution below is not elegant but at least make use of Spring code.

I assume you know how to include Jackson and JAXB library, otherwise there is no point to proceed. There are 3 Steps in total.

Step 1 - Create a standalone class, storing MessageConverters

This class plays no magic. It simply stores the message converters and a processor RequestResponseBodyMethodProcessor. The magic is inside that processor which will do all the job including content negotiation and converting the response body accordingly.

public class MessageProcessor { // Any name you like
    // List of HttpMessageConverter
    private List<HttpMessageConverter<?>> messageConverters;
    // under org.springframework.web.servlet.mvc.method.annotation
    private RequestResponseBodyMethodProcessor processor;

    /**
     * Below class name are copied from the framework.
     * (And yes, they are hard-coded, too)
     */
    private static final boolean jaxb2Present =
        ClassUtils.isPresent("javax.xml.bind.Binder", MessageProcessor.class.getClassLoader());

    private static final boolean jackson2Present =
        ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", MessageProcessor.class.getClassLoader()) &&
        ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", MessageProcessor.class.getClassLoader());

    private static final boolean gsonPresent =
        ClassUtils.isPresent("com.google.gson.Gson", MessageProcessor.class.getClassLoader());

    public MessageProcessor() {
        this.messageConverters = new ArrayList<HttpMessageConverter<?>>();

        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(new StringHttpMessageConverter());
        this.messageConverters.add(new ResourceHttpMessageConverter());
        this.messageConverters.add(new SourceHttpMessageConverter<Source>());
        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

        if (jaxb2Present) {
            this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }
        if (jackson2Present) {
            this.messageConverters.add(new MappingJackson2HttpMessageConverter());
        }
        else if (gsonPresent) {
            this.messageConverters.add(new GsonHttpMessageConverter());
        }

        processor = new RequestResponseBodyMethodProcessor(this.messageConverters);
    }

    /**
     * This method will convert the response body to the desire format.
     */
    public void handle(Object returnValue, HttpServletRequest request,
        HttpServletResponse response) throws Exception {
        ServletWebRequest nativeRequest = new ServletWebRequest(request, response);
        processor.handleReturnValue(returnValue, null, new ModelAndViewContainer(), nativeRequest);
    }

    /**
     * @return list of message converters
     */
    public List<HttpMessageConverter<?>> getMessageConverters() {
        return messageConverters;
    }
}

Step 2 - Create AuthenticationEntryPoint

As in many tutorials, this class is essential to implement custom error handling.

public class CustomEntryPoint implements AuthenticationEntryPoint {
    // The class from Step 1
    private MessageProcessor processor;

    public CustomEntryPoint() {
        // It is up to you to decide when to instantiate
        processor = new MessageProcessor();
    }

    @Override
    public void commence(HttpServletRequest request,
        HttpServletResponse response, AuthenticationException authException)
        throws IOException, ServletException {

        // This object is just like the model class, 
        // the processor will convert it to appropriate format in response body
        CustomExceptionObject returnValue = new CustomExceptionObject();
        try {
            processor.handle(returnValue, request, response);
        } catch (Exception e) {
            throw new ServletException();
        }
    }
}

Step 3 - Register the entry point

As mentioned, I do it with Java Config. I just show the relevant configuration here, there should be other configuration such as session stateless, etc.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling().authenticationEntryPoint(new CustomEntryPoint());
    }
}

Try with some authentication fail cases, remember the request header should include Accept : XXX and you should get the exception in JSON, XML or some other formats.

Victor Wong
  • 2,486
  • 23
  • 32
  • 1
    I am trying to catch a `InvalidGrantException` but my version of your `CustomEntryPoint` is not getting invoked. Any idea what I could be missing? – Stefan Falk Sep 15 '18 at 09:19
  • @displayname. All authentication exceptions that cannot be caught by `AuthenticationEntryPoint` and `AccessDeniedHandler` such as `UsernameNotFoundException` and `InvalidGrantException` can be handled by `AuthenticationFailureHandler` as [explained here](https://stackoverflow.com/a/61702950/7436256). – Wilson May 10 '20 at 07:41
  • As a side note, if you're using `httpBasic` you need to set `authenticationEntryPoint` from there instead of `exceptionHandling` in step 3. – hiroshin Feb 16 '23 at 14:33
9

We need to use HandlerExceptionResolver in that case.

@Component
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Autowired
    //@Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        resolver.resolveException(request, response, null, authException);
    }
}

Also, you need to add in the exception handler class to return your object.

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(AuthenticationException.class)
    public GenericResponseBean handleAuthenticationException(AuthenticationException ex, HttpServletResponse response){
        GenericResponseBean genericResponseBean = GenericResponseBean.build(MessageKeys.UNAUTHORIZED);
        genericResponseBean.setError(true);
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        return genericResponseBean;
    }
}

may you get an error at the time of running a project because of multiple implementations of HandlerExceptionResolver, In that case you have to add @Qualifier("handlerExceptionResolver") on HandlerExceptionResolver

Vinit Solanki
  • 1,863
  • 2
  • 15
  • 29
  • `GenericResponseBean` is just java pojo, may you can create your own – Vinit Solanki Mar 05 '19 at 11:08
  • could you help me please https://stackoverflow.com/questions/66688115/how-to-remove-a-variable-from-unauthorized-response-in-springboot – Catalina Mar 18 '21 at 09:34
  • 1
    You also need to make changes in SecurityConfig file. Without these changes control wont come to Handler. `http. ..... .and() .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);` – Onkar Patil Apr 06 '22 at 07:30
6

In case of Spring Boot and @EnableResourceServer, it is relatively easy and convenient to extend ResourceServerConfigurerAdapter instead of WebSecurityConfigurerAdapter in the Java configuration and register a custom AuthenticationEntryPoint by overriding configure(ResourceServerSecurityConfigurer resources) and using resources.authenticationEntryPoint(customAuthEntryPoint()) inside the method.

Something like this:

@Configuration
@EnableResourceServer
public class CommonSecurityConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.authenticationEntryPoint(customAuthEntryPoint());
    }

    @Bean
    public AuthenticationEntryPoint customAuthEntryPoint(){
        return new AuthFailureHandler();
    }
}

There's also a nice OAuth2AuthenticationEntryPoint that can be extended (since it's not final) and partially re-used while implementing a custom AuthenticationEntryPoint. In particular, it adds "WWW-Authenticate" headers with error-related details.

Hope this will help someone.

Vladimir Salin
  • 2,951
  • 2
  • 36
  • 49
  • 2
    I am trying this but the `commence()` function of my `AuthenticationEntryPoint` is not getting invoked - am I missing something? – Stefan Falk Sep 15 '18 at 09:15
4

Taking answers from @Nicola and @Victor Wing and adding a more standardized way:

import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class UnauthorizedErrorAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {

    private HttpMessageConverter messageConverter;

    @SuppressWarnings("unchecked")
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        MyGenericError error = new MyGenericError();
        error.setDescription(exception.getMessage());

        ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
        outputMessage.setStatusCode(HttpStatus.UNAUTHORIZED);

        messageConverter.write(error, null, outputMessage);
    }

    public void setMessageConverter(HttpMessageConverter messageConverter) {
        this.messageConverter = messageConverter;
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        if (messageConverter == null) {
            throw new IllegalArgumentException("Property 'messageConverter' is required");
        }
    }

}

Now, you can inject configured Jackson, Jaxb or whatever you use to convert response bodies on your MVC annotation or XML based configuration with its serializers, deserializers and so on.

Gabriel Villacis
  • 331
  • 7
  • 17
  • I am very new to spring boot : please tell me "how to pass messageConverter object to authenticationEntry point " – Kona Suresh Jan 01 '18 at 12:27
  • Through the setter. When you use XML you have to create a `` tag. When you use a `@Configuration` class just use the `setMessageConverter()` method. – Gabriel Villacis Jan 04 '18 at 15:59
2

I was able to handle that by simply overriding the method 'unsuccessfulAuthentication' in my filter. There, I send an error response to the client with the desired HTTP status code.

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException failed) throws IOException, ServletException {

    if (failed.getCause() instanceof RecordNotFoundException) {
        response.sendError((HttpServletResponse.SC_NOT_FOUND), failed.getMessage());
    }
}
thechaoticpanda
  • 396
  • 2
  • 18
user3619911
  • 107
  • 1
  • 9
2

Update: If you like and prefer to see the code directly, then I have two examples for you, one using standard Spring Security which is what you are looking for, the other one is using the equivalent of Reactive Web and Reactive Security:
- Normal Web + Jwt Security
- Reactive Jwt

The one that I always use for my JSON based endpoints looks like the following:

@Component
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {

    @Autowired
    ObjectMapper mapper;

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException e)
            throws IOException, ServletException {
        // Called when the user tries to access an endpoint which requires to be authenticated
        // we just return unauthorizaed
        logger.error("Unauthorized error. Message - {}", e.getMessage());

        ServletServerHttpResponse res = new ServletServerHttpResponse(response);
        res.setStatusCode(HttpStatus.UNAUTHORIZED);
        res.getServletResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        res.getBody().write(mapper.writeValueAsString(new ErrorResponse("You must authenticated")).getBytes());
    }
}

The object mapper becomes a bean once you add the spring web starter, but I prefer to customize it, so here is my implementation for ObjectMapper:

  @Bean
    public Jackson2ObjectMapperBuilder objectMapperBuilder() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.modules(new JavaTimeModule());

        // for example: Use created_at instead of createdAt
        builder.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);

        // skip null fields
        builder.serializationInclusion(JsonInclude.Include.NON_NULL);
        builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return builder;
    }

The default AuthenticationEntryPoint you set in your WebSecurityConfigurerAdapter class:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ............
   @Autowired
    private JwtAuthEntryPoint unauthorizedHandler;
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
                // .antMatchers("/api/auth**", "/api/login**", "**").permitAll()
                .anyRequest().permitAll()
                .and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);


        http.headers().frameOptions().disable(); // otherwise H2 console is not available
        // There are many ways to ways of placing our Filter in a position in the chain
        // You can troubleshoot any error enabling debug(see below), it will print the chain of Filters
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }
// ..........
}
thechaoticpanda
  • 396
  • 2
  • 18
Melardev
  • 1,101
  • 10
  • 22
2

Customize the filter, and determine what kind of abnormality, there should be a better method than this

public class ExceptionFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
    String msg = "";
    try {
        filterChain.doFilter(request, response);
    } catch (Exception e) {
        if (e instanceof JwtException) {
            msg = e.getMessage();
        }
        response.setCharacterEncoding("UTF-8");
        response.setContentType(MediaType.APPLICATION_JSON.getType());
        response.getWriter().write(JSON.toJSONString(Resp.error(msg)));
        return;
    }
}

}

HaiZi
  • 179
  • 1
  • 7
2

If you need a super quick solution, @Christophe Bornet purposed the easiest one.

  1. Create a Bean to send authentication exceptions to an exception resolver.
    @Bean(name = "restAuthenticationEntryPoint")
    public AuthenticationEntryPoint authenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
        return (request, response, exception) -> resolver.resolveException(request, response, null, exception);
    }

*You may put this bean somewhere inside your existing security config class.

  1. Add an exception handler method to catch the error, so you can return the response and status you want.
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<String> handleValidationException(AccessDeniedException e) {
        return ResponseEntity.status(401).body("{\"status\":\"FAILED\", \"reason\": \"Unauthorized\"}");
    }

*You may put it right near your auth endpoint in the controller.

Letfar
  • 3,253
  • 6
  • 25
  • 35
1

In ResourceServerConfigurerAdapter class, below code snipped worked for me. http.exceptionHandling().authenticationEntryPoint(new AuthFailureHandler()).and.csrf().. did not work. That's why I wrote it as separate call.

public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.exceptionHandling().authenticationEntryPoint(new AuthFailureHandler());

        http.csrf().disable()
                .anonymous().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .antMatchers("/subscribers/**").authenticated()
                .antMatchers("/requests/**").authenticated();
    }

Implementation of AuthenticationEntryPoint for catching token expiry and missing authorization header.


public class AuthFailureHandler implements AuthenticationEntryPoint {

  @Override
  public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e)
      throws IOException, ServletException {
    httpServletResponse.setContentType("application/json");
    httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

    if( e instanceof InsufficientAuthenticationException) {

      if( e.getCause() instanceof InvalidTokenException ){
        httpServletResponse.getOutputStream().println(
            "{ "
                + "\"message\": \"Token has expired\","
                + "\"type\": \"Unauthorized\","
                + "\"status\": 401"
                + "}");
      }
    }
    if( e instanceof AuthenticationCredentialsNotFoundException) {

      httpServletResponse.getOutputStream().println(
          "{ "
              + "\"message\": \"Missing Authorization Header\","
              + "\"type\": \"Unauthorized\","
              + "\"status\": 401"
              + "}");
    }

  }
}

Kemal Atik
  • 307
  • 3
  • 12
0

I'm using the objectMapper. Every Rest Service is mostly working with json, and in one of your configs you have already configured an object mapper.

Code is written in Kotlin, hopefully it will be ok.

@Bean
fun objectMapper(): ObjectMapper {
    val objectMapper = ObjectMapper()
    objectMapper.registerModule(JodaModule())
    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)

    return objectMapper
}

class UnauthorizedAuthenticationEntryPoint : BasicAuthenticationEntryPoint() {

    @Autowired
    lateinit var objectMapper: ObjectMapper

    @Throws(IOException::class, ServletException::class)
    override fun commence(request: HttpServletRequest, response: HttpServletResponse, authException: AuthenticationException) {
        response.addHeader("Content-Type", "application/json")
        response.status = HttpServletResponse.SC_UNAUTHORIZED

        val responseError = ResponseError(
            message = "${authException.message}",
        )

        objectMapper.writeValue(response.writer, responseError)
     }}
Spin1987
  • 11
  • 2
0

You can use objectMapper instead to write the value

ApiError response = new ApiError(HttpStatus.UNAUTHORIZED);
String message = messageSource.getMessage("errors.app.unauthorized", null, httpServletRequest.getLocale());
response.setMessage(message);
httpServletResponse.setContentType("application/json");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(out, response);
out.flush();
Omar Abdelhady
  • 1,528
  • 4
  • 19
  • 31
0

I just create one class that handle all the exceptions regarding authentication

@Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest httpServletRequest,
                     HttpServletResponse httpServletResponse,
                     AuthenticationException e) throws IOException, ServletException {
    logger.error("Responding with unauthorized error. Message - {}", e.getMessage());
    httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}

}