Spring security 当控制器上发生AccessDeniedException时,如何避免Spring数据Rest抛出HTTP 400

Spring security 当控制器上发生AccessDeniedException时,如何避免Spring数据Rest抛出HTTP 400,spring-security,spring-data,spring-data-rest,Spring Security,Spring Data,Spring Data Rest,我有一个SpringDataREST应用程序,它具有简单的一对多关系 (1个组织包含零名或多名员工) 在没有任何安全性的情况下,我可以向组织中添加员工,如下所示: curl -v -H "Content-Type:application/json" -d '{"name":"name1","organization":"http://localhost:8080/api/organizations/1"}' http://localhost:8080/api/employee 当员工有效负载

我有一个SpringDataREST应用程序,它具有简单的一对多关系

(1个组织包含零名或多名员工)

在没有任何安全性的情况下,我可以向组织中添加员工,如下所示:

curl -v -H "Content-Type:application/json" -d '{"name":"name1","organization":"http://localhost:8080/api/organizations/1"}'  http://localhost:8080/api/employee
当员工有效负载被推送到rest控制器时,springdatarest将把组织uri转换成一个组织实体,将其连接到员工上,一切正常

但是,假设我开始保护我的组织API,我只想允许授权用户查看组织(基于某些业务逻辑)

在日志中:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: Access is denied (through reference chain: com.example.Employee["organization"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:388) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:348) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1599) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:359) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2922) ~[jackson-databind-2.8.5.jar:2.8.5]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    ... 97 common frames omitted
Caused by: org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.1.4.RELEASE.jar:4.1.4.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-4.1.4.RELEASE.jar:4.1.4.RELEASE]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:65) ~[spring-security-core-4.1.4.RELEASE.jar:4.1.4.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at com.sun.proxy.$Proxy156.findOne(Unknown Source) ~[na:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_40]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_40]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_40]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_40]
    at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:216) ~[spring-core-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.data.repository.support.ReflectionRepositoryInvoker.invoke(ReflectionRepositoryInvoker.java:265) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
    at org.springframework.data.repository.support.ReflectionRepositoryInvoker.invokeFindOne(ReflectionRepositoryInvoker.java:140) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
    at org.springframework.data.repository.support.CrudRepositoryInvoker.invokeFindOne(CrudRepositoryInvoker.java:91) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
    at org.springframework.data.rest.core.support.UnwrappingRepositoryInvokerFactory$UnwrappingRepositoryInvoker.invokeFindOne(UnwrappingRepositoryInvokerFactory.java:130) ~[spring-data-rest-core-2.5.6.RELEASE.jar:na]
    at org.springframework.data.rest.core.UriToEntityConverter.convert(UriToEntityConverter.java:123) ~[spring-data-rest-core-2.5.6.RELEASE.jar:na]
    at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$UriStringDeserializer.deserialize(PersistentEntityJackson2Module.java:516) ~[spring-data-rest-webmvc-2.5.6.RELEASE.jar:na]
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:357) ~[jackson-databind-2.8.5.jar:2.8.5]
它向客户泄露内部信息的事实让我觉得有问题

我只是希望api在这种情况下返回403禁止,或者有一些自定义此错误消息的方法


我该怎么做呢?

当然,您应该使用标准,但您的问题比通常要复杂一些。我已经在一个演示项目上尝试了我的解决方案,这应该能奏效

在您的例子中,
AccessDeniedException
被包装在一个
JsonMappingException
中,它本身被包装在一个
httpMessageNodeTableException
中。这就是Spring异常处理得到的结果:

HttpMessageNotReadableException
|---JsonMappingException
    |---AccessDeniedException
根据Spring问题跟踪器
@ExceptionHandler
中的说明,自Spring 4.3以来,方法可以匹配包装的异常,但只能达到一级深度。为
AccessDeniedException
使用
@ExceptionHandler
将不起作用,因为它嵌套在Spring接收到的异常下面的两个级别

