带有Spring应用程序的KeycloakRestTemplate [英] KeycloakRestTemplate with spring application

查看:399
本文介绍了带有Spring应用程序的KeycloakRestTemplate的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有spring rest api的spring客户端,该api由keycloak保护. 我试图从另一个客户端使用keycloakresttemplate调用它,这是一个没有安全性的纯Java代码.我从Java客户端获取keycloak访问令牌,并将其设置在rest url的标头中.它无法初始化keycloakresttemplate.

I have a spring client with spring rest api's which are protected with keycloak. I am trying to call it using keycloakresttemplate from another client which is a pure java code with no security. I am getting the keycloak access token from java client and setting it in the header of rest url. It is not able to initialize the keycloakresttemplate.

任何人都知道我为什么要面对这个问题.

Any view why I am facing this issue.

//下面是使用keycloakresttemplate命中spring url的代码.我已经添加了keycloack适配器的依赖关系,并在我的java类中添加了bean.

//Below is the code to hit the spring url using keycloakresttemplate.I have added Dependency of keycloack adapter and added the bean in my java class.

restTemplate.getForObject(<restapiURL>, class1, requestData);

//以下是我用于从密钥斗篷获取访问令牌的代码

//Below is the code I am using for getting the access token from keycloak

MultiValueMap<String,String> requestMap = new LinkedMultiValueMap<String,String>();
requestMap.add("client_id", "employee-service");
requestMap.add("username", "rachel");
requestMap.add("password", "rachel");
requestMap.add("grant_type", "password");
requestMap.add("client_secret", "cccebf50-3f28-4af2-8716-c4bfcfe6f5e7");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);   
HttpEntity<MultiValueMap<String, String>> requestData = new HttpEntity<>(requestMap, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
String restApiurl = "http://localhost:8080/auth/realms/dev/protocol/openid-connect/token" ;
return restTemplate.postForObject(restApiurl, requestData, AccessTokenResponse.class);

客户端的Keycloak设置: KeycloakSetup

Keycloak Setup for client: KeycloakSetup

Myworkspace结构 Java客户端 Java客户端结构

Myworkspace structure Java Client Java Client Structure

Java Client2: REST API应用程序结构

Java Client2: REST API app structure

根据您在评论部分的建议,我添加了一种方法 另一种方式,

I have added one way ,a s per your suggestion in comment section and the other way,

import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.ws.rs.InternalServerErrorException;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.TrustStrategy;
import org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory;
import org.keycloak.adapters.springsecurity.client.KeycloakRestTemplate;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import com.dbaas.webagent.model.Employee;

public class KeycloakAgentClient {

    @Autowired
    KeycloakRestTemplate restTemplate;


    //RestTemplate restTemplate;
    public static final String REQUEST_URI = "http://localhost:8086/dbaasrest/addressService/getEmployees";
    public static void main(String[] args) throws KeyManagementException, KeyStoreException, NoSuchAlgorithmException {

        AuthzClient authzClient = AuthzClient.create();
        AuthorizationResponse response = authzClient.authorization("rachel", "rachel").authorize();

        String rpt = response.getToken();
        System.out.println("You got an RPT: " + rpt);


        KeycloakAgentClient client = new KeycloakAgentClient();
        List<Employee> list = client.getEmployeeList(rpt, new RestTemplate());
        System.out.println("Employee List:***"+list);

    }

    private List<Employee> getEmployeeList(String accessToken, RestTemplate restTemplate) {

        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer "+accessToken);

        HttpEntity<?> requestEntity = new HttpEntity<>(headers);
        //restTemplate.getInterceptors().add((ClientHttpRequestInterceptor) new BasicAuthentication("rachel","rachel"));
        ResponseEntity<ArrayList> response = restTemplate.exchange(REQUEST_URI, HttpMethod.GET, requestEntity, ArrayList.class);
        System.out.println("****"+response.getBody());
        return response.getBody();
    }

    private List<Employee> callKeycloakProtectedAPI(HttpHeaders headers, RestTemplate restTemplate) {
        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(REQUEST_URI);
        String url = builder.toUriString();

        //RestTemplate restTemplate = new RestTemplate();
         try {
                HttpEntity<Map<String,String>> requestEntity = new HttpEntity<>(headers);
                ResponseEntity<ArrayList> response = restTemplate.exchange(REQUEST_URI, HttpMethod.GET, requestEntity, ArrayList.class);

                if (response.getStatusCode().is2xxSuccessful()) {
                    return (List<Employee>) response;
                }
                System.out.println("Error response while getting response"+ response);
               throw new InternalServerErrorException("");

            } catch (Exception exp) {
                System.out.println("Exception while getting response"+exp);
               throw new InternalServerErrorException("");
            }


    }

}

推荐答案

与restTemplate.postForEntity()相似,而不与postForObject()

Looks the same apart from restTemplate.postForEntity(), not postForObject()

让我分享我们具有登录,注销和刷新令牌的代码:

Let me share code we have, with login, logout and refresh token:

import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.TrustStrategy;
import org.keycloak.RSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.exceptions.TokenNotActiveException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.LinkedHashMap;

@Component
public class KeyCloakServiceImpl implements KeyCloakService {

private RestTemplate restTemplate;

private final KeyCloakConnectionProvider keyCloakConnectionProvider;

public KeyCloakServiceImpl(KeyCloakConnectionProvider keyCloakConnectionProvider,
                           RestTemplateBuilder restTemplateBuilder) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {

    this.keyCloakConnectionProvider = keyCloakConnectionProvider;

    TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
    SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
            .loadTrustMaterial(null, acceptingTrustStrategy)
            .build();
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

    CloseableHttpClient httpClient = HttpClients.custom()
            .setSSLSocketFactory(csf)
            .build();

    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);

    this.restTemplate = restTemplateBuilder
            .requestFactory(requestFactory)
            .messageConverters(new MappingJackson2HttpMessageConverter(), new FormHttpMessageConverter())
            .build();
}

private AccessToken getAccessToken(String accessToken, boolean checkActive) throws VerificationException, NoSuchFieldException {
    try {
        PublicKey publicKey = getPublicKey();
        if (publicKey != null) {
            String realmUrl = keyCloakConnectionProvider.getRealmUrl();
            AccessToken token =
                    RSATokenVerifier.verifyToken(
                            accessToken,
                            publicKey,
                            realmUrl,
                            checkActive,
                            true);

            return token;
        } else {
            log.error("KeyCloakServiceImpl:verifyToken: SSO_PUBLIC_KEY is NULL.");
            throw new NoSuchFieldException("KeyCloakServiceImpl:verifyToken: SSO_PUBLIC_KEY is NULL.");
        }
    } catch (TokenNotActiveException e) {
        throw e;
    } catch (VerificationException e) {
        throw e;
    } catch (NoSuchFieldException e) {
        throw e;
    } catch (Exception e) {
        throw e;
    }
}

@Override
public AccessToken loadAccessToken(String accessToken) throws TokenNotActiveException, VerificationException, NoSuchFieldException {
    return getAccessToken(accessToken, true);
}

@Override
public AccessToken loadAccessTokenFromRefreshToken(String accessToken) throws TokenNotActiveException, VerificationException, NoSuchFieldException {
    return getAccessToken(accessToken, false);
}

/**
 * This method will call keycloak service to user login. after successful login it will provide
 * access token.
 */
@Override
public AccessTokenResponse login(String username, String password) {
    try {
        MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
        requestParams.add("client_id", keyCloakConnectionProvider.getResource());
        requestParams.add("username", username);
        requestParams.add("password", password);
        requestParams.add("grant_type", "password");
        requestParams.add("client_secret", keyCloakConnectionProvider.getClientSecret());
        requestParams.add("scope", "openid");


        AccessTokenResponse keycloakAccessToken = queryKeycloakByParams(requestParams);

        return keycloakAccessToken;
    } catch (Exception e) {
        log.info(e.getMessage(), e);
        throw e;
    }
}

@Override
public AccessTokenResponse refresh(String refreshToken) {
    try {
        MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
        requestParams.add("client_id", keyCloakConnectionProvider.getResource());
        requestParams.add("grant_type", "refresh_token");
        requestParams.add("client_secret", keyCloakConnectionProvider.getClientSecret());
        requestParams.add("refresh_token", refreshToken);

        AccessTokenResponse keycloakAccessToken = queryKeycloakByParams(requestParams);

        return keycloakAccessToken;
    } catch (Exception e) {
        log.info(e.getMessage(), e);
        throw e;
    }
}

private AccessTokenResponse queryKeycloakByParams(MultiValueMap<String, String> requestParams) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestParams, headers);

    String url = keyCloakConnectionProvider.getOpenIdConnectTokenUrl();

    AccessTokenResponse keycloakAccessToken = getAccessTokenResponse(request, url);

    return keycloakAccessToken;
}

