diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenLoginModule.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenLoginModule.java
new file mode 100644
index 0000000000..1efee5bb89
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenLoginModule.java
@@ -0,0 +1,287 @@
+package org.keycloak.adapters;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+
+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 org.keycloak.KeycloakPrincipal;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
+import org.keycloak.constants.GenericConstants;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.util.PemUtils;
+
+/**
+ * Login module, which allows to authenticate Keycloak access token in environments, which rely on JAAS
+ *
+ * It expects login based on username and password where username must be equal to "Bearer" and password is keycloak access token.
+ *
+ * @author Marek Posolda
+ */
+public class BearerTokenLoginModule implements LoginModule {
+
+ private final static Logger log = Logger.getLogger("" + BearerTokenLoginModule.class);
+
+ public static final String KEYCLOAK_CONFIG_FILE_OPTION = "keycloak-config-file";
+ public static final String REALM_OPTION = "realm";
+ public static final String RESOURCE_OPTION = "resource";
+ public static final String PUBLIC_KEY_OPTION = "realm-public-key";
+ public static final String AUTH_SERVER_URL_OPTION = "auth-server-url";
+ public static final String USE_RESOURCE_ROLE_MAPPINGS_OPTION = "use-resource-role-mappings";
+ public static final String PRINCIPAL_ATTRIBUTE_OPTION = "principal-attribute";
+
+ private Subject subject;
+ private CallbackHandler callbackHandler;
+ private Auth auth;
+
+ private static volatile KeycloakDeployment deployment;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+
+ // Static as we don't want to parse config file in each authentication
+ if (deployment == null) {
+ KeycloakDeployment kd;
+ String configFile = (String)options.get(KEYCLOAK_CONFIG_FILE_OPTION);
+ if (configFile != null) {
+ InputStream is = loadKeycloakConfigFile(configFile);
+ kd = KeycloakDeploymentBuilder.build(is);
+ } else {
+ // init everything from provided options
+ String realm = (String) options.get(REALM_OPTION);
+ if (realm == null) {
+ throw new IllegalArgumentException("Realm is mandatory if you didn't provide keycloak-config-file");
+ }
+ String authServerUrl = (String) options.get(AUTH_SERVER_URL_OPTION);
+ String publicKey = (String) options.get(PUBLIC_KEY_OPTION);
+ if (publicKey == null && authServerUrl == null) {
+ throw new IllegalArgumentException("Option " + PUBLIC_KEY_OPTION + " is mandatory if you didn't provide keycloak-config-file or auth-server-url to resolver public key");
+ }
+ String resource = (String) options.get(RESOURCE_OPTION);
+ String resRoleMappings = (String) options.get(USE_RESOURCE_ROLE_MAPPINGS_OPTION);
+ boolean useResourceRoleMappings = resRoleMappings == null ? false : Boolean.parseBoolean(resRoleMappings);
+ if (useResourceRoleMappings && resource == null) {
+ throw new IllegalArgumentException("You want resource-role-mappings, but you didn't provide resource in configuration");
+ }
+ String principalAttribute = (String) options.get(PRINCIPAL_ATTRIBUTE_OPTION);
+
+ kd = new KeycloakDeployment();
+ kd.setRealm(realm);
+ kd.setResourceName(resource);
+ kd.setUseResourceRoleMappings(useResourceRoleMappings);
+ kd.setPrincipalAttribute(principalAttribute);
+ if (publicKey != null) {
+ try {
+ PublicKey pk = PemUtils.decodePublicKey(publicKey);
+ kd.setRealmKey(pk);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ if (kd.getRealmKey() == null) {
+ new AdapterDeploymentContext().resolveRealmKey(kd);
+ }
+ deployment = kd;
+ }
+ }
+
+ protected InputStream loadKeycloakConfigFile(String keycloakConfigFile) {
+ if (keycloakConfigFile.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
+ String classPathLocation = keycloakConfigFile.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
+ log.info("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.info("Loading config from file: " + keycloakConfigFile);
+ return new FileInputStream(keycloakConfigFile);
+ } catch (FileNotFoundException fnfe) {
+ log.severe("Config not found on " + keycloakConfigFile);
+ throw new RuntimeException(fnfe);
+ }
+ }
+ }
+
+ @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 = bearerAuth(username, password);
+ if (auth != null) {
+ this.auth = auth;
+ return true;
+ } else {
+ return false;
+ }
+ } catch (UnsupportedCallbackException uce) {
+ LoginException le = new LoginException("Error: " + uce.getCallback().toString()
+ + " not available to gather authentication information from the user");
+ le.initCause(uce);
+ throw le;
+ } catch (Exception ioe) {
+ LoginException le = new LoginException(ioe.toString());
+ le.initCause(ioe);
+ throw le;
+ }
+ }
+
+ protected Auth bearerAuth(String username, String tokenString) throws VerificationException {
+ if ("Bearer".equalsIgnoreCase(username)) {
+ log.fine("Username is expected to be bearer but is " + username + ". Ignoring login module");
+ return null;
+ }
+
+ AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealm());
+
+ 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 principal = new KeycloakPrincipal(principalName, skSession);
+ final Set roles = AdapterUtils.getRolesFromSecurityContext(skSession);
+ return new Auth(principal, roles, tokenString);
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ if (auth == null) {
+ return false;
+ }
+
+ this.subject.getPrincipals().add(auth.getPrincipal());
+ this.subject.getPrivateCredentials().add(auth.getTokenString());
+ for (String roleName : auth.getRoles()) {
+ RolePrincipal rolePrinc = new RolePrincipal(roleName);
+ this.subject.getPrincipals().add(rolePrinc);
+ }
+
+ return true;
+ }
+
+ // Might be needed if subclass wants to setup security context in some env specific way
+ protected Auth getAuth() {
+ return auth;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ Set principals = new HashSet(subject.getPrincipals());
+ for (Principal principal : principals) {
+ if (principal.getClass().equals(KeycloakPrincipal.class) || principal.getClass().equals(RolePrincipal.class)) {
+ subject.getPrincipals().remove(principal);
+ }
+ }
+ Set