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 !
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 aCustomUser
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
where
customUser
is an instance ofCustomUser
that you create in the test.