Get the OAuth2/OIDC access token with every request in Spring - java

I'm trying to enable multi-tenancy for a previously single-user system. The application used to run on a local server and had a relatively simple frontend baked in.
Now I want to allow multiple users to simultaneously use it in a cloud environment. I went ahead and implemented Auth2 with OIDC and PKCE to redirect users to an external Auth Provider. What I want now is that for every request, the user sends his Access token with the request in order for me to decide what data to provide with the answer.
I could not figure out how to obtain that data, as it seems that the spring framework (by default) only sends the ID token with the request. I suspect the fact that my software would simultaneously be the client and the resource server has something to do with it.
This is my first question, so I'm very happy to modify or extend my question if I've forgotten anything.
What I've tried to far:
I've used Postman to verify that the three tokens, ID token, refresh token and access token are issued correctly and can be retrieved with my credentials.
I tried getting the access token from the request itself. Any parameters (like #AuthenticationPrincipal OidcUser oidcUser) in the controller that include the token, however, are only showing the ID token and not the access token.
Getting the token via the OAuth2AuthorizedClientService does not work either, same problem, as I can only get the ID token, but not the access token.
Update #1, 13.12.2022/11:40: I am using PingOne by PingIdentity as the authentication provider and the following dependencies are or might be related or helpful to this matter:
spring-boot-starter-web
spring-boot-starter-security
spring-boot-starter-thymeleaf
spring-boot-starter-web-services
spring-boot-starter-oauth2-resource-server

Split your security in two with http.securityMatcher in the first picked SecurityFilterChain bean to restrict the endpoints it applies to (use #Order to control that), and configure WebClient to add access-token as Authorization header to its requests to resource-server (REST endpoints). This is demoed in the tutorial I already linked in my comment to your question. This tutorial matches exactly what you are asking, the fact that it uses Keycloak instead of PingOne as authorization server is a detail (solved by editing a property or two).
Resource-server security filter-chain
As reminder, a resource-server is a REST API secured with OAuth2. All end-points of #RestController and #Controller with #ResponseBody should be secured with a SecurityFilterChain containing http.oauth2ResourceServer(). The easiest way to configure a resource-server with Spring is using spring-boot-starter-oauth2-resource-server (or one of the very thin wrappers I wrote around it which enable to configure it from properties with 0 java conf)
By default with such filter-chains, Spring populates the security-context with a sub-class of AbstractOAuth2TokenAuthenticationToken<?>. You can retrieve the access-token from it. At least 2 options to access this Authentication instance:
SecurityContextHolder.getContext().getAuthentication()
have Spring auto-magically inject AbstractOAuth2TokenAuthenticationToken<?> auth as controller method parameter
You can also have the original authorization header injected as controller method parameter with #RequestHeader(value = HttpHeaders.AUTHORIZATION) String authorizationHeader.
I expose various ways to configure resource-servers security filter-chain in this tutorials.
Client security filter-chain
End-points of #Controller returning a template name and secured with OAuth2 are clients. The easiest way to configure a Spring client is with spring-boot-starter-oauth2-client and http.oauth2Login().
Note that in this configuration, the request between the browser and the Spring client is not OAuth2 (it is most frequently secured with a session cookie, not a Bearer access-token in Authorization header). Only requests sent by the Spring client (on the server) to resource-servers are secured with OAuth2.
With client security filter-chain, security-context is populated with OAuth2AuthenticationToken which, on purpose, exposes ID-token and not access-token. As reminder, ID tokens are holding user identity data and are intended to be used by clients when access-tokens audience is resource-servers and is designed for access-control. Clients should consider access-tokens as black box and use it only to authorize their requests to resource-servers (set Bearer Authorization header).
You can get the access-token string from OAuth2AuthorizedClient: authorizedClient.getAccessToken().getTokenValue(), which is itself retrieved from the OAuth2AuthorizedClientService you can auto-wire in your controllers: authorizedClientService.loadAuthorizedClient("your-client-id", auth.getName()) (auth being the OAuth2AuthenticationToken instance retrieved from security-context via SecurityContextHolder or controller method parameter injection)
If you need to authorize a WebClient request from client to resource-server, you can do simpler than retrieve access token and position authorization header: do as in the UiController of the tutorial already linked in my comment to your question:
final var authorizedClient = authorizedClientService.loadAuthorizedClient("spring-addons-public", auth.getName());
final var response = webClient.get().uri("http://localhost:8080/api/greet")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient(authorizedClient))
...
With WebClient configured as follow:
#Bean
WebClient webClient(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository,
authorizedClientService));
oauth.setDefaultClientRegistrationId("spring-addons-public");
return WebClient.builder().apply(oauth.oauth2Configuration()).build();
}

