springspring-securityoauth-2.0spring-security-oauth2

Spring Security 5.2 Password Flow


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?


Solution

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