20

I am using the current version of Spring Data Rest and Spring Data JPA and have following entity:

public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String password;
    private String email;
   ...getter/setter methods...
}

I am also using Spring Security.

My User Repository:

   @RepositoryRestResource(
     collectionResourceRel = "user", 
     path = "user", 
    excerptProjection = UserSimpleProjection.class)
public interface UserRepository extends PagingAndSortingRepository<User, Long> {

}

For Example:

  • User 1 is logged in
  • User 1 requests http://localhost:8080/user/1 - all fields are visible
  • User 1 requests http://localhost:8080/user/2 - just id and name are visible.

I tried different solutions with Jackson, none of them solved my problem:

  • Use of JsonView: I found no way, to change the view for the ObjectMapper depending on the logged in User
  • Implemented different Jackson Filters as described here with the same issue that I found no way to change the ObjectMapper config for the different requests.

Then I found Projections.

I created a projection:

@Projection(name = "simple", types = User.class)
public interface UserSimpleProjection {

    public Long getId();

    public String getName();
}

and another detailed one:

@Projection(name = "detailed", types = User.class)
public interface UserDetailProjection extends UserSimpleProjection{

    public String getEmail();
}

So far so good, I get different results depending on my request.

Is there a way to automatically switch the projection depending on Spring Security and/or limit different Projections for different roles?

soumya
  • 3,801
  • 9
  • 35
  • 69
Plechi
  • 358
  • 2
  • 10

3 Answers3

20

You can add a "virtual" value property into the projection that invoke a service method with security checks:

@Projection(name = "detailed", types = User.class)
public interface UserDetailProjection extends UserSimpleProjection{

    @Value("#{@userService.checkAccess(target)? target.email : null}")
    public String getEmail();
}

Where your custom UserService component would return true if email should be exposed or simply has @PreAuthorize on checkAccess(..) to throw an AccessDeniedException whatever is better for you.

Note, the target property in the SpEL holds the original object - provided by Spring-DATA.

aux
  • 1,589
  • 12
  • 20
  • why is this not working @Value("#{principal.username.equalsIgnoreCase('admin') ? target.password : null}") – ArslanAnjum May 11 '17 at 07:24
  • 3
    Because `principal` is not a property of the SpEL root object used in this context. To access a bean you can use `@` prefix. Pls read more about SpEL: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html – aux May 11 '17 at 15:14
  • Note, in SPeL you can use variables (`#myvar`), beans (`@mybean`), "root object" properties and methods, etc. Note also that expressions have different "root object" in different contexts that is set by some handler. E.g. `@PreAuthorize` handled by `MethodSecurityExpressionHandler`, and projection `@Value` - by `SpelAwareProxyProjectionFactory`. You can check the source code for reference. – aux May 11 '17 at 15:46
2

You can also do it using a RegexRequestMatcher in your Spring Security config like this:

.regexMatchers(HttpMethod.GET,"/user/.*projection=simple.*").hasRole("ROLE_ADMIN")
Sparm
  • 529
  • 1
  • 6
  • 14
0

No, Spring Data REST projections don't support this.