您可以改变Spring在异常链中寻找匹配处理程序的深度,但如果只是为了解决这个问题,我只需要定义一个异常处理程序:

@ControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<String> accessDenied(Exception e) throws Exception {
        Throwable cause = e.getCause();
        if (cause != null) {
            Throwable nestedCause = cause.getCause();
            if (AccessDeniedException.class.isAssignableFrom(nestedCause.getClass())) {
                return new ResponseEntity<>(HttpStatus.FORBIDDEN);
            }
        }
        throw e;
    }
}
@ControllerAdvice
公共类例外建议{
@ExceptionHandler(HttpMessageTreadableException.class)
公共响应拒绝访问(异常e)引发异常{
可丢弃的原因=e.getCause();
如果(原因!=null){
Throwable nestedCause=cause.getCause();
if(AccessDeniedException.class.isAssignableFrom(nestedCause.getClass())){
返回新的响应状态(HttpStatus.FORBIDDEN);
}
}
投掷e;
}
}
您可以自定义匹配(可能在整个异常链中搜索想要的异常?)并添加返回消息


请注意,处理程序是在其自己的类中定义的-因为EmployeeController可能是一个接口,就像OrganizationController一样,@ExceptionHandler方法不能在其中定义(默认方法也不起作用)。

您可以共享securityService、employee和spring安全配置的代码吗?JSON映射中似乎有一些错误,这就是抛出400的原因。添加了stacktrace/Security服务实现。数据RESTURI/实体转换器触发存储库调用。不允许身份验证对象执行该调用(预授权),并引发AccessDeniedException。AccessDeniedException被推上堆栈,Spring向客户端抛出http 400。我仍然希望这里是403而不是400。
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Access is denied (through reference chain: com.example.Employee["organization"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:388) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:348) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1599) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:359) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2922) ~[jackson-databind-2.8.5.jar:2.8.5]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    ... 97 common frames omitted
Caused by: org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.1.4.RELEASE.jar:4.1.4.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-4.1.4.RELEASE.jar:4.1.4.RELEASE]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:65) ~[spring-security-core-4.1.4.RELEASE.jar:4.1.4.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at com.sun.proxy.$Proxy156.findOne(Unknown Source) ~[na:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_40]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_40]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_40]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_40]
    at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:216) ~[spring-core-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.data.repository.support.ReflectionRepositoryInvoker.invoke(ReflectionRepositoryInvoker.java:265) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
    at org.springframework.data.repository.support.ReflectionRepositoryInvoker.invokeFindOne(ReflectionRepositoryInvoker.java:140) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
    at org.springframework.data.repository.support.CrudRepositoryInvoker.invokeFindOne(CrudRepositoryInvoker.java:91) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
    at org.springframework.data.rest.core.support.UnwrappingRepositoryInvokerFactory$UnwrappingRepositoryInvoker.invokeFindOne(UnwrappingRepositoryInvokerFactory.java:130) ~[spring-data-rest-core-2.5.6.RELEASE.jar:na]
    at org.springframework.data.rest.core.UriToEntityConverter.convert(UriToEntityConverter.java:123) ~[spring-data-rest-core-2.5.6.RELEASE.jar:na]
    at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$UriStringDeserializer.deserialize(PersistentEntityJackson2Module.java:516) ~[spring-data-rest-webmvc-2.5.6.RELEASE.jar:na]
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:357) ~[jackson-databind-2.8.5.jar:2.8.5]
HttpMessageNotReadableException
|---JsonMappingException
    |---AccessDeniedException
@ControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<String> accessDenied(Exception e) throws Exception {
        Throwable cause = e.getCause();
        if (cause != null) {
            Throwable nestedCause = cause.getCause();
            if (AccessDeniedException.class.isAssignableFrom(nestedCause.getClass())) {
                return new ResponseEntity<>(HttpStatus.FORBIDDEN);
            }
        }
        throw e;
    }
}