【发布时间】:2013-06-20 23:53:41
【问题描述】:
有什么好的方法可以根据 Spring Security 角色过滤 JSON 输出吗?我正在寻找类似@JsonIgnore 的东西,但对于角色,例如@HasRole("ROLE_ADMIN")。我应该如何实现这个?
【问题讨论】:
标签: spring spring-security jackson
有什么好的方法可以根据 Spring Security 角色过滤 JSON 输出吗?我正在寻找类似@JsonIgnore 的东西,但对于角色,例如@HasRole("ROLE_ADMIN")。我应该如何实现这个?
【问题讨论】:
标签: spring spring-security jackson
对于那些从 Google 登陆的用户,这里有一个与 Spring Boot 1.4 类似的解决方案。
为每个角色定义接口,例如
public class View {
public interface Anonymous {}
public interface Guest extends Anonymous {}
public interface Organizer extends Guest {}
public interface BusinessAdmin extends Organizer {}
public interface TechnicalAdmin extends BusinessAdmin {}
}
在您的实体中声明@JsonView,例如
@Entity
public class SomeEntity {
@JsonView(View.Anonymous.class)
String anonymousField;
@JsonView(View.BusinessAdmin.class)
String adminField;
}
并定义一个@ControllerAdvice 以根据角色选择正确的JsonView:
@ControllerAdvice
public class JsonViewConfiguration extends AbstractMappingJacksonResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return super.supports(returnType, converterType);
}
@Override
protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
Class<?> viewClass = View.Anonymous.class;
if (SecurityContextHolder.getContext().getAuthentication() != null && SecurityContextHolder.getContext().getAuthentication().getAuthorities() != null) {
Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
if (authorities.stream().anyMatch(o -> o.getAuthority().equals(Role.GUEST.getValue()))) {
viewClass = View.Guest.class;
}
if (authorities.stream().anyMatch(o -> o.getAuthority().equals(Role.ORGANIZER.getValue()))) {
viewClass = View.Organizer.class;
}
if (authorities.stream().anyMatch(o -> o.getAuthority().equals(Role.BUSINESS_ADMIN.getValue()))) {
viewClass = View.BusinessAdmin.class;
}
if (authorities.stream().anyMatch(o -> o.getAuthority().equals(Role.TECHNICAL_ADMIN.getValue()))) {
viewClass = View.TechnicalAdmin.class;
}
}
bodyContainer.setSerializationView(viewClass);
}
}
【讨论】:
spring.jackson.mapper.default-view-inclusion=true,这样您就不必注释实体的每个属性
您应该考虑使用rkonovalov/jfilter。特别是@DynamicFilterComponent 有很大帮助。
您可以在这篇 DZone 文章中看到一个很好的指南。
@DynamicFilterComponent 解释为here。
我刚刚实现了您上面提到的要求。我的系统使用 Restful Jersey 1.17、Spring Security 3.0.7、Jackson 1.9.2。但该解决方案与 Jersey Restful API 无关,您可以在任何其他类型的 Servlet 实现中使用它。
这是我的解决方案的全部 5 个步骤:
首先你应该为你的目的创建一个 Annotation 类,像这样:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonSpringView {
String springRoles();
}
然后是 Annotation Introspector,它的大部分方法应该返回 null,根据您的需要填写方法,因为我刚刚使用了 isIgnorableField。 Feature 是我对 GrantedAuthority 接口的实现。像这样:
@Component
public class JsonSpringViewAnnotationIntrospector extends AnnotationIntrospector implements Versioned
{
// SOME METHODS HERE
@Override
public boolean isIgnorableField(AnnotatedField)
{
if(annotatedField.hasAnnotation(JsonSpringView.class))
{
JsonSpringView jsv = annotatedField.getAnnotation(JsonSpringView.class);
if(jsv.springRoles() != null)
{
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(principal != null && principal instanceof UserDetails)
{
UserDetails principalUserDetails = (UserDetails) principal;
Collection<? extends GrantedAuthority> authorities = principalUserDetails.getAuthorities();
List<String> requiredRoles = Arrays.asList(jsv.springRoles().split(","));
for(String requiredRole : requiredRoles)
{
Feature f = new Feature();
f.setName(requiredRole);
if(authorities.contains(f))
// if The Method Have @JsonSpringView Behind it, and Current User has The Required Permission(Feature, Right, ... . Anything You may Name It).
return false;
}
// if The Method Have @JsonSpringView Behind it, but the Current User doesn't have The required Permission(Feature, Right, ... . Anything You may Name It).
return true;
}
}
}
// if The Method Doesn't Have @JsonSpringView Behind it.
return false;
}
}
Jersey 服务器有一个默认的 ObjectMapper 用于其序列化/反序列化目的。如果你正在使用这样的系统并且你想改变它的默认 ObjectMapper,步骤 3、4 和 5 是你的,否则你可以阅读这一步,你的工作就在这里完成。
@Provider
public class JsonSpringObjectMapperProvider implements ContextResolver<ObjectMapper>
{
ObjectMapper mapper;
public JsonSpringObjectMapperProvider()
{
mapper = new ObjectMapper();
AnnotationIntrospector one = new JsonSpringViewAnnotationIntrospector();
AnnotationIntrospector two = new JacksonAnnotationIntrospector();
AnnotationIntrospector three = AnnotationIntrospector.pair(one, two);
mapper.setAnnotationIntrospector(three);
}
@Override
public ObjectMapper getContext(Class<?> arg0) {
return this.mapper;
}
}
您应该扩展 javax.ws.rs.core.Application 并在 Web.xml 中提及您的班级名称。我的是 RestApplication。像这样:
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
public class RestApplication extends Application
{
public Set<Class<?>> getClasses()
{
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(JsonSpringObjectMapperProvider.class);
return classes ;
}
}
这是最后一步。您应该在 web.xml 中提及您的 Application 类(来自第 4 步):
<servlet>
<servlet-name>RestService</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.package</param-name>
<param-value>your_restful_resources_package_here</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<!-- THIS IS THE PART YOU SHOULD PPPAYYY ATTTTENTTTTION TO-->
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>your_package_name_here.RestApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
从现在开始你只需要在任何你想要的属性后面提到@JsonSpringView注解。像这样:
public class PersonDataTransferObject
{
private String name;
@JsonSpringView(springRoles="ADMIN, SUPERUSER") // Only Admins And Super Users Will See the person National Code in the automatically produced Json.
private String nationalCode;
}
【讨论】:
虽然可以编写自定义 JSON 处理过滤器(例如,基于 JSON Pointers),但它会有点复杂。
最简单的方法是创建您自己的 DTO 并仅映射用户有权获取的那些属性。
【讨论】:
admin 角色的用户,而只返回 3 个字段给具有 user 角色的用户,则将无济于事。除非您有继承并检查端点内的角色。
if 语句的属性映射器时,您已经说得很清楚了。