Spring Boot中如何结合@PreAuthorize动态值与@ExceptionHandler?
问题场景
项目中使用带动态值的@PreAuthorize做权限校验,同时在请求模型上用@NotEmpty等注解做Bean校验,控制器代码如下:
@RestController @RequestMapping(value = "/../{id}/...") @PreAuthorize("hasAuthority(#id+'abc')") public class TestController { @Autowired SerivceAbc service; @PostMapping("/test") public ResponseEntity<Object> validate(@PathVariable String id, @Valid @RequestBody RequestModel Request, ContentCachingRequestWrapper requestWrapper) { return ResponseEntity.ok(service.abc(Request, requestWrapper)); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler( MethodArgumentNotValidException.class) ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex, ContentCachingRequestWrapper requestWrapper) { ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper); return ResponseEntity.badRequest().body(invalidResponse); } @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) ResponseEntity<Object> handleValidationExceptions(Exception ex, ContentCachingRequestWrapper requestWrapper) { ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper); return ResponseEntity.badRequest().body(invalidDssReponse); } }
问题现象
- 请求字段合法时,动态参数
id能正常配合@PreAuthorize完成权限校验,接口正常返回。 - 触发Bean校验(比如请求字段为空)时,
handleValidationExceptions方法无法被调用。 - 尝试给异常处理方法添加
id参数时,抛出错误:
java.lang.IllegalStateException: Could not resolve parameter [2] in org.springframework.http.ResponseEntity<java.lang.Object> com.controller.TestController.handleValidationExceptions(org.springframework.web.bind.MethodArgumentNotValidException,org.springframework.web.util.ContentCachingRequestWrapper,java.lang.String): No suitable resolver
原因分析
- 类级别
@PreAuthorize作用于所有控制器方法,但异常处理方法不属于请求处理方法范畴,Spring无法自动为其解析路径变量id。 - 异常处理方法的参数解析逻辑和常规请求方法不同,默认不支持直接注入
@PathVariable参数。
解决方案
方案1:从请求对象手动获取路径变量
不需要修改异常处理方法的参数列表,直接从HttpServletRequest中提取路径变量:
@ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) { // 从请求属性中获取已解析的路径变量 Map<String, String> uriVars = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); String id = uriVars.get("id"); // 包装成ContentCachingRequestWrapper(如果需要) ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request); ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper); return ResponseEntity.badRequest().body(invalidResponse); }
方案2:将@PreAuthorize移至方法级别
把类上的权限注解移到具体的请求处理方法上,避免类级别注解对异常处理逻辑的干扰:
@RestController @RequestMapping(value = "/../{id}/...") public class TestController { @Autowired SerivceAbc service; @PostMapping("/test") @PreAuthorize("hasAuthority(#id+'abc')") // 移到方法上 public ResponseEntity<Object> validate(@PathVariable String id, @Valid @RequestBody RequestModel Request, ContentCachingRequestWrapper requestWrapper) { return ResponseEntity.ok(service.abc(Request, requestWrapper)); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex, ContentCachingRequestWrapper requestWrapper) { ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper); return ResponseEntity.badRequest().body(invalidResponse); } @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) ResponseEntity<Object> handleGlobalExceptions(Exception ex, ContentCachingRequestWrapper requestWrapper) { ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper); // 修正状态码,和注解保持一致 return ResponseEntity.internalServerError().body(invalidResponse); } }
备注:原代码中全局异常处理标注了INTERNAL_SERVER_ERROR却返回badRequest(),这里需要修正状态码,避免前后不一致。
方案3:自定义参数解析器(复用性强)
如果多个异常处理方法都需要获取路径变量,可以自定义参数解析器,让Spring支持在异常处理方法中注入@PathVariable:
- 实现参数解析器:
public class PathVariableExceptionResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { // 支持带@PathVariable注解的参数 return parameter.hasParameterAnnotation(PathVariable.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request == null) { return null; } // 获取已解析的路径变量映射 Map<String, String> uriVars = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); String varName = parameter.getParameterAnnotation(PathVariable.class).value(); return uriVars.get(varName); } }
- 注册解析器:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new PathVariableExceptionResolver()); } }
- 使用方式:
现在异常处理方法可以直接添加@PathVariable参数:
@ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex, ContentCachingRequestWrapper requestWrapper, @PathVariable String id) { ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper); return ResponseEntity.badRequest().body(invalidResponse); }
内容的提问来源于stack exchange,提问作者M123
相关产品推荐
相关产品推荐

