KEYCLOAK-861 Support basic authentication against user credentials managed by KeyCloak.

This commit is contained in:
objectiser committed 2014-12-01 11:43:19 +00:00
1 parent 3e8495f435
commit 8e01f8ecf1
31 files changed
+644 -28

No files matched your search

+5
View File
@@ -41,6 +41,11 @@
<artifactId>jackson-xc</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.iharder</groupId>
<artifactId>base64</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -237,6 +237,16 @@ public class AdapterDeploymentContext {
delegate.setBearerOnly(bearerOnly);
}
@Override
public boolean isEnableBasicAuth() {
return delegate.isEnableBasicAuth();
}
@Override
public void setEnableBasicAuth(boolean enableBasicAuth) {
delegate.setEnableBasicAuth(enableBasicAuth);
}
@Override
public boolean isPublicClient() {
return delegate.isPublicClient();
@@ -0,0 +1,112 @@
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.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.KeycloakUriBuilder;
import java.util.List;
/**
* Basic auth request authenticator.
*/
public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticator {
protected Logger log = Logger.getLogger(BasicAuthRequestAuthenticator.class);
public BasicAuthRequestAuthenticator(KeycloakDeployment deployment) {
super(deployment);
}
public AuthOutcome authenticate(HttpFacade exchange) {
List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
if (authHeaders == null || authHeaders.size() == 0) {
challenge = challengeResponse(exchange, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
tokenString = null;
for (String authHeader : authHeaders) {
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) continue;
if (!split[0].equalsIgnoreCase("Basic")) continue;
tokenString = split[1];
}
if (tokenString == null) {
challenge = challengeResponse(exchange, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
AccessTokenResponse atr=null;
try {
String userpw=new String(net.iharder.Base64.decode(tokenString));
String[] parts=userpw.split(":");
atr = getToken(parts[0], parts[1]);
} catch (Exception e) {
log.debug("Failed to obtain token", e);
challenge = challengeResponse(exchange, "no_token", e.getMessage());
return AuthOutcome.FAILED;
}
return authenticateToken(exchange, atr.getToken());
}
private AccessTokenResponse getToken(String username, String password) throws Exception {
AccessTokenResponse tokenResponse=null;
HttpClient client = new HttpClientBuilder().disableTrustManager().build();
try {
HttpPost post = new HttpPost(
KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl())
.path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build(deployment.getRealm()));
java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
formparams.add(new BasicNameValuePair("username", username));
formparams.add(new BasicNameValuePair("password", password));
if (deployment.isPublicClient()) {
formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, deployment.getResourceName()));
} else {
String authorization = BasicAuthHelper.createHeader(deployment.getResourceName(),
deployment.getResourceCredentials().get("secret"));
post.setHeader("Authorization", authorization);
}
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
throw new java.io.IOException("Bad status: " + status);
}
if (entity == null) {
throw new java.io.IOException("No Entity");
}
java.io.InputStream is = entity.getContent();
try {
tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
} finally {
try {
is.close();
} catch (java.io.IOException ignored) { }
}
} finally {
client.getConnectionManager().shutdown();
}
return (tokenResponse);
}
}
@@ -59,6 +59,10 @@ public class BearerTokenRequestAuthenticator {
return AuthOutcome.NOT_ATTEMPTED;
}
return (authenticateToken(exchange, tokenString));
}
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
try {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealm());
} catch (VerificationException e) {
@@ -38,6 +38,7 @@ public class KeycloakDeployment {
protected String resourceName;
protected boolean bearerOnly;
protected boolean enableBasicAuth;
protected boolean publicClient;
protected Map<String, String> resourceCredentials = new HashMap<String, String>();
protected HttpClient client;
@@ -199,6 +200,14 @@ public class KeycloakDeployment {
this.bearerOnly = bearerOnly;
}
public boolean isEnableBasicAuth() {
return enableBasicAuth;
}
public void setEnableBasicAuth(boolean enableBasicAuth) {
this.enableBasicAuth = enableBasicAuth;
}
public boolean isPublicClient() {
return publicClient;
}
@@ -67,6 +67,7 @@ public class KeycloakDeploymentBuilder {
}
deployment.setBearerOnly(adapterConfig.isBearerOnly());
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
@@ -36,10 +36,12 @@ public abstract class RequestAuthenticator {
if (log.isTraceEnabled()) {
log.trace("--> authenticate()");
}
BearerTokenRequestAuthenticator bearer = createBearerTokenAuthenticator();
if (log.isTraceEnabled()) {
log.trace("try bearer");
}
AuthOutcome outcome = bearer.authenticate(facade);
if (outcome == AuthOutcome.FAILED) {
challenge = bearer.getChallenge();
@@ -47,7 +49,7 @@ public abstract class RequestAuthenticator {
return AuthOutcome.FAILED;
} else if (outcome == AuthOutcome.AUTHENTICATED) {
if (verifySSL()) return AuthOutcome.FAILED;
completeAuthentication(bearer);
completeAuthentication(bearer, "KEYCLOAK");
log.debug("Bearer AUTHENTICATED");
return AuthOutcome.AUTHENTICATED;
} else if (deployment.isBearerOnly()) {
@@ -56,6 +58,24 @@ public abstract class RequestAuthenticator {
return AuthOutcome.NOT_ATTEMPTED;
}
if (deployment.isEnableBasicAuth()) {
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
if (log.isTraceEnabled()) {
log.trace("try basic auth");
}
outcome = basicAuth.authenticate(facade);
if (outcome == AuthOutcome.FAILED) {
challenge = basicAuth.getChallenge();
log.debug("BasicAuth FAILED");
return AuthOutcome.FAILED;
} else if (outcome == AuthOutcome.AUTHENTICATED) {
log.debug("BasicAuth AUTHENTICATED");
completeAuthentication(basicAuth, "BASIC");
return AuthOutcome.AUTHENTICATED;
}
}
if (log.isTraceEnabled()) {
log.trace("try oauth");
}
@@ -104,6 +124,10 @@ public abstract class RequestAuthenticator {
return new BearerTokenRequestAuthenticator(deployment);
}
protected BasicAuthRequestAuthenticator createBasicAuthAuthenticator() {
return new BasicAuthRequestAuthenticator(deployment);
}
protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
@@ -111,13 +135,13 @@ public abstract class RequestAuthenticator {
}
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
protected abstract String getHttpSessionId(boolean create);
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) {
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer, String method) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, bearer.getToken()), session);
completeBearerAuthentication(principal);
completeBearerAuthentication(principal, method);
}
}
@@ -84,7 +84,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
if (log.isDebugEnabled()) {
@@ -92,7 +92,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
}
Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext);
request.setUserPrincipal(generalPrincipal);
request.setAuthType("KEYCLOAK");
request.setAuthType(method);
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
}
@@ -6,6 +6,7 @@ import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.AuthOutcome;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.BasicAuthRequestAuthenticator;
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
@@ -188,10 +189,16 @@ public class JaxrsBearerTokenFilterImpl implements JaxrsBearerTokenFilter {
}
protected void bearerAuthentication(JaxrsHttpFacade facade, ContainerRequestContext request, KeycloakDeployment resolvedDeployment) {
BearerTokenRequestAuthenticator bearer = new BearerTokenRequestAuthenticator(resolvedDeployment);
AuthOutcome outcome = bearer.authenticate(facade);
BearerTokenRequestAuthenticator authenticator = new BearerTokenRequestAuthenticator(resolvedDeployment);
AuthOutcome outcome = authenticator.authenticate(facade);
if (outcome == AuthOutcome.NOT_ATTEMPTED && resolvedDeployment.isEnableBasicAuth()) {
authenticator = new BasicAuthRequestAuthenticator(resolvedDeployment);
outcome = authenticator.authenticate(facade);
}
if (outcome == AuthOutcome.FAILED || outcome == AuthOutcome.NOT_ATTEMPTED) {
AuthChallenge challenge = bearer.getChallenge();
AuthChallenge challenge = authenticator.getChallenge();
log.fine("Authentication outcome: " + outcome);
boolean challengeSent = challenge.challenge(facade);
if (!challengeSent) {
@@ -210,7 +217,7 @@ public class JaxrsBearerTokenFilterImpl implements JaxrsBearerTokenFilter {
}
}
propagateSecurityContext(facade, request, resolvedDeployment, bearer);
propagateSecurityContext(facade, request, resolvedDeployment, authenticator);
handleAuthActions(facade, resolvedDeployment);
}
@@ -79,7 +79,7 @@ public abstract class AbstractJettyRequestAuthenticator extends RequestAuthentic
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
this.principal = principal;
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
@@ -65,6 +65,12 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition ENABLE_BASIC_AUTH =
new SimpleAttributeDefinitionBuilder("enable-basic-auth", ModelType.BOOLEAN, true)
.setXmlName("enable-basic-auth")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition PUBLIC_CLIENT =
new SimpleAttributeDefinitionBuilder("public-client", ModelType.BOOLEAN, true)
.setXmlName("public-client")
@@ -78,6 +84,7 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE);
DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS);
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
}
@@ -68,6 +68,7 @@ keycloak.secure-deployment.resource=Application name
keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
keycloak.secure-deployment.credentials=Adapter credentials
keycloak.secure-deployment.bearer-only=Bearer Token Auth only
keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
keycloak.secure-deployment.public-client=Public client
keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
keycloak.secure-deployment.client-keystore=n/a
@@ -86,6 +86,7 @@
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
@@ -86,7 +86,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
if (log.isLoggable(Level.FINE)) {
@@ -94,7 +94,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
}
Principal generalPrincipal = principalFactory.createPrincipal(request.getContext().getRealm(), principal, roles, securityContext);
request.setUserPrincipal(generalPrincipal);
request.setAuthType("KEYCLOAK");
request.setAuthType(method);
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
}
@@ -20,6 +20,7 @@ import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.session.Session;
import io.undertow.util.Sessions;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.HttpFacade;
@@ -69,9 +70,9 @@ public abstract class AbstractUndertowRequestAuthenticator extends RequestAuthen
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
KeycloakUndertowAccount account = createAccount(principal);
securityContext.authenticationComplete(account, "KEYCLOAK", false);
securityContext.authenticationComplete(account, method, false);
propagateKeycloakContext(account);
}