private AccessTokenResponse getAccessTokenResponse(HttpEntity<MultiValueMap<String, String>> request, String url) {
    try {
        ResponseEntity<AccessTokenResponse> response = restTemplate.postForEntity(url, request, AccessTokenResponse.class);
        return response.getBody();
    } catch (ResourceAccessException e) {
        log.error("KeyCloak getAccessTokenResponse: " + e.getMessage());
        try {
            ResponseEntity<AccessTokenResponse> response = restTemplate.postForEntity(url, request, AccessTokenResponse.class);
            return response.getBody();
        } catch (Exception ex) {
            throw ex;
        }
    } catch (Exception e) {
        throw e;
    }
}

@Override
public void logout(String refreshToken) {
    try {
        MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
        requestParams.add("client_id", keyCloakConnectionProvider.getResource());
        requestParams.add("client_secret", keyCloakConnectionProvider.getClientSecret());
        requestParams.add("refresh_token", refreshToken);

        logoutUserSession(requestParams);

    } catch (Exception e) {
        log.info(e.getMessage(), e);
        throw e;
    }
}

private void logoutUserSession(MultiValueMap<String, String> requestParams) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestParams, headers);

    String url = keyCloakConnectionProvider.getOpenIdConnectLogoutUrl();

    restTemplate.postForEntity(url, request, Object.class);
}

