【发布时间】:2017-07-28 08:20:51
【问题描述】:
我有一个简单的一对多关系的 spring 数据休息应用程序。
(1 个组织包含零个或多个员工)。
在没有任何安全措施的情况下,我可以像这样将员工添加到组织中:
curl -v -H "Content-Type:application/json" -d '{"name":"name1","organization":"http://localhost:8080/api/organizations/1"}' http://localhost:8080/api/employee
当员工有效负载被推送到休息控制器时,Spring Data Rest 会将组织 uri 转换为组织实体,将其连接到员工,一切正常。
但是,假设我开始保护我的组织 API,并且我只想允许授权用户查看组织(基于某些业务逻辑)。
@RepositoryRestResource
public interface OrganizationController extends CrudRepository<Organization, Long> {
@PreAuthorize("@securityService.isAllowedToSeeTheOrganization(#id)")
Organization findOne(@P("id") Long id);
}
SecurityService 实现了一些业务逻辑,以确定用户是否能够根据其个人资料检索组织:
@Service
public class SecurityService {
public boolean isAllowedToSeeOrganization(Long organizationId) {
return isAdmin() || belongsToOrganization(organizationId);
}
private boolean isAdmin() {
return SecurityContextHolder.getContext().getAuthentication().getAuthorities().contains(new SimpleGrantedAuthority(ADMIN.getRoleName()));
}
private boolean belongsToOrganization(Long organizationId) {
return organizationId == Long.parseLong(getUserDetails().get(ORGANIZATION_ID_FIELD).toString());
}
protected Map<String,Object> getUserDetails() {
JwtAuthentication jwtAuthentication = (JwtAuthentication) SecurityContextHolder.getContext().getAuthentication();
return jwtAuthentication.getJwtClaimsSet().getClaims();
}
}
这当然也会对上述调用产生影响,因为它将不再能够将组织 uri 转换为实体。
但是,API 调用失败并显示 HTTP 400(错误请求)和以下正文,而不是抛出 403 Forbidden:
{
"cause":{
"cause":{
"cause":null,
"message":"Access is denied"
},
"message":"Access is denied (through reference chain: com.example.Employee[\"organization\"])"
},
"message":"Could not read document: Access is denied (through reference chain: com.example.Employee[\"organization\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Access is denied (through reference chain: com.example.Employee[\"organization\"])"
}
在日志中:
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 Forbidden,或者有一些方法来自定义此错误消息。
我该怎么做呢?
【问题讨论】:
-
能分享一下securityService、employee和spring security config的代码吗?似乎JSON映射有错误,这就是抛出400的原因。
-
添加了堆栈跟踪/安全服务实现。 Data Rest Uri / Entity 转换器触发存储库调用。不允许身份验证对象执行该调用 (PreAuthorize),并引发 AccessDeniedException。 AccessDeniedException 被推上堆栈,Spring 向客户端抛出一个 http 400。我仍然希望这里是 403 而不是 400。
标签: spring-security spring-data spring-data-rest