Reputation: 11
I am new to Spring security using OAuth2 - I am working on publishing the JSON message to an endpoint in a non-servlet environment (which doesn't involve any UI). To publish this we would need accessToken(using OIDC) which we get it from another authorization service.This access token is just valid for 5 mins. My initial thoughts of implementing this is the usual approach where the response time is stored and check with the system time periodically , if the time is close, would pass the refresh token to the service to get the new access token.
I would like to know if there ways in Spring that can help me achieve this.
I have tried using webClient which does the access token refresh by itself - but I would want to understand how the access token can be retrieved from it
ClientRegistration registration = ClientRegistration .withRegistrationId("id-name") .tokenUri(token_uri) .clientId(client_id) .clientSecret(client_secret) .scope(scope) .authorizationGrantType(new AuthorizationGrantType(authorizationGrantType)) .build(); return new InMemoryReactiveClientRegistrationRepository(registration);
@Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
ServerOAuth2AuthorizedClientRepository authorizedClients) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
oauth.setDefaultClientRegistrationId("id-name");
// oauth.setDefaultOAuth2AuthorizedClient(true);
return WebClient.builder()
.filter(oauth)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
any idea on how i can read the access token retrieved? as I would need to set it up in the header for the subsequent request
something like :
webClient
.method(HttpMethod.POST)
.uri("<URI>")
.header("<key>","Bearer " <accesstokenretrieved>)
.exchange()
.flatMap(response -> response.bodyToMono(String.class))
Was trying different ways - however i get the 401 unauthorized access when executing the above piece - so just wanted make sure if i really get the access token
Upvotes: 1
Views: 5116
Reputation: 291
I have implemented nearly exactly this use case. In my case, the authentication provider is MS Azure and I use their framework, so you probably have to change it a bit. I use OAuth for the login process (client acts as the person who logs in) and for some API calls (client acts as an application), so I registered two clientRegistrations in the ClientRegistrationRepository, one for the login (with AuthorizationGrantType.AUTHORIZATION_CODE
) and one for the API (with AuthorizationGrantType.CLIENT_CREDENTIALS
). With the correct bean name, this should be injected in the oauth2 login flow (old fashioned xml style):
<bean class="de.mypackage.sopa.security.HardWiredInMemoryTokenRepository" id="clientRegistrationRepository">
<constructor-arg name="loginBaseUri" value="${ad.login.base.url}"/>
<constructor-arg name="clientName" value="${ad.client.name}"/>
<constructor-arg name="clientId" value="${ad.client.id}"/>
<constructor-arg name="clientSecret" value="${ad.client.secret}"/>
<constructor-arg name="tenantId" value="${ad.tenant.id}"/>
<constructor-arg name="redirectUriTemplate" value="${ad.redirect.uri}"/>
<constructor-arg name="userInfoUri" value="${ad.userinfo.uri}"/>
</bean>
Don't worry about the customized ClientRegistrationRepository
, you can usually use the default implementation InMemoryClientRegistrationRepository
.
So far, I have two client registrations in my repository, the one for the login is out of scope, there are several tutorials on how to implement OAuth login with spring security. For The API call, I implemented an AuthenticationProvider
, but since I use an external framework (msal), I had to write my own one and couldn't use an already existing oauth2 implementation of AuthenticationProvider. So mine looks like this, including a potential token refresh:
package de.mypackage.security;
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.http.IHttpRequest;
import de.mypackage.GlobalConstants;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import java.time.Instant;
/**
* Authentication provider to authenticate against Azure AD.
* This service authenticates unlike the login process as an application and is "plugged into"
* the msal API to provide an authentication provider.
* It uses the Grant Type "ClientCredentials" and acts with as an admin user contrary to the login,
* which uses the actual permissions of the logged-in user.
* Finally, it sets the authentication header in the API request provided by the msal SDK.
*
* @author me, myself and I
* @see com.microsoft.graph.authentication.IAuthenticationProvider
* @see com.microsoft.graph.http.IHttpRequest
*/
public class SpringSecurityWrappingAuthenticationProvider implements IAuthenticationProvider {
private final static Logger LOG = LogManager.getLogger(SpringSecurityWrappingAuthenticationProvider.class);
public static final String BEARER = "Bearer ";
private ClientRegistration clientRegistration;
private DefaultClientCredentialsTokenResponseClient client;
private OAuth2AccessToken accessToken;
public SpringSecurityWrappingAuthenticationProvider(ClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistration = clientRegistrationRepository.findByRegistrationId(GlobalConstants.OAUTH_GRAPH_REGISTRATION_ID);
this.client = new DefaultClientCredentialsTokenResponseClient();
}
@Override
public void authenticateRequest(IHttpRequest iHttpRequest) {
try {
refreshAccessTokenIfNeeded();
String tokenString = accessToken.getTokenValue();
iHttpRequest.addHeader("Authorization", BEARER + tokenString);
LOG.debug("Headers: \n" + iHttpRequest.getHeaders());
} catch (Exception e) {
LOG.error("Error while obtaining OAuth access token.", e);
}
}
private void refreshAccessTokenIfNeeded() {
if (accessToken == null || accessToken.getExpiresAt().compareTo(Instant.now()) < 0) {
OAuth2AccessTokenResponse tokenResponse = client.getTokenResponse(new OAuth2ClientCredentialsGrantRequest(clientRegistration));
accessToken = tokenResponse.getAccessToken();
}
}
}
I know that this is not exactly what you're looking for but it might be a hint as it's quite hard to find an example and it took me some fiddling to get it straight.
Upvotes: 1