11

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

4

3 回答 3

19

对于那些从谷歌登陆这里的人,这里有一个与 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);
    }
}
于 2016-10-04T12:40:33.490 回答
9

更新:新答案

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

@DynamicFilterComponent在这里解释。

旧答案

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

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

  1. 首先,您应该为您的目的创建一个 Annotation 类,如下所示:

    JsonSpringView.java

    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    public @interface JsonSpringView {
        String springRoles();
    }
    
  2. 然后是一个注解内省,它的大部分方法应该返回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;
        }
    }
    
  3. 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;
        }
    }
    
  4. 您应该在 Web.xml 中扩展javax.ws.rs.core.Application并提及您的班级名称。我的是 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 ;
        }
    }
    
  5. 这是最后一步。您应该在 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;
}
于 2013-09-19T13:51:52.820 回答
3

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

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

于 2013-06-24T18:57:50.833 回答