private PublicKey getPublicKey() {
    PublicKey publicKey = keyCloakConnectionProvider.getPublicKey();
    if (publicKey == null) {
        LinkedHashMap publicKeyMap = requestKeyFromKeycloak(keyCloakConnectionProvider.getOpenIdConnectCertsUrl());
        publicKey = KeyCloakRsaKeyLoader.getPublicKeyFromKeyCloak(publicKeyMap);
        keyCloakConnectionProvider.setPublicKey(publicKey);
    }
    return publicKey;
}

/**
 * This method will connect to keycloak server using API call for getting public key.
 *
 * @param url A string value having keycloak base URL
 * @return Public key JSON response string
 */
private LinkedHashMap requestKeyFromKeycloak(String url) {
    try {
        ResponseEntity<LinkedHashMap> response = restTemplate.getForEntity(url, LinkedHashMap.class);
        LinkedHashMap body = response.getBody();

        if (body != null) {
            return body;
        } else {
            log.error("KeyCloakRsaKeyLoader:requestKeyFromKeycloak: Not able to fetch SSO public key from keycloak server");
        }
    } catch (Exception e) {
        log.error("KeyCloakRsaKeyLoader:requestKeyFromKeycloak: Exception occurred with message = " + e.getMessage());
    }
    return null;
}
}


import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
import org.springframework.stereotype.Component;

import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;

