Custom Error Objects for Spring Rest API And Authentication

岁酱吖の 提交于 2020-07-15 09:45:12

问题


I have a spring boot rest API project and I am pondering how to change the default error object returned from spring boot.

UseCase: /token api is to be called without authentication and other apis are to be called by passing a token. The swagger UI needs to be used for the same purpose. Error response needs to be modified as a custom object.

eg: The default structure is like this for Unauthorised api request.

{
    "timestamp": "2020-06-14T05:46:37.538+00:00",
    "status": 401,
    "error": "Unauthorized",
    "message": "Unauthorized",
    "path": "/scholarship/api/v1.0/applicant" 
}

I would want it to be like

{
  "status": "error",
  "message": "Unauthorised"
}

I have swagger ui also configured for the project.

My Config file is like this which helps me open the swagger-ui. html and i have also tried to get a handle to the Unauthorised abject in the exception handler in this but it doesn't fire.

@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
            new AntPathRequestMatcher("/api/**")
    );

    AuthenticationProvider provider;

    public SecurityConfig(final AuthenticationProvider authenticationProvider) {
        super();
        this.provider = authenticationProvider;
    }

    @Override
    protected void configure(final AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(provider);
    }

    @Override
    public void configure(final WebSecurity webSecurity) {
        webSecurity.ignoring().antMatchers("/token/**");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
                .and()
                .authenticationProvider(provider)
                .addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
                .authorizeRequests()
                .requestMatchers(PROTECTED_URLS)
                .authenticated()
                .and()
                .csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .logout().disable();
    }

    @Bean
    AuthenticationFilter authenticationFilter() throws Exception {
        log.error("in authenticationFilter");
        final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
        filter.setAuthenticationManager(authenticationManager());

        //filter.setAuthenticationSuccessHandler(successHandler());
        return filter;
    }


    @Autowired
    private HandlerExceptionResolver handlerExceptionResolver;

    public AuthenticationEntryPoint authenticationEntryPoint() {
        log.error("in authenticationEntryPoint");
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response,
                                 AuthenticationException authException) throws IOException, ServletException {
                log.error("in commence");
                try {
                    log.error(authException.getLocalizedMessage());
                    handlerExceptionResolver.resolveException(request, response, null, authException);
                } catch (RuntimeException e) {
                    throw e;
                } catch (Exception e) {
                    throw new ServletException(e);
                }
            }
        };
    }
}

Alternatively, i have a @ControllerAdvice to reformat the error object but it is also not working

@ExceptionHandler(value = {InsufficientAuthenticationException.class})
    public final ResponseEntity<Object> authenticationException(InsufficientAuthenticationException ex) {
        List<String> details = new ArrayList<>();
        details.add("Authentication is required to access this resource");
        ErrorResponse error = new ErrorResponse("error", "Unauthorized", details);
        return new ResponseEntity(error, HttpStatus.FORBIDDEN);
    }

What could be possibly misconfiguration?

Update1:

If I change to this then I get my custom object when invoking the secure api, but swagger Ui is not opening and I get my custom error for swagger UI access

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

        http.authorizeRequests()
                .anyRequest()
                .fullyAuthenticated();

        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint());
    }

Error when accessing Swagger UI

{"status":"error","message":"Unauthorized","errors":["Authentication is required to access this resource"]}

Update 2:

Changing to the below opens up the Swagger UI but again the error objects are not custom then

 @Override
    public void configure(HttpSecurity http) throws Exception {
        log.error("in configure");
        http.authenticationProvider(provider)
                .addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
                .authorizeRequests()
                .requestMatchers(PROTECTED_URLS)
                .fullyAuthenticated();

        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint());
    }

Update 3:

The core issues lie's in configure(HttpSecurity http) method

If i add the below two lines the API's are authenticated but the error object is the default object. If I remove these lines the API are not authenticated(even if you pass the token they keep failing) but I get the custom error object.

.authenticationProvider(provider)
                .addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)

回答1:


The exceptions from Servlet Filter which do not reach the controller are handled by BasicErrorController.

You need to override the BasicErrorController to change the default exception body thrown by spring.

How to override:

@RestController
@Slf4j
public class BasicErrorControllerOverride extends AbstractErrorController {

  public BasicErrorControllerOverride(ErrorAttributes errorAttributes) {
    super(errorAttributes);
  }

  @RequestMapping
  public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    HttpStatus status = this.getStatus(request);

    /*
       If you want to pull default error attributes and modify them, use this
       Map<String, Object> defaultErrorAttributes = this.getErrorAttributes(request, false);
    */

    Map<String, Object> errorCustomAttribute = new HashMap<>();
    errorCustomAttribute.put("status", "error");
    errorCustomAttribute.put("message", status.name());
    return new ResponseEntity(errorCustomAttribute, status);
  }

  @Override
  public String getErrorPath() {
    return "/error";
  }
}

How error response will look like

{
    "message": "UNAUTHORIZED",
    "status": "error"
}


来源:https://stackoverflow.com/questions/62368896/custom-error-objects-for-spring-rest-api-and-authentication

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!