This commit is contained in:
Bill Burke 2014-11-10 17:09:00 -05:00
commit 3b6f10913c
39 changed files with 1150 additions and 154 deletions

View file

@ -1,4 +1,4 @@
package org.keycloak.adapters; package org.keycloak.constants;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -0,0 +1,10 @@
package org.keycloak.constants;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GenericConstants {
public static final String PROTOCOL_CLASSPATH = "classpath:";
}

View file

@ -1,4 +1,4 @@
package org.keycloak; package org.keycloak.constants;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -5,18 +5,18 @@ import java.io.FileInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.security.KeyStore; import java.security.KeyStore;
import org.keycloak.constants.GenericConstants;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class KeystoreUtil { public class KeystoreUtil {
private static final String PROTOCOL_CLASSPATH = "classpath:";
public static KeyStore loadKeyStore(String filename, String password) throws Exception { public static KeyStore loadKeyStore(String filename, String password) throws Exception {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream trustStream = (filename.startsWith(PROTOCOL_CLASSPATH)) InputStream trustStream = (filename.startsWith(GenericConstants.PROTOCOL_CLASSPATH))
?KeystoreUtil.class.getResourceAsStream(filename.replace(PROTOCOL_CLASSPATH, "")) ?KeystoreUtil.class.getResourceAsStream(filename.replace(GenericConstants.PROTOCOL_CLASSPATH, ""))
:new FileInputStream(new File(filename)); :new FileInputStream(new File(filename));
trustStore.load(trustStream, password.toCharArray()); trustStore.load(trustStream, password.toCharArray());
trustStream.close(); trustStream.close();

View file

@ -30,6 +30,8 @@
<module>tomcat7-adapter-zip</module> <module>tomcat7-adapter-zip</module>
<module>eap6-adapter-zip</module> <module>eap6-adapter-zip</module>
<module>wildfly-adapter-zip</module> <module>wildfly-adapter-zip</module>
<module>jetty91-adapter-zip</module>
<module>jetty92-adapter-zip</module>
<module>examples-docs-zip</module> <module>examples-docs-zip</module>
<module>theme-template-zip</module> <module>theme-template-zip</module>
<module>war-zip</module> <module>war-zip</module>

View file

@ -9,7 +9,7 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.ServiceUrlConstants; import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.adapters.HttpClientBuilder; import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;

View file

@ -2,6 +2,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import java.io.IOException; import java.io.IOException;

View file

@ -6,6 +6,7 @@ import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.RSATokenVerifier; import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException; import org.keycloak.VerificationException;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;

View file

@ -2,7 +2,7 @@ package org.keycloak.adapters;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.ServiceUrlConstants; import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.enums.RelativeUrlsUsed; import org.keycloak.enums.RelativeUrlsUsed;
import org.keycloak.enums.SslRequired; import org.keycloak.enums.SslRequired;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;

View file

@ -2,6 +2,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Version; import org.keycloak.Version;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.adapters.action.AdminAction; import org.keycloak.representations.adapters.action.AdminAction;
@ -166,7 +167,7 @@ public class PreAuthActionsHandler {
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
log.warn("SSL is required for adapter admin action"); log.warn("SSL is required for adapter admin action");
facade.getResponse().sendError(403, "ssl required"); facade.getResponse().sendError(403, "ssl required");
return null;
} }
String token = StreamUtil.readString(facade.getRequest().getInputStream()); String token = StreamUtil.readString(facade.getRequest().getInputStream());
if (token == null) { if (token == null) {

View file

@ -8,6 +8,7 @@ import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.BasicAuthHelper;

View file

@ -13,7 +13,7 @@ import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig; import org.apache.catalina.deploy.LoginConfig;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AuthChallenge; import org.keycloak.adapters.AuthChallenge;

View file

@ -14,11 +14,6 @@
<description/> <description/>
<dependencies> <dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId> <artifactId>jaxrs-api</artifactId>
@ -43,6 +38,12 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId> <artifactId>jackson-core-asl</artifactId>
@ -69,6 +70,13 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>4.3.0</version>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>

View file

@ -1,115 +1,14 @@
package org.keycloak.jaxrs; package org.keycloak.jaxrs;
import org.jboss.logging.Logger; import javax.annotation.Priority;
import org.jboss.resteasy.spi.ResteasyProviderFactory; import javax.ws.rs.Priorities;
import org.keycloak.KeycloakPrincipal; import javax.ws.rs.container.ContainerRequestFilter;
import org.keycloak.KeycloakSecurityContext; import javax.ws.rs.container.PreMatching;
import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException; /**
import org.keycloak.representations.AccessToken; * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
import javax.annotation.Priority; @PreMatching
import javax.ws.rs.Priorities; @Priority(Priorities.AUTHENTICATION)
import javax.ws.rs.container.ContainerRequestContext; public interface JaxrsBearerTokenFilter extends ContainerRequestFilter {
import javax.ws.rs.container.ContainerRequestFilter; }
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import java.io.IOException;
import java.security.Principal;
import java.security.PublicKey;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Priority(Priorities.AUTHENTICATION)
public class JaxrsBearerTokenFilter implements ContainerRequestFilter {
private static Logger log = Logger.getLogger(JaxrsBearerTokenFilter.class);
protected String realm;
protected PublicKey realmPublicKey;
protected String resourceName;
public JaxrsBearerTokenFilter(String realm, PublicKey realmPublicKey, String resourceName) {
this.realm = realm;
this.realmPublicKey = realmPublicKey;
this.resourceName = resourceName;
}
protected void challengeResponse(ContainerRequestContext request, String error, String description) {
StringBuilder header = new StringBuilder("Bearer realm=\"");
header.append(realm).append("\"");
if (error != null) {
header.append(", error=\"").append(error).append("\"");
}
if (description != null) {
header.append(", error_description=\"").append(description).append("\"");
}
request.abortWith(Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, header.toString()).build());
return;
}
@Context
protected SecurityContext securityContext;
@Override
public void filter(ContainerRequestContext request) throws IOException {
String authHeader = request.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authHeader == null) {
challengeResponse(request, null, null);
return;
}
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) challengeResponse(request, null, null);
if (!split[0].equalsIgnoreCase("Bearer")) challengeResponse(request, null, null);
String tokenString = split[1];
try {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realmPublicKey, realm);
KeycloakSecurityContext skSession = new KeycloakSecurityContext(tokenString, token, null, null);
ResteasyProviderFactory.pushContext(KeycloakSecurityContext.class, skSession);
final KeycloakPrincipal<KeycloakSecurityContext> principal = new KeycloakPrincipal<KeycloakSecurityContext>(token.getSubject(), skSession);
final boolean isSecure = securityContext.isSecure();
final AccessToken.Access access;
if (resourceName != null) {
access = token.getResourceAccess(resourceName);
} else {
access = token.getRealmAccess();
}
SecurityContext ctx = new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return principal;
}
@Override
public boolean isUserInRole(String role) {
if (access == null) return false;
if (access.getRoles() == null) return false;
return access.getRoles().contains(role);
}
@Override
public boolean isSecure() {
return isSecure;
}
@Override
public String getAuthenticationScheme() {
return "OAUTH_BEARER";
}
};
request.setSecurityContext(ctx);
} catch (VerificationException e) {
log.error("Failed to verify token", e);
challengeResponse(request, "invalid_token", e.getMessage());
}
}
}

View file

@ -0,0 +1,289 @@
package org.keycloak.jaxrs;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.AuthOutcome;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.UserSessionManagement;
import org.keycloak.constants.GenericConstants;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.Principal;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@PreMatching
@Priority(Priorities.AUTHENTICATION)
public class JaxrsBearerTokenFilterImpl implements JaxrsBearerTokenFilter {
private final static Logger log = Logger.getLogger("" + JaxrsBearerTokenFilterImpl.class);
private String keycloakConfigFile;
private String keycloakConfigResolverClass;
protected volatile boolean started;
protected AdapterDeploymentContext deploymentContext;
// TODO: Should also somehow handle stop lifecycle for de-registration
protected NodesRegistrationManagement nodesRegistrationManagement;
protected UserSessionManagement userSessionManagement = new EmptyUserSessionManagement();
public void setKeycloakConfigFile(String configFile) {
this.keycloakConfigFile = configFile;
attemptStart();
}
public String getKeycloakConfigFile() {
return this.keycloakConfigFile;
}
public String getKeycloakConfigResolverClass() {
return keycloakConfigResolverClass;
}
public void setKeycloakConfigResolverClass(String keycloakConfigResolverClass) {
this.keycloakConfigResolverClass = keycloakConfigResolverClass;
attemptStart();
}
// INITIALIZATION AND STARTUP
protected void attemptStart() {
if (started) {
throw new IllegalStateException("Filter already started. Make sure to specify just keycloakConfigResolver or keycloakConfigFile but not both");
}
if (isInitialized()) {
start();
} else {
log.fine("Not yet initialized");
}
}
protected boolean isInitialized() {
return this.keycloakConfigFile != null || this.keycloakConfigResolverClass != null;
}
protected void start() {
if (started) {
throw new IllegalStateException("Filter already started. Make sure to specify just keycloakConfigResolver or keycloakConfigFile but not both");
}
if (keycloakConfigResolverClass != null) {
Class<? extends KeycloakConfigResolver> resolverClass = loadResolverClass();
try {
KeycloakConfigResolver resolver = resolverClass.newInstance();
log.info("Using " + resolver + " to resolve Keycloak configuration on a per-request basis.");
this.deploymentContext = new AdapterDeploymentContext(resolver);
} catch (Exception e) {
throw new RuntimeException("Unable to instantiate resolver " + resolverClass);
}
} else {
if (keycloakConfigFile == null) {
throw new IllegalArgumentException("You need to specify either keycloakConfigResolverClass or keycloakConfigFile in configuration");
}
InputStream is = loadKeycloakConfigFile();
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
deploymentContext = new AdapterDeploymentContext(kd);
log.info("Keycloak is using a per-deployment configuration loaded from: " + keycloakConfigFile);
}
nodesRegistrationManagement = new NodesRegistrationManagement();
started = true;
}
protected Class<? extends KeycloakConfigResolver> loadResolverClass() {
try {
return (Class<? extends KeycloakConfigResolver>)getClass().getClassLoader().loadClass(keycloakConfigResolverClass);
} catch (ClassNotFoundException cnfe) {
// Fallback to tccl
try {
return (Class<? extends KeycloakConfigResolver>)Thread.currentThread().getContextClassLoader().loadClass(keycloakConfigResolverClass);
} catch (ClassNotFoundException cnfe2) {
throw new RuntimeException("Unable to find resolver class: " + keycloakConfigResolverClass);
}
}
}
protected InputStream loadKeycloakConfigFile() {
if (keycloakConfigFile.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
String classPathLocation = keycloakConfigFile.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
log.fine("Loading config from classpath on location: " + classPathLocation);
// Try current class classloader first
InputStream is = getClass().getClassLoader().getResourceAsStream(classPathLocation);
if (is == null) {
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathLocation);
}
if (is != null) {
return is;
} else {
throw new RuntimeException("Unable to find config from classpath: " + keycloakConfigFile);
}
} else {
// Fallback to file
try {
log.fine("Loading config from file: " + keycloakConfigFile);
return new FileInputStream(keycloakConfigFile);
} catch (FileNotFoundException fnfe) {
log.severe("Config not found on " + keycloakConfigFile);
throw new RuntimeException(fnfe);
}
}
}
// REQUEST HANDLING
@Override
public void filter(ContainerRequestContext request) throws IOException {
SecurityContext securityContext = getRequestSecurityContext(request);
JaxrsHttpFacade facade = new JaxrsHttpFacade(request, securityContext);
if (handlePreauth(facade)) {
return;
}
KeycloakDeployment resolvedDeployment = deploymentContext.resolveDeployment(facade);
nodesRegistrationManagement.tryRegister(resolvedDeployment);
bearerAuthentication(facade, request, resolvedDeployment);
}
protected boolean handlePreauth(JaxrsHttpFacade facade) {
PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deploymentContext, facade);
if (handler.handleRequest()) {
// Send response now (if not already sent)
if (!facade.isResponseFinished()) {
facade.getResponse().end();
}
return true;
}
return false;
}
protected void bearerAuthentication(JaxrsHttpFacade facade, ContainerRequestContext request, KeycloakDeployment resolvedDeployment) {
BearerTokenRequestAuthenticator bearer = new BearerTokenRequestAuthenticator(resolvedDeployment);
AuthOutcome outcome = bearer.authenticate(facade);
if (outcome == AuthOutcome.FAILED || outcome == AuthOutcome.NOT_ATTEMPTED) {
AuthChallenge challenge = bearer.getChallenge();
log.fine("Authentication outcome: " + outcome);
boolean challengeSent = challenge.challenge(facade);
if (!challengeSent) {
// Use some default status code
facade.getResponse().setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
}
// Send response now (if not already sent)
if (!facade.isResponseFinished()) {
facade.getResponse().end();
}
return;
} else {
if (verifySslFailed(facade, resolvedDeployment)) {
return;
}
}
propagateSecurityContext(facade, request, resolvedDeployment, bearer);
handleAuthActions(facade, resolvedDeployment);
}
protected void propagateSecurityContext(JaxrsHttpFacade facade, ContainerRequestContext request, KeycloakDeployment resolvedDeployment, BearerTokenRequestAuthenticator bearer) {
RefreshableKeycloakSecurityContext skSession = new RefreshableKeycloakSecurityContext(resolvedDeployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
// Not needed to do resteasy specifics as KeycloakSecurityContext can be always retrieved from SecurityContext by typecast SecurityContext.getUserPrincipal to KeycloakPrincipal
// ResteasyProviderFactory.pushContext(KeycloakSecurityContext.class, skSession);
facade.setSecurityContext(skSession);
String principalName = AdapterUtils.getPrincipalName(resolvedDeployment, bearer.getToken());
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(principalName, skSession);
SecurityContext anonymousSecurityContext = getRequestSecurityContext(request);
final boolean isSecure = anonymousSecurityContext.isSecure();
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(skSession);
SecurityContext ctx = new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return principal;
}
@Override
public boolean isUserInRole(String role) {
return roles.contains(role);
}
@Override
public boolean isSecure() {
return isSecure;
}
@Override
public String getAuthenticationScheme() {
return "OAUTH_BEARER";
}
};
request.setSecurityContext(ctx);
}
protected boolean verifySslFailed(JaxrsHttpFacade facade, KeycloakDeployment deployment) {
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
log.warning("SSL is required to authenticate, but request is not secured");
facade.getResponse().sendError(403, "SSL required!");
return true;
}
return false;
}
protected SecurityContext getRequestSecurityContext(ContainerRequestContext request) {
return request.getSecurityContext();
}
protected void handleAuthActions(JaxrsHttpFacade facade, KeycloakDeployment deployment) {
AuthenticatedActionsHandler authActionsHandler = new AuthenticatedActionsHandler(deployment, facade);
if (authActionsHandler.handledRequest()) {
// Send response now (if not already sent)
if (!facade.isResponseFinished()) {
facade.getResponse().end();
}
}
}
// We don't have any sessions to manage with pure jaxrs filter
private static class EmptyUserSessionManagement implements UserSessionManagement {
@Override
public void logoutAll() {
}
@Override
public void logoutHttpSessions(List<String> ids) {
}
}
}

View file

@ -0,0 +1,172 @@
package org.keycloak.jaxrs;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import javax.security.cert.X509Certificate;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.SecurityContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.HttpFacade;
import org.keycloak.util.HostUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JaxrsHttpFacade implements HttpFacade {
protected final ContainerRequestContext requestContext;
protected final SecurityContext securityContext;
protected final RequestFacade requestFacade = new RequestFacade();
protected final ResponseFacade responseFacade = new ResponseFacade();
protected KeycloakSecurityContext keycloakSecurityContext;
protected boolean responseFinished;
public JaxrsHttpFacade(ContainerRequestContext containerRequestContext, SecurityContext securityContext) {
this.requestContext = containerRequestContext;
this.securityContext = securityContext;
}
protected class RequestFacade implements HttpFacade.Request {
@Override
public String getMethod() {
return requestContext.getMethod();
}
@Override
public String getURI() {
return requestContext.getUriInfo().getRequestUri().toString();
}
@Override
public boolean isSecure() {
return securityContext.isSecure();
}
@Override
public String getQueryParamValue(String param) {
MultivaluedMap<String, String> queryParams = requestContext.getUriInfo().getQueryParameters();
if (queryParams == null)
return null;
return queryParams.getFirst(param);
}
@Override
public Cookie getCookie(String cookieName) {
Map<String, javax.ws.rs.core.Cookie> cookies = requestContext.getCookies();
if (cookies == null)
return null;
javax.ws.rs.core.Cookie cookie = cookies.get(cookieName);
if (cookie == null)
return null;
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
}
@Override
public String getHeader(String name) {
return requestContext.getHeaderString(name);
}
@Override
public List<String> getHeaders(String name) {
MultivaluedMap<String, String> headers = requestContext.getHeaders();
return (headers == null) ? null : headers.get(name);
}
@Override
public InputStream getInputStream() {
return requestContext.getEntityStream();
}
@Override
public String getRemoteAddr() {
// TODO: implement properly
return HostUtils.getIpAddress();
}
}
protected class ResponseFacade implements HttpFacade.Response {
private javax.ws.rs.core.Response.ResponseBuilder responseBuilder = javax.ws.rs.core.Response.status(204);
@Override
public void setStatus(int status) {
responseBuilder.status(status);
}
@Override
public void addHeader(String name, String value) {
responseBuilder.header(name, value);
}
@Override
public void setHeader(String name, String value) {
responseBuilder.header(name, value);
}
@Override
public void resetCookie(String name, String path) {
// For now doesn't need to be supported
throw new IllegalStateException("Not supported yet");
}
@Override
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
// For now doesn't need to be supported
throw new IllegalStateException("Not supported yet");
}
@Override
public OutputStream getOutputStream() {
// For now doesn't need to be supported
throw new IllegalStateException("Not supported yet");
}
@Override
public void sendError(int code, String message) {
javax.ws.rs.core.Response response = responseBuilder.status(code).entity(message).build();
requestContext.abortWith(response);
responseFinished = true;
}
@Override
public void end() {
javax.ws.rs.core.Response response = responseBuilder.build();
requestContext.abortWith(response);
responseFinished = true;
}
}
@Override
public KeycloakSecurityContext getSecurityContext() {
return keycloakSecurityContext;
}
public void setSecurityContext(KeycloakSecurityContext securityContext) {
this.keycloakSecurityContext = securityContext;
}
@Override
public Request getRequest() {
return requestFacade;
}
@Override
public Response getResponse() {
return responseFacade;
}
@Override
public X509Certificate[] getCertificateChain() {
throw new IllegalStateException("Not supported yet");
}
public boolean isResponseFinished() {
return responseFinished;
}
}

View file

@ -1,6 +1,5 @@
package org.keycloak.jaxrs; package org.keycloak.jaxrs;
import org.jboss.logging.Logger;
import org.keycloak.AbstractOAuthClient; import org.keycloak.AbstractOAuthClient;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
@ -18,6 +17,7 @@ import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger;
/** /**
* Helper code to obtain oauth access tokens via browser redirects * Helper code to obtain oauth access tokens via browser redirects
@ -26,7 +26,7 @@ import java.util.Map;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class JaxrsOAuthClient extends AbstractOAuthClient { public class JaxrsOAuthClient extends AbstractOAuthClient {
protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class); private final static Logger logger = Logger.getLogger("" + JaxrsOAuthClient.class);
protected Client client; protected Client client;
/** /**
@ -80,8 +80,8 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
URI url = uriBuilder.build(); URI url = uriBuilder.build();
NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true); NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
logger.debug("NewCookie: " + cookie.toString()); logger.fine("NewCookie: " + cookie.toString());
logger.debug("Oauth Redirect to: " + url); logger.fine("Oauth Redirect to: " + url);
return Response.status(302) return Response.status(302)
.location(url) .location(url)
.cookie(cookie).build(); .cookie(cookie).build();

View file

@ -0,0 +1,78 @@
package org.keycloak.jaxrs;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.logging.Logger;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.PreMatching;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.constants.GenericConstants;
import org.osgi.framework.BundleContext;
/**
* Variant of JaxrsBearerTokenFilter, which can be used to properly use resources from current osgi bundle
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@PreMatching
@Priority(Priorities.AUTHENTICATION)
public class OsgiJaxrsBearerTokenFilterImpl extends JaxrsBearerTokenFilterImpl {
private final static Logger log = Logger.getLogger("" + JaxrsBearerTokenFilterImpl.class);
private BundleContext bundleContext;
public BundleContext getBundleContext() {
return bundleContext;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
attemptStart();
}
@Override
protected boolean isInitialized() {
return super.isInitialized() && bundleContext != null;
}
@Override
protected Class<? extends KeycloakConfigResolver> loadResolverClass() {
String resolverClass = getKeycloakConfigResolverClass();
try {
return (Class<? extends KeycloakConfigResolver>) bundleContext.getBundle().loadClass(resolverClass);
} catch (ClassNotFoundException cnfe) {
log.warning("Not able to find class from bundleContext. Fallback to current classloader");
return super.loadResolverClass();
}
}
@Override
protected InputStream loadKeycloakConfigFile() {
String keycloakConfigFile = getKeycloakConfigFile();
if (keycloakConfigFile.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
// Load from classpath of current bundle
String classPathLocation = keycloakConfigFile.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
log.fine("Loading config from classpath on location: " + classPathLocation);
URL cfgUrl = bundleContext.getBundle().getResource(classPathLocation);
if (cfgUrl == null) {
log.warning("Not able to find configFile from bundleContext. Fallback to current classloader");
return super.loadKeycloakConfigFile();
}
try {
return cfgUrl.openStream();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
} else {
return super.loadKeycloakConfigFile();
}
}
}

View file

@ -12,7 +12,6 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterConstants;
import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.AdapterUtils;
@ -26,6 +25,7 @@ import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement; import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler; import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.adapters.config.AdapterConfig;

View file

@ -1,7 +1,7 @@
package org.keycloak.servlet; package org.keycloak.servlet;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.keycloak.ServiceUrlConstants; import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.adapters.HttpClientBuilder; import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.enums.RelativeUrlsUsed; import org.keycloak.enums.RelativeUrlsUsed;
import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.adapters.config.AdapterConfig;

View file

@ -12,7 +12,7 @@ import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig; import org.apache.catalina.deploy.LoginConfig;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AuthChallenge; import org.keycloak.adapters.AuthChallenge;

View file

@ -32,7 +32,7 @@ import io.undertow.servlet.api.LoginConfig;
import io.undertow.servlet.api.ServletSessionConfig; import io.undertow.servlet.api.ServletSessionConfig;
import io.undertow.servlet.util.ImmediateInstanceHandle; import io.undertow.servlet.util.ImmediateInstanceHandle;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder; import org.keycloak.adapters.KeycloakDeploymentBuilder;

View file

@ -14,7 +14,7 @@ import org.keycloak.Config;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.RSATokenVerifier; import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
@ -41,7 +41,6 @@ import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.Urls; import org.keycloak.services.resources.flows.Urls;
import org.keycloak.util.Base64Url;
import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.StreamUtil; import org.keycloak.util.StreamUtil;
import org.keycloak.util.UriUtils; import org.keycloak.util.UriUtils;

View file

@ -6,7 +6,7 @@ import org.jboss.resteasy.client.ClientRequest;
import org.jboss.resteasy.client.ClientResponse; import org.jboss.resteasy.client.ClientResponse;
import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor; import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
import org.keycloak.TokenIdGenerator; import org.keycloak.TokenIdGenerator;
import org.keycloak.adapters.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.models.ApplicationModel; import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;

View file

@ -1,6 +1,5 @@
package org.keycloak.services.resources; package org.keycloak.services.resources;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -22,7 +21,7 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.UnauthorizedException; import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.ClientConnection; import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;

View file

@ -124,6 +124,11 @@
<artifactId>keycloak-undertow-adapter</artifactId> <artifactId>keycloak-undertow-adapter</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>federation-properties-example</artifactId> <artifactId>federation-properties-example</artifactId>

View file

@ -26,6 +26,7 @@ package org.keycloak.testsuite;
*/ */
public class Constants { public class Constants {
public static String AUTH_SERVER_ROOT = "http://localhost:8081/auth"; public static String SERVER_ROOT = "http://localhost:8081";
public static String AUTH_SERVER_ROOT = SERVER_ROOT + "/auth";
} }

View file

@ -28,7 +28,7 @@ import org.junit.Test;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.Version; import org.keycloak.Version;
import org.keycloak.adapters.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.models.ApplicationModel; import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;

View file

@ -8,7 +8,7 @@ import org.junit.Assert;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.adapters.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OpenIDConnectService; import org.keycloak.protocol.oidc.OpenIDConnectService;

View file

@ -0,0 +1,345 @@
package org.keycloak.testsuite.jaxrs;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
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 org.apache.http.impl.client.DefaultHttpClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.keycloak.OAuth2Constants;
import org.keycloak.TokenIdGenerator;
import org.keycloak.adapters.CorsHeaders;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.Constants;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.keycloak.util.Time;
import org.openqa.selenium.WebDriver;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JaxrsFilterTest {
private static final String JAXRS_APP_URL = Constants.SERVER_ROOT + "/jaxrs-simple/res";
private static final String JAXRS_APP_PUSN_NOT_BEFORE_URL = Constants.SERVER_ROOT + "/jaxrs-simple/" + AdapterConstants.K_PUSH_NOT_BEFORE;
public static final String CONFIG_FILE_INIT_PARAM = "config-file";
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ApplicationModel app = appRealm.addApplication("jaxrs-app");
app.setEnabled(true);
RoleModel role = app.addRole("jaxrs-app-user");
UserModel user = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
user.grantRole(role);
JaxrsFilterTest.appRealm = appRealm;
}
});
@ClassRule
public static ExternalResource clientRule = new ExternalResource() {
@Override
protected void before() throws Throwable {
DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder().build();
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
client = new ResteasyClientBuilder().httpEngine(engine).build();
}
@Override
protected void after() {
client.close();
}
};
private static ResteasyClient client;
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
protected WebDriver driver;
// Used for signing admin action
protected static RealmModel appRealm;
@Test
public void testBasic() {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
Map<String,String> initParams = new TreeMap<String,String>();
initParams.put(CONFIG_FILE_INIT_PARAM, "classpath:jaxrs-test/jaxrs-keycloak.json");
keycloakRule.deployJaxrsApplication("JaxrsSimpleApp", "/jaxrs-simple", JaxrsTestApplication.class, initParams);
}
});
// Send GET request without token, it should fail
Response getResp = client.target(JAXRS_APP_URL).request().get();
Assert.assertEquals(getResp.getStatus(), 401);
getResp.close();
// Send POST request without token, it should fail
Response postResp = client.target(JAXRS_APP_URL).request().post(Entity.form(new Form()));
Assert.assertEquals(postResp.getStatus(), 401);
postResp.close();
// Retrieve token
OAuthClient.AccessTokenResponse accessTokenResp = retrieveAccessToken();
String authHeader = "Bearer " + accessTokenResp.getAccessToken();
// Send GET request with token and assert it's passing
JaxrsTestResource.SimpleRepresentation getRep = client.target(JAXRS_APP_URL).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.get(JaxrsTestResource.SimpleRepresentation.class);
Assert.assertEquals("get", getRep.getMethod());
Assert.assertTrue(getRep.getHasUserRole());
Assert.assertFalse(getRep.getHasAdminRole());
Assert.assertFalse(getRep.getHasJaxrsAppRole());
// Assert that principal is ID of user (should be in UUID format)
UUID.fromString(getRep.getPrincipal());
// Send POST request with token and assert it's passing
JaxrsTestResource.SimpleRepresentation postRep = client.target(JAXRS_APP_URL).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.post(Entity.form(new Form()), JaxrsTestResource.SimpleRepresentation.class);
Assert.assertEquals("post", postRep.getMethod());
Assert.assertEquals(getRep.getPrincipal(), postRep.getPrincipal());
}
@Test
public void testRelativeUriAndPublicKey() {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
Map<String,String> initParams = new TreeMap<String,String>();
initParams.put(CONFIG_FILE_INIT_PARAM, "classpath:jaxrs-test/jaxrs-keycloak-relative.json");
keycloakRule.deployJaxrsApplication("JaxrsSimpleApp", "/jaxrs-simple", JaxrsTestApplication.class, initParams);
}
});
// Send GET request without token, it should fail
Response getResp = client.target(JAXRS_APP_URL).request().get();
Assert.assertEquals(getResp.getStatus(), 401);
getResp.close();
// Retrieve token
OAuthClient.AccessTokenResponse accessTokenResp = retrieveAccessToken();
String authHeader = "Bearer " + accessTokenResp.getAccessToken();
// Send GET request with token and assert it's passing
JaxrsTestResource.SimpleRepresentation getRep = client.target(JAXRS_APP_URL).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.get(JaxrsTestResource.SimpleRepresentation.class);
Assert.assertEquals("get", getRep.getMethod());
Assert.assertTrue(getRep.getHasUserRole());
Assert.assertFalse(getRep.getHasAdminRole());
Assert.assertFalse(getRep.getHasJaxrsAppRole());
// Assert that principal is ID of user (should be in UUID format)
UUID.fromString(getRep.getPrincipal());
}
@Test
public void testSslRequired() {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
Map<String, String> initParams = new TreeMap<String, String>();
initParams.put(CONFIG_FILE_INIT_PARAM, "classpath:jaxrs-test/jaxrs-keycloak-ssl.json");
keycloakRule.deployJaxrsApplication("JaxrsSimpleApp", "/jaxrs-simple", JaxrsTestApplication.class, initParams);
}
});
// Retrieve token
OAuthClient.AccessTokenResponse accessTokenResp = retrieveAccessToken();
String authHeader = "Bearer " + accessTokenResp.getAccessToken();
// Fail due to non-https
Response getResp = client.target(JAXRS_APP_URL).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.get();
Assert.assertEquals(getResp.getStatus(), 403);
getResp.close();
}
@Test
public void testResourceRoleMappings() {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
Map<String, String> initParams = new TreeMap<String, String>();
initParams.put(CONFIG_FILE_INIT_PARAM, "classpath:jaxrs-test/jaxrs-keycloak-resource-mappings.json");
keycloakRule.deployJaxrsApplication("JaxrsSimpleApp", "/jaxrs-simple", JaxrsTestApplication.class, initParams);
}
});
// Retrieve token
OAuthClient.AccessTokenResponse accessTokenResp = retrieveAccessToken();
String authHeader = "Bearer " + accessTokenResp.getAccessToken();
// Send GET request with token and assert it's passing
JaxrsTestResource.SimpleRepresentation getRep = client.target(JAXRS_APP_URL).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.get(JaxrsTestResource.SimpleRepresentation.class);
Assert.assertEquals("get", getRep.getMethod());
// principal is username
Assert.assertEquals("test-user@localhost", getRep.getPrincipal());
// User is in jaxrs-app-user role thanks to use-resource-role-mappings
Assert.assertFalse(getRep.getHasUserRole());
Assert.assertFalse(getRep.getHasAdminRole());
Assert.assertTrue(getRep.getHasJaxrsAppRole());
}
@Test
public void testCors() {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
Map<String,String> initParams = new TreeMap<String,String>();
initParams.put(CONFIG_FILE_INIT_PARAM, "classpath:jaxrs-test/jaxrs-keycloak.json");
keycloakRule.deployJaxrsApplication("JaxrsSimpleApp", "/jaxrs-simple", JaxrsTestApplication.class, initParams);
}
});
// Send OPTIONS request
Response optionsResp = client.target(JAXRS_APP_URL).request()
.header(CorsHeaders.ORIGIN, "http://localhost:8081")
.options();
Assert.assertEquals("true", optionsResp.getHeaderString(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS));
Assert.assertEquals("http://localhost:8081", optionsResp.getHeaderString(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
optionsResp.close();
// Retrieve token
OAuthClient.AccessTokenResponse accessTokenResp = retrieveAccessToken();
String authHeader = "Bearer " + accessTokenResp.getAccessToken();
// Send GET request with token but bad origin
Response badOriginResp = client.target(JAXRS_APP_URL).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.header(CorsHeaders.ORIGIN, "http://evil.org")
.get();
Assert.assertEquals(403, badOriginResp.getStatus());
badOriginResp.close();
// Send GET request with token and good origin
Response goodResp = client.target(JAXRS_APP_URL).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.header(CorsHeaders.ORIGIN, "http://localhost:8081")
.get();
Assert.assertEquals(200, goodResp.getStatus());
Assert.assertEquals("true", optionsResp.getHeaderString(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS));
Assert.assertEquals("http://localhost:8081", optionsResp.getHeaderString(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
JaxrsTestResource.SimpleRepresentation getRep = goodResp.readEntity(JaxrsTestResource.SimpleRepresentation.class);
Assert.assertEquals("get", getRep.getMethod());
goodResp.close();
}
@Test
public void testPushNotBefore() {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
Map<String,String> initParams = new TreeMap<String,String>();
initParams.put(CONFIG_FILE_INIT_PARAM, "classpath:jaxrs-test/jaxrs-keycloak.json");
keycloakRule.deployJaxrsApplication("JaxrsSimpleApp", "/jaxrs-simple", JaxrsTestApplication.class, initParams);
}
});
// Retrieve token
OAuthClient.AccessTokenResponse accessTokenResp = retrieveAccessToken();
String authHeader = "Bearer " + accessTokenResp.getAccessToken();
// Send GET request with token and assert it's passing
JaxrsTestResource.SimpleRepresentation getRep = client.target(JAXRS_APP_URL).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.get(JaxrsTestResource.SimpleRepresentation.class);
Assert.assertEquals("get", getRep.getMethod());
Assert.assertTrue(getRep.getHasUserRole());
// Push new notBefore now TODO: should use admin console (admin client) instead..
int currentTime = Time.currentTime();
PushNotBeforeAction action = new PushNotBeforeAction(TokenIdGenerator.generateId(), currentTime + 30, "jaxrs-app", currentTime + 1);
String token = new TokenManager().encodeToken(appRealm, action);
Response response = client.target(JAXRS_APP_PUSN_NOT_BEFORE_URL).request().post(Entity.text(token));
Assert.assertEquals(204, response.getStatus());
response.close();
// Assert that previous token shouldn't pass anymore
response = client.target(JAXRS_APP_URL).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.get();
Assert.assertEquals(401, response.getStatus());
response.close();
}
// @Test
public void testCxfExample() {
String uri = "http://localhost:9000/customerservice/customers/123";
Response resp = client.target(uri).request()
.get();
Assert.assertEquals(resp.getStatus(), 401);
resp.close();
// Retrieve token
OAuthClient.AccessTokenResponse accessTokenResp = retrieveAccessToken();
String authHeader = "Bearer " + accessTokenResp.getAccessToken();
String resp2 = client.target(uri).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.get(String.class);
System.out.println(resp2);
}
private OAuthClient.AccessTokenResponse retrieveAccessToken() {
OAuthClient oauth = new OAuthClient(driver);
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
Assert.assertEquals(200, response.getStatusCode());
return response;
}
}

