带有Spring应用程序的KeycloakRestTemplate [英] KeycloakRestTemplate with spring application
问题描述
我有一个带有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屋!