Read JWT token in Spring Boot RouterFunction - java

In Spring Boot Controller implementation we can get the JwtAuthenticationToken as a parameter in our method. Same token can be read, manipulated and validated for authorization like below
#PostMapping("/hello")
#PreAuthorize("hasAuthority('SCOPE_Internal') or hasAuthority('ROLE_User')")
public Mono<String> testHello(JwtAuthenticationToken token) {
log.info("token is " + token.getTokenAttributes().toString());
return Mono.just("OK");
}
We are using reactive Spring Boot and we have replaced our controllers with RouterFunction. We are wondering how above feature - Authorization and get the token in our router method calls.
public RouterFunction<ServerResponse> route() {
return RouterFunctions.route(GET("/hello"), helloHandler::testHello);
}
When we tried passing the JwtAuthenticationToken in the router method call, it threw
Could not autowire. No beans of 'JwtAuthenticationToken' type found.
public RouterFunction<ServerResponse> route(JwtAuthenticationToken jwtAuthenticationToken) {
return RouterFunctions.route(GET("/hello"), helloHandler::testHello);
}

We came up this solution if it makes any sense, or valid. We ran into same issue lately as we began a journey of converting our legacy and synchronous spring boot server app to an asynchronous one. The JwtAuthenticationToken which we use to get some added attribute to the token used by the system works out of the box when we used the RestController and pass it as an argument in the protected endpoint method. But with Router Function we could not get it to work. After 1 day of painstaking research on google, stackoverflow, spring's reactive application resource server docs, we could not get any head way. However, this post got us thinking even more. so we came up with this solution:
#Slf4j
#Component
#RequiredArgsConstructor
public class FitnessAccountWebHandler {
private final FitnessAccountService accountService;
public Mono<ServerResponse> getAccountByUserId(ServerRequest request) {
String userId = request.pathVariable(AccountResourceConfig.USER_ID);
// This will give you the JwtAuthenticationToken if a Principal is
// returned or empty if no authentication is found
Mono<JwtAuthenticationToken> authentication = request
.principal()
.cast(JwtAuthenticationToken.class);
return authentication.flatMap(auth -> {
log.info("Subject: {}", auth.getName());
return accountService.getSomething(userId)
.flatMap(ServerResponse.ok()::bodyValue)
.switchIfEmpty(ServerResponse.notFound().build());
});
}
}
Hope this helps someone and save some time.

Related

Lost authentication when using GraphQL subscriptions with Spring Security

I'm using spring-boot-starter-graphql for my GraphQL server application. I protected the application with basic authentication using Spring security.
GraphQL subscription are used to return a type with a subtype. As soon as subscription returns a type, authentication gets lost from security context.
Here is short version of my configuration:
type Post {
id: String!
title: String!
author: Author!
}
type Author {
id: String!
name: String!
}
type Subscription {
getNewPost: Post
}
#SchemaMapping(typeName = "Post", field = "author")
public Author getAuthor(Post post) {
return this.appService.getAuthorById(post.getAuthorId());
}
#Bean
Many<Post> publisher() {
return Sinks.many().multicast().directBestEffort();
}
#SubscriptionMapping("getNewPost")
public Publisher<Post> getNewPost() {
return this.publisher.asFlux();
}
public Post createPost(CreatePostInput postInput) {
...
this.publisher.tryEmitNext(post);
...
}
Everything works as expected until subscription returns first object. After that security context becomes empty and any following request gets redirected to login page.
I noticed two things:
In case that object that subscription returns (Post in this example) doesn't contain nested object (Author), everything works as it should
With Java Kickstart everything works as it should
I created an example application for reproducing this issue: https://github.com/stojsavljevic/graphql-security-issue
Any help is very much appreciated!
According to Rossen Stoyanchev's presentation at Spring IO last May (which you can watch here), Spring for GraphQL has a layered architecture that has a Web transport layer at the top (be it HTTP or Websocket), then comes the GraphQL engine, and then the Data Controllers.
It's at this last level that we find the Components, like a mutation or a query service, or also one of this "service" operations invoked by a Controller method.
Spring for GraphQL makes sure that the Security context (e.g. the SecurityFilterChain) gets propagated through the GraphQL engine layer and remains available.
For that to work in your example, according to what I saw in your Github repository I believe you should use a #Secured("ROLE_ADMIN") annotation in your methods.
#Secured("ROLE_ADMIN")
public Post createPost(CreatePostInput postInput) {
Post post = new Post("1", postInput.getTitle(), postInput.getAuthorId());
// not actually saving anything
// publish newly created Post
this.publisher.tryEmitNext(post);
return post;
}
Make sure to enable method level security as well.
Hope it helps.
Note: For the Security part of Rossen's talk go to 24:05 onwards.

