@WithMockUser with custom User implementation

2020-07-14 04:00发布

问题:

I'm using spring OAuth2 and JWT tokens to secure an application. I am extending org.springframework.security.core.userdetails in order to add some additional attributes to the token which can then be used to perform authorization ones an endpoint is called.

public class CustomUser extends User {
     private Set<String> bookIds;

     public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
         super(username, password, authorities);
     }

     public CustomUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
         super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
     }
}

I also have a custom implementation of org.springframework.security.access.PermissionEvaluator which is able to deserialize the token and check that the custom attributes are part of it which can be added to controller endpoints as such.

@PreAuthorize("hasPermission(authentication, #bookId,'read')")

Now it all works fine, I can test my application through postman and only users with a valid JWT token that has bookID in the URL part of their bookIds set can access the resource as such

@PreAuthorize("hasPermission(authentication, #bookId,'read')")
@GetMapping(value = "api/books/{bookId}")
public Book getBook(@PathVariable String bookId) {}

HOWEVER, I'm struggling to test this as this is part of a microservice application where authentication and services are not part of the same project and would run on separate VMs. IDEALLY, i'd like to be able to mock the token in each of the services and add whatever values I need inside of this bookId set. I know that after spring 4 we can use @WithMockUser, however, from what I can tell this is limited to username/password/role/authority, e.g. :

@WithMockUser(username = "ram", roles={"ADMIN"})

What I'd really like to do is either extend this annotation to support my custom attribute 'bookId' or a way to inject a mock set in it. Is this possible, and if not, what are my alternatives, as I wouldn't be able to call my authentication provider in my unit tests as the implementation would live in another spring context hosted on a separate app.

Many thanks in advance !

回答1:

You can use the @WithUserDetails annotation.
If you have a user with the username "user1", then you can annotate your test with @WithUserDetails("user1") and it will execute with a CustomUser principal, including the custom attribute "bookId" associated with "user1".
You can find more information about the annotation in the spring-security docs.

If you want more flexibility, you can also use the @WithSecurityContext annotation.
This allows you to create your own annotation, e.g @WithMockCustomUser, where you set the security context yourself.
You can find more details in the spring-security docs.

One more option, if you are using MockMvc to run your tests, it to use a request post processor.
Your test would look like this

mvc
    .perform(get("/api/books/1")
    .with(user(customUser)));

where customUser is an instance of CustomUser that you create in the test.