How do I customize the Spring Boot AccessTokenProvider? - java

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
...

Related

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.

Read JWT token in Spring Boot RouterFunction

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.

Access user data after login with Spring Boot OAuth2 Client

I am trying to build an OAuth2 Client using spring boot to access a custom API.
Here is my code so far:
#SpringBootApplication
public class MyClient {
public static void main(String[] args){
SpringApplication.run(MyClient.class, args);
}
}
#Configuration
#EnableOAuth2Sso
public class ApplicationSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception{
http.antMatcher("/**")
.authorizeRequests().antMatchers("/","/callback","/login**","/error**")
.permitAll().anyRequest().authenticated();
}
}
#RestController
public class HomeController {
#RequestMapping("/user")
public String login() {
String userInfoJson = "get data returned from API somehow";
return userInfoJson;
}
#RequestMapping("/callback")
public void callback(HttpServletResponse response) {
response.sendRedirect("/user");
}
}
I have created an application.yml file with all the properties needed and the login process works perfectly, returning the flow to /callback after a successful login.
At that point I should have received a token that I can use to fetch the user's data from the server.
How can I access this token?
Also, does spring boot have any classes to do the token validation process automatically, or do I have to create the request manually?
Thanks
from the documentation
An OAuth2 Client can be used to fetch user details from the provider (if such features are available) and then convert them into an Authentication token for Spring Security.
The Resource Server above support this via the user-info-uri property This is the basis for a Single Sign On (SSO) protocol based on OAuth2, and Spring Boot makes it easy to participate by providing an annotation #EnableOAuth2Sso.
The Github client above can protect all its resources and authenticate using the Github /user/ endpoint, by adding that annotation and declaring where to find the endpoint (in addition to the security.oauth2.client.* configuration already listed above):
application.yml.
security:
oauth2:
...
resource:
userInfoUri: https://api.github.com/user
preferTokenInfo: false
So as you can read this should be done automatically if you set where the user info shall be fetched from, and then it will be populated.
When an Authentication token is populated you can fetch this from the security context in multiple different ways.
#RequestMapping("/callback")
public void callback(Authentication authentication) {
final String name = authentication.getName();
response.sendRedirect("/user");
}
If you wish to access the raw json, you'll probably have to make the rest call yourself. If you want to add custom values to the authentication object you have to implement your own UserDetails class.

Conflict in Using #RepositoryRestController and #RepositoryRestResource For a Single Resource in Spring Boot

I have a Payment entity in my spring boot application. Considering all possible CRUD operations, I'm using spring data rest for read and want to implement a custom create operation. Also delete and update are not allowed for this entity.
So this is my desired URLs and resoponsible component for each one:
GET /payments : PaymentRepository
GET /payments/{id} : PaymentRepository
POST /payments : PaymentController
This is my repository:
#RepositoryRestResource
public interface PaymentRepository extends PagingAndSortingRepository<Payment, Long> {
// disable create and update
#Override
#RestResource(exported = false)
Payment save(Payment entity);
// disable delete
#Override
#RestResource(exported = false)
void delete(Payment entity);
}
And this is my controller:
#RepositoryRestController
#RequestMapping("/payments")
public class PaymentController {
#PostMapping("")
#ResponseBody
public Payment create() {
// some code...
}
}
If I map create operation to a url like POST /payments/create, everything works fine, but If I use the above code and map create to POST /payments, the GET /payments url does not work any more and I get 405 Method Not Allowed error. (GET /payments/{id} is still working)
It seems in this case presence of #PostMapping("") annotation, cause the PaymentController to responsd the GET /payments request and it fails.
I hope my explanations were clear. How can I solve this problem?
The Spring Data REST reference states that:
Sometimes you may want to write a custom handler for a specific resource. To take advantage of Spring Data REST’s settings, message converters, exception handling, and more, use the #RepositoryRestController annotation instead of a standard Spring MVC #Controller or #RestController.
It is not explicitly mentionned, but annotating your controller with #RepositoryRestController also allows you to define a custom behavior for one endpoint while keeping all the other endpoints that Spring automatically generates... On one condition: the #RequestMapping annotation can only be used at the method level (this is actually what is done in the example of the reference documentation).
Your example becomes:
#RepositoryRestController
public class PaymentController {
#PostMapping("/payments")
#ResponseBody
public Payment create() {
// some code...
}
}
With this, you get your custom endpoint mapped to POST /payments requests, plus all endpoints automatically generated by Spring, minus the ones annotated with #RestResource(exported = false).
#BasePathAwareController
#RepositoryRestController
public class PaymentController {
#PostMapping("/payments")
#ResponseBody
public Payment create() {
// some code...
}
}
You should modify your controller in the above way. #BasePathAwareController enables the custom REST URI's to get registered under your base URI.
With the above modification : both API's can work fine.

Spring Security: How to reject a request by default if no #PreAuthorize was specified

I have a simple Spring Boot application which exposes a REST API.
I have successfully configured the spring security to secure every method in the rest API according to its ROLE, using the #PreAuthorize("hasRole('ROLE_4')") annotation.
I have noticed that If I don't put the #PreAuthorize annotation at all, the framework allows this request to any authenticated user. I want to reverse this behavior. So if one of the programmers will forget to add the #PreAuthorize annotation, any request to this method will be rejected automatically.
Here is my configuration:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//Disable HTTP Basic authentication
http.httpBasic().disable();
//Add the filters
http.addFilterBefore(new AuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(securityServiceAuthenticationProvider());
}
#Bean
public AuthenticationProvider securityServiceAuthenticationProvider() {
return new SecurityServiceAuthenticationProvider();
}
}
Thanks
Guy Hudara
You can use a MethodSecurityInterceptor; sample XML configuration is here. The example applies security to a single bean, but the expressions are very flexible, you can protect e.g. all public members of any class with name ending in "Controller". I have used a similar XML configuration before, but I haven't done this with Java configuration; but I suppose you can do the same thing in Java configuration.
You could specify a custom AccessDecisionManager and, if the queried object is a MethodInvocation, then check if it has a #PreAuthorize annotation; if yes, let it pass. Otherwise, fail it. You can add it to the configuration like this: http.authorizeRequests().accessDecisionManager(myManager).

Categories