@Component
@Slf4j
@AllArgsConstructor
public class KeyCloakConnectionProvider {

private static final Map<String, PublicKey> cache = new HashMap<>();
public static final String PUBLIC_KEY = "publicKey";

private KeycloakSpringBootProperties keycloakProperties;

public String getAuthServerUrl() {
    return keycloakProperties.getAuthServerUrl();
}

public String getRealmUrl() {
    return getAuthServerUrl()
            + "/realms/"
            + getRealm();
}

public String getOpenIdConnectUrl() {
    return getRealmUrl() + "/protocol/openid-connect";
}

public String getOpenIdConnectTokenUrl() {
    return getOpenIdConnectUrl() + "/token";
}

public String getOpenIdConnectLogoutUrl() {
    return getOpenIdConnectUrl() + "/logout";
}

public String getOpenIdConnectCertsUrl() {
    return getOpenIdConnectUrl() + "/certs";
}

public String getRealm() {
    return keycloakProperties.getRealm();
}

public String getResource() {
    return keycloakProperties.getResource();
}

public String getClientId() {
    return getResource();
}

public String getClientSecret() {
    return String.valueOf(keycloakProperties.getCredentials().get("secret"));
}

public int getConnectionPoolSize() {
    return keycloakProperties.getConnectionPoolSize();
}

public PublicKey getPublicKey() {
    return cache.get(PUBLIC_KEY);
}

public PublicKey setPublicKey(PublicKey publicKey) {
    if (publicKey != null) {
        cache.put(PUBLIC_KEY, publicKey);
    }
    return getPublicKey();
}
}


import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.*;
import java.util.Base64.Decoder;

/**
 * Class to fetch SSO public key from Keycloak server using 'certs' HTTP API call.
 */
@Slf4j
public class KeyCloakRsaKeyLoader {
    private static final String MODULUS = "modulusBase64";
    private static final String EXPONENT = "exponentBase64";
    private static final ObjectMapper mapper = new ObjectMapper();

    /**
     * This method will accept keycloak base URL and realm name. Based on provided values it will
     * fetch public key from keycloak.
     *
     * @param publicKeyMap A string value having keycloak public key as string
     * @return Public key used to verify user access token.
     */
    public static PublicKey getPublicKeyFromKeyCloak(LinkedHashMap publicKeyMap) {
        try {
            Decoder urlDecoder = Base64.getUrlDecoder();
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            if (publicKeyMap != null) {
                Map<String, String> valueMap = getValuesFromMap(publicKeyMap);
                if (valueMap != null) {
                    BigInteger modulus = new BigInteger(1, urlDecoder.decode(valueMap.get(MODULUS)));
                    BigInteger publicExponent = new BigInteger(1, urlDecoder.decode(valueMap.get(EXPONENT)));

                    PublicKey publicKey = keyFactory.generatePublic(new RSAPublicKeySpec(modulus, publicExponent));

                    return publicKey;
                }
            }
        } catch (Exception e) {
            log.error("KeyCloakRsaKeyLoader:getPublicKeyFromKeyCloak: Exception occurred with message = " + e.getMessage());
        }
        return null;
    }


    /**
     * This method will return a map containing values extracted from public key JSON string.
     *
     * @param publicKeyMap Public key map response
     */
    private static Map<String, String> getValuesFromMap(LinkedHashMap publicKeyMap) {
        try {
            Map<String, String> values = new HashMap<>();
            ArrayList keys = (ArrayList) publicKeyMap.get("keys");
            if (keys != null && keys.size() > 0) {
                LinkedHashMap value = (LinkedHashMap) keys.get(0);
                values.put(MODULUS, (String) value.get("n"));
                values.put(EXPONENT, (String) value.get("e"));
            }
            return values;
        } catch (Exception e) {
            log.error("KeyCloakRsaKeyLoader:getValuesFromJson: Exception occurred with message = " + e.getMessage());
        }
        return null;
    }
}

这篇关于带有Spring应用程序的KeycloakRestTemplate的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