Thanks to those who tried to help me, but eventually I figured it out myself.
I extended my Controllers by two attributes: OAuth2AuthenticationToken authentication and HttpServletRequest request.
Also, I #Autowired in the OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository.
This then allows the following call returning the value of the accessToken:
(oAuth2AuthorizedClientRepository.loadAuthorizedClient(myClientRegistrationId, authentication, request)).client.getAccessToken().getTokenValue();.
After that, it's just parsing the token and retrieving the values using JWTParser.parse() and provided methods from the JWT-result.
Personal note: Don't do the parsing and retrieving value parts in your controller to keep any more complex logic out of it.
I hope this helps somebody!

Related

Stateful and statless auth in Spring security for the same path

I have application (with server side rendered html) that uses session based authentication, it just sets JSESSIONID cookie after successful authentication.
Now I'd would like to add second type of authentication by providing JWT token in Bearer header. I added new once per request filter and registered it. When I hit some private path I see that my filter is used and I set new UsernamePasswordAuthenticationToken in auth context but in postman i see error that there is infite redirect loop.
Is it possible to make it wokring? Every other post I've seen is about having different auth for different paths, like JWT for every path that starts with /api/** and session based for /** but I haven't seen anything like I described. The problem is only with this redirect to /login page I guess, how can I prevent it if I already set auth context in JWT filter?

How to get oauth2 access token in a spring boot application (not a web application) using spring security 5

I need to get access token (grant_type = client_credentials) in the service layer of my spring boot application to talk to other microservice (service to service interaction). There is no spring http session or auth at this layer, I just have client_id, client_secret and token url. These properties are set in application.properties as:
spring.security.oauth2.client.registration.auth1.client-id=***
spring.security.oauth2.client.registration.auth1.client-secret=***
spring.security.oauth2.client.registration.auth1.authorization-grant-type=client_credentials
spring.security.oauth2.client.provider.auth1.tokenUri=***
This seemed simple with Spring Security OAuth, but can't figure out with Spring security. Referred the documents for spring security 5, but everything seems to be in context of web interface. I understand I could just make http call to get the token with the info I have, but I wanted to utilize the framework...
Scenario:
Lets call this spring boot app service A. There are other services which might call A to process updates on http or send kafka message on a topic which A listens to. When A processes the update/message, it needs to send some data to service B which requires access token for authz. This is where I need the access token. So the interaction is essentially service-service and is not specific to user.
When not in the context of a web interface, you'll want to look at the service layer.
From Spring Security's Javadocs for AuthorizedClientServiceOAuth2AuthorizedClientManager:
An implementation of an {#link OAuth2AuthorizedClientManager} that is capable of operating outside of a {#code HttpServletRequest} context, e.g. in a scheduled/background thread and/or in the service-tier.
Here's a #Bean definition that may help, which I'll explain below:
#Bean
OAuth2AuthorizedClientManager authorizedClientManager
(ClientRegistrationRepository clients) {
OAuth2AuthorizedClientService service =
new InMemoryOAuth2AuthorizedClientService(clients);
AuthorizedClientServiceOAuth2AuthorizedClientManager manager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(clients, service);
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
manager.setAuthorizedClientProvider(authorizedClientProvider);
return manager;
}
An OAuth2AuthorizedClientManager manages authorizing OAuth 2.0 client definitions. These definitions are stored in a ClientRegistrationRepository, and a default instance of ClientRegistrationRepository is created by Spring Boot via the properties you've already got defined.
The manager typically needs two things to function:
The first is an OAuth2AuthorizedClientService, which is handy if you are wanting to store tokens in a database - in Spring Security 5.2, the only implementation is an in-memory one; however, it appears that 5.3 will ship with a JDBC implementation.
The second is an OAuth2AuthorizedClientProvider, which is what actually performs the token requests, like the client credentials one you want to make.
With this manager created, you can wire it into your web client:
#Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction
(authorizedClientManager);
oauth2.setDefaultClientRegistrationId("auth1");
return WebClient.builder()
.apply(oauth2.oauth2Configuration())
.build();
}
The exchange filter function used above is the thing that adds the bearer token to the Authorization header. It calls the manager to ask it for a token, the manager pulls it from the service. If it's expired, the manager asks the provider to refresh it. Now, with a fresh token, the manager hands it back to the filter to get it added into the request.

