Reputation: 584
I am trying to authenticate the user using the password flow in the latest version of Spring Security - 5.2.
The docs seem to suggest how to do that.
@Bean
public OAuth2AuthorizedClientManager passwordFlowAuthorizedClientManager(
HttpClient httpClient,
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
DefaultPasswordTokenResponseClient c = new DefaultPasswordTokenResponseClient();
RestTemplate client = new RestTemplate(requestFactory);
client.setMessageConverters(Arrays.asList(
new FormHttpMessageConverter(),
new OAuth2AccessTokenResponseHttpMessageConverter()));
client.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
c.setRestOperations(client);
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.password(configurer -> configurer.accessTokenResponseClient(c))
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
authorizedClientManager.setContextAttributesMapper(authorizeRequest -> {
Map<String, Object> contextAttributes = new HashMap<>();
String username = authorizeRequest.getAttribute(OAuth2ParameterNames.USERNAME);
String password = authorizeRequest.getAttribute(OAuth2ParameterNames.PASSWORD);
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
return contextAttributes;
});
return authorizedClientManager;
}
I execute the request, I can see the access token returned in HTTP header but the SecurityContext is not populated and the session user remains anonymous.
String username = "joe";
String password = "joe";
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
ClientRegistration r = clientRegistrationRepository.findByRegistrationId("keycloak");
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(r.getRegistrationId())
.principal(authentication)
.attributes(attrs -> {
attrs.put(OAuth2ParameterNames.USERNAME, username);
attrs.put(OAuth2ParameterNames.PASSWORD, password);
})
.build();
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
Any ideas?
Upvotes: 7
Views: 4164
Reputation: 817
Probably .password(..)
is not the best povider in this case, because by default DefaultPasswordTokenResponseClient
extracts token from reponse body.
If,
.. the access token returned in HTTP header..
.. (which is strange for this type of flow 🤔), then we need to override getTokenResponse
method with our own.
DefaultPasswordTokenResponseClient
getTokenResponse
method code with:@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
Assert.notNull(clientCredentialsGrantRequest, "clientCredentialsGrantRequest cannot be null");
RequestEntity<?> request = this.requestEntityConverter.convert(clientCredentialsGrantRequest);
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
String token = response.getHeaders().getFirst("<accessToken header name>");
return OAuth2AccessTokenResponse.withToken(token);
}
Then we can use this client as .password(...)
provider configurer
.password(configurer -> configurer.accessTokenResponseClient(<instance of your client>))
Upvotes: 0
Reputation: 584
After reading into the documentation a bit more I do not think that Oauth 2 password flow in Spring Security 5.2 is supported the same way authorisation flow is. Spring Security 5.2 has password flow support for the http client which can cache the authorization request and refresh the token before it expires - but there is no end user password flow support in which the client proxies the credentials to the authorization server.
Of course, it is entirely possible to authenticate the end user by harvesting the credentials, implementing a custom AuthenticationProvider that swaps the credentials for a token with the authorization server and returns an OAuth2AuthenticationToken that is persisted to the context.
Upvotes: 2