Remove JAAS login modules
Closes #28789 Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
parent
eae20c76bd
commit
cca660067a
11 changed files with 3 additions and 908 deletions
|
@ -46,8 +46,6 @@
|
||||||
org.apache.http.impl.cookie.*;version=${apache.httpcomponents.fuse.version},
|
org.apache.http.impl.cookie.*;version=${apache.httpcomponents.fuse.version},
|
||||||
org.apache.http.impl.execchain.*;version=${apache.httpcomponents.fuse.version},
|
org.apache.http.impl.execchain.*;version=${apache.httpcomponents.fuse.version},
|
||||||
org.apache.http.*;version=${apache.httpcomponents.httpcore.fuse.version},
|
org.apache.http.*;version=${apache.httpcomponents.httpcore.fuse.version},
|
||||||
org.apache.karaf.jaas.boot.principal;resolution:=optional,
|
|
||||||
org.apache.karaf.jaas.modules;resolution:=optional,
|
|
||||||
*;resolution:=optional
|
*;resolution:=optional
|
||||||
</keycloak.osgi.import>
|
</keycloak.osgi.import>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
|
@ -1,261 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.jaas;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
import org.keycloak.common.util.FindFile;
|
|
||||||
import org.keycloak.common.util.reflections.Reflections;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
|
|
||||||
import javax.security.auth.Subject;
|
|
||||||
import javax.security.auth.callback.Callback;
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
|
||||||
import javax.security.auth.callback.NameCallback;
|
|
||||||
import javax.security.auth.callback.PasswordCallback;
|
|
||||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
|
||||||
import javax.security.auth.login.LoginException;
|
|
||||||
import javax.security.auth.spi.LoginModule;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
|
||||||
|
|
||||||
public static final String KEYCLOAK_CONFIG_FILE_OPTION = "keycloak-config-file";
|
|
||||||
public static final String ROLE_PRINCIPAL_CLASS_OPTION = "role-principal-class";
|
|
||||||
public static final String PROFILE_RESOURCE = "profile:";
|
|
||||||
protected Subject subject;
|
|
||||||
protected CallbackHandler callbackHandler;
|
|
||||||
protected Auth auth;
|
|
||||||
protected KeycloakDeployment deployment;
|
|
||||||
protected String rolePrincipalClass;
|
|
||||||
|
|
||||||
// This is to avoid parsing keycloak.json file in each request. Key is file location, Value is parsed keycloak deployment
|
|
||||||
private static ConcurrentMap<String, KeycloakDeployment> deployments = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
|
|
||||||
this.subject = subject;
|
|
||||||
this.callbackHandler = callbackHandler;
|
|
||||||
|
|
||||||
String configFile = (String)options.get(KEYCLOAK_CONFIG_FILE_OPTION);
|
|
||||||
rolePrincipalClass = (String)options.get(ROLE_PRINCIPAL_CLASS_OPTION);
|
|
||||||
getLogger().debug("Declared options: " + KEYCLOAK_CONFIG_FILE_OPTION + "=" + configFile + ", " + ROLE_PRINCIPAL_CLASS_OPTION + "=" + rolePrincipalClass);
|
|
||||||
|
|
||||||
if (configFile != null) {
|
|
||||||
deployment = deployments.get(configFile);
|
|
||||||
if (deployment == null) {
|
|
||||||
// lazy init of our deployment
|
|
||||||
deployment = resolveDeployment(configFile);
|
|
||||||
deployments.putIfAbsent(configFile, deployment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected KeycloakDeployment resolveDeployment(String keycloakConfigFile) {
|
|
||||||
try {
|
|
||||||
InputStream is = null;
|
|
||||||
if (keycloakConfigFile.startsWith(PROFILE_RESOURCE)) {
|
|
||||||
try {
|
|
||||||
is = new URL(keycloakConfigFile).openStream();
|
|
||||||
} catch (MalformedURLException mfue) {
|
|
||||||
throw new RuntimeException(mfue);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new RuntimeException(ioe);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
is = FindFile.findFile(keycloakConfigFile);
|
|
||||||
}
|
|
||||||
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
|
|
||||||
return kd;
|
|
||||||
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean login() throws LoginException {
|
|
||||||
// get username and password
|
|
||||||
Callback[] callbacks = new Callback[2];
|
|
||||||
callbacks[0] = new NameCallback("username");
|
|
||||||
callbacks[1] = new PasswordCallback("password", false);
|
|
||||||
|
|
||||||
try {
|
|
||||||
callbackHandler.handle(callbacks);
|
|
||||||
String username = ((NameCallback) callbacks[0]).getName();
|
|
||||||
char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
|
|
||||||
String password = new String(tmpPassword);
|
|
||||||
((PasswordCallback) callbacks[1]).clearPassword();
|
|
||||||
|
|
||||||
Auth auth = doAuth(username, password);
|
|
||||||
if (auth != null) {
|
|
||||||
this.auth = auth;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (UnsupportedCallbackException uce) {
|
|
||||||
getLogger().warn("Error: " + uce.getCallback().toString()
|
|
||||||
+ " not available to gather authentication information from the user");
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
LoginException le = new LoginException(e.toString());
|
|
||||||
le.initCause(e);
|
|
||||||
throw le;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean commit() throws LoginException {
|
|
||||||
if (auth == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.subject.getPrincipals().add(auth.getPrincipal());
|
|
||||||
this.subject.getPrivateCredentials().add(auth.getTokenString());
|
|
||||||
if (auth.getRoles() != null) {
|
|
||||||
for (String roleName : auth.getRoles()) {
|
|
||||||
Principal rolePrinc = createRolePrincipal(roleName);
|
|
||||||
this.subject.getPrincipals().add(rolePrinc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected Principal createRolePrincipal(String roleName) {
|
|
||||||
if (rolePrincipalClass != null && rolePrincipalClass.length() > 0) {
|
|
||||||
try {
|
|
||||||
Class<Principal> clazz = Reflections.classForName(rolePrincipalClass, getClass().getClassLoader());
|
|
||||||
Constructor<Principal> constructor = clazz.getDeclaredConstructor(String.class);
|
|
||||||
return constructor.newInstance(roleName);
|
|
||||||
} catch (Exception e) {
|
|
||||||
getLogger().warn("Unable to create declared roleClass " + rolePrincipalClass + " due to " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to default rolePrincipal class
|
|
||||||
return new RolePrincipal(roleName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean abort() throws LoginException {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean logout() throws LoginException {
|
|
||||||
Set<Principal> principals = new HashSet<Principal>(subject.getPrincipals());
|
|
||||||
for (Principal principal : principals) {
|
|
||||||
if (principal.getClass().equals(KeycloakPrincipal.class) || principal.getClass().equals(RolePrincipal.class)) {
|
|
||||||
subject.getPrincipals().remove(principal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Set<Object> creds = subject.getPrivateCredentials();
|
|
||||||
for (Object cred : creds) {
|
|
||||||
subject.getPrivateCredentials().remove(cred);
|
|
||||||
}
|
|
||||||
subject = null;
|
|
||||||
callbackHandler = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected Auth bearerAuth(String tokenString) throws VerificationException {
|
|
||||||
AccessToken token = AdapterTokenVerifier.verifyToken(tokenString, deployment);
|
|
||||||
return postTokenVerification(tokenString, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after accessToken was verified (including signature, expiration etc)
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
protected Auth postTokenVerification(String tokenString, AccessToken token) {
|
|
||||||
boolean verifyCaller;
|
|
||||||
if (deployment.isUseResourceRoleMappings()) {
|
|
||||||
verifyCaller = token.isVerifyCaller(deployment.getResourceName());
|
|
||||||
} else {
|
|
||||||
verifyCaller = token.isVerifyCaller();
|
|
||||||
}
|
|
||||||
if (verifyCaller) {
|
|
||||||
throw new IllegalStateException("VerifyCaller not supported yet in login module");
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshableKeycloakSecurityContext skSession = new RefreshableKeycloakSecurityContext(deployment, null, tokenString, token, null, null, null);
|
|
||||||
String principalName = AdapterUtils.getPrincipalName(deployment, token);
|
|
||||||
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(principalName, skSession);
|
|
||||||
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(skSession);
|
|
||||||
return new Auth(principal, roles, tokenString);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected abstract Auth doAuth(String username, String password) throws Exception;
|
|
||||||
|
|
||||||
protected abstract Logger getLogger();
|
|
||||||
|
|
||||||
|
|
||||||
public static class Auth {
|
|
||||||
private final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
|
|
||||||
private final Set<String> roles;
|
|
||||||
private final String tokenString;
|
|
||||||
|
|
||||||
public Auth(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, Set<String> roles, String accessToken) {
|
|
||||||
this.principal = principal;
|
|
||||||
this.roles = roles;
|
|
||||||
this.tokenString = accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeycloakPrincipal<RefreshableKeycloakSecurityContext> getPrincipal() {
|
|
||||||
return principal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getRoles() {
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTokenString() {
|
|
||||||
return tokenString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.jaas;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Login module, which allows to authenticate Keycloak access token in environments, which rely on JAAS
|
|
||||||
* <p/>
|
|
||||||
* It expects login based on username and password where username doesn't matter and password is keycloak access token.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class BearerTokenLoginModule extends AbstractKeycloakLoginModule {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(BearerTokenLoginModule.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Auth doAuth(String username, String password) throws VerificationException {
|
|
||||||
// Should do some checking of authenticated username if it's equivalent to passed value?
|
|
||||||
return bearerAuth(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Logger getLogger() {
|
|
||||||
return log;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,193 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.jaas;
|
|
||||||
|
|
||||||
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.adapters.AdapterUtils;
|
|
||||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
|
||||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
import javax.security.auth.Subject;
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
|
||||||
import javax.security.auth.login.LoginException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Login module based on Resource Owner password credentials grant from OAuth2 specs. It's supposed to be used in environments. which
|
|
||||||
* can't rely on HTTP (like SSH authentication for instance). It needs that Direct Grant is enabled on particular realm in Keycloak.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(DirectAccessGrantsLoginModule.class);
|
|
||||||
|
|
||||||
public static final String SCOPE_OPTION = "scope";
|
|
||||||
|
|
||||||
private String refreshToken;
|
|
||||||
private String scope;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
|
|
||||||
super.initialize(subject, callbackHandler, sharedState, options);
|
|
||||||
this.scope = (String)options.get(SCOPE_OPTION);
|
|
||||||
|
|
||||||
// This is used just for logout
|
|
||||||
Iterator<RefreshTokenHolder> iterator = subject.getPrivateCredentials(RefreshTokenHolder.class).iterator();
|
|
||||||
if (iterator.hasNext()) {
|
|
||||||
refreshToken = iterator.next().refreshToken;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Auth doAuth(String username, String password) throws IOException, VerificationException {
|
|
||||||
return directGrantAuth(username, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Logger getLogger() {
|
|
||||||
return log;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Auth directGrantAuth(String username, String password) throws IOException, VerificationException {
|
|
||||||
String authServerBaseUrl = deployment.getAuthServerBaseUrl();
|
|
||||||
HttpPost post = new HttpPost(deployment.getTokenUrl());
|
|
||||||
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
|
||||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
|
|
||||||
formparams.add(new BasicNameValuePair("username", username));
|
|
||||||
formparams.add(new BasicNameValuePair("password", password));
|
|
||||||
|
|
||||||
if (scope != null) {
|
|
||||||
formparams.add(new BasicNameValuePair(OAuth2Constants.SCOPE, scope));
|
|
||||||
}
|
|
||||||
|
|
||||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
|
||||||
|
|
||||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
|
||||||
post.setEntity(form);
|
|
||||||
|
|
||||||
HttpClient client = deployment.getClient();
|
|
||||||
HttpResponse response = client.execute(post);
|
|
||||||
int status = response.getStatusLine().getStatusCode();
|
|
||||||
HttpEntity entity = response.getEntity();
|
|
||||||
if (status != 200) {
|
|
||||||
StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status);
|
|
||||||
if (entity != null) {
|
|
||||||
InputStream is = entity.getContent();
|
|
||||||
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
|
||||||
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
|
||||||
.append(", Error description: " + errorRep.getErrorDescription());
|
|
||||||
}
|
|
||||||
String error = errorBuilder.toString();
|
|
||||||
log.warn(error);
|
|
||||||
throw new IOException(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity == null) {
|
|
||||||
throw new IOException("No Entity");
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream is = entity.getContent();
|
|
||||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
|
|
||||||
|
|
||||||
// refreshToken will be saved to privateCreds of Subject for now
|
|
||||||
refreshToken = tokenResponse.getRefreshToken();
|
|
||||||
|
|
||||||
AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenResponse.getToken(), tokenResponse.getIdToken(), deployment);
|
|
||||||
return postTokenVerification(tokenResponse.getToken(), tokens.getAccessToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean commit() throws LoginException {
|
|
||||||
boolean superCommit = super.commit();
|
|
||||||
|
|
||||||
// refreshToken will be saved to privateCreds of Subject for now
|
|
||||||
if (refreshToken != null) {
|
|
||||||
RefreshTokenHolder refreshTokenHolder = new RefreshTokenHolder();
|
|
||||||
refreshTokenHolder.refreshToken = refreshToken;
|
|
||||||
subject.getPrivateCredentials().add(refreshTokenHolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
return superCommit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean logout() throws LoginException {
|
|
||||||
if (refreshToken != null) {
|
|
||||||
try {
|
|
||||||
URI logoutUri = deployment.getLogoutUrl().clone().build();
|
|
||||||
HttpPost post = new HttpPost(logoutUri);
|
|
||||||
|
|
||||||
List<NameValuePair> formparams = new ArrayList<>();
|
|
||||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
|
||||||
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
|
||||||
|
|
||||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
|
||||||
post.setEntity(form);
|
|
||||||
|
|
||||||
HttpClient client = deployment.getClient();
|
|
||||||
HttpResponse response = client.execute(post);
|
|
||||||
int status = response.getStatusLine().getStatusCode();
|
|
||||||
HttpEntity entity = response.getEntity();
|
|
||||||
if (status != 204) {
|
|
||||||
StringBuilder errorBuilder = new StringBuilder("Logout of refreshToken failed. Invalid status: " + status);
|
|
||||||
if (entity != null) {
|
|
||||||
InputStream is = entity.getContent();
|
|
||||||
if (status == 400) {
|
|
||||||
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
|
||||||
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
|
||||||
.append(", Error description: " + errorRep.getErrorDescription());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (is != null) is.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should do something better than warn if logout failed? Perhaps update of refresh tokens on existing subject might be supported too...
|
|
||||||
log.warn(errorBuilder.toString());
|
|
||||||
}
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
log.warn(ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RefreshTokenHolder implements Serializable {
|
|
||||||
private String refreshToken;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.jaas;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.security.Principal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class RolePrincipal implements Principal, Serializable {
|
|
||||||
|
|
||||||
private String roleName = null;
|
|
||||||
|
|
||||||
public RolePrincipal(String roleName) {
|
|
||||||
this.roleName = roleName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals (Object p) {
|
|
||||||
if (! (p instanceof RolePrincipal))
|
|
||||||
return false;
|
|
||||||
return getName().equals(((RolePrincipal)p).getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public int hashCode () {
|
|
||||||
return getName().hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName () {
|
|
||||||
return this.roleName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString ()
|
|
||||||
{
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
[[_jaas_adapter]]
|
|
||||||
==== JAAS plugin
|
|
||||||
|
|
||||||
include::adapter-deprecation-notice.adoc[]
|
|
||||||
|
|
||||||
It's generally not needed to use JAAS for most of the applications, especially if they are HTTP based, and you should most likely choose one of our other adapters.
|
|
||||||
However, some applications and systems may still rely on pure legacy JAAS solution.
|
|
||||||
{project_name} provides two login modules to help in these situations.
|
|
||||||
|
|
||||||
The provided login modules are:
|
|
||||||
|
|
||||||
org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule::
|
|
||||||
This login module allows to authenticate with username/password from {project_name}.
|
|
||||||
It's using <<_resource_owner_password_credentials_flow,Resource Owner Password Credentials>> flow to validate if the provided
|
|
||||||
username/password is valid. It's useful for non-web based systems, which need to rely on JAAS and want to use {project_name}, but can't use the standard browser
|
|
||||||
based flows due to their non-web nature. Example of such application could be messaging or SSH.
|
|
||||||
|
|
||||||
org.keycloak.adapters.jaas.BearerTokenLoginModule::
|
|
||||||
This login module allows to authenticate with {project_name} access token passed to it through CallbackHandler as password.
|
|
||||||
It may be useful for example in case, when you have {project_name} access token from standard based authentication flow and your web application then
|
|
||||||
needs to talk to external non-web based system, which rely on JAAS. For example a messaging system.
|
|
||||||
|
|
||||||
Both modules use the following configuration properties:
|
|
||||||
|
|
||||||
keycloak-config-file::
|
|
||||||
The location of the `keycloak.json` configuration file. The configuration file can either be located on the filesystem or on the classpath. If it's located
|
|
||||||
on the classpath you need to prefix the location with `classpath:` (for example `classpath:/path/keycloak.json`).
|
|
||||||
This is _REQUIRED._
|
|
||||||
|
|
||||||
`role-principal-class`::
|
|
||||||
Configure alternative class for Role principals attached to JAAS Subject.
|
|
||||||
Default value is `org.keycloak.adapters.jaas.RolePrincipal`. Note: The class is required to have a constructor with a single `String` argument.
|
|
||||||
|
|
||||||
`scope`::
|
|
||||||
This option is only applicable to the `DirectAccessGrantsLoginModule`. The specified value will be used as the OAuth2 `scope`
|
|
||||||
parameter in the Resource Owner Password Credentials Grant request.
|
|
||||||
|
|
|
@ -21,10 +21,6 @@ ifeval::[{project_community}==true]
|
||||||
include::servlet-filter-adapter.adoc[]
|
include::servlet-filter-adapter.adoc[]
|
||||||
endif::[]
|
endif::[]
|
||||||
|
|
||||||
ifeval::[{project_community}==true]
|
|
||||||
include::jaas.adoc[]
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
ifeval::[{project_community}==true]
|
ifeval::[{project_community}==true]
|
||||||
include::adapter-context.adoc[]
|
include::adapter-context.adoc[]
|
||||||
include::adapter_error_handling.adoc[]
|
include::adapter_error_handling.adoc[]
|
||||||
|
|
|
@ -14,23 +14,12 @@
|
||||||
<property name="hawtio.authenticationEnabled" value="true" />
|
<property name="hawtio.authenticationEnabled" value="true" />
|
||||||
<property name="hawtio.realm" value="hawtio" />
|
<property name="hawtio.realm" value="hawtio" />
|
||||||
<property name="hawtio.roles" value="admin,viewer" />
|
<property name="hawtio.roles" value="admin,viewer" />
|
||||||
<property name="hawtio.rolePrincipalClasses" value="org.keycloak.adapters.jaas.RolePrincipal" />
|
|
||||||
<property name="hawtio.keycloakEnabled" value="true" />
|
<property name="hawtio.keycloakEnabled" value="true" />
|
||||||
<property name="hawtio.keycloakClientConfig" value="${{jboss.server.config.dir}}/keycloak-hawtio-client.json" />
|
<property name="hawtio.keycloakClientConfig" value="${{jboss.server.config.dir}}/keycloak-hawtio-client.json" />
|
||||||
<property name="hawtio.keycloakServerConfig" value="${{jboss.server.config.dir}}/keycloak-hawtio.json" />
|
<property name="hawtio.keycloakServerConfig" value="${{jboss.server.config.dir}}/keycloak-hawtio.json" />
|
||||||
</system-properties>
|
</system-properties>
|
||||||
</xsl:template>
|
</xsl:template>
|
||||||
|
|
||||||
<xsl:template match="//*[local-name()='security-domain' and @name = 'hawtio-domain']">
|
|
||||||
<security-domain name="hawtio" cache-type="default" xmlns="urn:jboss:domain:security:1.2">
|
|
||||||
<authentication>
|
|
||||||
<login-module code="org.keycloak.adapters.jaas.BearerTokenLoginModule" flag="required">
|
|
||||||
<module-option name="keycloak-config-file" value="${{hawtio.keycloakServerConfig}}"/>
|
|
||||||
</login-module>
|
|
||||||
</authentication>
|
|
||||||
</security-domain>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $keycloakNamespace)]">
|
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $keycloakNamespace)]">
|
||||||
<xsl:copy>
|
<xsl:copy>
|
||||||
<secure-deployment name="hawtio.war" xmlns="urn:jboss:domain:keycloak:1.2"/>
|
<secure-deployment name="hawtio.war" xmlns="urn:jboss:domain:keycloak:1.2"/>
|
||||||
|
|
|
@ -1,298 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite.jaas;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.security.auth.Subject;
|
|
||||||
import javax.security.auth.callback.Callback;
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
|
||||||
import javax.security.auth.callback.NameCallback;
|
|
||||||
import javax.security.auth.callback.PasswordCallback;
|
|
||||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
|
||||||
import javax.security.auth.login.AppConfigurationEntry;
|
|
||||||
import javax.security.auth.login.Configuration;
|
|
||||||
import javax.security.auth.login.LoginContext;
|
|
||||||
import javax.security.auth.login.LoginException;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.Assume;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.adapters.jaas.AbstractKeycloakLoginModule;
|
|
||||||
import org.keycloak.adapters.jaas.BearerTokenLoginModule;
|
|
||||||
import org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule;
|
|
||||||
import org.keycloak.adapters.jaas.RolePrincipal;
|
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
|
||||||
import org.keycloak.testsuite.Assert;
|
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
|
||||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
|
|
||||||
import org.keycloak.testsuite.utils.io.IOUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class LoginModulesTest extends AbstractKeycloakTest {
|
|
||||||
|
|
||||||
public static final URI DIRECT_GRANT_CONFIG;
|
|
||||||
public static final URI BEARER_CONFIG;
|
|
||||||
|
|
||||||
private static final File DIRECT_GRANT_CONFIG_FILE;
|
|
||||||
private static final File BEARER_CONFIG_FILE;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
DIRECT_GRANT_CONFIG = MethodHandles.lookup().lookupClass().getResource("/adapter-test/customer-portal/WEB-INF/keycloak.json").toURI();
|
|
||||||
BEARER_CONFIG = MethodHandles.lookup().lookupClass().getResource("/adapter-test/customer-db-audience-required/WEB-INF/keycloak.json").toURI();
|
|
||||||
|
|
||||||
DIRECT_GRANT_CONFIG_FILE = File.createTempFile("LoginModulesTest", "testDirectAccessGrantLoginModuleLoginFailed");
|
|
||||||
BEARER_CONFIG_FILE = File.createTempFile("LoginModulesTest", "testBearerLoginFailedLogin");
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
|
||||||
testRealms.add(IOUtil.loadRealm("/adapter-test/demorealm.json"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void enabled() {
|
|
||||||
Assume.assumeTrue(AUTH_SERVER_SSL_REQUIRED);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void createTemporaryFiles() throws Exception {
|
|
||||||
enabled();
|
|
||||||
|
|
||||||
copyContentAndReplaceAuthServerAddress(new File(DIRECT_GRANT_CONFIG), DIRECT_GRANT_CONFIG_FILE);
|
|
||||||
copyContentAndReplaceAuthServerAddress(new File(BEARER_CONFIG), BEARER_CONFIG_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void removeTemporaryFiles() {
|
|
||||||
DIRECT_GRANT_CONFIG_FILE.deleteOnExit();
|
|
||||||
BEARER_CONFIG_FILE.deleteOnExit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void copyContentAndReplaceAuthServerAddress(File input, File output) throws IOException {
|
|
||||||
try (InputStream inputStream = httpsAwareConfigurationStream(new FileInputStream(input))) {
|
|
||||||
try (FileOutputStream outputStream = new FileOutputStream(output)) {
|
|
||||||
byte[] buffer = new byte[inputStream.available()];
|
|
||||||
inputStream.read(buffer);
|
|
||||||
outputStream.write(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void generateAudienceClientScope() {
|
|
||||||
if (ApiUtil.findClientScopeByName(adminClient.realm("demo"), "customer-db-audience-required") != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate audience client scope
|
|
||||||
String clientScopeId = testingClient.testing().generateAudienceClientScope("demo", "customer-db-audience-required");
|
|
||||||
|
|
||||||
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal");
|
|
||||||
client.addOptionalClientScope(clientScopeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDirectAccessGrantLoginModuleLoginFailed() throws Exception {
|
|
||||||
LoginContext loginContext = new LoginContext("does-not-matter", null,
|
|
||||||
createJaasCallbackHandler("bburke@redhat.com", "bad-password"),
|
|
||||||
createJaasConfigurationForDirectGrant(null));
|
|
||||||
|
|
||||||
try {
|
|
||||||
loginContext.login();
|
|
||||||
Assert.fail("Not expected to successfully login");
|
|
||||||
} catch (LoginException le) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDirectAccessGrantLoginModuleLoginSuccess() throws Exception {
|
|
||||||
oauth.realm("demo");
|
|
||||||
|
|
||||||
LoginContext loginContext = directGrantLogin(null);
|
|
||||||
Subject subject = loginContext.getSubject();
|
|
||||||
|
|
||||||
// Assert principals in subject
|
|
||||||
KeycloakPrincipal principal = subject.getPrincipals(KeycloakPrincipal.class).iterator().next();
|
|
||||||
Assert.assertEquals("bburke@redhat.com", principal.getKeycloakSecurityContext().getToken().getPreferredUsername());
|
|
||||||
assertToken(principal.getKeycloakSecurityContext().getTokenString(), true);
|
|
||||||
|
|
||||||
Set<RolePrincipal> roles = subject.getPrincipals(RolePrincipal.class);
|
|
||||||
Assert.assertEquals(1, roles.size());
|
|
||||||
Assert.assertEquals("user", roles.iterator().next().getName());
|
|
||||||
|
|
||||||
// Logout and assert token not valid anymore
|
|
||||||
loginContext.logout();
|
|
||||||
assertToken(principal.getKeycloakSecurityContext().getTokenString(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBearerLoginFailedLogin() throws Exception {
|
|
||||||
oauth.realm("demo");
|
|
||||||
|
|
||||||
LoginContext directGrantCtx = directGrantLogin(null);
|
|
||||||
String accessToken = directGrantCtx.getSubject().getPrincipals(KeycloakPrincipal.class).iterator().next()
|
|
||||||
.getKeycloakSecurityContext().getTokenString();
|
|
||||||
|
|
||||||
LoginContext bearerCtx = new LoginContext("does-not-matter", null,
|
|
||||||
createJaasCallbackHandler("doesn-not-matter", accessToken),
|
|
||||||
createJaasConfigurationForBearer());
|
|
||||||
|
|
||||||
// Login should fail due insufficient audience in the token
|
|
||||||
try {
|
|
||||||
bearerCtx.login();
|
|
||||||
Assert.fail("Not expected to successfully login");
|
|
||||||
} catch (LoginException le) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
directGrantCtx.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBearerLoginSuccess() throws Exception {
|
|
||||||
oauth.realm("demo");
|
|
||||||
|
|
||||||
LoginContext directGrantCtx = directGrantLogin("customer-db-audience-required");
|
|
||||||
String accessToken = directGrantCtx.getSubject().getPrincipals(KeycloakPrincipal.class).iterator().next()
|
|
||||||
.getKeycloakSecurityContext().getTokenString();
|
|
||||||
|
|
||||||
LoginContext bearerCtx = new LoginContext("does-not-matter", null,
|
|
||||||
createJaasCallbackHandler("doesn-not-matter", accessToken),
|
|
||||||
createJaasConfigurationForBearer());
|
|
||||||
|
|
||||||
// Login should be successful
|
|
||||||
bearerCtx.login();
|
|
||||||
|
|
||||||
// Assert subject
|
|
||||||
Subject subject = bearerCtx.getSubject();
|
|
||||||
|
|
||||||
KeycloakPrincipal principal = subject.getPrincipals(KeycloakPrincipal.class).iterator().next();
|
|
||||||
Assert.assertEquals("bburke@redhat.com", principal.getKeycloakSecurityContext().getToken().getPreferredUsername());
|
|
||||||
assertToken(principal.getKeycloakSecurityContext().getTokenString(), true);
|
|
||||||
|
|
||||||
Set<RolePrincipal> roles = subject.getPrincipals(RolePrincipal.class);
|
|
||||||
Assert.assertEquals(1, roles.size());
|
|
||||||
Assert.assertEquals("user", roles.iterator().next().getName());
|
|
||||||
|
|
||||||
// Logout
|
|
||||||
bearerCtx.logout();
|
|
||||||
directGrantCtx.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private LoginContext directGrantLogin(String scope) throws LoginException {
|
|
||||||
LoginContext loginContext = new LoginContext("does-not-matter", null,
|
|
||||||
createJaasCallbackHandler("bburke@redhat.com", "password"),
|
|
||||||
createJaasConfigurationForDirectGrant(scope));
|
|
||||||
|
|
||||||
loginContext.login();
|
|
||||||
|
|
||||||
return loginContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void assertToken(String accessToken, boolean expectActive) throws IOException {
|
|
||||||
String introspectionResponse = oauth.introspectAccessTokenWithClientCredential("customer-portal", "password", accessToken);
|
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
JsonNode jsonNode = objectMapper.readTree(introspectionResponse);
|
|
||||||
Assert.assertEquals(expectActive, jsonNode.get("active").asBoolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private CallbackHandler createJaasCallbackHandler(final String principal, final String password) {
|
|
||||||
return new CallbackHandler() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
|
||||||
for (Callback callback : callbacks) {
|
|
||||||
if (callback instanceof NameCallback) {
|
|
||||||
NameCallback nameCallback = (NameCallback) callback;
|
|
||||||
nameCallback.setName(principal);
|
|
||||||
} else if (callback instanceof PasswordCallback) {
|
|
||||||
PasswordCallback passwordCallback = (PasswordCallback) callback;
|
|
||||||
passwordCallback.setPassword(password.toCharArray());
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedCallbackException(callback, "Unsupported callback: " + callback.getClass().getCanonicalName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Configuration createJaasConfigurationForDirectGrant(String scope) {
|
|
||||||
return new Configuration() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
|
|
||||||
Map<String, Object> options = new HashMap<>();
|
|
||||||
options.put(AbstractKeycloakLoginModule.KEYCLOAK_CONFIG_FILE_OPTION, DIRECT_GRANT_CONFIG_FILE.getAbsolutePath());
|
|
||||||
if (scope != null) {
|
|
||||||
options.put(DirectAccessGrantsLoginModule.SCOPE_OPTION, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
AppConfigurationEntry LMConfiguration = new AppConfigurationEntry(DirectAccessGrantsLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
|
|
||||||
return new AppConfigurationEntry[] { LMConfiguration };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Configuration createJaasConfigurationForBearer() {
|
|
||||||
return new Configuration() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
|
|
||||||
Map<String, Object> options = new HashMap<>();
|
|
||||||
options.put(AbstractKeycloakLoginModule.KEYCLOAK_CONFIG_FILE_OPTION, BEARER_CONFIG_FILE.getAbsolutePath());
|
|
||||||
|
|
||||||
AppConfigurationEntry LMConfiguration = new AppConfigurationEntry(BearerTokenLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
|
|
||||||
return new AppConfigurationEntry[] { LMConfiguration };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,7 +19,6 @@ feature,4
|
||||||
federation,5
|
federation,5
|
||||||
forms,5
|
forms,5
|
||||||
i18n,5
|
i18n,5
|
||||||
jaas,5
|
|
||||||
javascript,5
|
javascript,5
|
||||||
keys,4
|
keys,4
|
||||||
login,4
|
login,4
|
||||||
|
|
|
@ -114,7 +114,7 @@ public class FuseUtils {
|
||||||
"feature:repo-add mvn:org.keycloak/keycloak-osgi-features/" + fuseAdapterVersion + "/xml/features; " +
|
"feature:repo-add mvn:org.keycloak/keycloak-osgi-features/" + fuseAdapterVersion + "/xml/features; " +
|
||||||
"feature:repo-add mvn:org.keycloak.testsuite/fuse-example-keycloak-features/" + projectVersion + "/xml/features; " +
|
"feature:repo-add mvn:org.keycloak.testsuite/fuse-example-keycloak-features/" + projectVersion + "/xml/features; " +
|
||||||
"feature:install pax-web-http-undertow; " +
|
"feature:install pax-web-http-undertow; " +
|
||||||
"feature:install keycloak-jaas keycloak-pax-http-undertow; " +
|
"feature:install keycloak-pax-http-undertow; " +
|
||||||
"feature:install keycloak-fuse-7.0-example",
|
"feature:install keycloak-fuse-7.0-example",
|
||||||
Result.OK);
|
Result.OK);
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ public class FuseUtils {
|
||||||
"system:property -p hawtio.keycloakClientConfig ${karaf.etc}/keycloak-hawtio-client.json; " +
|
"system:property -p hawtio.keycloakClientConfig ${karaf.etc}/keycloak-hawtio-client.json; " +
|
||||||
"system:property -p hawtio.keycloakServerConfig ${karaf.etc}/keycloak-bearer.json; " +
|
"system:property -p hawtio.keycloakServerConfig ${karaf.etc}/keycloak-bearer.json; " +
|
||||||
"system:property -p hawtio.roles admin,manager,viewer,ssh; " +
|
"system:property -p hawtio.roles admin,manager,viewer,ssh; " +
|
||||||
"system:property -p hawtio.rolePrincipalClasses org.keycloak.adapters.jaas.RolePrincipal,org.apache.karaf.jaas.boot.principal.RolePrincipal;",
|
"system:property -p hawtio.rolePrincipalClasses org.apache.karaf.jaas.boot.principal.RolePrincipal;",
|
||||||
Result.EMPTY);
|
Result.EMPTY);
|
||||||
|
|
||||||
// KEYCLOAK-17873 For older version of Fuse
|
// KEYCLOAK-17873 For older version of Fuse
|
||||||
|
@ -189,7 +189,7 @@ public class FuseUtils {
|
||||||
"system-property -p hawtio.keycloakEnabled true; " +
|
"system-property -p hawtio.keycloakEnabled true; " +
|
||||||
"system-property -p hawtio.realm keycloak; " +
|
"system-property -p hawtio.realm keycloak; " +
|
||||||
"system-property -p hawtio.keycloakClientConfig file://" + appServerHome + "/etc/keycloak-hawtio-client.json; " +
|
"system-property -p hawtio.keycloakClientConfig file://" + appServerHome + "/etc/keycloak-hawtio-client.json; " +
|
||||||
"system-property -p hawtio.rolePrincipalClasses org.keycloak.adapters.jaas.RolePrincipal,org.apache.karaf.jaas.boot.principal.RolePrincipal; ",
|
"system-property -p hawtio.rolePrincipalClasses org.apache.karaf.jaas.boot.principal.RolePrincipal; ",
|
||||||
Result.EMPTY);
|
Result.EMPTY);
|
||||||
|
|
||||||
String output = getCommandOutput(managementUser, managementPassword, "osgi:list | grep hawtio | grep web;");
|
String output = getCommandOutput(managementUser, managementPassword, "osgi:list | grep hawtio | grep web;");
|
||||||
|
|
Loading…
Reference in a new issue