What's the Spring Security 5.2 / WebClient way of using username & password to connect to another service?

We currently have several Spring Boot applications connecting to other services using a service account. Till now we used the AccessTokenRequest of the OAuth2ClientContext on a RestTemplate to put the user and password of the service account in and used the returned OAuth token to connect to the other services.
Right now we're building a new application using Spring Boot 5.2 and as the new Spring Security OAuth should be used now the separate OAuth library has become deprecated, we would also like to replace the RestTemplate solution with a WebClient one as the RestTemplate will become deprecated in the near future as well. I've tried several approaches of retrieving the token, but couldn't find a solution that works.
I found set-ups like the one mentioned on Spring Security 5 Replacement for OAuth2RestTemplate, but no way of putting a username and password inside the WebClient.
I found other approaches using a ClientRegistrationRepository instead of a ReactiveClientRegistrationRepository and some of those approaches actually have options (like How to re-initialize password grant in Spring security 5.2 OAuth) of putting a username and password in an AuthorizedClientManager that gets to be a parameter when instantiating the filter, but somehow I always end up with the message that no Bean of the ClientRegistrationRepository could be found, no matter what properties I put in the application.yaml (maybe this doesn't work because the application is an MVC application instead of a WebFlux one?)
I know that I need to set the authorization-grant-type to be 'password', but there's already someone else asking how to get that working (Spring Security 5.2.1 + spring-security-oauth2 + WebClient: how to use password grant-type) and no answers to that question, yet.
So ... did they 'deprecate' this easy way of using a username and password to retrieve a token and use that token to connect to another service in Spring Security 5.2? And if yes, what should be used now?
Yes, RestTemplate and OAuthRestTemplate are all deprecated. WebClient supports OAuth out of the box. You don't need to do anything special or add any headers yourself. It's actually extremely trivial.
Create a configuration class that exposes a WebClient bean, make sure you take the client repository as a param. In the method, you pass the repo into a filter function:
#Bean
public WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
return WebClient.builder().filter(filterFunction(clientRegistrations))
.baseUrl(String.format("http://%s:8080", getHostName()))
.build();
}
private ServerOAuth2AuthorizedClientExchangeFilterFunction filterFunction(ReactiveClientRegistrationRepository clientRegistrations) {
ServerOAuth2AuthorizedClientExchangeFilterFunction filterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
filterFunction.setDefaultClientRegistrationId("myKey");
return filterFunction;
}
NOTE: In the filter function, replace the "myKey" with something that matches the following property structure in application.properties (replace myKey in the property paths with your name):
spring.security.oauth2.client.registration.myKey.authorization-grant-type=password
spring.security.oauth2.client.registration.myKey.client-id=xxx
spring.security.oauth2.client.registration.myKey.client-secret=xxx
spring.security.oauth2.client.provider.myKey.token-uri=http://localhost:8080/oauth/token
Aaaannddd.... you're done! OAuth token refresh is also built in.
Well, it turned out to be a little bit different. A comment on the last SO question I linked requested the author to use debugging in the PasswordOAuth2AuthorizedClientProvider to see what went on / wrong. So I started debugging as well and with the setup you provided there are 4 Providers for 4 login types that are supplied, but out of the 4 it's not the PasswordReactiveOAuth2AuthorizedProvider that's used, and also not the ClientCredentialsReactiveOAuth2AuthorizedProvider or RefreshTokenReactiveOAuth2AuthorizedProvider one, but the AuthorizationCodeReactiveOAuth2AuthorizedProvider is called and that's quite weird when authorization-grant-type is set to password. That seems like a bug to me ...
Anyway, I found another SO question from rigon with a problem a bit different than mine, but close enough: Create route in Spring Cloud Gateway with OAuth2 Resource Owner Password grant type and he provided code I could get working as the Provider used with that codebase is in fact the PasswordReactiveOAuth2AuthorizedClientProvider
In the end I only needed to enter 3 items in the yaml-file: the client-id (with the client_id that used to be put as attribute on the AccessTokenRequest), the authorization-grant-type and the token-uri
Besides that I copied the WebClient and ReactiveOAuth2AuthorizedClientManager setup from the code provided by rigon and put the username and password from the configuration file into the settings (I left the seperate contextAttributesMapper out as I only needed to provide 2 context parameters directly into a map).

