Spring 3.2: Spring-based Jackson JSON output filtering Security Role

Is there a good way to filter JSON output based on Spring security roles? I am looking for something like @JsonIgnore, but for a role like @HasRole ("ROLE_ADMIN"). How to implement this?

+9
spring jackson spring-security
source share
3 answers

For those who land here from Google, this is a similar solution with Spring Boot 1.4.

Define interfaces for each of your roles, e.g.

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 {} } 

Declare @JsonView in your entities, for example.

 @Entity public class SomeEntity { @JsonView(View.Anonymous.class) String anonymousField; @JsonView(View.BusinessAdmin.class) String adminField; } 

And define @ControllerAdvice to select the correct JsonView -based 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); } } 
+12
source share

Update: new answer

You should consider using rkonovalov / jfilter . especially @DynamicFilterComponent helps a lot. You can see a good guide in this zone article.

Old answer

I just fulfilled the above requirement. My system uses Restful Jersey 1.17 , Spring Security 3.0.7 , Jackson 1.9.2 . But the solution has nothing to do with the Restful Jersey API, and you can use it for any other servlet implementations.

These are all five steps of my solution:

  1. You must first create an Annotation class for your purpose. For example:

    JsonSpringView.java

     import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface JsonSpringView { String springRoles(); } 
  2. Then the Annotation Introspector, most of which Methods should return null . Fill in methods based on your need for my requirements, which I just used isIgnorableField . Feature My implementation for the GrantedAuthority "interface. How is it:

    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 servers have a default ObjectMapper for their serialization / deserialization purposes. If you use such a system and want to change it by default, ObjectMapper, steps 3, 4 and 5 are yours, otherwise you can read this step and your work will be done here.

    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. You must extend javax.ws.rs.core.Application and provide your class name in Web.xml. Mine - RestApplication.Like:

    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. and this is the last step. you must specify your application class (from step 4) in your web.xml:

    Part of my 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> 

and now you only need to mention the @JsonSpringView annotation “Beyond any desired property”. Like this:

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; } 
+9
source share

You can write a custom JSON processing filter (for example, based on JSON Pointers ), it will be a little more complicated.

The easiest way is to create your own DTO and display only those properties that the user is allowed to receive.

+2
source share

All Articles