Merge pull request #138 from patriot1burke/master
refactor out resteasy from adapters
This commit is contained in:
commit
addefbb587
57 changed files with 2444 additions and 670 deletions
|
@ -1,155 +0,0 @@
|
|||
package org.keycloak;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.jboss.resteasy.util.BasicAuthHelper;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.InternalServerErrorException;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.security.KeyStore;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AbstractOAuthClient {
|
||||
public static final String OAUTH_TOKEN_REQUEST_STATE = "OAuth_Token_Request_State";
|
||||
protected String clientId;
|
||||
protected String password;
|
||||
protected KeyStore truststore;
|
||||
protected String authUrl;
|
||||
protected String codeUrl;
|
||||
protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
|
||||
protected String stateCookiePath;
|
||||
protected Client client;
|
||||
protected boolean isSecure;
|
||||
protected final AtomicLong counter = new AtomicLong();
|
||||
|
||||
protected String getStateCode() {
|
||||
return counter.getAndIncrement() + "/" + UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Client for obtaining access token from code
|
||||
*/
|
||||
public void start() {
|
||||
if (client == null) {
|
||||
client = new ResteasyClientBuilder().trustStore(truststore)
|
||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
|
||||
.connectionPoolSize(10)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* closes cllient
|
||||
*/
|
||||
public void stop() {
|
||||
client.close();
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public KeyStore getTruststore() {
|
||||
return truststore;
|
||||
}
|
||||
|
||||
public void setTruststore(KeyStore truststore) {
|
||||
this.truststore = truststore;
|
||||
}
|
||||
|
||||
public String getAuthUrl() {
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setAuthUrl(String authUrl) {
|
||||
this.authUrl = authUrl;
|
||||
}
|
||||
|
||||
public String getCodeUrl() {
|
||||
return codeUrl;
|
||||
}
|
||||
|
||||
public void setCodeUrl(String codeUrl) {
|
||||
this.codeUrl = codeUrl;
|
||||
}
|
||||
|
||||
public String getStateCookieName() {
|
||||
return stateCookieName;
|
||||
}
|
||||
|
||||
public void setStateCookieName(String stateCookieName) {
|
||||
this.stateCookieName = stateCookieName;
|
||||
}
|
||||
|
||||
public String getStateCookiePath() {
|
||||
return stateCookiePath;
|
||||
}
|
||||
|
||||
public void setStateCookiePath(String stateCookiePath) {
|
||||
this.stateCookiePath = stateCookiePath;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String resolveBearerToken(String redirectUri, String code) {
|
||||
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
||||
String authHeader = BasicAuthHelper.createHeader(clientId, password);
|
||||
Form codeForm = new Form()
|
||||
.param("grant_type", "authorization_code")
|
||||
.param("code", code)
|
||||
.param("client_id", clientId)
|
||||
.param("password", password)
|
||||
.param("redirect_uri", redirectUri);
|
||||
Response res = client.target(codeUrl).request().header(HttpHeaders.AUTHORIZATION, authHeader).post(Entity.form(codeForm));
|
||||
try {
|
||||
if (res.getStatus() == 400) {
|
||||
throw new BadRequestException();
|
||||
} else if (res.getStatus() != 200) {
|
||||
throw new InternalServerErrorException(new Exception("Unknown error when getting acess token"));
|
||||
}
|
||||
AccessTokenResponse tokenResponse = res.readEntity(AccessTokenResponse.class);
|
||||
return tokenResponse.getToken();
|
||||
} finally {
|
||||
res.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected String stripOauthParametersFromRedirect(String uri) {
|
||||
UriBuilder builder = UriBuilder.fromUri(uri)
|
||||
.replaceQueryParam("code", null)
|
||||
.replaceQueryParam("state", null);
|
||||
return builder.build().toString();
|
||||
}
|
||||
|
||||
}
|
94
core/src/main/java/org/keycloak/AbstractOAuthClient.java
Executable file
94
core/src/main/java/org/keycloak/AbstractOAuthClient.java
Executable file
|
@ -0,0 +1,94 @@
|
|||
package org.keycloak;
|
||||
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AbstractOAuthClient {
|
||||
public static final String OAUTH_TOKEN_REQUEST_STATE = "OAuth_Token_Request_State";
|
||||
protected String clientId;
|
||||
protected String password;
|
||||
protected KeyStore truststore;
|
||||
protected String authUrl;
|
||||
protected String codeUrl;
|
||||
protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
|
||||
protected String stateCookiePath;
|
||||
protected boolean isSecure;
|
||||
protected final AtomicLong counter = new AtomicLong();
|
||||
|
||||
protected String getStateCode() {
|
||||
return counter.getAndIncrement() + "/" + UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public KeyStore getTruststore() {
|
||||
return truststore;
|
||||
}
|
||||
|
||||
public void setTruststore(KeyStore truststore) {
|
||||
this.truststore = truststore;
|
||||
}
|
||||
|
||||
public String getAuthUrl() {
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setAuthUrl(String authUrl) {
|
||||
this.authUrl = authUrl;
|
||||
}
|
||||
|
||||
public String getCodeUrl() {
|
||||
return codeUrl;
|
||||
}
|
||||
|
||||
public void setCodeUrl(String codeUrl) {
|
||||
this.codeUrl = codeUrl;
|
||||
}
|
||||
|
||||
public String getStateCookieName() {
|
||||
return stateCookieName;
|
||||
}
|
||||
|
||||
public void setStateCookieName(String stateCookieName) {
|
||||
this.stateCookieName = stateCookieName;
|
||||
}
|
||||
|
||||
public String getStateCookiePath() {
|
||||
return stateCookiePath;
|
||||
}
|
||||
|
||||
public void setStateCookiePath(String stateCookiePath) {
|
||||
this.stateCookiePath = stateCookiePath;
|
||||
}
|
||||
|
||||
protected String stripOauthParametersFromRedirect(String uri) {
|
||||
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(uri)
|
||||
.replaceQueryParam("code", null)
|
||||
.replaceQueryParam("state", null);
|
||||
return builder.build().toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -34,4 +34,18 @@ public class SkeletonKeySession implements Serializable {
|
|||
return metadata;
|
||||
}
|
||||
|
||||
protected static ThreadLocal<SkeletonKeySession> local = new ThreadLocal<SkeletonKeySession>();
|
||||
|
||||
public static void pushContext(SkeletonKeySession session) {
|
||||
local.set(session);
|
||||
}
|
||||
|
||||
public static void clearContext() {
|
||||
local.set(null);
|
||||
}
|
||||
|
||||
public static SkeletonKeySession getContext() {
|
||||
return local.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
526
core/src/main/java/org/keycloak/util/Encode.java
Executable file
526
core/src/main/java/org/keycloak/util/Encode.java
Executable file
|
@ -0,0 +1,526 @@
|
|||
package org.keycloak.util;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Encode
|
||||
{
|
||||
private static final String UTF_8 = "UTF-8";
|
||||
|
||||
private static final Pattern PARAM_REPLACEMENT = Pattern.compile("_resteasy_uri_parameter");
|
||||
|
||||
private static final String[] pathEncoding = new String[128];
|
||||
private static final String[] pathSegmentEncoding = new String[128];
|
||||
private static final String[] matrixParameterEncoding = new String[128];
|
||||
private static final String[] queryNameValueEncoding = new String[128];
|
||||
private static final String[] queryStringEncoding = new String[128];
|
||||
|
||||
static
|
||||
{
|
||||
/*
|
||||
* Encode via <a href="http://ietf.org/rfc/rfc3986.txt">RFC 3986</a>. PCHAR is allowed allong with '/'
|
||||
*
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
/ "*" / "+" / "," / ";" / "="
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
*
|
||||
*/
|
||||
for (int i = 0; i < 128; i++)
|
||||
{
|
||||
if (i >= 'a' && i <= 'z') continue;
|
||||
if (i >= 'A' && i <= 'Z') continue;
|
||||
if (i >= '0' && i <= '9') continue;
|
||||
switch ((char) i)
|
||||
{
|
||||
case '-':
|
||||
case '.':
|
||||
case '_':
|
||||
case '~':
|
||||
case '!':
|
||||
case '$':
|
||||
case '&':
|
||||
case '\'':
|
||||
case '(':
|
||||
case ')':
|
||||
case '*':
|
||||
case '+':
|
||||
case ',':
|
||||
case '/':
|
||||
case ';':
|
||||
case '=':
|
||||
case ':':
|
||||
case '@':
|
||||
continue;
|
||||
}
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append((char) i);
|
||||
pathEncoding[i] = URLEncoder.encode(sb.toString());
|
||||
}
|
||||
pathEncoding[' '] = "%20";
|
||||
System.arraycopy(pathEncoding, 0, matrixParameterEncoding, 0, pathEncoding.length);
|
||||
matrixParameterEncoding[';'] = "%3B";
|
||||
matrixParameterEncoding['='] = "%3D";
|
||||
matrixParameterEncoding['/'] = "%2F"; // RESTEASY-729
|
||||
System.arraycopy(pathEncoding, 0, pathSegmentEncoding, 0, pathEncoding.length);
|
||||
pathSegmentEncoding['/'] = "%2F";
|
||||
/*
|
||||
* Encode via <a href="http://ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
|
||||
*
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* space encoded as '+'
|
||||
*
|
||||
*/
|
||||
for (int i = 0; i < 128; i++)
|
||||
{
|
||||
if (i >= 'a' && i <= 'z') continue;
|
||||
if (i >= 'A' && i <= 'Z') continue;
|
||||
if (i >= '0' && i <= '9') continue;
|
||||
switch ((char) i)
|
||||
{
|
||||
case '-':
|
||||
case '.':
|
||||
case '_':
|
||||
case '~':
|
||||
case '?':
|
||||
continue;
|
||||
case ' ':
|
||||
queryNameValueEncoding[i] = "+";
|
||||
continue;
|
||||
}
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append((char) i);
|
||||
queryNameValueEncoding[i] = URLEncoder.encode(sb.toString());
|
||||
}
|
||||
|
||||
/*
|
||||
* query = *( pchar / "/" / "?" )
|
||||
|
||||
*/
|
||||
for (int i = 0; i < 128; i++)
|
||||
{
|
||||
if (i >= 'a' && i <= 'z') continue;
|
||||
if (i >= 'A' && i <= 'Z') continue;
|
||||
if (i >= '0' && i <= '9') continue;
|
||||
switch ((char) i)
|
||||
{
|
||||
case '-':
|
||||
case '.':
|
||||
case '_':
|
||||
case '~':
|
||||
case '!':
|
||||
case '$':
|
||||
case '&':
|
||||
case '\'':
|
||||
case '(':
|
||||
case ')':
|
||||
case '*':
|
||||
case '+':
|
||||
case ',':
|
||||
case ';':
|
||||
case '=':
|
||||
case ':':
|
||||
case '@':
|
||||
case '?':
|
||||
case '/':
|
||||
continue;
|
||||
case ' ':
|
||||
queryStringEncoding[i] = "%20";
|
||||
continue;
|
||||
}
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append((char) i);
|
||||
queryStringEncoding[i] = URLEncoder.encode(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep encoded values "%..." and template parameters intact.
|
||||
*/
|
||||
public static String encodeQueryString(String value)
|
||||
{
|
||||
return encodeValue(value, queryStringEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep encoded values "%...", matrix parameters, template parameters, and '/' characters intact.
|
||||
*/
|
||||
public static String encodePath(String value)
|
||||
{
|
||||
return encodeValue(value, pathEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep encoded values "%...", matrix parameters and template parameters intact.
|
||||
*/
|
||||
public static String encodePathSegment(String value)
|
||||
{
|
||||
return encodeValue(value, pathSegmentEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep encoded values "%..." and template parameters intact.
|
||||
*/
|
||||
public static String encodeFragment(String value)
|
||||
{
|
||||
return encodeValue(value, queryNameValueEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep encoded values "%..." and template parameters intact.
|
||||
*/
|
||||
public static String encodeMatrixParam(String value)
|
||||
{
|
||||
return encodeValue(value, matrixParameterEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep encoded values "%..." and template parameters intact.
|
||||
*/
|
||||
public static String encodeQueryParam(String value)
|
||||
{
|
||||
return encodeValue(value, queryNameValueEncoding);
|
||||
}
|
||||
|
||||
//private static final Pattern nonCodes = Pattern.compile("%([^a-fA-F0-9]|$)");
|
||||
private static final Pattern nonCodes = Pattern.compile("%([^a-fA-F0-9]|[a-fA-F0-9]$|$|[a-fA-F0-9][^a-fA-F0-9])");
|
||||
private static final Pattern encodedChars = Pattern.compile("%([a-fA-F0-9][a-fA-F0-9])");
|
||||
private static final Pattern encodedCharsMulti = Pattern.compile("((%[a-fA-F0-9][a-fA-F0-9])+)");
|
||||
|
||||
public static String decodePath(String path)
|
||||
{
|
||||
Matcher matcher = encodedCharsMulti.matcher(path);
|
||||
StringBuffer buf = new StringBuffer();
|
||||
CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
|
||||
while (matcher.find())
|
||||
{
|
||||
decoder.reset();
|
||||
String decoded = decodeBytes(matcher.group(1), decoder);
|
||||
decoded = decoded.replace("\\", "\\\\");
|
||||
decoded = decoded.replace("$", "\\$");
|
||||
matcher.appendReplacement(buf, decoded);
|
||||
}
|
||||
matcher.appendTail(buf);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String decodeBytes(String enc, CharsetDecoder decoder)
|
||||
{
|
||||
Matcher matcher = encodedChars.matcher(enc);
|
||||
StringBuffer buf = new StringBuffer();
|
||||
ByteBuffer bytes = ByteBuffer.allocate(enc.length() / 3);
|
||||
while (matcher.find())
|
||||
{
|
||||
int b = Integer.parseInt(matcher.group(1), 16);
|
||||
bytes.put((byte) b);
|
||||
}
|
||||
bytes.flip();
|
||||
try
|
||||
{
|
||||
return decoder.decode(bytes).toString();
|
||||
}
|
||||
catch (CharacterCodingException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode '%' if it is not an encoding sequence
|
||||
*
|
||||
* @param string
|
||||
* @return
|
||||
*/
|
||||
public static String encodeNonCodes(String string)
|
||||
{
|
||||
Matcher matcher = nonCodes.matcher(string);
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
|
||||
// FYI: we do not use the no-arg matcher.find()
|
||||
// coupled with matcher.appendReplacement()
|
||||
// because the matched text may contain
|
||||
// a second % and we must make sure we
|
||||
// encode it (if necessary).
|
||||
int idx = 0;
|
||||
while (matcher.find(idx))
|
||||
{
|
||||
int start = matcher.start();
|
||||
buf.append(string.substring(idx, start));
|
||||
buf.append("%25");
|
||||
idx = start + 1;
|
||||
}
|
||||
buf.append(string.substring(idx));
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static boolean savePathParams(String segment, StringBuffer newSegment, List<String> params)
|
||||
{
|
||||
boolean foundParam = false;
|
||||
// Regular expressions can have '{' and '}' characters. Replace them to do match
|
||||
segment = PathHelper.replaceEnclosedCurlyBraces(segment);
|
||||
Matcher matcher = PathHelper.URI_TEMPLATE_PATTERN.matcher(segment);
|
||||
while (matcher.find())
|
||||
{
|
||||
foundParam = true;
|
||||
String group = matcher.group();
|
||||
// Regular expressions can have '{' and '}' characters. Recover earlier replacement
|
||||
params.add(PathHelper.recoverEnclosedCurlyBraces(group));
|
||||
matcher.appendReplacement(newSegment, "_resteasy_uri_parameter");
|
||||
}
|
||||
matcher.appendTail(newSegment);
|
||||
return foundParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep encoded values "%..." and template parameters intact i.e. "{x}"
|
||||
*
|
||||
* @param segment
|
||||
* @param encoding
|
||||
* @return
|
||||
*/
|
||||
public static String encodeValue(String segment, String[] encoding)
|
||||
{
|
||||
ArrayList<String> params = new ArrayList<String>();
|
||||
boolean foundParam = false;
|
||||
StringBuffer newSegment = new StringBuffer();
|
||||
if (savePathParams(segment, newSegment, params))
|
||||
{
|
||||
foundParam = true;
|
||||
segment = newSegment.toString();
|
||||
}
|
||||
String result = encodeFromArray(segment, encoding, false);
|
||||
result = encodeNonCodes(result);
|
||||
segment = result;
|
||||
if (foundParam)
|
||||
{
|
||||
segment = pathParamReplacement(segment, params);
|
||||
}
|
||||
return segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode via <a href="http://ietf.org/rfc/rfc3986.txt">RFC 3986</a>. PCHAR is allowed allong with '/'
|
||||
* <p/>
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
*/
|
||||
public static String encodePathAsIs(String segment)
|
||||
{
|
||||
return encodeFromArray(segment, pathEncoding, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p"
|
||||
*
|
||||
* @param segment
|
||||
* @return
|
||||
*/
|
||||
public static String encodePathSaveEncodings(String segment)
|
||||
{
|
||||
String result = encodeFromArray(segment, pathEncoding, false);
|
||||
result = encodeNonCodes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode via <a href="http://ietf.org/rfc/rfc3986.txt">RFC 3986</a>. PCHAR is allowed allong with '/'
|
||||
* <p/>
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
*/
|
||||
public static String encodePathSegmentAsIs(String segment)
|
||||
{
|
||||
return encodeFromArray(segment, pathSegmentEncoding, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p"
|
||||
*
|
||||
* @param segment
|
||||
* @return
|
||||
*/
|
||||
public static String encodePathSegmentSaveEncodings(String segment)
|
||||
{
|
||||
String result = encodeFromArray(segment, pathSegmentEncoding, false);
|
||||
result = encodeNonCodes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes everything of a query parameter name or value.
|
||||
*
|
||||
* @param nameOrValue
|
||||
* @return
|
||||
*/
|
||||
public static String encodeQueryParamAsIs(String nameOrValue)
|
||||
{
|
||||
return encodeFromArray(nameOrValue, queryNameValueEncoding, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p"
|
||||
*
|
||||
* @param segment
|
||||
* @return
|
||||
*/
|
||||
public static String encodeQueryParamSaveEncodings(String segment)
|
||||
{
|
||||
String result = encodeFromArray(segment, queryNameValueEncoding, false);
|
||||
result = encodeNonCodes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String encodeFragmentAsIs(String nameOrValue)
|
||||
{
|
||||
return encodeFromArray(nameOrValue, queryNameValueEncoding, true);
|
||||
}
|
||||
|
||||
protected static String encodeFromArray(String segment, String[] encodingMap, boolean encodePercent)
|
||||
{
|
||||
StringBuffer result = new StringBuffer();
|
||||
for (int i = 0; i < segment.length(); i++)
|
||||
{
|
||||
if (!encodePercent && segment.charAt(i) == '%')
|
||||
{
|
||||
result.append(segment.charAt(i));
|
||||
continue;
|
||||
}
|
||||
int idx = segment.charAt(i);
|
||||
String encoding = encode(idx, encodingMap);
|
||||
if (encoding == null)
|
||||
{
|
||||
result.append(segment.charAt(i));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.append(encoding);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zhar integer representation of character
|
||||
* @param encodingMap encoding map
|
||||
* @return URL encoded character
|
||||
*/
|
||||
private static String encode(int zhar, String[] encodingMap)
|
||||
{
|
||||
String encoded;
|
||||
if (zhar < encodingMap.length)
|
||||
{
|
||||
encoded = encodingMap[zhar];
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
encoded = URLEncoder.encode(Character.toString((char) zhar), UTF_8);
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
public static String pathParamReplacement(String segment, List<String> params)
|
||||
{
|
||||
StringBuffer newSegment = new StringBuffer();
|
||||
Matcher matcher = PARAM_REPLACEMENT.matcher(segment);
|
||||
int i = 0;
|
||||
while (matcher.find())
|
||||
{
|
||||
String replacement = params.get(i++);
|
||||
// double encode slashes, so that slashes stay where they are
|
||||
replacement = replacement.replace("\\", "\\\\");
|
||||
replacement = replacement.replace("$", "\\$");
|
||||
matcher.appendReplacement(newSegment, replacement);
|
||||
}
|
||||
matcher.appendTail(newSegment);
|
||||
segment = newSegment.toString();
|
||||
return segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* decode an encoded map
|
||||
*
|
||||
* @param map
|
||||
* @return
|
||||
*/
|
||||
public static MultivaluedHashMap<String, String> decode(MultivaluedHashMap<String, String> map)
|
||||
{
|
||||
MultivaluedHashMap<String, String> decoded = new MultivaluedHashMap<String, String>();
|
||||
for (Map.Entry<String, List<String>> entry : map.entrySet())
|
||||
{
|
||||
List<String> values = entry.getValue();
|
||||
for (String value : values)
|
||||
{
|
||||
try
|
||||
{
|
||||
decoded.add(URLDecoder.decode(entry.getKey(), UTF_8), URLDecoder.decode(value, UTF_8));
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
public static MultivaluedHashMap<String, String> encode(MultivaluedHashMap<String, String> map)
|
||||
{
|
||||
MultivaluedHashMap<String, String> decoded = new MultivaluedHashMap<String, String>();
|
||||
for (Map.Entry<String, List<String>> entry : map.entrySet())
|
||||
{
|
||||
List<String> values = entry.getValue();
|
||||
for (String value : values)
|
||||
{
|
||||
try
|
||||
{
|
||||
decoded.add(URLEncoder.encode(entry.getKey(), UTF_8), URLEncoder.encode(value, UTF_8));
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
public static String decode(String string)
|
||||
{
|
||||
try
|
||||
{
|
||||
return URLDecoder.decode(string, UTF_8);
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
717
core/src/main/java/org/keycloak/util/KeycloakUriBuilder.java
Executable file
717
core/src/main/java/org/keycloak/util/KeycloakUriBuilder.java
Executable file
|
@ -0,0 +1,717 @@
|
|||
package org.keycloak.util;
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakUriBuilder {
|
||||
|
||||
private String host;
|
||||
private String scheme;
|
||||
private int port = -1;
|
||||
|
||||
private String userInfo;
|
||||
private String path;
|
||||
private String query;
|
||||
private String fragment;
|
||||
private String ssp;
|
||||
private String authority;
|
||||
|
||||
public static KeycloakUriBuilder fromUri(URI uri) {
|
||||
return new KeycloakUriBuilder().uri(uri);
|
||||
}
|
||||
|
||||
public static KeycloakUriBuilder fromUri(String uriTemplate) {
|
||||
return new KeycloakUriBuilder().uri(uriTemplate);
|
||||
}
|
||||
|
||||
public static KeycloakUriBuilder fromPath(String path) throws IllegalArgumentException {
|
||||
return new KeycloakUriBuilder().path(path);
|
||||
}
|
||||
|
||||
|
||||
public KeycloakUriBuilder clone() {
|
||||
KeycloakUriBuilder impl = new KeycloakUriBuilder();
|
||||
impl.host = host;
|
||||
impl.scheme = scheme;
|
||||
impl.port = port;
|
||||
impl.userInfo = userInfo;
|
||||
impl.path = path;
|
||||
impl.query = query;
|
||||
impl.fragment = fragment;
|
||||
impl.ssp = ssp;
|
||||
impl.authority = authority;
|
||||
|
||||
return impl;
|
||||
}
|
||||
|
||||
public static final Pattern opaqueUri = Pattern.compile("^([^:/?#]+):([^/].*)");
|
||||
public static final Pattern hierarchicalUri = Pattern.compile("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?");
|
||||
private static final Pattern hostPortPattern = Pattern.compile("([^/:]+):(\\d+)");
|
||||
|
||||
public static boolean compare(String s1, String s2) {
|
||||
if (s1 == s2) return true;
|
||||
if (s1 == null || s2 == null) return false;
|
||||
return s1.equals(s2);
|
||||
}
|
||||
|
||||
public static URI relativize(URI from, URI to) {
|
||||
if (!compare(from.getScheme(), to.getScheme())) return to;
|
||||
if (!compare(from.getHost(), to.getHost())) return to;
|
||||
if (from.getPort() != to.getPort()) return to;
|
||||
if (from.getPath() == null && to.getPath() == null) return URI.create("");
|
||||
else if (from.getPath() == null) return URI.create(to.getPath());
|
||||
else if (to.getPath() == null) return to;
|
||||
|
||||
|
||||
String fromPath = from.getPath();
|
||||
if (fromPath.startsWith("/")) fromPath = fromPath.substring(1);
|
||||
String[] fsplit = fromPath.split("/");
|
||||
String toPath = to.getPath();
|
||||
if (toPath.startsWith("/")) toPath = toPath.substring(1);
|
||||
String[] tsplit = toPath.split("/");
|
||||
|
||||
int f = 0;
|
||||
|
||||
for (; f < fsplit.length && f < tsplit.length; f++) {
|
||||
if (!fsplit[f].equals(tsplit[f])) break;
|
||||
}
|
||||
|
||||
KeycloakUriBuilder builder = KeycloakUriBuilder.fromPath("");
|
||||
for (int i = f; i < fsplit.length; i++) builder.path("..");
|
||||
for (int i = f; i < tsplit.length; i++) builder.path(tsplit[i]);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* You may put path parameters anywhere within the uriTemplate except port
|
||||
*
|
||||
* @param uriTemplate
|
||||
* @return
|
||||
*/
|
||||
public static KeycloakUriBuilder fromTemplate(String uriTemplate) {
|
||||
KeycloakUriBuilder impl = new KeycloakUriBuilder();
|
||||
impl.uriTemplate(uriTemplate);
|
||||
return impl;
|
||||
}
|
||||
|
||||
/**
|
||||
* You may put path parameters anywhere within the uriTemplate except port
|
||||
*
|
||||
* @param uriTemplate
|
||||
* @return
|
||||
*/
|
||||
public KeycloakUriBuilder uriTemplate(String uriTemplate) {
|
||||
if (uriTemplate == null) throw new IllegalArgumentException("uriTemplate parameter is null");
|
||||
Matcher opaque = opaqueUri.matcher(uriTemplate);
|
||||
if (opaque.matches()) {
|
||||
this.authority = null;
|
||||
this.host = null;
|
||||
this.port = -1;
|
||||
this.userInfo = null;
|
||||
this.query = null;
|
||||
this.scheme = opaque.group(1);
|
||||
this.ssp = opaque.group(2);
|
||||
return this;
|
||||
} else {
|
||||
Matcher match = hierarchicalUri.matcher(uriTemplate);
|
||||
if (match.matches()) {
|
||||
ssp = null;
|
||||
return parseHierarchicalUri(uriTemplate, match);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Illegal uri template: " + uriTemplate);
|
||||
}
|
||||
|
||||
protected KeycloakUriBuilder parseHierarchicalUri(String uriTemplate, Matcher match) {
|
||||
boolean scheme = match.group(2) != null;
|
||||
if (scheme) this.scheme = match.group(2);
|
||||
String authority = match.group(4);
|
||||
if (authority != null) {
|
||||
this.authority = null;
|
||||
String host = match.group(4);
|
||||
int at = host.indexOf('@');
|
||||
if (at > -1) {
|
||||
String user = host.substring(0, at);
|
||||
host = host.substring(at + 1);
|
||||
this.userInfo = user;
|
||||
}
|
||||
Matcher hostPortMatch = hostPortPattern.matcher(host);
|
||||
if (hostPortMatch.matches()) {
|
||||
this.host = hostPortMatch.group(1);
|
||||
int val = 0;
|
||||
try {
|
||||
this.port = Integer.parseInt(hostPortMatch.group(2));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Illegal uri template: " + uriTemplate, e);
|
||||
}
|
||||
} else {
|
||||
this.host = host;
|
||||
}
|
||||
}
|
||||
if (match.group(5) != null) {
|
||||
String group = match.group(5);
|
||||
if (!scheme && !"".equals(group) && !group.startsWith("/") && group.indexOf(':') > -1)
|
||||
throw new IllegalArgumentException("Illegal uri template: " + uriTemplate);
|
||||
if (!"".equals(group)) replacePath(group);
|
||||
}
|
||||
if (match.group(7) != null) replaceQuery(match.group(7));
|
||||
if (match.group(9) != null) fragment(match.group(9));
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder uri(String uriTemplate) throws IllegalArgumentException {
|
||||
return uriTemplate(uriTemplate);
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder uri(URI uri) throws IllegalArgumentException {
|
||||
if (uri == null) throw new IllegalArgumentException("URI was null");
|
||||
|
||||
if (uri.getRawFragment() != null) fragment = uri.getRawFragment();
|
||||
|
||||
if (uri.isOpaque()) {
|
||||
scheme = uri.getScheme();
|
||||
ssp = uri.getRawSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
if (uri.getScheme() == null) {
|
||||
if (ssp != null) {
|
||||
if (uri.getRawSchemeSpecificPart() != null) {
|
||||
ssp = uri.getRawSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scheme = uri.getScheme();
|
||||
}
|
||||
|
||||
ssp = null;
|
||||
if (uri.getRawAuthority() != null) {
|
||||
if (uri.getRawUserInfo() == null && uri.getHost() == null && uri.getPort() == -1) {
|
||||
authority = uri.getRawAuthority();
|
||||
userInfo = null;
|
||||
host = null;
|
||||
port = -1;
|
||||
} else {
|
||||
authority = null;
|
||||
if (uri.getRawUserInfo() != null) {
|
||||
userInfo = uri.getRawUserInfo();
|
||||
}
|
||||
if (uri.getHost() != null) {
|
||||
host = uri.getHost();
|
||||
}
|
||||
if (uri.getPort() != -1) {
|
||||
port = uri.getPort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uri.getRawPath() != null && uri.getRawPath().length() > 0) {
|
||||
path = uri.getRawPath();
|
||||
}
|
||||
if (uri.getRawQuery() != null && uri.getRawQuery().length() > 0) {
|
||||
query = uri.getRawQuery();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder scheme(String scheme) throws IllegalArgumentException {
|
||||
this.scheme = scheme;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder schemeSpecificPart(String ssp) throws IllegalArgumentException {
|
||||
if (ssp == null) throw new IllegalArgumentException("schemeSpecificPart was null");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (scheme != null) sb.append(scheme).append(':');
|
||||
if (ssp != null)
|
||||
sb.append(ssp);
|
||||
if (fragment != null && fragment.length() > 0) sb.append('#').append(fragment);
|
||||
URI uri = URI.create(sb.toString());
|
||||
|
||||
if (uri.getRawSchemeSpecificPart() != null && uri.getRawPath() == null) {
|
||||
this.ssp = uri.getRawSchemeSpecificPart();
|
||||
} else {
|
||||
this.ssp = null;
|
||||
userInfo = uri.getRawUserInfo();
|
||||
host = uri.getHost();
|
||||
port = uri.getPort();
|
||||
path = uri.getRawPath();
|
||||
query = uri.getRawQuery();
|
||||
|
||||
}
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder userInfo(String ui) {
|
||||
this.userInfo = ui;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder host(String host) throws IllegalArgumentException {
|
||||
if (host != null && host.equals("")) throw new IllegalArgumentException("invalid host");
|
||||
this.host = host;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder port(int port) throws IllegalArgumentException {
|
||||
if (port < -1) throw new IllegalArgumentException("Invalid port value");
|
||||
this.port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected static String paths(boolean encode, String basePath, String... segments) {
|
||||
String path = basePath;
|
||||
if (path == null) path = "";
|
||||
for (String segment : segments) {
|
||||
if ("".equals(segment)) continue;
|
||||
if (path.endsWith("/")) {
|
||||
if (segment.startsWith("/")) {
|
||||
segment = segment.substring(1);
|
||||
if ("".equals(segment)) continue;
|
||||
}
|
||||
if (encode) segment = Encode.encodePath(segment);
|
||||
path += segment;
|
||||
} else {
|
||||
if (encode) segment = Encode.encodePath(segment);
|
||||
if ("".equals(path)) {
|
||||
path = segment;
|
||||
} else if (segment.startsWith("/")) {
|
||||
path += segment;
|
||||
} else {
|
||||
path += "/" + segment;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder path(String segment) throws IllegalArgumentException {
|
||||
if (segment == null) throw new IllegalArgumentException("path was null");
|
||||
path = paths(true, path, segment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder replaceMatrix(String matrix) throws IllegalArgumentException {
|
||||
if (matrix == null) matrix = "";
|
||||
if (!matrix.startsWith(";")) matrix = ";" + matrix;
|
||||
matrix = Encode.encodePath(matrix);
|
||||
if (path == null) {
|
||||
path = matrix;
|
||||
} else {
|
||||
int start = path.lastIndexOf('/');
|
||||
if (start < 0) start = 0;
|
||||
int matrixIndex = path.indexOf(';', start);
|
||||
if (matrixIndex > -1) path = path.substring(0, matrixIndex) + matrix;
|
||||
else path += matrix;
|
||||
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder replaceQuery(String query) throws IllegalArgumentException {
|
||||
if (query == null || query.length() == 0) {
|
||||
this.query = null;
|
||||
return this;
|
||||
}
|
||||
this.query = Encode.encodeQueryString(query);
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder fragment(String fragment) throws IllegalArgumentException {
|
||||
if (fragment == null) {
|
||||
this.fragment = null;
|
||||
return this;
|
||||
}
|
||||
this.fragment = Encode.encodeFragment(fragment);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only replace path params in path of URI. This changes state of URIBuilder.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @param isEncoded
|
||||
* @return
|
||||
*/
|
||||
public KeycloakUriBuilder substitutePathParam(String name, Object value, boolean isEncoded) {
|
||||
if (path != null) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
replacePathParameter(name, value.toString(), isEncoded, path, buffer, false);
|
||||
path = buffer.toString();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public URI buildFromMap(Map<String, ? extends Object> values) throws IllegalArgumentException {
|
||||
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||
return buildUriFromMap(values, false, true);
|
||||
}
|
||||
|
||||
public URI buildFromEncodedMap(Map<String, ? extends Object> values) throws IllegalArgumentException {
|
||||
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||
return buildUriFromMap(values, true, false);
|
||||
}
|
||||
|
||||
public URI buildFromMap(Map<String, ?> values, boolean encodeSlashInPath) throws IllegalArgumentException {
|
||||
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||
return buildUriFromMap(values, false, encodeSlashInPath);
|
||||
}
|
||||
|
||||
protected URI buildUriFromMap(Map<String, ? extends Object> paramMap, boolean fromEncodedMap, boolean encodeSlash) throws IllegalArgumentException {
|
||||
String buf = buildString(paramMap, fromEncodedMap, false, encodeSlash);
|
||||
try {
|
||||
return URI.create(buf);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create URI: " + buf, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildString(Map<String, ? extends Object> paramMap, boolean fromEncodedMap, boolean isTemplate, boolean encodeSlash) {
|
||||
for (Map.Entry<String, ? extends Object> entry : paramMap.entrySet()) {
|
||||
if (entry.getKey() == null) throw new IllegalArgumentException("map key is null");
|
||||
if (entry.getValue() == null) throw new IllegalArgumentException("map value is null");
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
|
||||
if (scheme != null)
|
||||
replaceParameter(paramMap, fromEncodedMap, isTemplate, scheme, buffer, encodeSlash).append(":");
|
||||
if (ssp != null) {
|
||||
buffer.append(ssp);
|
||||
} else if (userInfo != null || host != null || port != -1) {
|
||||
buffer.append("//");
|
||||
if (userInfo != null)
|
||||
replaceParameter(paramMap, fromEncodedMap, isTemplate, userInfo, buffer, encodeSlash).append("@");
|
||||
if (host != null) {
|
||||
if ("".equals(host)) throw new RuntimeException("empty host name");
|
||||
replaceParameter(paramMap, fromEncodedMap, isTemplate, host, buffer, encodeSlash);
|
||||
}
|
||||
if (port != -1) buffer.append(":").append(Integer.toString(port));
|
||||
} else if (authority != null) {
|
||||
buffer.append("//");
|
||||
replaceParameter(paramMap, fromEncodedMap, isTemplate, authority, buffer, encodeSlash);
|
||||
}
|
||||
if (path != null) {
|
||||
StringBuffer tmp = new StringBuffer();
|
||||
replaceParameter(paramMap, fromEncodedMap, isTemplate, path, tmp, encodeSlash);
|
||||
String tmpPath = tmp.toString();
|
||||
if (userInfo != null || host != null) {
|
||||
if (!tmpPath.startsWith("/")) buffer.append("/");
|
||||
}
|
||||
buffer.append(tmpPath);
|
||||
}
|
||||
if (query != null) {
|
||||
buffer.append("?");
|
||||
replaceQueryStringParameter(paramMap, fromEncodedMap, isTemplate, query, buffer);
|
||||
}
|
||||
if (fragment != null) {
|
||||
buffer.append("#");
|
||||
replaceParameter(paramMap, fromEncodedMap, isTemplate, fragment, buffer, encodeSlash);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
protected StringBuffer replacePathParameter(String name, String value, boolean isEncoded, String string, StringBuffer buffer, boolean encodeSlash) {
|
||||
Matcher matcher = createUriParamMatcher(string);
|
||||
while (matcher.find()) {
|
||||
String param = matcher.group(1);
|
||||
if (!param.equals(name)) continue;
|
||||
if (!isEncoded) {
|
||||
if (encodeSlash) value = Encode.encodePath(value);
|
||||
else value = Encode.encodePathSegment(value);
|
||||
|
||||
} else {
|
||||
value = Encode.encodeNonCodes(value);
|
||||
}
|
||||
// if there is a $ then we must backslash it or it will screw up regex group substitution
|
||||
value = value.replace("$", "\\$");
|
||||
matcher.appendReplacement(buffer, value);
|
||||
}
|
||||
matcher.appendTail(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static Matcher createUriParamMatcher(String string) {
|
||||
Matcher matcher = PathHelper.URI_PARAM_PATTERN.matcher(PathHelper.replaceEnclosedCurlyBraces(string));
|
||||
return matcher;
|
||||
}
|
||||
|
||||
protected StringBuffer replaceParameter(Map<String, ? extends Object> paramMap, boolean fromEncodedMap, boolean isTemplate, String string, StringBuffer buffer, boolean encodeSlash) {
|
||||
Matcher matcher = createUriParamMatcher(string);
|
||||
while (matcher.find()) {
|
||||
String param = matcher.group(1);
|
||||
Object valObj = paramMap.get(param);
|
||||
if (valObj == null && !isTemplate) {
|
||||
throw new IllegalArgumentException("NULL value for template parameter: " + param);
|
||||
} else if (valObj == null && isTemplate) {
|
||||
matcher.appendReplacement(buffer, matcher.group());
|
||||
continue;
|
||||
}
|
||||
String value = valObj.toString();
|
||||
if (value != null) {
|
||||
if (!fromEncodedMap) {
|
||||
if (encodeSlash) value = Encode.encodePathSegmentAsIs(value);
|
||||
else value = Encode.encodePathAsIs(value);
|
||||
} else {
|
||||
if (encodeSlash) value = Encode.encodePathSegmentSaveEncodings(value);
|
||||
else value = Encode.encodePathSaveEncodings(value);
|
||||
}
|
||||
matcher.appendReplacement(buffer, Matcher.quoteReplacement(value));
|
||||
} else {
|
||||
throw new IllegalArgumentException("path param " + param + " has not been provided by the parameter map");
|
||||
}
|
||||
}
|
||||
matcher.appendTail(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
protected StringBuffer replaceQueryStringParameter(Map<String, ? extends Object> paramMap, boolean fromEncodedMap, boolean isTemplate, String string, StringBuffer buffer) {
|
||||
Matcher matcher = createUriParamMatcher(string);
|
||||
while (matcher.find()) {
|
||||
String param = matcher.group(1);
|
||||
Object valObj = paramMap.get(param);
|
||||
if (valObj == null && !isTemplate) {
|
||||
throw new IllegalArgumentException("NULL value for template parameter: " + param);
|
||||
} else if (valObj == null && isTemplate) {
|
||||
matcher.appendReplacement(buffer, matcher.group());
|
||||
continue;
|
||||
}
|
||||
String value = valObj.toString();
|
||||
if (value != null) {
|
||||
if (!fromEncodedMap) {
|
||||
value = Encode.encodeQueryParamAsIs(value);
|
||||
} else {
|
||||
value = Encode.encodeQueryParamSaveEncodings(value);
|
||||
}
|
||||
matcher.appendReplacement(buffer, value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("path param " + param + " has not been provided by the parameter map");
|
||||
}
|
||||
}
|
||||
matcher.appendTail(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a unique order list of path params
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<String> getPathParamNamesInDeclarationOrder() {
|
||||
List<String> params = new ArrayList<String>();
|
||||
HashSet<String> set = new HashSet<String>();
|
||||
if (scheme != null) addToPathParamList(params, set, scheme);
|
||||
if (userInfo != null) addToPathParamList(params, set, userInfo);
|
||||
if (host != null) addToPathParamList(params, set, host);
|
||||
if (path != null) addToPathParamList(params, set, path);
|
||||
if (query != null) addToPathParamList(params, set, query);
|
||||
if (fragment != null) addToPathParamList(params, set, fragment);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
private void addToPathParamList(List<String> params, HashSet<String> set, String string) {
|
||||
Matcher matcher = PathHelper.URI_PARAM_PATTERN.matcher(PathHelper.replaceEnclosedCurlyBraces(string));
|
||||
while (matcher.find()) {
|
||||
String param = matcher.group(1);
|
||||
if (set.contains(param)) continue;
|
||||
else {
|
||||
set.add(param);
|
||||
params.add(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public URI build(Object... values) throws IllegalArgumentException {
|
||||
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||
return buildFromValues(true, false, values);
|
||||
}
|
||||
|
||||
protected URI buildFromValues(boolean encodeSlash, boolean encoded, Object... values) {
|
||||
List<String> params = getPathParamNamesInDeclarationOrder();
|
||||
if (values.length < params.size())
|
||||
throw new IllegalArgumentException("You did not supply enough values to fill path parameters");
|
||||
|
||||
Map<String, Object> pathParams = new HashMap<String, Object>();
|
||||
|
||||
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
String pathParam = params.get(i);
|
||||
Object val = values[i];
|
||||
if (val == null) throw new IllegalArgumentException("A value was null");
|
||||
pathParams.put(pathParam, val.toString());
|
||||
}
|
||||
String buf = null;
|
||||
try {
|
||||
buf = buildString(pathParams, encoded, false, encodeSlash);
|
||||
return new URI(buf);
|
||||
//return URI.create(buf);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create URI: " + buf, e);
|
||||
}
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder matrixParam(String name, Object... values) throws IllegalArgumentException {
|
||||
if (name == null) throw new IllegalArgumentException("name parameter is null");
|
||||
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||
if (path == null) path = "";
|
||||
for (Object val : values) {
|
||||
if (val == null) throw new IllegalArgumentException("null value");
|
||||
path += ";" + Encode.encodeMatrixParam(name) + "=" + Encode.encodeMatrixParam(val.toString());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final Pattern PARAM_REPLACEMENT = Pattern.compile("_resteasy_uri_parameter");
|
||||
|
||||
|
||||
public KeycloakUriBuilder queryParam(String name, Object... values) throws IllegalArgumentException {
|
||||
if (name == null) throw new IllegalArgumentException("name parameter is null");
|
||||
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||
for (Object value : values) {
|
||||
if (value == null) throw new IllegalArgumentException("A passed in value was null");
|
||||
if (query == null) query = "";
|
||||
else query += "&";
|
||||
query += Encode.encodeQueryParam(name) + "=" + Encode.encodeQueryParam(value.toString());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder replaceQueryParam(String name, Object... values) throws IllegalArgumentException {
|
||||
if (name == null) throw new IllegalArgumentException("name parameter is null");
|
||||
if (query == null || query.equals("")) {
|
||||
if (values != null) return queryParam(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
String[] params = query.split("&");
|
||||
query = null;
|
||||
|
||||
String replacedName = Encode.encodeQueryParam(name);
|
||||
|
||||
|
||||
for (String param : params) {
|
||||
int pos = param.indexOf('=');
|
||||
if (pos >= 0) {
|
||||
String paramName = param.substring(0, pos);
|
||||
if (paramName.equals(replacedName)) continue;
|
||||
} else {
|
||||
if (param.equals(replacedName)) continue;
|
||||
}
|
||||
if (query == null) query = "";
|
||||
else query += "&";
|
||||
query += param;
|
||||
}
|
||||
// don't set values if values is null
|
||||
if (values == null) return this;
|
||||
// don't set values if values is null
|
||||
if (values == null) return this;
|
||||
return queryParam(name, values);
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public String getScheme() {
|
||||
return scheme;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getUserInfo() {
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public String getFragment() {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder segment(String... segments) throws IllegalArgumentException {
|
||||
if (segments == null) throw new IllegalArgumentException("segments parameter was null");
|
||||
for (String segment : segments) {
|
||||
if (segment == null) throw new IllegalArgumentException("A segment is null");
|
||||
path(Encode.encodePathSegment(segment));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder replacePath(String path) {
|
||||
if (path == null) {
|
||||
this.path = null;
|
||||
return this;
|
||||
}
|
||||
this.path = Encode.encodePath(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
public URI build(Object[] values, boolean encodeSlashInPath) throws IllegalArgumentException {
|
||||
if (values == null) throw new IllegalArgumentException("values param is null");
|
||||
return buildFromValues(encodeSlashInPath, false, values);
|
||||
}
|
||||
|
||||
public String toTemplate() {
|
||||
return buildString(new HashMap<String, Object>(), true, true, true);
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder resolveTemplate(String name, Object value) throws IllegalArgumentException {
|
||||
if (name == null) throw new IllegalArgumentException("name param is null");
|
||||
if (value == null) throw new IllegalArgumentException("value param is null");
|
||||
HashMap<String, Object> vals = new HashMap<String, Object>();
|
||||
vals.put(name, value);
|
||||
return resolveTemplates(vals);
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder resolveTemplates(Map<String, Object> templateValues) throws IllegalArgumentException {
|
||||
if (templateValues == null) throw new IllegalArgumentException("templateValues param null");
|
||||
String str = buildString(templateValues, false, true, true);
|
||||
return fromTemplate(str);
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder resolveTemplate(String name, Object value, boolean encodeSlashInPath) throws IllegalArgumentException {
|
||||
if (name == null) throw new IllegalArgumentException("name param is null");
|
||||
if (value == null) throw new IllegalArgumentException("value param is null");
|
||||
HashMap<String, Object> vals = new HashMap<String, Object>();
|
||||
vals.put(name, value);
|
||||
String str = buildString(vals, false, true, encodeSlashInPath);
|
||||
return fromTemplate(str);
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder resolveTemplates(Map<String, Object> templateValues, boolean encodeSlashInPath) throws IllegalArgumentException {
|
||||
if (templateValues == null) throw new IllegalArgumentException("templateValues param null");
|
||||
String str = buildString(templateValues, false, true, encodeSlashInPath);
|
||||
return fromTemplate(str);
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder resolveTemplatesFromEncoded(Map<String, Object> templateValues) throws IllegalArgumentException {
|
||||
if (templateValues == null) throw new IllegalArgumentException("templateValues param null");
|
||||
String str = buildString(templateValues, true, true, true);
|
||||
return fromTemplate(str);
|
||||
}
|
||||
}
|
62
core/src/main/java/org/keycloak/util/PathHelper.java
Executable file
62
core/src/main/java/org/keycloak/util/PathHelper.java
Executable file
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.keycloak.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* A utility class for handling URI template parameters. As the Java
|
||||
* regulare expressions package does not handle named groups, this
|
||||
* class attempts to simulate that functionality by using groups.
|
||||
*
|
||||
* @author Ryan J. McDonough
|
||||
* @author Bill Burke
|
||||
* @since 1.0
|
||||
* Nov 8, 2006
|
||||
*/
|
||||
public class PathHelper
|
||||
{
|
||||
public static final String URI_PARAM_NAME_REGEX = "\\w[\\w\\.-]*";
|
||||
public static final String URI_PARAM_REGEX_REGEX = "[^{}][^{}]*";
|
||||
public static final String URI_PARAM_REGEX = "\\{\\s*(" + URI_PARAM_NAME_REGEX + ")\\s*(:\\s*(" + URI_PARAM_REGEX_REGEX + "))?\\}";
|
||||
public static final Pattern URI_PARAM_PATTERN = Pattern.compile(URI_PARAM_REGEX);
|
||||
|
||||
/**
|
||||
* A regex pattern that searches for a URI template parameter in the form of {*}
|
||||
*/
|
||||
public static final Pattern URI_TEMPLATE_PATTERN = Pattern.compile("(\\{([^}]+)\\})");
|
||||
|
||||
public static final char openCurlyReplacement = 6;
|
||||
public static final char closeCurlyReplacement = 7;
|
||||
|
||||
public static String replaceEnclosedCurlyBraces(String str)
|
||||
{
|
||||
char[] chars = str.toCharArray();
|
||||
int open = 0;
|
||||
for (int i = 0; i < chars.length; i++)
|
||||
{
|
||||
if (chars[i] == '{')
|
||||
{
|
||||
if (open != 0) chars[i] = openCurlyReplacement;
|
||||
open++;
|
||||
}
|
||||
else if (chars[i] == '}')
|
||||
{
|
||||
open--;
|
||||
if (open != 0)
|
||||
{
|
||||
chars[i] = closeCurlyReplacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
public static String recoverEnclosedCurlyBraces(String str)
|
||||
{
|
||||
return str.replace(openCurlyReplacement, '{').replace(closeCurlyReplacement, '}');
|
||||
}
|
||||
|
||||
}
|
|
@ -28,26 +28,6 @@
|
|||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core-jaxrs</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-as7-adapter</artifactId>
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package org.jboss.resteasy.example.oauth;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.keycloak.SkeletonKeySession;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.core.GenericType;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CustomerDatabaseClient
|
||||
{
|
||||
public static List<String> getCustomers(HttpServletRequest request)
|
||||
{
|
||||
SkeletonKeySession session = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName());
|
||||
ResteasyClient client = new ResteasyClientBuilder()
|
||||
.trustStore(session.getMetadata().getTruststore())
|
||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
|
||||
try
|
||||
{
|
||||
Response response = client.target("http://localhost:8080/database/customers").request()
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + session.getTokenString()).get();
|
||||
return response.readEntity(new GenericType<List<String>>(){});
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.keycloak.SkeletonKeySession;
|
||||
import org.keycloak.adapters.HttpClientBuilder;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CustomerDatabaseClient {
|
||||
|
||||
static class TypedList extends ArrayList<String> {}
|
||||
|
||||
public static List<String> getCustomers() {
|
||||
SkeletonKeySession session = SkeletonKeySession.getContext();
|
||||
HttpClient client = new HttpClientBuilder()
|
||||
.trustStore(session.getMetadata().getTruststore())
|
||||
.hostnameVerification(HttpClientBuilder.HostnameVerificationPolicy.ANY).build();
|
||||
try {
|
||||
HttpGet get = new HttpGet("http://localhost:8080/database/customers");
|
||||
get.addHeader("Authorization", "Bearer " + session.getTokenString());
|
||||
try {
|
||||
HttpResponse response = client.execute(get);
|
||||
HttpEntity entity = response.getEntity();
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
return JsonSerialization.readValue(is, TypedList.class);
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} finally {
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,9 +2,6 @@
|
|||
<deployment>
|
||||
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
|
||||
<dependencies>
|
||||
<module name="org.bouncycastle"/>
|
||||
<module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/>
|
||||
<module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
|
||||
</dependencies>
|
||||
</deployment>
|
||||
</jboss-deployment-structure>
|
|
@ -1,5 +1,5 @@
|
|||
<jboss-web>
|
||||
<valve>
|
||||
<class-name>org.keycloak.adapters.as7.OAuthManagedResourceValve</class-name>
|
||||
<class-name>org.keycloak.adapters.as7.OAuthAuthenticatorValve</class-name>
|
||||
</valve>
|
||||
</jboss-web>
|
2
examples/as7-eap-demo/customer-app/src/main/webapp/admin/admin.jsp
Normal file → Executable file
2
examples/as7-eap-demo/customer-app/src/main/webapp/admin/admin.jsp
Normal file → Executable file
|
@ -2,7 +2,7 @@
|
|||
pageEncoding="ISO-8859-1"%>
|
||||
<html>
|
||||
<head>
|
||||
<title>Customer Admin Iterface</title>
|
||||
<title>Customer Admin Interface</title>
|
||||
</head>
|
||||
<body bgcolor="#E3F6CE">
|
||||
<h1>Customer Admin Interface</h1>
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
<%@ page import="javax.ws.rs.core.UriBuilder" language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1"%>
|
||||
<%@ page import="org.keycloak.example.CustomerDatabaseClient" %>
|
||||
<%@ page import="org.keycloak.util.KeycloakUriBuilder" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Customer View Page</title>
|
||||
</head>
|
||||
<body bgcolor="#E3F6CE">
|
||||
<%
|
||||
String logoutUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
|
||||
.queryParam("redirect_uri", "http://localhost:8080/customer-portal").build().toString();
|
||||
String acctUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/account").build().toString();
|
||||
String logoutUri = KeycloakUriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
|
||||
.queryParam("redirect_uri", "http://localhost:8080/customer-portal").build().toString();
|
||||
String acctUri = "http://localhost:8080/auth-server/rest/realms/demo/account";
|
||||
%>
|
||||
<p>Goto: <a href="http://localhost:8080/product-portal">products</a> | <a href="<%=logoutUri%>">logout</a> | <a href="<%=acctUri%>">manage acct</a></p>
|
||||
User <b><%=request.getUserPrincipal().getName()%></b> made this request.
|
||||
<h2>Customer Listing</h2>
|
||||
<%
|
||||
java.util.List<String> list = org.jboss.resteasy.example.oauth.CustomerDatabaseClient.getCustomers(request);
|
||||
java.util.List<String> list = CustomerDatabaseClient.getCustomers();
|
||||
for (String cust : list)
|
||||
{
|
||||
out.print("<p>");
|
||||
|
|
|
@ -30,24 +30,23 @@
|
|||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core-jaxrs</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-as7-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<!--
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
-->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
<deployment>
|
||||
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
|
||||
<dependencies>
|
||||
<module name="org.bouncycastle"/>
|
||||
</dependencies>
|
||||
</deployment>
|
||||
</jboss-deployment-structure>
|
|
@ -28,26 +28,6 @@
|
|||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core-jaxrs</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-as7-adapter</artifactId>
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package org.jboss.resteasy.example.oauth;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.keycloak.SkeletonKeySession;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.core.GenericType;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ProductDatabaseClient
|
||||
{
|
||||
public static List<String> getProducts(HttpServletRequest request)
|
||||
{
|
||||
SkeletonKeySession session = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName());
|
||||
ResteasyClient client = new ResteasyClientBuilder()
|
||||
.trustStore(session.getMetadata().getTruststore())
|
||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
|
||||
try
|
||||
{
|
||||
Response response = client.target("http://localhost:8080/database/products").request()
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + session.getTokenString()).get();
|
||||
return response.readEntity(new GenericType<List<String>>(){});
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package org.keycloak.example.oauth;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.keycloak.SkeletonKeySession;
|
||||
import org.keycloak.adapters.HttpClientBuilder;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ProductDatabaseClient
|
||||
{
|
||||
static class TypedList extends ArrayList<String> {}
|
||||
|
||||
public static List<String> getProducts() {
|
||||
SkeletonKeySession session = SkeletonKeySession.getContext();
|
||||
HttpClient client = new HttpClientBuilder()
|
||||
.trustStore(session.getMetadata().getTruststore())
|
||||
.hostnameVerification(HttpClientBuilder.HostnameVerificationPolicy.ANY).build();
|
||||
try {
|
||||
HttpGet get = new HttpGet("http://localhost:8080/database/products");
|
||||
get.addHeader("Authorization", "Bearer " + session.getTokenString());
|
||||
try {
|
||||
HttpResponse response = client.execute(get);
|
||||
HttpEntity entity = response.getEntity();
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
return JsonSerialization.readValue(is, TypedList.class);
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} finally {
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<jboss-web>
|
||||
<valve>
|
||||
<class-name>org.keycloak.adapters.as7.OAuthManagedResourceValve</class-name>
|
||||
<class-name>org.keycloak.adapters.as7.OAuthAuthenticatorValve</class-name>
|
||||
</valve>
|
||||
</jboss-web>
|
|
@ -1,21 +1,23 @@
|
|||
<%@ page import="javax.ws.rs.core.UriBuilder" language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1"%>
|
||||
<%@ page import="org.keycloak.example.oauth.ProductDatabaseClient" %>
|
||||
<%@ page import="org.keycloak.util.KeycloakUriBuilder" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Product View Page</title>
|
||||
</head>
|
||||
<body bgcolor="#F5F6CE">
|
||||
<%
|
||||
String logoutUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
|
||||
String logoutUri = KeycloakUriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
|
||||
.queryParam("redirect_uri", "http://localhost:8080/product-portal").build().toString();
|
||||
String acctUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/account").build().toString();
|
||||
String acctUri = "http://localhost:8080/auth-server/rest/realms/demo/account";
|
||||
%>
|
||||
|
||||
<p>Goto: <a href="http://localhost:8080/customer-portal">customers</a> | <a href="<%=logoutUri%>">logout</a> | <a href="<%=acctUri%>">manage acct</a></p>
|
||||
User <b><%=request.getUserPrincipal().getName()%></b> made this request.
|
||||
<h2>Product Listing</h2>
|
||||
<%
|
||||
java.util.List<String> list = org.jboss.resteasy.example.oauth.ProductDatabaseClient.getProducts(request);
|
||||
java.util.List<String> list = ProductDatabaseClient.getProducts();
|
||||
for (String cust : list)
|
||||
{
|
||||
out.print("<p>");
|
||||
|
|
12
examples/as7-eap-demo/third-party/pom.xml
vendored
12
examples/as7-eap-demo/third-party/pom.xml
vendored
|
@ -21,19 +21,9 @@
|
|||
<version>1.0.1.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core-jaxrs</artifactId>
|
||||
<artifactId>keycloak-servlet-oauth-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.jboss.resteasy.example.oauth;
|
||||
package org.keycloak.example.oauth;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.keycloak.servlet.ServletOAuthClient;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
|
@ -55,7 +54,6 @@ public class Bootstrap implements ServletContextListener {
|
|||
client.setPassword("password");
|
||||
client.setAuthUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/login");
|
||||
client.setCodeUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes");
|
||||
client.setClient(new ResteasyClientBuilder().build());
|
||||
client.start();
|
||||
sce.getServletContext().setAttribute(ServletOAuthClient.class.getName(), client);
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
package org.jboss.resteasy.example.oauth;
|
||||
package org.keycloak.example.oauth;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.keycloak.adapters.TokenGrantRequest;
|
||||
import org.keycloak.servlet.ServletOAuthClient;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.core.GenericType;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -31,6 +34,8 @@ public class ProductDatabaseClient {
|
|||
}
|
||||
}
|
||||
|
||||
static class TypedList extends ArrayList<String> {}
|
||||
|
||||
public static List<String> getProducts(HttpServletRequest request) {
|
||||
// This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute
|
||||
// that is set in the Bootstrap context listenr in this project.
|
||||
|
@ -38,32 +43,30 @@ public class ProductDatabaseClient {
|
|||
// and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
|
||||
// and take a look how it works.
|
||||
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
|
||||
String token = oAuthClient.getBearerToken(request);
|
||||
ResteasyClient client = new ResteasyClientBuilder()
|
||||
.trustStore(oAuthClient.getTruststore())
|
||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
|
||||
String token = null;
|
||||
try {
|
||||
// invoke without the Authorization header
|
||||
Response response = client.target("http://localhost:8080/database/products").request().get();
|
||||
response.close();
|
||||
if (response.getStatus() != 401) {
|
||||
response.close();
|
||||
client.close();
|
||||
throw new RuntimeException("Expecting an auth status code: " + response.getStatus());
|
||||
}
|
||||
} finally {
|
||||
token = oAuthClient.getBearerToken(request);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (TokenGrantRequest.HttpFailure failure) {
|
||||
throw new RuntimeException(failure);
|
||||
}
|
||||
|
||||
HttpClient client = oAuthClient.getClient();
|
||||
|
||||
HttpGet get = new HttpGet("http://localhost:8080/database/products");
|
||||
get.addHeader("Authorization", "Bearer " + token);
|
||||
try {
|
||||
Response response = client.target("http://localhost:8080/database/products").request()
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get();
|
||||
if (response.getStatus() != 200) {
|
||||
response.close();
|
||||
throw new RuntimeException("Failed to access!: " + response.getStatus());
|
||||
HttpResponse response = client.execute(get);
|
||||
HttpEntity entity = response.getEntity();
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
return JsonSerialization.readValue(is, TypedList.class);
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
return response.readEntity(new GenericType<List<String>>() {
|
||||
});
|
||||
} finally {
|
||||
client.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,6 @@
|
|||
<deployment>
|
||||
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
|
||||
<dependencies>
|
||||
<module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/>
|
||||
<module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
|
||||
</dependencies>
|
||||
</deployment>
|
||||
</jboss-deployment-structure>
|
|
@ -7,7 +7,7 @@
|
|||
<module-name>oauth-client</module-name>
|
||||
|
||||
<listener>
|
||||
<listener-class>org.jboss.resteasy.example.oauth.Bootstrap</listener-class>
|
||||
<listener-class>org.keycloak.example.oauth.Bootstrap</listener-class>
|
||||
</listener>
|
||||
<!--
|
||||
<security-constraint>
|
||||
|
|
3
examples/as7-eap-demo/third-party/src/main/webapp/pull_data.jsp
vendored
Normal file → Executable file
3
examples/as7-eap-demo/third-party/src/main/webapp/pull_data.jsp
vendored
Normal file → Executable file
|
@ -1,3 +1,4 @@
|
|||
<%@ page import="org.keycloak.example.oauth.ProductDatabaseClient" %>
|
||||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1"%>
|
||||
<html>
|
||||
|
@ -7,7 +8,7 @@
|
|||
<body>
|
||||
<h2>Pulled Product Listing</h2>
|
||||
<%
|
||||
java.util.List<String> list = org.jboss.resteasy.example.oauth.ProductDatabaseClient.getProducts(request);
|
||||
java.util.List<String> list = ProductDatabaseClient.getProducts(request);
|
||||
for (String prod : list)
|
||||
{
|
||||
out.print("<p>");
|
||||
|
|
4
examples/as7-eap-demo/third-party/src/main/webapp/redirect.jsp
vendored
Normal file → Executable file
4
examples/as7-eap-demo/third-party/src/main/webapp/redirect.jsp
vendored
Normal file → Executable file
|
@ -1,3 +1,3 @@
|
|||
<%
|
||||
org.jboss.resteasy.example.oauth.ProductDatabaseClient.redirect(request, response);
|
||||
<%@ page import="org.keycloak.example.oauth.ProductDatabaseClient" %><%
|
||||
ProductDatabaseClient.redirect(request, response);
|
||||
%>
|
|
@ -22,14 +22,17 @@
|
|||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-xc</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
@ -37,13 +40,9 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.1.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
package org.keycloak.adapters;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.conn.ClientConnectionManager;
|
||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||
import org.apache.http.conn.scheme.Scheme;
|
||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.impl.conn.SingleClientConnManager;
|
||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.HttpConnectionParams;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Abstraction for creating HttpClients. Allows SSL configuration.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class HttpClientBuilder {
|
||||
public static enum HostnameVerificationPolicy {
|
||||
/**
|
||||
* Hostname verification is not done on the server's certificate
|
||||
*/
|
||||
ANY,
|
||||
/**
|
||||
* Allows wildcards in subdomain names i.e. *.foo.com
|
||||
*/
|
||||
WILDCARD,
|
||||
/**
|
||||
* CN must match hostname connecting to
|
||||
*/
|
||||
STRICT
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
private static class PassthroughTrustManager implements X509TrustManager {
|
||||
public void checkClientTrusted(X509Certificate[] chain,
|
||||
String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain,
|
||||
String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected KeyStore truststore;
|
||||
protected KeyStore clientKeyStore;
|
||||
protected String clientPrivateKeyPassword;
|
||||
protected boolean disableTrustManager;
|
||||
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
|
||||
protected SSLContext sslContext;
|
||||
protected int connectionPoolSize = 100;
|
||||
protected int maxPooledPerRoute = 0;
|
||||
protected long connectionTTL = -1;
|
||||
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
|
||||
protected HostnameVerifier verifier = null;
|
||||
protected long socketTimeout = -1;
|
||||
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||
protected long establishConnectionTimeout = -1;
|
||||
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||
|
||||
|
||||
/**
|
||||
* Socket inactivity timeout
|
||||
*
|
||||
* @param timeout
|
||||
* @param unit
|
||||
* @return
|
||||
*/
|
||||
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit)
|
||||
{
|
||||
this.socketTimeout = timeout;
|
||||
this.socketTimeoutUnits = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When trying to make an initial socket connection, what is the timeout?
|
||||
*
|
||||
* @param timeout
|
||||
* @param unit
|
||||
* @return
|
||||
*/
|
||||
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit)
|
||||
{
|
||||
this.establishConnectionTimeout = timeout;
|
||||
this.establishConnectionTimeoutUnits = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
|
||||
this.connectionTTL = ttl;
|
||||
this.connectionTTLUnit = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
|
||||
this.maxPooledPerRoute = maxPooledPerRoute;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
|
||||
this.connectionPoolSize = connectionPoolSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable trust management and hostname verification. <i>NOTE</i> this is a security
|
||||
* hole, so only set this option if you cannot or do not want to verify the identity of the
|
||||
* host you are communicating with.
|
||||
*/
|
||||
public HttpClientBuilder disableTrustManager() {
|
||||
this.disableTrustManager = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* SSL policy used to verify hostnames
|
||||
*
|
||||
* @param policy
|
||||
* @return
|
||||
*/
|
||||
public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
|
||||
this.policy = policy;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public HttpClientBuilder sslContext(SSLContext sslContext) {
|
||||
this.sslContext = sslContext;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder trustStore(KeyStore truststore) {
|
||||
this.truststore = truststore;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
|
||||
this.clientKeyStore = keyStore;
|
||||
this.clientPrivateKeyPassword = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
|
||||
this.clientKeyStore = keyStore;
|
||||
this.clientPrivateKeyPassword = new String(password);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
static class VerifierWrapper implements X509HostnameVerifier {
|
||||
protected HostnameVerifier verifier;
|
||||
|
||||
VerifierWrapper(HostnameVerifier verifier) {
|
||||
this.verifier = verifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String host, SSLSocket ssl) throws IOException {
|
||||
if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String host, X509Certificate cert) throws SSLException {
|
||||
throw new SSLException("This verification path not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
|
||||
throw new SSLException("This verification path not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String s, SSLSession sslSession) {
|
||||
return verifier.verify(s, sslSession);
|
||||
}
|
||||
}
|
||||
|
||||
public HttpClient build() {
|
||||
X509HostnameVerifier verifier = null;
|
||||
if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
|
||||
else {
|
||||
switch (policy) {
|
||||
case ANY:
|
||||
verifier = new AllowAllHostnameVerifier();
|
||||
break;
|
||||
case WILDCARD:
|
||||
verifier = new BrowserCompatHostnameVerifier();
|
||||
break;
|
||||
case STRICT:
|
||||
verifier = new StrictHostnameVerifier();
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
SSLSocketFactory sslsf = null;
|
||||
SSLContext theContext = sslContext;
|
||||
if (disableTrustManager) {
|
||||
theContext = SSLContext.getInstance("SSL");
|
||||
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
|
||||
new SecureRandom());
|
||||
verifier = new AllowAllHostnameVerifier();
|
||||
sslsf = new SSLSocketFactory(theContext, verifier);
|
||||
} else if (theContext != null) {
|
||||
sslsf = new SSLSocketFactory(theContext, verifier);
|
||||
} else if (clientKeyStore != null || truststore != null) {
|
||||
sslsf = new SSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
|
||||
} else {
|
||||
final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
|
||||
tlsContext.init(null, null, null);
|
||||
sslsf = new SSLSocketFactory(tlsContext, verifier);
|
||||
}
|
||||
SchemeRegistry registry = new SchemeRegistry();
|
||||
registry.register(
|
||||
new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
|
||||
Scheme httpsScheme = new Scheme("https", 443, sslsf);
|
||||
registry.register(httpsScheme);
|
||||
ClientConnectionManager cm = null;
|
||||
if (connectionPoolSize > 0) {
|
||||
ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit);
|
||||
tcm.setMaxTotal(connectionPoolSize);
|
||||
if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
|
||||
tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
|
||||
cm = tcm;
|
||||
|
||||
} else {
|
||||
cm = new SingleClientConnManager(registry);
|
||||
}
|
||||
BasicHttpParams params = new BasicHttpParams();
|
||||
if (socketTimeout > -1)
|
||||
{
|
||||
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
|
||||
|
||||
}
|
||||
if (establishConnectionTimeout > -1)
|
||||
{
|
||||
HttpConnectionParams.setConnectionTimeout(params, (int)establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
|
||||
}
|
||||
return new DefaultHttpClient(cm, params);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package org.keycloak.adapters;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class TokenGrantRequest {
|
||||
|
||||
public static class HttpFailure extends Exception {
|
||||
private int status;
|
||||
private String error;
|
||||
|
||||
public HttpFailure(int status, String error) {
|
||||
this.status = status;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public static AccessTokenResponse invoke(RealmConfiguration config, String code, String redirectUri) throws HttpFailure, IOException {
|
||||
String codeUrl = config.getCodeUrl();
|
||||
String client_id = config.getMetadata().getResourceName();
|
||||
Map<String,String> credentials = config.getResourceCredentials();
|
||||
HttpClient client = config.getClient();
|
||||
|
||||
return invoke(client, code, codeUrl, redirectUri, client_id, credentials);
|
||||
}
|
||||
|
||||
public static AccessTokenResponse invoke(HttpClient client, String code, String codeUrl, String redirectUri, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
|
||||
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
||||
String password = credentials.get("password");
|
||||
formparams.add(new BasicNameValuePair("grant_type", "authorization_code"));
|
||||
formparams.add(new BasicNameValuePair("code", code));
|
||||
formparams.add(new BasicNameValuePair("client_id", client_id));
|
||||
formparams.add(new BasicNameValuePair(CredentialRepresentation.PASSWORD, password));
|
||||
formparams.add(new BasicNameValuePair("redirect_uri", redirectUri));
|
||||
HttpResponse response = null;
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
HttpPost post = new HttpPost(codeUrl);
|
||||
post.setEntity(form);
|
||||
response = client.execute(post);
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (status != 200) {
|
||||
error(status, entity);
|
||||
}
|
||||
if (entity == null) {
|
||||
throw new HttpFailure(status, null);
|
||||
}
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
return JsonSerialization.readValue(is, AccessTokenResponse.class);
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static String readString(InputStream in) throws IOException
|
||||
{
|
||||
char[] buffer = new char[1024];
|
||||
StringBuilder builder = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
int wasRead = 0;
|
||||
do
|
||||
{
|
||||
wasRead = reader.read(buffer, 0, 1024);
|
||||
if (wasRead > 0)
|
||||
{
|
||||
builder.append(buffer, 0, wasRead);
|
||||
}
|
||||
}
|
||||
while (wasRead > -1);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
protected static void error(int status, HttpEntity entity) throws HttpFailure, IOException {
|
||||
String body = null;
|
||||
if (entity != null) {
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
body = readString(is);
|
||||
} catch (IOException e) {
|
||||
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new HttpFailure(status, body);
|
||||
}
|
||||
|
||||
protected static String stripOauthParametersFromRedirect(String uri) {
|
||||
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(uri)
|
||||
.replaceQueryParam("code", null)
|
||||
.replaceQueryParam("state", null);
|
||||
return builder.build().toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -21,7 +21,7 @@ public class AdapterConfig
|
|||
protected String resource;
|
||||
@JsonProperty("realm-public-key")
|
||||
protected String realmKey;
|
||||
@JsonProperty("auth-url")
|
||||
@JsonProperty("auth-url")
|
||||
protected String authUrl;
|
||||
@JsonProperty("code-url")
|
||||
protected String codeUrl;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package org.keycloak.adapters;
|
||||
package org.keycloak.adapters.config;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.keycloak.ResourceMetadata;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -13,10 +13,10 @@ import javax.ws.rs.core.UriBuilder;
|
|||
*/
|
||||
public class RealmConfiguration {
|
||||
protected ResourceMetadata metadata;
|
||||
protected ResteasyClient client;
|
||||
protected UriBuilder authUrl;
|
||||
protected ResteasyWebTarget codeUrl;
|
||||
protected Form resourceCredentials = new Form();
|
||||
protected HttpClient client;
|
||||
protected KeycloakUriBuilder authUrl;
|
||||
protected String codeUrl;
|
||||
protected Map<String, String> resourceCredentials = new HashMap<String, String>();
|
||||
protected boolean sslRequired = true;
|
||||
protected String stateCookieName = "OAuth_Token_Request_State";
|
||||
|
||||
|
@ -31,33 +31,6 @@ public class RealmConfiguration {
|
|||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public ResteasyClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(ResteasyClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public UriBuilder getAuthUrl() {
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
public void setAuthUrl(UriBuilder authUrl) {
|
||||
this.authUrl = authUrl;
|
||||
}
|
||||
|
||||
public Form getResourceCredentials() {
|
||||
return resourceCredentials;
|
||||
}
|
||||
|
||||
public ResteasyWebTarget getCodeUrl() {
|
||||
return codeUrl;
|
||||
}
|
||||
|
||||
public void setCodeUrl(ResteasyWebTarget codeUrl) {
|
||||
this.codeUrl = codeUrl;
|
||||
}
|
||||
|
||||
public boolean isSslRequired() {
|
||||
return sslRequired;
|
||||
|
@ -74,4 +47,37 @@ public class RealmConfiguration {
|
|||
public void setStateCookieName(String stateCookieName) {
|
||||
this.stateCookieName = stateCookieName;
|
||||
}
|
||||
|
||||
public HttpClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(HttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder getAuthUrl() {
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
public void setAuthUrl(KeycloakUriBuilder authUrl) {
|
||||
this.authUrl = authUrl;
|
||||
}
|
||||
|
||||
public String getCodeUrl() {
|
||||
return codeUrl;
|
||||
}
|
||||
|
||||
public void setCodeUrl(String codeUrl) {
|
||||
this.codeUrl = codeUrl;
|
||||
}
|
||||
|
||||
public Map<String, String> getResourceCredentials() {
|
||||
return resourceCredentials;
|
||||
}
|
||||
|
||||
public void setResourceCredentials(Map<String, String> resourceCredentials) {
|
||||
this.resourceCredentials = resourceCredentials;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +1,17 @@
|
|||
package org.keycloak.adapters.undertow;
|
||||
package org.keycloak.adapters.config;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.adapters.RealmConfiguration;
|
||||
import org.keycloak.adapters.config.AdapterConfigLoader;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.keycloak.adapters.HttpClientBuilder;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RealmConfigurationLoader extends AdapterConfigLoader {
|
||||
protected ResteasyClient client;
|
||||
protected HttpClient client;
|
||||
protected RealmConfiguration realmConfiguration;
|
||||
|
||||
public RealmConfigurationLoader() {
|
||||
|
@ -45,36 +40,23 @@ public class RealmConfigurationLoader extends AdapterConfigLoader {
|
|||
}
|
||||
realmConfiguration.setMetadata(resourceMetadata);
|
||||
realmConfiguration.setSslRequired(!adapterConfig.isSslNotRequired());
|
||||
realmConfiguration.setResourceCredentials(adapterConfig.getCredentials());
|
||||
|
||||
for (Map.Entry<String, String> entry : getAdapterConfig().getCredentials().entrySet()) {
|
||||
realmConfiguration.getResourceCredentials().param(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
ResteasyClient client = getClient();
|
||||
HttpClient client = getClient();
|
||||
|
||||
realmConfiguration.setClient(client);
|
||||
realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
|
||||
realmConfiguration.setCodeUrl(client.target(tokenUrl));
|
||||
realmConfiguration.setAuthUrl(KeycloakUriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
|
||||
realmConfiguration.setCodeUrl(tokenUrl);
|
||||
}
|
||||
|
||||
protected void initClient() {
|
||||
int size = 10;
|
||||
if (adapterConfig.getConnectionPoolSize() > 0)
|
||||
size = adapterConfig.getConnectionPoolSize();
|
||||
ResteasyClientBuilder.HostnameVerificationPolicy policy = ResteasyClientBuilder.HostnameVerificationPolicy.WILDCARD;
|
||||
HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
|
||||
if (adapterConfig.isAllowAnyHostname())
|
||||
policy = ResteasyClientBuilder.HostnameVerificationPolicy.ANY;
|
||||
ResteasyProviderFactory providerFactory = new ResteasyProviderFactory();
|
||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(RealmConfigurationLoader.class.getClassLoader());
|
||||
try {
|
||||
ResteasyProviderFactory.getInstance(); // initialize builtins
|
||||
RegisterBuiltin.register(providerFactory);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(old);
|
||||
}
|
||||
ResteasyClientBuilder builder = new ResteasyClientBuilder()
|
||||
.providerFactory(providerFactory)
|
||||
policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
|
||||
HttpClientBuilder builder = new HttpClientBuilder()
|
||||
.connectionPoolSize(size)
|
||||
.hostnameVerification(policy)
|
||||
.keyStore(clientCertKeystore, adapterConfig.getClientKeyPassword());
|
||||
|
@ -86,7 +68,7 @@ public class RealmConfigurationLoader extends AdapterConfigLoader {
|
|||
client = builder.build();
|
||||
}
|
||||
|
||||
public ResteasyClient getClient() {
|
||||
public HttpClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
|
@ -23,30 +23,38 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-xc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.web</groupId>
|
||||
<artifactId>jbossweb</artifactId>
|
||||
|
@ -59,12 +67,6 @@
|
|||
<version>7.1.2.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.picketbox</groupId>
|
||||
<artifactId>picketbox</artifactId>
|
||||
<version>4.0.7.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
|
|
@ -18,9 +18,9 @@ import java.util.Set;
|
|||
|
||||
/**
|
||||
* Pre-installed actions that must be authenticated
|
||||
*
|
||||
* <p/>
|
||||
* Actions include:
|
||||
*
|
||||
* <p/>
|
||||
* CORS Origin Check and Response headers
|
||||
* K_QUERY_BEARER_TOKEN: Get bearer token from server for Javascripts CORS requests
|
||||
*
|
||||
|
@ -47,24 +47,24 @@ public class AuthenticatedActionsValve extends ValveBase {
|
|||
if (corsRequest(request, response, session)) return;
|
||||
String requestUri = request.getRequestURI();
|
||||
if (requestUri.endsWith("K_QUERY_BEARER_TOKEN")) {
|
||||
queryBearerToken(request, response, session);
|
||||
return;
|
||||
queryBearerToken(request, response, session);
|
||||
return;
|
||||
}
|
||||
getNext().invoke(request, response);
|
||||
}
|
||||
|
||||
public SkeletonKeySession getSkeletonKeySession(Request request) {
|
||||
SkeletonKeySession skSession = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName());
|
||||
SkeletonKeySession skSession = (SkeletonKeySession) request.getAttribute(SkeletonKeySession.class.getName());
|
||||
if (skSession != null) return skSession;
|
||||
Session session = request.getSessionInternal();
|
||||
if (session != null) {
|
||||
return (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
|
||||
return (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void queryBearerToken(Request request, Response response, SkeletonKeySession session) throws IOException, ServletException {
|
||||
log.debugv("queryBearerToken {0}",request.getRequestURI());
|
||||
log.debugv("queryBearerToken {0}", request.getRequestURI());
|
||||
if (abortTokenResponse(request, response, session)) return;
|
||||
response.setStatus(200);
|
||||
response.setContentType("text/plain");
|
||||
|
@ -75,7 +75,7 @@ public class AuthenticatedActionsValve extends ValveBase {
|
|||
|
||||
protected boolean abortTokenResponse(Request request, Response response, SkeletonKeySession session) throws IOException {
|
||||
if (session == null) {
|
||||
log.debugv("session was null, sending back 401: {0}",request.getRequestURI());
|
||||
log.debugv("session was null, sending back 401: {0}", request.getRequestURI());
|
||||
response.sendError(401);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ import org.apache.catalina.connector.Response;
|
|||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.deploy.LoginConfig;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.ResourceMetadata;
|
||||
import org.keycloak.SkeletonKeySession;
|
||||
import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader;
|
||||
import org.keycloak.adapters.config.AdapterConfig;
|
||||
import org.keycloak.adapters.config.AdapterConfigLoader;
|
||||
|
@ -63,7 +63,7 @@ public class BearerTokenAuthenticatorValve extends AuthenticatorBase implements
|
|||
}
|
||||
super.invoke(request, response);
|
||||
} finally {
|
||||
ResteasyProviderFactory.clearContextData(); // to clear push of SkeletonKeySession
|
||||
SkeletonKeySession.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package org.keycloak.adapters.as7;
|
|||
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.ResourceMetadata;
|
||||
import org.keycloak.SkeletonKeyPrincipal;
|
||||
|
@ -109,7 +108,7 @@ public class CatalinaBearerTokenAuthenticator {
|
|||
request.setAuthType("OAUTH_BEARER");
|
||||
SkeletonKeySession skSession = new SkeletonKeySession(tokenString, token, resourceMetadata);
|
||||
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
|
||||
ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
|
||||
SkeletonKeySession.pushContext(skSession);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -13,14 +13,13 @@ import org.apache.catalina.core.StandardContext;
|
|||
import org.apache.catalina.deploy.LoginConfig;
|
||||
import org.apache.catalina.realm.GenericPrincipal;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.adapters.RealmConfiguration;
|
||||
import org.keycloak.ResourceMetadata;
|
||||
import org.keycloak.SkeletonKeyPrincipal;
|
||||
import org.keycloak.SkeletonKeySession;
|
||||
import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader;
|
||||
import org.keycloak.adapters.as7.config.RealmConfigurationLoader;
|
||||
import org.keycloak.adapters.config.AdapterConfig;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.adapters.config.RealmConfigurationLoader;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.SkeletonKeyToken;
|
||||
|
@ -43,9 +42,9 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OAuthManagedResourceValve extends FormAuthenticator implements LifecycleListener {
|
||||
public class OAuthAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
|
||||
protected RealmConfiguration realmConfiguration;
|
||||
private static final Logger log = Logger.getLogger(OAuthManagedResourceValve.class);
|
||||
private static final Logger log = Logger.getLogger(OAuthAuthenticatorValve.class);
|
||||
protected UserSessionManagement userSessionManagement = new UserSessionManagement();
|
||||
protected AdapterConfig adapterConfig;
|
||||
protected ResourceMetadata resourceMetadata;
|
||||
|
@ -87,7 +86,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
|
|||
}
|
||||
super.invoke(request, response);
|
||||
} finally {
|
||||
ResteasyProviderFactory.clearContextData(); // to clear push of SkeletonKeySession
|
||||
SkeletonKeySession.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +164,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
|
|||
return;
|
||||
|
||||
}
|
||||
String user = action.getUser();
|
||||
String user = action.getUser();
|
||||
if (user != null) {
|
||||
log.debug("logout of session for: " + user);
|
||||
userSessionManagement.logout(user);
|
||||
|
@ -200,7 +199,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
|
|||
SkeletonKeySession skSession = (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
|
||||
if (skSession != null) {
|
||||
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
|
||||
ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
|
||||
SkeletonKeySession.pushContext(skSession);
|
||||
|
||||
}
|
||||
}
|
|
@ -2,19 +2,16 @@ package org.keycloak.adapters.as7;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.RealmConfiguration;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.adapters.TokenGrantRequest;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.SkeletonKeyToken;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
@ -137,7 +134,7 @@ public class ServletOAuthLogin {
|
|||
// disabled?
|
||||
return null;
|
||||
}
|
||||
UriBuilder secureUrl = UriBuilder.fromUri(url).scheme("https").port(-1);
|
||||
KeycloakUriBuilder secureUrl = KeycloakUriBuilder.fromUri(url).scheme("https").port(-1);
|
||||
if (port != 443) secureUrl.port(port);
|
||||
url = secureUrl.build().toString();
|
||||
}
|
||||
|
@ -159,7 +156,7 @@ public class ServletOAuthLogin {
|
|||
String state = getStateCode();
|
||||
String redirect = getRedirectUri(state);
|
||||
if (redirect == null) {
|
||||
sendError(Response.Status.FORBIDDEN.getStatusCode());
|
||||
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
setCookie(realmInfo.getStateCookieName(), state, null, getDefaultCookiePath(), realmInfo.isSslRequired());
|
||||
|
@ -216,42 +213,28 @@ public class ServletOAuthLogin {
|
|||
// abort if not HTTPS
|
||||
if (realmInfo.isSslRequired() && !isRequestSecure()) {
|
||||
log.error("SSL is required");
|
||||
sendError(Response.Status.FORBIDDEN.getStatusCode());
|
||||
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkStateCookie()) return false;
|
||||
|
||||
String client_id = realmInfo.getMetadata().getResourceName();
|
||||
String password = realmInfo.getResourceCredentials().asMap().getFirst("password");
|
||||
//String authHeader = BasicAuthHelper.createHeader(client_id, password);
|
||||
String redirectUri = stripOauthParametersFromRedirect();
|
||||
Form form = new Form();
|
||||
form.param("grant_type", "authorization_code")
|
||||
.param("code", code)
|
||||
.param("client_id", client_id)
|
||||
.param(CredentialRepresentation.PASSWORD, password)
|
||||
.param("redirect_uri", redirectUri);
|
||||
|
||||
Response res = realmInfo.getCodeUrl().request()
|
||||
//.header(HttpHeaders.AUTHORIZATION, authHeader)
|
||||
.post(Entity.form(form));
|
||||
AccessTokenResponse tokenResponse;
|
||||
AccessTokenResponse tokenResponse = null;
|
||||
try {
|
||||
if (res.getStatus() != 200) {
|
||||
log.error("failed to turn code into token");
|
||||
log.error("status from server: " + res.getStatus());
|
||||
if (res.getStatus() == 400 && res.getMediaType() != null) {
|
||||
log.error(" " + res.readEntity(String.class));
|
||||
}
|
||||
sendError(Response.Status.FORBIDDEN.getStatusCode());
|
||||
return false;
|
||||
tokenResponse = TokenGrantRequest.invoke(realmInfo, code, redirectUri);
|
||||
} catch (TokenGrantRequest.HttpFailure failure) {
|
||||
log.error("failed to turn code into token");
|
||||
log.error("status from server: " + failure.getStatus());
|
||||
if (failure.getStatus() == 400 && failure.getError() != null) {
|
||||
log.error(" " + failure.getError());
|
||||
}
|
||||
log.debug("media type: " + res.getMediaType());
|
||||
log.debug("Content-Type header: " + res.getHeaderString("Content-Type"));
|
||||
tokenResponse = res.readEntity(AccessTokenResponse.class);
|
||||
} finally {
|
||||
res.close();
|
||||
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return false;
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("failed to turn code into token");
|
||||
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
tokenString = tokenResponse.getToken();
|
||||
|
@ -260,7 +243,7 @@ public class ServletOAuthLogin {
|
|||
log.debug("Token Verification succeeded!");
|
||||
} catch (VerificationException e) {
|
||||
log.error("failed verification of token");
|
||||
sendError(Response.Status.FORBIDDEN.getStatusCode());
|
||||
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return false;
|
||||
}
|
||||
// redirect to URL without oauth query parameters
|
||||
|
@ -273,7 +256,7 @@ public class ServletOAuthLogin {
|
|||
*/
|
||||
protected String stripOauthParametersFromRedirect() {
|
||||
StringBuffer buf = request.getRequestURL().append("?").append(request.getQueryString());
|
||||
UriBuilder builder = UriBuilder.fromUri(buf.toString())
|
||||
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(buf.toString())
|
||||
.replaceQueryParam("code", null)
|
||||
.replaceQueryParam("state", null);
|
||||
return builder.build().toString();
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package org.keycloak.adapters.as7.config;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.keycloak.adapters.config.RealmConfigurationLoader;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class CatalinaAdapterConfigLoader extends RealmConfigurationLoader
|
||||
{
|
||||
public class CatalinaAdapterConfigLoader extends RealmConfigurationLoader {
|
||||
|
||||
public CatalinaAdapterConfigLoader(Context context) {
|
||||
InputStream is = null;
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
package org.keycloak.adapters.as7.config;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.adapters.RealmConfiguration;
|
||||
import org.keycloak.adapters.config.AdapterConfigLoader;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RealmConfigurationLoader extends AdapterConfigLoader {
|
||||
protected ResteasyClient client;
|
||||
protected RealmConfiguration realmConfiguration;
|
||||
|
||||
public RealmConfigurationLoader() {
|
||||
}
|
||||
|
||||
public RealmConfigurationLoader(InputStream is) {
|
||||
loadConfig(is);
|
||||
}
|
||||
|
||||
public void init(boolean setupClient) {
|
||||
init();
|
||||
initRealmConfiguration(setupClient);
|
||||
}
|
||||
|
||||
protected void initRealmConfiguration(boolean setupClient) {
|
||||
if (!setupClient || adapterConfig.isBearerOnly()) return;
|
||||
initClient();
|
||||
realmConfiguration = new RealmConfiguration();
|
||||
String authUrl = adapterConfig.getAuthUrl();
|
||||
if (authUrl == null) {
|
||||
throw new RuntimeException("You must specify auth-url");
|
||||
}
|
||||
String tokenUrl = adapterConfig.getCodeUrl();
|
||||
if (tokenUrl == null) {
|
||||
throw new RuntimeException("You mut specify code-url");
|
||||
}
|
||||
realmConfiguration.setMetadata(resourceMetadata);
|
||||
realmConfiguration.setSslRequired(!adapterConfig.isSslNotRequired());
|
||||
|
||||
for (Map.Entry<String, String> entry : getAdapterConfig().getCredentials().entrySet()) {
|
||||
realmConfiguration.getResourceCredentials().param(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
ResteasyClient client = getClient();
|
||||
|
||||
realmConfiguration.setClient(client);
|
||||
realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
|
||||
realmConfiguration.setCodeUrl(client.target(tokenUrl));
|
||||
}
|
||||
|
||||
protected void initClient() {
|
||||
int size = 10;
|
||||
if (adapterConfig.getConnectionPoolSize() > 0)
|
||||
size = adapterConfig.getConnectionPoolSize();
|
||||
ResteasyClientBuilder.HostnameVerificationPolicy policy = ResteasyClientBuilder.HostnameVerificationPolicy.WILDCARD;
|
||||
if (adapterConfig.isAllowAnyHostname())
|
||||
policy = ResteasyClientBuilder.HostnameVerificationPolicy.ANY;
|
||||
ResteasyProviderFactory providerFactory = new ResteasyProviderFactory();
|
||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(RealmConfigurationLoader.class.getClassLoader());
|
||||
try {
|
||||
ResteasyProviderFactory.getInstance(); // initialize builtins
|
||||
RegisterBuiltin.register(providerFactory);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(old);
|
||||
}
|
||||
ResteasyClientBuilder builder = new ResteasyClientBuilder()
|
||||
.providerFactory(providerFactory)
|
||||
.connectionPoolSize(size)
|
||||
.hostnameVerification(policy)
|
||||
.keyStore(clientCertKeystore, adapterConfig.getClientKeyPassword());
|
||||
if (adapterConfig.isDisableTrustManager()) {
|
||||
builder.disableTrustManager();
|
||||
} else {
|
||||
builder.trustStore(truststore);
|
||||
}
|
||||
client = builder.build();
|
||||
}
|
||||
|
||||
public ResteasyClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public RealmConfiguration getRealmConfiguration() {
|
||||
return realmConfiguration;
|
||||
}
|
||||
|
||||
}
|
71
integration/jaxrs-oauth-client/pom.xml
Executable file
71
integration/jaxrs-oauth-client/pom.xml
Executable file
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-alpha-1-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
|
||||
<name>Keycloak JAX-RS OAuth Client</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jackson-provider</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -1,11 +1,17 @@
|
|||
package org.keycloak.jaxrs;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.jboss.resteasy.util.BasicAuthHelper;
|
||||
import org.keycloak.AbstractOAuthClient;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.InternalServerErrorException;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.NewCookie;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -21,6 +27,56 @@ import java.net.URI;
|
|||
*/
|
||||
public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||
protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class);
|
||||
protected Client client;
|
||||
|
||||
/**
|
||||
* Creates a Client for obtaining access token from code
|
||||
*/
|
||||
public void start() {
|
||||
if (client == null) {
|
||||
client = new ResteasyClientBuilder().trustStore(truststore)
|
||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
|
||||
.connectionPoolSize(10)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* closes cllient
|
||||
*/
|
||||
public void stop() {
|
||||
client.close();
|
||||
}
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String resolveBearerToken(String redirectUri, String code) {
|
||||
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
||||
String authHeader = BasicAuthHelper.createHeader(clientId, password);
|
||||
Form codeForm = new Form()
|
||||
.param("grant_type", "authorization_code")
|
||||
.param("code", code)
|
||||
.param("client_id", clientId)
|
||||
.param("password", password)
|
||||
.param("redirect_uri", redirectUri);
|
||||
Response res = client.target(codeUrl).request().header(HttpHeaders.AUTHORIZATION, authHeader).post(Entity.form(codeForm));
|
||||
try {
|
||||
if (res.getStatus() == 400) {
|
||||
throw new BadRequestException();
|
||||
} else if (res.getStatus() != 200) {
|
||||
throw new InternalServerErrorException(new Exception("Unknown error when getting acess token"));
|
||||
}
|
||||
AccessTokenResponse tokenResponse = res.readEntity(AccessTokenResponse.class);
|
||||
return tokenResponse.getToken();
|
||||
} finally {
|
||||
res.close();
|
||||
}
|
||||
}
|
||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
||||
return redirect(uriInfo, redirectUri, null);
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
<modules>
|
||||
<module>adapter-core</module>
|
||||
<module>jaxrs-oauth-client</module>
|
||||
<module>servlet-oauth-client</module>
|
||||
<module>as7-eap6/adapter</module>
|
||||
<module>undertow</module>
|
||||
<!-- <module>as7-eap6/jboss-modules</module> -->
|
||||
|
|
67
integration/servlet-oauth-client/pom.xml
Executable file
67
integration/servlet-oauth-client/pom.xml
Executable file
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-alpha-1-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-servlet-oauth-client</artifactId>
|
||||
<name>Keycloak Servlet OAuth Client</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -1,23 +1,58 @@
|
|||
package org.keycloak.servlet;
|
||||
|
||||
import org.jboss.resteasy.plugins.server.servlet.ServletUtil;
|
||||
import org.jboss.resteasy.spi.ResteasyUriInfo;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.keycloak.AbstractOAuthClient;
|
||||
import org.keycloak.adapters.HttpClientBuilder;
|
||||
import org.keycloak.adapters.TokenGrantRequest;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.InternalServerErrorException;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ServletOAuthClient extends AbstractOAuthClient {
|
||||
protected HttpClient client;
|
||||
|
||||
/**
|
||||
* Creates a Client for obtaining access token from code
|
||||
*/
|
||||
public void start() {
|
||||
if (client == null) {
|
||||
client = new HttpClientBuilder().trustStore(truststore)
|
||||
.hostnameVerification(HttpClientBuilder.HostnameVerificationPolicy.ANY)
|
||||
.connectionPoolSize(10)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* closes cllient
|
||||
*/
|
||||
public void stop() {
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
public HttpClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(HttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String resolveBearerToken(String redirectUri, String code) throws IOException, TokenGrantRequest.HttpFailure {
|
||||
Map<String, String> credentials = new HashMap<String, String>();
|
||||
credentials.put("password", password);
|
||||
return TokenGrantRequest.invoke(client, code, codeUrl, redirectUri, clientId, credentials).getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the process of obtaining an access token by redirecting the browser
|
||||
* to the authentication server
|
||||
|
@ -28,8 +63,11 @@ public class ServletOAuthClient extends AbstractOAuthClient {
|
|||
* @throws IOException
|
||||
*/
|
||||
public void redirectRelative(String relativePath, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ResteasyUriInfo uriInfo = ServletUtil.extractUriInfo(request, null);
|
||||
String redirect = uriInfo.getBaseUriBuilder().path(relativePath).toTemplate();
|
||||
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(request.getRequestURL().toString())
|
||||
.replacePath(request.getContextPath())
|
||||
.replaceQuery(null)
|
||||
.path(relativePath);
|
||||
String redirect = builder.toTemplate();
|
||||
redirect(redirect, request, response);
|
||||
}
|
||||
|
||||
|
@ -46,7 +84,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
|
|||
public void redirect(String redirectUri, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String state = getStateCode();
|
||||
|
||||
URI url = UriBuilder.fromUri(authUrl)
|
||||
URI url = KeycloakUriBuilder.fromUri(authUrl)
|
||||
.queryParam("client_id", clientId)
|
||||
.queryParam("redirect_uri", redirectUri)
|
||||
.queryParam("state", state)
|
||||
|
@ -92,24 +130,24 @@ public class ServletOAuthClient extends AbstractOAuthClient {
|
|||
*
|
||||
* @param request
|
||||
* @return
|
||||
* @throws BadRequestException
|
||||
* @throws InternalServerErrorException
|
||||
* @throws IOException
|
||||
* @throws org.keycloak.adapters.TokenGrantRequest.HttpFailure
|
||||
*/
|
||||
public String getBearerToken(HttpServletRequest request) throws BadRequestException, InternalServerErrorException {
|
||||
public String getBearerToken(HttpServletRequest request) throws IOException, TokenGrantRequest.HttpFailure {
|
||||
String error = request.getParameter("error");
|
||||
if (error != null) throw new BadRequestException(new Exception("OAuth error: " + error));
|
||||
if (error != null) throw new IOException("OAuth error: " + error);
|
||||
String redirectUri = request.getRequestURL().append("?").append(request.getQueryString()).toString();
|
||||
String stateCookie = getCookieValue(stateCookieName, request);
|
||||
if (stateCookie == null) throw new BadRequestException(new Exception("state cookie not set"));
|
||||
if (stateCookie == null) throw new IOException("state cookie not set");
|
||||
// we can call get parameter as this should be a redirect
|
||||
String state = request.getParameter("state");
|
||||
String code = request.getParameter("code");
|
||||
|
||||
if (state == null) throw new BadRequestException(new Exception("state parameter was null"));
|
||||
if (state == null) throw new IOException("state parameter was null");
|
||||
if (!state.equals(stateCookie)) {
|
||||
throw new BadRequestException(new Exception("state parameter invalid"));
|
||||
throw new IOException("state parameter invalid");
|
||||
}
|
||||
if (code == null) throw new BadRequestException(new Exception("code parameter was null"));
|
||||
if (code == null) throw new IOException("code parameter was null");
|
||||
return resolveBearerToken(redirectUri, code);
|
||||
}
|
||||
|
|
@ -23,29 +23,38 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-xc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
|
|
|
@ -6,7 +6,7 @@ import io.undertow.security.idm.Account;
|
|||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.util.AttachmentKey;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.RealmConfiguration;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.ResourceMetadata;
|
||||
import org.keycloak.SkeletonKeyPrincipal;
|
||||
import org.keycloak.SkeletonKeySession;
|
||||
|
|
|
@ -8,6 +8,7 @@ import io.undertow.servlet.api.DeploymentInfo;
|
|||
import io.undertow.servlet.api.ServletSessionConfig;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.config.AdapterConfig;
|
||||
import org.keycloak.adapters.config.RealmConfigurationLoader;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import java.io.InputStream;
|
||||
|
|
|
@ -8,16 +8,14 @@ import io.undertow.server.handlers.CookieImpl;
|
|||
import io.undertow.util.Headers;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.RealmConfiguration;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.adapters.TokenGrantRequest;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.SkeletonKeyToken;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.IOException;
|
||||
import java.util.Deque;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
@ -60,7 +58,7 @@ public class OAuthAuthenticator {
|
|||
}
|
||||
|
||||
protected String getRequestUrl() {
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(exchange.getRequestURI())
|
||||
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
|
||||
.replaceQuery(exchange.getQueryString());
|
||||
if (!exchange.isHostIncludedInRequestURI()) uriBuilder.scheme(exchange.getRequestScheme()).host(exchange.getHostAndPort());
|
||||
return uriBuilder.build().toString();
|
||||
|
@ -107,7 +105,7 @@ public class OAuthAuthenticator {
|
|||
// disabled?
|
||||
return null;
|
||||
}
|
||||
UriBuilder secureUrl = UriBuilder.fromUri(url).scheme("https").port(-1);
|
||||
KeycloakUriBuilder secureUrl = KeycloakUriBuilder.fromUri(url).scheme("https").port(-1);
|
||||
if (port != 443) secureUrl.port(port);
|
||||
url = secureUrl.build().toString();
|
||||
}
|
||||
|
@ -237,34 +235,20 @@ public class OAuthAuthenticator {
|
|||
KeycloakChallenge challenge = checkStateCookie();
|
||||
if (challenge != null) return challenge;
|
||||
|
||||
String client_id = realmInfo.getMetadata().getResourceName();
|
||||
String password = realmInfo.getResourceCredentials().asMap().getFirst("password");
|
||||
//String authHeader = BasicAuthHelper.createHeader(client_id, password);
|
||||
redirectUri = stripOauthParametersFromRedirect();
|
||||
Form form = new Form();
|
||||
form.param("grant_type", "authorization_code")
|
||||
.param("code", code)
|
||||
.param("client_id", client_id)
|
||||
.param(CredentialRepresentation.PASSWORD, password)
|
||||
.param("redirect_uri", redirectUri);
|
||||
|
||||
Response res = realmInfo.getCodeUrl().request()
|
||||
.post(Entity.form(form));
|
||||
AccessTokenResponse tokenResponse;
|
||||
AccessTokenResponse tokenResponse = null;
|
||||
try {
|
||||
if (res.getStatus() != 200) {
|
||||
log.error("failed to turn code into token");
|
||||
log.error("status from server: " + res.getStatus());
|
||||
if (res.getStatus() == 400 && res.getMediaType() != null) {
|
||||
log.error(" " + res.readEntity(String.class));
|
||||
}
|
||||
return challenge(403);
|
||||
tokenResponse = TokenGrantRequest.invoke(realmInfo, code, redirectUri);
|
||||
} catch (TokenGrantRequest.HttpFailure failure) {
|
||||
log.error("failed to turn code into token");
|
||||
log.error("status from server: " + failure.getStatus());
|
||||
if (failure.getStatus() == 400 && failure.getError() != null) {
|
||||
log.error(" " + failure.getError());
|
||||
}
|
||||
log.debug("media type: " + res.getMediaType());
|
||||
log.debug("Content-Type header: " + res.getHeaderString("Content-Type"));
|
||||
tokenResponse = res.readEntity(AccessTokenResponse.class);
|
||||
} finally {
|
||||
res.close();
|
||||
return challenge(403);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("failed to turn code into token");
|
||||
return challenge(403);
|
||||
}
|
||||
|
||||
tokenString = tokenResponse.getToken();
|
||||
|
@ -283,7 +267,7 @@ public class OAuthAuthenticator {
|
|||
* strip out unwanted query parameters and redirect so bookmarks don't retain oauth protocol bits
|
||||
*/
|
||||
protected String stripOauthParametersFromRedirect() {
|
||||
UriBuilder builder = UriBuilder.fromUri(exchange.getRequestURI())
|
||||
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
|
||||
.replaceQuery(exchange.getQueryString())
|
||||
.replaceQueryParam("code", null)
|
||||
.replaceQueryParam("state", null);
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.keycloak.adapters.undertow;
|
|||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.servlet.api.ConfidentialPortManager;
|
||||
import io.undertow.servlet.handlers.ServletRequestContext;
|
||||
import org.keycloak.adapters.RealmConfiguration;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.ResourceMetadata;
|
||||
import org.keycloak.SkeletonKeySession;
|
||||
import org.keycloak.adapters.config.AdapterConfig;
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.keycloak.adapters.undertow;
|
|||
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.servlet.api.ConfidentialPortManager;
|
||||
import org.keycloak.adapters.RealmConfiguration;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
|
11
pom.xml
11
pom.xml
|
@ -76,8 +76,8 @@
|
|||
<module>core</module>
|
||||
<module>core-jaxrs</module>
|
||||
<module>model</module>
|
||||
<module>services</module>
|
||||
<module>integration</module>
|
||||
<module>services</module>
|
||||
<module>social</module>
|
||||
<module>forms</module>
|
||||
<module>admin-ui-styles</module>
|
||||
|
@ -327,7 +327,14 @@
|
|||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Keep this at 4.1.2 to make sure we're compatible with AS7 -->
|
||||
<!-- the dependency seems to override Resteasy 3.0.5's dependeing on 4.2.1
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.1.2</version>
|
||||
</dependency>
|
||||
-->
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
@ -35,6 +35,12 @@
|
|||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-jpa</artifactId>
|
||||
|
@ -83,7 +89,7 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>jaxrs-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
|
@ -162,11 +168,11 @@
|
|||
<artifactId>hibernate-entitymanager</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.icegreen</groupId>
|
||||
<artifactId>greenmail</artifactId>
|
||||
<dependency>
|
||||
<groupId>com.icegreen</groupId>
|
||||
<artifactId>greenmail</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -9,6 +9,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -34,7 +35,13 @@ public class DummySocialServlet extends HttpServlet {
|
|||
String state = null;
|
||||
String redirectUri = null;
|
||||
|
||||
List<NameValuePair> query = URLEncodedUtils.parse(req.getQueryString(), Charset.forName("UTF-8"));
|
||||
List<NameValuePair> query = null;
|
||||
try {
|
||||
URI uri = URI.create(req.getRequestURL().append('?').append(req.getQueryString()).toString());
|
||||
query = URLEncodedUtils.parse(uri, "UTF-8");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
for (NameValuePair p : query) {
|
||||
if ("state".equals(p.getName())) {
|
||||
state = p.getValue();
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.openqa.selenium.By;
|
|||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
|
@ -127,7 +128,12 @@ public class OAuthClient {
|
|||
parameters.add(new BasicNameValuePair("password", password));
|
||||
}
|
||||
|
||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8"));
|
||||
UrlEncodedFormEntity formEntity = null;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
post.setEntity(formEntity);
|
||||
|
||||
try {
|
||||
|
|
Loading…
Reference in a new issue