Spring Boot中@PathVariable绑定实体为null时抛出404的实现方案
这个问题我之前也碰到过,核心矛盾在于Spring Data参数解析器返回null时,Spring Security的@PreAuthorize权限校验会优先触发,导致本该返回404(资源不存在)的场景变成了403(权限不足)。下面给你几个可行的解决思路:
方案一:手动查询实体并主动抛出404异常
放弃Spring Data自动绑定实体的方式,手动通过路径变量中的ID查询实体,并用Optional的orElseThrow方法在找不到实体时抛出异常,再通过全局异常处理器返回404。
步骤1:修改控制器方法
把路径变量从实体类型改为ID类型,手动查询实体:
@PutMapping(value = "/{addressId}") @PreAuthorize("isAuthenticated() and (#address.user.id == #user.id)") public ResponseEntity<ResponseModel> update(@Valid @RequestBody AddressModel addressModel, @PathVariable Long addressId, @AuthenticationPrincipal User user) { // 查询实体,找不到则抛出异常 Address address = addressRepository.findById(addressId) .orElseThrow(() -> new EntityNotFoundException("地址不存在,ID:" + addressId)); try { addressService.update(user, address, addressModel); } catch (Exception e) { return new ResponseEntity<>(new ResponseModel(e.getMessage(), ResponseModel.ResponseStatus.ERROR), HttpStatus.BAD_REQUEST); } return new ResponseEntity<>(new ResponseModel(messages.get("def.op.success"), ResponseModel.ResponseStatus.SUCCESS), HttpStatus.OK); }
步骤2:添加全局异常处理器
创建一个全局异常处理类,捕获EntityNotFoundException并返回404响应:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity<ResponseModel> handleEntityNotFound(EntityNotFoundException ex) { ResponseModel response = new ResponseModel(ex.getMessage(), ResponseModel.ResponseStatus.ERROR); return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); } }
这样当ID无效时,会先抛出异常返回404,根本不会触发@PreAuthorize的权限校验,完美解决403的问题。
方案二:自定义参数解析器,让Spring Data绑定失败时抛出异常
如果你想保留Spring Data自动绑定实体的特性,可以自定义一个参数解析器,替换默认的Spring Data解析逻辑,当找不到实体时直接抛出EntityNotFoundException,再通过全局异常处理器返回404。
自定义参数解析器示例
@Component public class EntityArgumentResolver implements HandlerMethodArgumentResolver { private final EntityManager entityManager; public EntityArgumentResolver(EntityManager entityManager) { this.entityManager = entityManager; } @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(PathVariable.class) && parameter.getParameterType().isAnnotationPresent(Entity.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class); String idStr = webRequest.getParameter(pathVariable.value()); Class<?> entityClass = parameter.getParameterType(); Object entity = entityManager.find(entityClass, Long.parseLong(idStr)); if (entity == null) { throw new EntityNotFoundException(entityClass.getSimpleName() + "不存在,ID:" + idStr); } return entity; } }
注册参数解析器
在配置类中注册这个自定义解析器:
@Configuration public class WebConfig implements WebMvcConfigurer { private final EntityArgumentResolver entityArgumentResolver; public WebConfig(EntityArgumentResolver entityArgumentResolver) { this.entityArgumentResolver = entityArgumentResolver; } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(entityArgumentResolver); } }
这种方式可以让你继续使用@PathVariable Address address的写法,同时保证找不到实体时返回404。
为什么你之前的尝试没用?
你之前在@PreAuthorize中加了#address != null的判断,当address为null时,整个表达式isAuthenticated() and (#address != null and #address.user.id == #user.id)会返回false,Spring Security会认为权限校验不通过,所以返回403。这本质上是把“资源不存在”和“权限不足”的逻辑混在了一起,导致错误的响应码。
内容的提问来源于stack exchange,提问作者AHS441

