refactor out resteasy from adapters

This commit is contained in:
Bill Burke 2013-12-17 12:07:02 -05:00
parent ac64eba667
commit 6380dc3d1b
57 changed files with 2444 additions and 670 deletions

View file

@ -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();
}
}

View 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();
}
}

View file

@ -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();
}
}

View 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);
}
}
}

View 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);
}
}

View 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, '}');
}
}

View file

@ -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>

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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")
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 = 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/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>");

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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>

View file

@ -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>");

View file

@ -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>

View file

@ -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);

View file

@ -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());
}
return response.readEntity(new GenericType<List<String>>() {
});
HttpResponse response = client.execute(get);
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
try {
return JsonSerialization.readValue(is, TypedList.class);
} finally {
client.close();
is.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -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>

View file

@ -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
View 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
View 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);
%>

View file

@ -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>

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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>

View file

@ -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
*

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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();
}
}
@ -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);
}
}

View file

@ -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) {
tokenResponse = TokenGrantRequest.invoke(realmInfo, code, redirectUri);
} catch (TokenGrantRequest.HttpFailure failure) {
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));
log.error("status from server: " + failure.getStatus());
if (failure.getStatus() == 400 && failure.getError() != null) {
log.error(" " + failure.getError());
}
sendError(Response.Status.FORBIDDEN.getStatusCode());
sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
}
log.debug("media type: " + res.getMediaType());
log.debug("Content-Type header: " + res.getHeaderString("Content-Type"));
tokenResponse = res.readEntity(AccessTokenResponse.class);
} finally {
res.close();
} 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();

View file

@ -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;

View file

@ -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;
}
}

View 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>

View file

@ -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);
}

View file

@ -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> -->

View 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>

View file

@ -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);
}

View file

@ -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>

View file

@ -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;

View file

@ -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;

View file

@ -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) {
tokenResponse = TokenGrantRequest.invoke(realmInfo, code, redirectUri);
} catch (TokenGrantRequest.HttpFailure failure) {
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));
log.error("status from server: " + failure.getStatus());
if (failure.getStatus() == 400 && failure.getError() != null) {
log.error(" " + failure.getError());
}
return challenge(403);
}
log.debug("media type: " + res.getMediaType());
log.debug("Content-Type header: " + res.getHeaderString("Content-Type"));
tokenResponse = res.readEntity(AccessTokenResponse.class);
} finally {
res.close();
} 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);

View file

@ -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;

View file

@ -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
View file

@ -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>

View file

@ -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>

View file

@ -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();

View file

@ -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 {