Testing a Spring controller secured with Keycloak

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.

Webflux Http 404 Response when header is added to Request

I am currently trying to shift my application from spring boot 1.5.x to 2.x.x on reactive stack. I am facing a kinda weird problem that I can't figure out. Hope someone knows the solution to this.
I implemented an api to receive a user jwt token as "Authorization" field on the header. The api is a POST method that receives a certain json data from the user in the body, goes to the backend and processes it.
The unfortunate thing is i keep getting a http 404 error when i add in the header, a normal 200 when i remove it in postman.
Here is my controller.
#RestController
#RequestMapping("/user")
#Slf4j
public class UserHandler {
#Autowired
private UserService service;
#Autowired
private Utility utility;
#PostMapping("/updateLink")
public Mono<ServerResponse> addNewAccountLinkAPI(#RequestHeader(name="Authorization") String id, #RequestBody UpdateAccountLink request){
return Mono.just(request)
.flatMap(s -> service.addNewAccountLink(s))
.flatMap(s -> ok().body(BodyInserters.fromObject(new RespWrap("Success", new Date(), null, s))))
.switchIfEmpty(badRequest().body(BodyInserters.fromObject(new RespWrap("Failed", new Date(), "Failed to create new link", null))));
}
}
Here is my simple security config
#Configuration
#EnableWebFluxSecurity
#EnableWebFlux
public class ResourceServerConfig implements WebFluxConfigurer {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, FirebaseAuthenticationManager manager) {
http
.authorizeExchange().anyExchange().permitAll()
.and().csrf().disable();
return http.build();
}
}
Can anyone please point me out on the problem. This sure seems like a lack of config problem.
I can see two issues with your code snippets.
First, you shouldn't add #EnableWebFlux as it completely disables the auto-configuration done by Spring Boot. Same goes for #EnableWebMvc in a Spring MVC application.
Second, you're mixing WebFlux annotations and WebFlux functional. The annotations you're using are fine, but the ServerResponse type should only be used when writing functional handlers. You should try instead here to use ResponseEntity.

How to refresh OAuth2 token with Spring Security 5 OAuth2 client and RestTemplate

Spring Security 5.1.0.M2 (release notes) added support for automatic refreshing of tokens when using WebClient. However, I am using RestTemplate. Is there a similar mechanism for RestTemplate or do I need to implement that behavior myself?
The OAuth2RestTemplate class looks promising but it's from the separate Spring Security OAuth module and I would like to use plain Spring Security 5.1 on the client if possible.
OAuth2RestTemplate Will refresh tokens automatically. RestTemplate will not (refresh tokens is part of the OAut2 spec, hence the OAuth2RestTemplate.
You have 2 options:
Use Spring Security OAuth2 module and everything will work pretty much out of the box (configuration properties provided by Spring)
Create your own RestTemplate based on Spring's OAut2RestTemplate
Spring's OAuth2 module will be integrated into Spring Security in the future.
I would go for option 1.
OAuth2RestTemplate should be used instead of RestTemplate when JWT authentication is required. You can set AccessTokenProvider to it, which will tell how the JWT token will be retrieved: oAuth2RestTemplate.setAccessTokenProvider(new MyAccessTokenProvider());
In class implementing AccessTokenProvider you need to implement obtainAccessToken and refreshAccessToken methods. So in obtainAccessToken method it can be checked if token is expired, and if it is - token is retrieved through refreshAccessToken. Sample implementation (without the details of actual token retrieval and refreshing):
public class MyAccessTokenProvider implements AccessTokenProvider {
#Override
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest parameters)
throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException {
if (parameters.getExistingToken() != null && parameters.getExistingToken().isExpired()) {
return refreshAccessToken(details, parameters.getExistingToken().getRefreshToken(), parameters);
}
OAuth2AccessToken retrievedAccessToken = null;
//TODO access token retrieval
return retrievedAccessToken;
}
#Override
public boolean supportsResource(OAuth2ProtectedResourceDetails resource) {
return false;
}
#Override
public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resource,
OAuth2RefreshToken refreshToken, AccessTokenRequest request)
throws UserRedirectRequiredException {
OAuth2AccessToken refreshedAccessToken = null;
//TODO refresh access token
return refreshedAccessToken;
}
#Override
public boolean supportsRefresh(OAuth2ProtectedResourceDetails resource) {
return true;
}
}
Did not find a way for Spring to call the refreshAccessToken automatically, if someone knows how to do that - please share.