View file

@ -0,0 +1,38 @@
package org.keycloak.testsuite.jaxrs;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import org.keycloak.jaxrs.JaxrsBearerTokenFilterImpl;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JaxrsTestApplication extends Application {
protected Set<Class<?>> classes = new HashSet<Class<?>>();
protected Set<Object> singletons = new HashSet<Object>();
public JaxrsTestApplication(@Context ServletContext context) throws Exception {
singletons.add(new JaxrsTestResource());
String configFile = context.getInitParameter(JaxrsFilterTest.CONFIG_FILE_INIT_PARAM);
JaxrsBearerTokenFilterImpl filter = new JaxrsBearerTokenFilterImpl();
filter.setKeycloakConfigFile(configFile);
singletons.add(filter);
}
@Override
public Set<Class<?>> getClasses() {
return classes;
}
@Override
public Set<Object> getSingletons() {
return singletons;
}
}

View file

@ -0,0 +1,93 @@
package org.keycloak.testsuite.jaxrs;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@Path("res")
public class JaxrsTestResource {
@Context
protected SecurityContext securityContext;
@GET
@Produces("application/json")
public SimpleRepresentation get() {
return new SimpleRepresentation("get", securityContext.getUserPrincipal().getName(), securityContext.isUserInRole("user"),
securityContext.isUserInRole("admin"), securityContext.isUserInRole("jaxrs-app-user"));
}
@POST
@Produces("application/json")
public SimpleRepresentation post() {
return new SimpleRepresentation("post", securityContext.getUserPrincipal().getName(), securityContext.isUserInRole("user"),
securityContext.isUserInRole("admin"), securityContext.isUserInRole("jaxrs-app-user"));
}
public static class SimpleRepresentation {
private String method;
private String principal;
private Boolean hasUserRole;
private Boolean hasAdminRole;
private Boolean hasJaxrsAppRole;
public SimpleRepresentation() {
}
public SimpleRepresentation(String method, String principal, boolean hasUserRole, boolean hasAdminRole,
boolean hasJaxrsAppRole) {
this.method = method;
this.principal = principal;
this.hasUserRole = hasUserRole;
this.hasAdminRole = hasAdminRole;
this.hasJaxrsAppRole = hasJaxrsAppRole;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getPrincipal() {
return principal;
}
public void setPrincipal(String principal) {
this.principal = principal;
}
public Boolean getHasUserRole() {
return hasUserRole;
}
public void setHasUserRole(Boolean hasUserRole) {
this.hasUserRole = hasUserRole;
}
public Boolean getHasAdminRole() {
return hasAdminRole;
}
public void setHasAdminRole(Boolean hasAdminRole) {
this.hasAdminRole = hasAdminRole;
}
public Boolean getHasJaxrsAppRole() {
return hasJaxrsAppRole;
}
public void setHasJaxrsAppRole(Boolean hasJaxrsAppRole) {
this.hasJaxrsAppRole = hasJaxrsAppRole;
}
}
}

View file

@ -1,10 +1,14 @@
package org.keycloak.testsuite.rule; package org.keycloak.testsuite.rule;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo;
import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.LoginConfig;
import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityConstraint;
import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletInfo;
import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.api.WebResourceCollection;
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.junit.rules.ExternalResource; import org.junit.rules.ExternalResource;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -14,16 +18,23 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.filters.ClientConnectionFilter;
import org.keycloak.services.filters.KeycloakSessionServletFilter;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.Retry; import org.keycloak.testsuite.Retry;
import org.keycloak.testutils.KeycloakServer; import org.keycloak.testutils.KeycloakServer;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import javax.servlet.DispatcherType;
import javax.servlet.Servlet; import javax.servlet.Servlet;
import javax.ws.rs.core.Application;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.Socket; import java.net.Socket;
import java.util.Map;
import org.keycloak.adapters.KeycloakConfigResolver; import org.keycloak.adapters.KeycloakConfigResolver;
/** /**
@ -158,6 +169,22 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
server.getServer().deploy(di); server.getServer().deploy(di);
} }
public void deployJaxrsApplication(String name, String contextPath, Class<? extends Application> applicationClass, Map<String,String> initParams) {
ResteasyDeployment deployment = new ResteasyDeployment();
deployment.setApplicationClass(applicationClass.getName());
DeploymentInfo di = server.getServer().undertowDeployment(deployment, "");
di.setClassLoader(getClass().getClassLoader());
di.setContextPath(contextPath);
di.setDeploymentName(name);
for (Map.Entry<String,String> param : initParams.entrySet()) {
di.addInitParameter(param.getKey(), param.getValue());
}
server.getServer().deploy(di);
}
@Override @Override
protected void after() { protected void after() {
removeTestRealms(); removeTestRealms();

View file

@ -0,0 +1,7 @@
{
"realm": "test",
"resource": "jaxrs-app",
"auth-server-url": "/auth",
"ssl-required" : "external",
"bearer-only": true
}

View file

@ -0,0 +1,10 @@
{
"realm": "test",
"resource": "jaxrs-app",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8081/auth",
"ssl-required" : "external",
"bearer-only": true,
"principal-attribute": "preferred_username",
"use-resource-role-mappings": true
}

View file

@ -0,0 +1,8 @@
{
"realm": "test",
"resource": "jaxrs-app",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8081/auth",
"ssl-required" : "all",
"bearer-only": true
}

View file

@ -0,0 +1,9 @@
{
"realm": "test",
"resource": "jaxrs-app",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8081/auth",
"ssl-required" : "external",
"bearer-only": true,
"enable-cors": true
}

View file

@ -4,7 +4,7 @@ import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration; import freemarker.template.Configuration;
import freemarker.template.Template; import freemarker.template.Template;
import freemarker.template.TemplateException; import freemarker.template.TemplateException;
import org.keycloak.adapters.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
import org.keycloak.util.Time; import org.keycloak.util.Time;

View file

@ -36,7 +36,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OpenIDConnectService; import org.keycloak.protocol.oidc.OpenIDConnectService;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule; import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebResource;
@ -48,18 +47,12 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.security.Principal; import java.security.Principal;
import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
/** /**