Spring Security Oauth2: Fallback when access token is invalid/expired

I am using Spring Security to protect my endpoints.
My problem is, is it possible to response differently when users are using valid/invalid access token?
For example, for a single /api/info
(1) When an invalid/expired access token is passed in the request, only very limited information will be returned
(2) When a valid access token is passed in the request, very customized and rich content is returned according to a different user.
I've tried to use access=permitAll(), but it doesn't work because invalid tokens can not pass oauth2 validation.
Using security="none" is also not working because it will not try to get user info at all.
Write a custom OAuth2AccessDeniedHandler and plug it to your /info endpoint using security:access-denied-handler XML tag or Similar Java Config.

Spring Security 3.2 Token Authentication

I know this has been asked already, but I am not able to get it to work.
Here is what I would like to get accomplished:
I am using Spring Security 3.2 to secure a REST-like service. No server side sessions.
I am not using basic auth, because that would mean that I need to store the user's password in a cookie on client side. Otherwise the user would need to login with each page refresh/ change. Storing a token is I guess the lesser evil.
A web client (browser, mobile app) calls a REST-like URL to login "/login" with username and password
The server authenticates the user and sends a token back to the client
The client stores the token and adds it to the http request header with each api call
The server checks the validity of the token and sends a response accordingly
I did not even look at the token generation part yet. I know it is backwards, but I wanted to get the token validation part implemented first.
I am trying to get this accomplished by using a custom filer (implementation of AbstractAuthenticationProcessingFilter), however I seem to have the wrong idea about it.
Defining it like this:
public TokenAuthenticationFilter() {
super("/");
}
will only trigger the filter for this exact URL.
I am sticking to some sample implementation, where it calls AbstractAuthenticationProcessingFilter#requiresAuthentication which does not accept wildcards.
I can of course alter that behavior, but this somehow makes me think that I am on the wrong path.
I also started implementing a custom AuthenticationProvider. Maybe that is the right thing?
Can someone give me a push into the right direction?
I think pre-auth filter is a better fit for your scenario.
Override AbstractPreAuthenticatedProcessingFilter's getPrincipal and getCredentials methods.
In case the token is not present in the header, return null from getPrincipal.
Flow:
User logs in for the first time, no header passed, so no
authentication object set in securityContext, normal authentication
process follows i.e. ExceptionTranslation filter redirtects the user
to /login page based on form-logon filter or your custom authenticationEntryPoint
After successful authentication, user requests secured url, pre-auth filter gets token from header authentication object set in
securityContext, if user have access he is allowed to access secured
url

Categories

Resources