How do I customize the Spring Boot AccessTokenProvider?

I want to enhance the token request for my OAuth2 provider. I need to add an additional parameter to the POST request. I don't understand where to hook into the Spring Boot framework to accomplish this.
The Spring Boot framework provides a hook for customizing the OAuth2RestTemplate as described in "Customizing the User Info RestTemplate". I have implemented the following customizer, which gets instantiated and called as expected. Unfortunately, my provider does not seem to get called when the token request is made.
public class AadUserInfoRestTemplateCustomizer implements UserInfoRestTemplateCustomizer {
#Override
public void customize(OAuth2RestTemplate oAuth2RestTemplate) {
oAuth2RestTemplate.setAuthenticator(new AadOauth2RequestAuthenticator());
// Attempt 1: Use my own token provider, but it never gets called...
oAuth2RestTemplate.setAccessTokenProvider(new AadAccessTokenProvider());
// Even better, if only OAuth2RestTemplate provided a getter for AccessTokenProvider, I could add interceptors and or enhancers
// Can't do this :( AuthorizationCodeAccessTokenProvider provider = oAuth2RestTemplate.getAccessTokenProvider();
}
}
QUESTION:
How does set a custom AccessTokeProvder, or even better, get a reference to the default one and hook into the request with an interceptor or enhancer?
CODE SAMPLE
In the fork below, please see the /simple module. Add your AAD tenant info into the /simple/src/main/resources/application.yml file:
https://github.com/bmillerbma/tut-spring-boot-oauth2/tree/aad
NOTES:
This commit to the framework seems to make this possible, but how does one leverage this functionality?
This question seems to be related. Somehow the fella added a custom provider. But where?
As a workaround, I added the resource to my config file and added the following two classes to capture the OAuth2RestTemplate and add request enhancers.
application.yaml:
aad:
resource: https://graph.windows.net
security:
oauth2:
client:
clientId: [clientid]
etc.
#Component
public class AzureRequestEnhancerCustomizer {
#Autowired
private OAuth2RestTemplate userInfoRestTemplate;
#Autowired
private AzureRequestEnhancer azureRequestEnhancer;
#PostConstruct
public void testWiring() {
AuthorizationCodeAccessTokenProvider authorizationCodeAccessTokenProvider = new AuthorizationCodeAccessTokenProvider();
authorizationCodeAccessTokenProvider.setTokenRequestEnhancer(azureRequestEnhancer);
userInfoRestTemplate.setAccessTokenProvider(authorizationCodeAccessTokenProvider);
}
}
#Component
public class AzureRequestEnhancer implements RequestEnhancer {
#Value("${aad.resource:null}")
private String aadResource;
#Override
public void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form, HttpHeaders headers) {
if (!StringUtils.isEmpty(resource)) {
form.set("resource", aadResource);
}
}
}
I came across with the same issue and used this workaround but because of this I stuck with spring boot 1.3.8
So I started to dig deeper and then I finally found an easier method. Just add a resource parameter after the userAuthorizationUri.
security:
oauth2:
client:
...
userAuthorizationUri: https://login.microsoftonline.com/<<tenantId>>/oauth2/authorize?resource=https://graph.windows.net
...

Categories