Spring 3.2:基于 Spring Security 角色过滤 Jackson JSON 输出

Posted

技术标签:

【中文标题】Spring 3.2:基于 Spring Security 角色过滤 Jackson JSON 输出【英文标题】:Spring 3.2: Filtering Jackson JSON output based on Spring Security role 【发布时间】:2013-06-20 23:53:41 【问题描述】:

有什么好的方法可以根据 Spring Security 角色过滤 JSON 输出吗?我正在寻找类似@JsonIgnore 的东西,但对于角色,例如@HasRole("ROLE_ADMIN")。我应该如何实现这个?

【问题讨论】:

【参考方案1】:

对于那些从 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 属性 spring.jackson.mapper.default-view-inclusion=true,这样您就不必注释实体的每个属性 这是一个有趣的方法。但请注意,如果您使用 Spring Data 并定义投影,它将优先于视图,可能让用户通过将投影查询参数传递给查询来排除限制。 是否可以有一个从多个视图扩展的视图?如果我想拥有一个拥有View1和View2权限的视图怎么办?【参考方案2】:

更新:新答案

您应该考虑使用rkonovalov/jfilter。特别是@DynamicFilterComponent 有很大帮助。 您可以在这篇 DZone 文章中看到一个很好的指南。

@DynamicFilterComponent 解释为here。

旧答案

我刚刚实现了您上面提到的要求。我的系统使用 Restful Jersey 1.17Spring Security 3.0.7Jackson 1.9.2。但该解决方案与 Jersey Restful API 无关,您可以在任何其他类型的 Servlet 实现中使用它。

这是我的解决方案的全部 5 个步骤:

    首先你应该为你的目的创建一个 Annotation 类,像这样:

    JsonSpringView.java

    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    public @interface JsonSpringView 
        String springRoles();
    
    

    然后是 Annotation Introspector,它的大部分方法应该返回 null,根据您的需要填写方法,因为我刚刚使用了 isIgnorableFieldFeature 是我对 GrantedAuthority 接口的实现。像这样:

    JsonSpringViewAnnotationIntrospector.java

    @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 是你的,否则你可以阅读这一步,你的工作就在这里完成。

    JsonSpringObjectMapperProvider.java

    @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。像这样:

    RestApplication.java

    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 步):

    我的 web.xml 的一部分

    <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注解。像这样:

PersonDataTransferObject.java

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;

【讨论】:

这对我不起作用,过滤器只对第一个登录的用户应用一次。请你看看我的新问题:***.com/questions/35558218/…。您有针对此问题的更新解决方案吗? 如果有人能解决这个问题,请告诉我?我也面临同样的问题,即过滤器只对第一个登录的用户应用一次。 这太棒了,有机会为当前的春季版本更新您的示例吗? @BigDong 真的很抱歉,目前我自己的任务压力很大,所以不能真正更新答案:( 那太糟糕了。这应该默认使用 spring-security 实现。【参考方案3】:

虽然可以编写自定义 JSON 处理过滤器(例如,基于 JSON Pointers),但它会有点复杂。

最简单的方法是创建您自己的 DTO 并仅映射用户有权获取的那些属性。

【讨论】:

如果您想将 10 个字段返回给具有 admin 角色的用户,而只返回 3 个字段给具有 user 角色的用户,则将无济于事。除非您有继承并检查端点内的角色。 @prettyvoid 我不明白评论。你能详细说明一下吗?我描述的解决方案由一个 DTO 类和一个使用少量 IF 语句的属性映射器组成。 没关系对误导性评论感到抱歉,我认为您只是建议一个带有构造函数的 DTO,该构造函数采用具有所有字段的类。但是,当您说带有一些 if 语句的属性映射器时,您已经说得很清楚了。

以上是关于Spring 3.2:基于 Spring Security 角色过滤 Jackson JSON 输出的主要内容,如果未能解决你的问题,请参考以下文章

Spring Oauth2 - 自定义异常处理程序

基于struts2.5.2+hibernate5.2.2+spring4.3.2搭建SSH框架

利用OAuth2.0来构建一个基于Spring Boot的安全的端到端通信应用

搭建SpringMVC+Spring4.3.2+Hibernate5.2.2框架

通用后台管理系统(ExtJS 4.2 + Spring MVC 3.2 + Hibernate)

spring security 1