繁体   English   中英

Spring Boot + REST + Spring Security: how to generate a custom JSON response for HTTP 403

[英]Spring Boot + REST + Spring Security: how to generate a custom JSON response for HTTP 403

我有一个 Spring-Boot REST controller,受 Z586B26955D265287BAA7D042EZA 保护。 它工作正常。 If the client sends an HTTP request without having the proper access token on the HTTP header then he gets back an HTTP 403 response, as I expect.

curl -i localhost:8301/user/11:

HTTP/1.1 403 
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 07 Nov 2019 16:25:45 GMT

{
  "timestamp" : 1573143945072,
  "status" : 403,
  "error" : "Forbidden",
  "message" : "Access Denied",
  "path" : "/user/11"
}

我还有一个自定义错误处理程序,它处理 REST 方法中出现的所有错误:

@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(
            HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        ...
        return new ResponseEntity<>(json, httpStatus);
    }

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

        return new ResponseEntity<>(json, httpStatus);
    }
}

现在我想做的是在 HTTP 403 的情况下自定义错误响应,并且我想将自定义 JSON 响应发送回客户端(与ControllerExceptionHandler处理程序中的 Z0ECD11C1D7A287401D148A23BBDA 2 中的相同)。

不幸的是,上面的错误处理程序没有处理 HTTP 403 因为请求在到达我的 REST 方法之前被 Spring-Security 阻止。

看来我需要向 Spring Security 添加一些额外的代码,但我不确定。

你能把我引向正确的方向吗?

你试过了吗?

@ExceptionHandler({Exception.class})
    public ResponseEntity<Message> handleException(HttpServletRequest httpServletRequest, Throwable ex) {

Spring 引导使用BasicErrorController作为全局错误处理程序。 @ExceptionHander方法未处理的异常。 要覆盖此默认行为,您需要实现ErrorController接口,如下所示。

CustomErrorController.java

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
public class CustomErrorController implements ErrorController {

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

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity<Map<String, Object>>(status);
        }
        Map<String, Object> body = new HashMap<String, Object>();
        body.put("timestamp", new Date());
        body.put("status", HttpStatus.FORBIDDEN.value());
        body.put("error", "Forbidden");
        body.put("message", "My Custom Error Message");
        return new ResponseEntity<>(body, status);
    }

    protected HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        try {
            return HttpStatus.valueOf(statusCode);
        } catch (Exception ex) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }

}

请注意,使用这种方法,您将覆盖所有未由其他@ExceptionHandler方法处理的异常(不仅仅是 AccessDeniedException)的响应。

如果您不想这样做并且只想覆盖AccessDeniedException的响应,那么您需要实现如下所示的AccessDeniedHandler接口并将其添加到 spring 安全性的 http 配置中。

CustomAccessDeniedHandler.java

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.gson.Gson;

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException, ServletException {
        Map<String, Object> body = new HashMap<String, Object>();
        body.put("timestamp", new Date());
        body.put("status", HttpStatus.FORBIDDEN.value());
        body.put("error", "Forbidden");
        body.put("message", "Custom Error Message from CustomAccessDeniedHandler");
        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        new Gson().toJson(body, new TypeReference<Map<String, Object>>() {
        }.getType(), response.getWriter());

    }

}

WebSecurityConfig.java

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler()).and().httpBasic().and()
                .authorizeRequests().antMatchers("/rest/**").hasAnyRole("ROLE_ADMIN").anyRequest().authenticated().and()
                .formLogin().disable();

    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication().withUser("user").password("{noop}password").roles("USER").and().withUser("admin")
                .password("{noop}password").roles("USER", "ADMIN");

    }

尝试使用自定义身份验证入口点 class 覆盖 WebSecurityConfigurerAdapter 中的 http.execptionalHandling().authenticationEntryPoint() 。

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

private static final Logger log = 
LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException authException) throws IOException, ServletException {
    log.error("Responding for UnAuthorized request{} ", authException.getMessage());
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM