I am trying to write a few tests for my Spring controller. The endpoints are secured with Keycloak (open id connect).
I tried mocking an authenticated user using the #WithMockUser annotation but I need to retrieve claims from the token (preferred_username) and I end up getting a null pointer exception from here:
return Long.parseLong(((KeycloakPrincipal) authentication.getPrincipal()).getKeycloakSecurityContext().getToken().getPreferredUsername());
Is there any way to mock the Keycloak token? I came across this similar question but I do not want to use the suggested external library.
Thank you guys in advance, any help would be greatly appreciated as I have been stuck on this problem for a while.
I came across this similar question but I do not want to use the suggested external library.
Well, you'd better reconsider that.
Are you using the deprecated Keycloak adapters?
If yes, and if you still don't want to use spring-addons-keycloak, you'll have to manualy populate test security context with a KeycloakAuthenticationToken instance or mock:
#Test
public void test() {
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var account = mock(OidcKeycloakAccount.class);
when(account.getRoles()).thenReturn(Set.of("offline_access", "uma_authorization"));
when(account.getPrincipal()).thenReturn(principal);
final var authentication = mock(KeycloakAuthenticationToken.class);
when(authentication.getAccount()).thenReturn(account);
// post(...).with(authentication(authentication))
// limits to testing secured #Controller with MockMvc
// I prefer to set security context directly instead:
SecurityContextHolder.getContext().setAuthentication(authentication);
//TODO: invoque mockmvc to test #Controller or test any other type of #Component as usual
}
You'll soon understand why this #WithMockKeycloakAuth was created.
If you already migrated to something else than Keycloak adapters, solution with manualy setting test-security context still applies, just adapt the Authentication instance. If your authentication type is JwtAuthenticationToken, you can use either:
jwt() request post processor for MockMvc I wrote (it is available from spring-security-test)
#Test
void testWithPostProcessor() throws Exception {
mockMvc.perform(get("/greet").with(jwt().jwt(jwt -> {
jwt.claim("preferred_username", "Tonton Pirate");
}).authorities(List.of(new SimpleGrantedAuthority("NICE_GUY"), new SimpleGrantedAuthority("AUTHOR")))))
.andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE_GUY, AUTHOR]."));
}
#WithMockJwtAuth, same author, different lib
#Test
#WithMockJwtAuth(authorities = { "NICE_GUY", "AUTHOR" }, claims = #OpenIdClaims(preferredUsername = "Tonton Pirate"))
void testWithPostProcessor() throws Exception {
mockMvc.perform(get("/greet"))
.andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE_GUY, AUTHOR]."));
}
Note that only second option will work if you want to unit-test a secured #Component that is not a #Controller (a #Service or #Repository for instance).
My two cent advices:
drop Keycloak adapters now: it will disapear soon, is not adapted to boot 2.7+ (web-security config should not extend WebSecurityConfigurerAdapter any more) and is way too adherent to Keycloak. Just have a look at this tutorial to see how easy it can be to configure and unit-test a JWT resource-server (with identities issued by Keycloak or any other OIDC authorization-server)
if your team does not let you abandon Keycloak adapters yet, use #WithMockKeycloakAuth, you'll save tones of time and your test code will be way more readable.
Related
I have a client service like this,
#Service
public class PersonClientService {
private final String EXTERNAL_API;
private RestTemplate restTemplate;
#Autowired
public PersonClientService(RestTemplate restTemplate, #Value("${person.url}") String apiUrl) {
this.restTemplate = restTemplate;
EXTERNAL_API = apiUrl
}
public ResponseDTO createData(PersonDTO personDTO) throws Exception {
try {
HttpEntity<PersonDTO> input = new HttpEntity<>(personDTO);
ResponseEntity<ResponseDTO> restponseDTO = restTemplate.exchange(EXTERNAL_API, HttpMethod.POST, input, ResponseDTO.class);
return responseDTO.getBody();
} catch(Exception e) {
//catch exception
}
}
}
Now the rest template here that I am using is secured with OAuth2 implementation and it is using client_id and secret with grant_type as client_credentials to generate a token and then using this token as header to call the EXTERNAL_API
I am following this guide here but it's not really helpful since it is using JUnit4 and I am on JUnit5: https://www.baeldung.com/oauth-api-testing-with-spring-mvc
I'm confused. What do you want to test?
The sample you link is achieving controller unit-testing with mockmvc.
They use an annotation which loads security context. As a consequence test security context must be configured for the request to reach controller endpoint.
I don't see any security rules on your service (#PreAuthorize or something) => you don't need any security context, just don't load security config.
If you add security rules you want to unit test, load security config and setup test security context (either explicitly or with something like https://github.com/ch4mpy/spring-addons/tree/master/samples/webmvc-jwtauthenticationtoken/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken)
The call to external service is a complete different story: the external service is running with a different security context than the one attached to your tested service thread). Either:
#MockBean RestTemplate (and configure mock for the Rest call your service is issuing) => unit test
ensure test configuration for RestTemplate and external service points to the same started authorization server, load rest template config, auto wire RestTemplate as normal and let it issue request for real to actual external service (which must be started too) => integration test.
You should not start with integration test. Unit test are for more stable and easier to maintain.
I'm recently working with microservices, developed as Spring Boot applications (v 2.2) and in my company we're using Keycloak as authorization server.
We chose it because we need complex policies, roles and groups, and we also need the User Managed Authorization (UMA) to share resources between users.
We configured Keycloak with a single realm and many clients (one client per microservice).
Now, I understand that I need to explicitly define Resources within Keycloak and this is fine, but the question is: do I really need to duplicate all of them in my microservice's property file?
All the documentation, examples and tutorials end up with the same thing, that is something like:
keycloak.policy-enforcer-config.enforcement-mode=PERMISSIVE
keycloak.policy-enforcer-config.paths[0].name=Car Resource
keycloak.policy-enforcer-config.paths[0].path=/cars/create
keycloak.policy-enforcer-config.paths[0].scopes[0]=car:create
keycloak.policy-enforcer-config.paths[1].path=/cars/{id}
keycloak.policy-enforcer-config.paths[1].methods[0].method=GET
keycloak.policy-enforcer-config.paths[1].methods[0].scopes[0]=car:view-detail
keycloak.policy-enforcer-config.paths[1].methods[1].method=DELETE
keycloak.policy-enforcer-config.paths[1].methods[1].scopes[0]=car:delete
(this second example fits better our case because it also uses different authorization scopes per http method).
In real life each microservice we're developing has dozens of endpoints and define them one by one seems to me a waste of time and a weakness in the code's robustness: we change an endpoint, we need to reconfigure it in both Keycloak and the application properties.
Is there a way to use some kind of annotation at Controller level? Something like the following pseudo-code:
#RestController
#RequestMapping("/foo")
public class MyController {
#GetMapping
#KeycloakPolicy(scope = "foo:view")
public ResponseEntity<String> foo() {
...
}
#PostMapping
#KeycloakPolicy(scope = "bar:create")
public ResponseEntity<String> bar() {
...
}
}
In the end, I developed my own project that provides auto-configuration capabilities to a spring-boot project that needs to work as a resource server.
The project is released under MIT2 license and it's available on my github:
keycloak-resource-autoconf
I know that there are already similar questions, here and here, regarding this problem but every solution proposed failed to help me. There is also mention to this library in most of those answers but (with all due respect) I would like to avoid depending on an external library just to be able to test a simple controller.
So, I have a very simple api that is accessed using a bearer token generated by keycloak and I would like to test the controller. Something along these lines:
#Test
#DisplayName("Should be ok")
#WithMockUser
void whenCalled_shouldBeOk() throws Exception {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
mockMvc.perform(
post("/api/url/something")
.content("{}")
.contentType(APPLICATION_JSON)
.with(authentication(authentication))
).andExpect(status().isOk());
}
The problem is that I will always get a null pointer exception thrown by the KeycloakDeploymentBuilder for it's missing the adapter config. In our SecurityConfig we extend the KeycloakWebSecurityConfigurerAdapter and do all the required configurations for the app to work but I am failing to mock/by-pass this process in the test. Normally I find my way around this authentication problems in the tests (when keycloak isn't used) with #WithMockUser annotation but not this time.
Isn't there way to mock the adapter or the filter process in order to by-pass this issue?
I have tried everything that was answered in the other questions (except the library) with no luck. If you have any clue that could be of help or at least point me in the correct direction (since this can be due to a lack of knowledge on spring security from my part) that would very much appreciated.
2023 update
The deprecated Keycloak adapters for Spring (where KeycloakAuthenticationToken is defined) are not compatible with spring-boot 3. Alternatives in the accepted answer to "Use Keycloak Spring Adapter with Spring Boot 3"
Original answer
As I already wrote in my answer to the first question you linked, #WithMockUser populates the security context with a UsernamePaswordAuthenticationToken when your code / conf probably expect a KeycloakAuthenticationToken.
If you read carefully the same answer, you'll find an alternative to using my lib to do this: manually populate the security-context with a KeycloakAuthenticationToken instance or mock in each test.
Minimal sample with Mockito I added to my repo:
#Test
public void test() {
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var account = mock(OidcKeycloakAccount.class);
when(account.getRoles()).thenReturn(Set.of("offline_access", "uma_authorization"));
when(account.getPrincipal()).thenReturn(principal);
final var authentication = mock(KeycloakAuthenticationToken.class);
when(authentication.getAccount()).thenReturn(account);
// post(...).with(authentication(authentication))
// limits to testing secured #Controller with MockMvc
// I prefer to set security context directly instead:
SecurityContextHolder.getContext().setAuthentication(authentication);
//TODO: invoque mockmvc to test #Controller or test any other type of #Component as usual
}
Maybe, after you mesure how much this clutters your tests (there are very few claims set here) you'll reconsider using my lib (or copying from it as it's opensource).
With my annotation, above sample becomes:
#Test
#WithMockKeycloakAuth
public void test() throws Exception {
//TODO: invoque mockmvc to test #Controller or test any other type of #Component as usual
}
Regarding spring test config with Keycloak involved, you could dig a bit into the tests of spring-addons-keycloak module. You'd find a complete app using KeycloakAuthenticationToken with unit tests (and working test conf).
Last (and maybe off-topic) you could read the repo main README and consider using a more generic OIDC implementation than Keycloak's one. I provide one, along with test annotation, and wrote tutorials on how to extend it to your app specific needs.
The setup of the RESPApi project is:
SpringBoot
Spring's OAuth2
In the project we have many clients, so SQL queries almost always have "... and clientId = ?" in the where clause.
We store clientId in the SecurityContext with other user details (we extend Spring's User class).
The question is: how to get the User object in the #Repository?
Possible solutions we can think of:
In every repository implementation add
SecurityContextHolder.getContext().getAuthentication()
cast the result to our custom UserDetails implementation and use it.
Cons: somehow I feel there's a better solution.
Add #AuthenticationPrincipal annotated parameters to the controllers and then pass the parameters to the service layer and then to the repository layer.
Cons: passing the paremeter though 2 layers only to obtain clientId doesn't seem reasonable.
I thought about #Autowired paramter MyUser user in the #Repository class. The first try was to create #Configuration annotated class in which there will be a method
#Bean
public MyUser getUser() {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
return (MyUser) authentication.getPrincipal();
}
}
return null;
}
But the bean is null and I cannot use it.
For now we've ended up with solution nr 1 but I feel there must be a better way.
Any ideas how to solve this problem?
If you're using Spring Data (or have the time to switch to using it), you can use the SecurityEvaluationContextExtension and use principal directly in your queries:
https://stackoverflow.com/a/29692158/1777072
If not, you could hide the static access if it offends (or if you want more control over it changing in future):
#Component
public class AuthenticationHelper {
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Then inject that class into your Repository.
Or your Service. That's probably a better fit than the Repository.
I like to keep Repositories dumb (ultimately using Spring Data to avoid writing them entirely).
And I like to think of Services as being separated out of the web layer, running on separate boxes (even if they aren't). In that situation, you would never pass the Authentication details over HTTP from Controller to Service. The service would obtain authentication details for itself, rather than just trusting what the web layer sent it.
So I think the Service should get the details itself, rather than the Controller passing them through.
Your bean is null because by default beans are singleton and they are created when the application starts, and as you can imagine, you are not going to have a SecurityContext at that point.
Try declaring your bean with request scope, in this way:
#Bean
#Scope(value=WebApplicationContext.SCOPE_REQUEST, proxyMode=ScopedProxyMode.TARGET_CLASS)
public MyUser getUser() {
.....
}
Recently I came accross this issue. Let's say that I want to give different authorizations to different methods within my controller. I would like to do this using the cookie that was send with the request and to do that I would like to use annotations. I tried to figure out how force #PreAuthorize annotation to work but I am missing something here.
Does anyone know if it's possible scenario (pseudo-code)
#Controller
class myController
{
#SomeAuthorizationAnnotation
#RequestMapping("/")
void doSth()
{
//something is done after authorization
}
}
And now i'd like to write my custom method which grants me access to http request sent to this controller where i could let's say write something like
HttpServletRequest ht = ....
Optional<Cookie> cookie = getCookie(ht.getCookies(), AUTH_TOKEN);
if (cookie.isPresent()) {
//grant authorization for the method call
}
I hope that this is somewhat understandable.
P.S I don't want to bind filters or sth to url's, i want to bind it to methods.
In way to secure your methods you can use #Secured or #PreAuthorize annotations but also you should declare it <global-method-security secured-annotations="enabled" />
See also method security
Well, my solution to this problem was by simply using Authentication Providers within Spring Security and implement interface of UserDetailsService to match my demands according to this project. So then my sessions are automatically handled without necessity for handling Cookies myself. And of course, annotations work.