user3467471 user3467471 - 1 month ago 34
reST (reStructuredText) Question

Consuming REST API secured with Keycloak as Broker for OAUTH2-Provider

I have to consume REST API of an app secured with Keycloak, which serves as Broker to OAUTH2-Provider.

For this aim I use OAuth2RestTemplate and ResourceOwnerPasswordDetails.
I can obtain an access token from third-part provider without any problem, but how have I use it further, it is the question. Using it in a Header as Bearer doesn't help.

Any suggestions?

OAuthConfig.java

@Bean
public ResourceOwnerPasswordResourceDetails resource(){
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setClientAuthenticationScheme(AuthenticationScheme.form);
resource.setAccessTokenUri(env.getProperty("access.token.uri"));
resource.setClientId(env.getProperty("access.client.id"));
resource.setGrantType("password");
resource.setClientSecret(env.getProperty("access.client.secret"));
resource.setUsername(env.getProperty("access.client.username"));
resource.setPassword(env.getProperty("access.client.password"));
resource.setScope(Arrays.asList(env.getProperty("access.client.scope")));
return resource;
}


Service to obtain an Access Token

@Autowired
private OAuthConfig authConfig;

@Override
public OAuth2AccessToken getAccessToken(){
ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider();
OAuth2AccessToken accessToken = provider.obtainAccessToken(authConfig.resource(), new DefaultAccessTokenRequest());
return accessToken;
}


OAuth2RestTemplate

@Autowired private ProdAuthService authService;

OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(authConfig.resource(), new DefaultOAuth2ClientContext(authService.getAccessToken()));
restTemplate.setRequestFactory(requestFactory);
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_JSON);
header.set("Authorization", "Bearer " + authService.getAccessToken());
HttpEntity<String> request = new HttpEntity<String>(header);
ResponseEntity <ProcessInstanceLogWrapper> response = restTemplate.exchange(uri, HttpMethod.GET, request, new ParameterizedTypeReference<ProcessInstanceLogWrapper>(){});
ProcessInstanceLogWrapper json = response.getBody();


An error I get

Caused by: org.springframework.security.oauth2.client.http.AccessTokenRequiredException: OAuth2 access denied.


In Keycloak we use also Authorizaion URL, but it seems not possible to use it with ResourceOwnerPasswordResourceDetails. It could also according to my opinion be the case, it doesn't work.

Answer

RestTemplate Creation:

 protected RestKeyCloakClient()
{
    MultiValueMap<String, String> header = new LinkedMultiValueMap<String, String>();
    OAuth2RestTemplate client;
    DefaultAccessTokenRequest accessTokenRequest = new DefaultAccessTokenRequest();
    DefaultOAuth2ClientContext context = new DefaultOAuth2ClientContext(accessTokenRequest);
    OAuth2AccessTokenSupport support = new OAuth2AccessTokenSupport()
    {
    };
    List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
    messageConverters.add(new FormOAuth2AccessTokenMessageConverter());
    messageConverters.add(new FormOAuth2ExceptionHttpMessageConverter());
    MappingJackson2HttpMessageConverter jackson = new MappingJackson2HttpMessageConverter();
    List<MediaType> mediaTypes = new ArrayList<MediaType>();
    mediaTypes.add(new MediaType("application", "x-www-form-urlencoded"));
    jackson.setSupportedMediaTypes(mediaTypes);
    messageConverters.add(jackson);
    support.setMessageConverters(messageConverters);
    client = new OAuth2RestTemplate(getAuthDetails(null, null), context);
    client.setErrorHandler(errorHandler);
    client.setRequestFactory(factory);
    token = client.getAccessToken();
}

ResourceOwnerPasswordResourceDetails:

private ResourceOwnerPasswordResourceDetails getAuthDetails(String userName, String userPwd)
{

    TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager()
    {
        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers()
        {
            return null;
        }

        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
        {
        }

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
        {
        }
    }};

    try {
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    } catch (Exception e) {
    }

    ResourceOwnerPasswordResourceDetails authDetails = new ResourceOwnerPasswordResourceDetails();
    authDetails.setAccessTokenUri(LoggerAndReader.getInstance().getoAuth2tokenRequestUrl());
    authDetails.setClientId(LoggerAndReader.getInstance().getoAuth2ClientId());
    authDetails.setClientSecret(LoggerAndReader.getInstance().getoAuth2SecretToken());
    authDetails.setGrantType(LoggerAndReader.getInstance().getOauth2granttype());
    if (StringUtils.isNotBlank(userName) && StringUtils.isNotBlank(userPwd)) {
        authDetails.setUsername(userName);
        authDetails.setPassword(userPwd);
    } else {
        authDetails.setUsername(LoggerAndReader.getInstance().getOauth2UserName());
        authDetails.setPassword(LoggerAndReader.getInstance().getOauth2password());
    }
    // authDetails.setScope(Arrays.asList(new String[] {"cn mail sn givenname uid employeeNumber"}));
    return authDetails;
}

Execution and futher usage:

public ResponseEntity<String> execute(String url, HttpMethod httpMethod, Object o)
{
    HttpEntity request = new HttpEntity(o, this.header);
    ResponseEntity<String> resp = null;
    this.header.set("Authorization", token.getTokenType() + " " + token.getValue());
    try {
        resp = this.client.exchange(url, httpMethod, request, String.class);
    } catch (Exception e) {
        String str = getStackTrace(e);
        if (StringUtils.containsIgnoreCase(str, "SocketTimeoutException")) {
            throw new KeycloakHTTPClientSocketException(
                "Got a SocketTimeoutException for URL:" + url + ", HTTPMethod:" + httpMethod);
        } else
            throw new Exception(...);
    }
    return resp;
}

For adding further headers, you need to add them:

public void setThisHeaderValue(String key, String value)
{
    this.header.add(key, value);
}