我有一个受Spring-Security保护的Spring-Boot REST控制器。工作正常。如果客户端发送HTTP请求而在HTTP标头上没有正确的访问令牌,那么他将获得HTTP 403响应,正如我所期望的。
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
中发送的JSON相同)。
不幸的是,上面的错误处理程序无法处理HTTP 403,因为该请求在到达我的REST方法之前就被Spring-Security阻止了。
似乎我需要向Spring Security添加一些额外的代码,但不确定。
您能给我正确的方向吗?
您尝试过吗?
@ExceptionHandler({Exception.class})
public ResponseEntity<Message> handleException(HttpServletRequest httpServletRequest, Throwable ex) {
Spring boot使用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 Security的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");
}