Merge pull request #283 from patriot1burke/master
adapter security context propagation
This commit is contained in:
commit
53f671e8af
32 changed files with 907 additions and 33 deletions
|
@ -38,6 +38,18 @@
|
|||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="node()[name(.)='security-domains']">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="node()[name(.)='security-domain']"/>
|
||||
<security-domain name="keycloak">
|
||||
<authentication>
|
||||
<login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule" flag="required"/>
|
||||
</authentication>
|
||||
</security-domain>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<!-- for some reason, Wildfly 8 final decided to turn off management-native which means jboss-as-maven-plugin no
|
||||
longer works -->
|
||||
<xsl:template match="node()[name(.)='management-interfaces']">
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<excludes>
|
||||
<exclude>org/keycloak/keycloak-undertow-adapter/**</exclude>
|
||||
<exclude>org/keycloak/keycloak-wildfly-subsystem/**</exclude>
|
||||
<exclude>org/keycloak/keycloak-wildfly-adapter/**</exclude>
|
||||
</excludes>
|
||||
<outputDirectory>modules</outputDirectory>
|
||||
</fileSet>
|
||||
|
|
|
@ -59,6 +59,10 @@
|
|||
<maven-resource group="org.keycloak" artifact="keycloak-adapter-core"/>
|
||||
</module-def>
|
||||
|
||||
<module-def name="org.keycloak.keycloak-jboss-adapter-core">
|
||||
<maven-resource group="org.keycloak" artifact="keycloak-jboss-adapter-core"/>
|
||||
</module-def>
|
||||
|
||||
<module-def name="org.keycloak.keycloak-as7-adapter">
|
||||
<maven-resource group="org.keycloak" artifact="keycloak-as7-adapter"/>
|
||||
</module-def>
|
||||
|
@ -67,6 +71,10 @@
|
|||
<maven-resource group="org.keycloak" artifact="keycloak-undertow-adapter"/>
|
||||
</module-def>
|
||||
|
||||
<module-def name="org.keycloak.keycloak-wildfly-adapter">
|
||||
<maven-resource group="org.keycloak" artifact="keycloak-wildfly-adapter"/>
|
||||
</module-def>
|
||||
|
||||
<module-def name="org.keycloak.keycloak-wildfly-subsystem">
|
||||
<maven-resource group="org.keycloak" artifact="keycloak-wildfly-subsystem"/>
|
||||
</module-def>
|
||||
|
|
|
@ -49,6 +49,11 @@
|
|||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jboss-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-as7-adapter</artifactId>
|
||||
|
@ -59,6 +64,11 @@
|
|||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-wildfly-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-wildfly-subsystem</artifactId>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ JBoss, Home of Professional Open Source.
|
||||
~ Copyright 2010, Red Hat, Inc., and individual contributors
|
||||
~ as indicated by the @author tags. See the copyright.txt file in the
|
||||
~ distribution for a full listing of individual contributors.
|
||||
~
|
||||
~ This is free software; you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU Lesser General Public License as
|
||||
~ published by the Free Software Foundation; either version 2.1 of
|
||||
~ the License, or (at your option) any later version.
|
||||
~
|
||||
~ This software is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
~ Lesser General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Lesser General Public
|
||||
~ License along with this software; if not, write to the Free
|
||||
~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
-->
|
||||
|
||||
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-jboss-adapter-core">
|
||||
<resources>
|
||||
<!-- Insert resources here -->
|
||||
</resources>
|
||||
<dependencies>
|
||||
<module name="javax.api"/>
|
||||
<module name="org.jboss.logging"/>
|
||||
<module name="org.picketbox"/>
|
||||
<module name="org.keycloak.keycloak-adapter-core"/>
|
||||
<module name="org.keycloak.keycloak-core"/>
|
||||
</dependencies>
|
||||
|
||||
</module>
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ JBoss, Home of Professional Open Source.
|
||||
~ Copyright 2010, Red Hat, Inc., and individual contributors
|
||||
~ as indicated by the @author tags. See the copyright.txt file in the
|
||||
~ distribution for a full listing of individual contributors.
|
||||
~
|
||||
~ This is free software; you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU Lesser General Public License as
|
||||
~ published by the Free Software Foundation; either version 2.1 of
|
||||
~ the License, or (at your option) any later version.
|
||||
~
|
||||
~ This software is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
~ Lesser General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Lesser General Public
|
||||
~ License along with this software; if not, write to the Free
|
||||
~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
-->
|
||||
|
||||
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-wildfly-adapter">
|
||||
<resources>
|
||||
<!-- Insert resources here -->
|
||||
</resources>
|
||||
<dependencies>
|
||||
<module name="javax.api"/>
|
||||
<module name="org.bouncycastle"/>
|
||||
<module name="org.codehaus.jackson.jackson-core-asl"/>
|
||||
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
|
||||
<module name="org.codehaus.jackson.jackson-xc"/>
|
||||
<module name="org.apache.httpcomponents" />
|
||||
<module name="javax.servlet.api"/>
|
||||
<module name="org.jboss.logging"/>
|
||||
<module name="io.undertow.core"/>
|
||||
<module name="io.undertow.servlet"/>
|
||||
<module name="org.picketbox"/>
|
||||
<module name="org.keycloak.keycloak-undertow-adapter"/>
|
||||
<module name="org.keycloak.keycloak-adapter-core"/>
|
||||
<module name="org.keycloak.keycloak-core"/>
|
||||
</dependencies>
|
||||
|
||||
</module>
|
|
@ -73,6 +73,66 @@ $ unzip keycloak-as7-adapter-dist.zip
|
|||
]]>
|
||||
</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Finally, for both AS7, EAP 6.x, and Wildfly installations you must specify a shared keycloak security domain.
|
||||
This security domain should be used with EJBs and other components when you need the security context created
|
||||
in the secured web tier to be propagated to the EJBs (other EE component) you are invoking. Otherwise
|
||||
this configuration is optional.
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
<server xmlns="urn:jboss:domain:1.4">
|
||||
<subsystem xmlns="urn:jboss:domain:security:1.2">
|
||||
<security-domains>
|
||||
...
|
||||
<security-domain name="keycloak">
|
||||
<authentication>
|
||||
<login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule"
|
||||
flag="required"/>
|
||||
</authentication>
|
||||
</security-domain>
|
||||
</security-domains>
|
||||
]]>
|
||||
</programlisting>
|
||||
<para>
|
||||
For example, if you have a JAX-RS service that is an EJB within your WEB-INF/classes directory, you'll want
|
||||
to annotate it with the @SecurityDomain annotation as follows:
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
import org.jboss.ejb3.annotation.SecurityDomain;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
import javax.ejb.EJB;
|
||||
import javax.ejb.Stateless;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Path("customers")
|
||||
@Stateless
|
||||
@SecurityDomain("keycloak")
|
||||
public class CustomerService {
|
||||
|
||||
@EJB
|
||||
CustomerDB db;
|
||||
|
||||
@GET
|
||||
@Produces("application/json")
|
||||
@NoCache
|
||||
@RolesAllowed("db_user")
|
||||
public List<String> getCustomers() {
|
||||
return db.getCustomers();
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</programlisting>
|
||||
<para>
|
||||
We hope to improve our integration in the future so that you don't have to specify the @SecurityDomain
|
||||
annotation when you want to propagate a keycloak security context to the EJB tier.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
<section>
|
||||
<title>Per WAR Configuration</title>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package org.keycloak.adapters;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface KeycloakAccount {
|
||||
Principal getPrincipal();
|
||||
Set<String> getRoles();
|
||||
KeycloakSecurityContext getKeycloakSecurityContext();
|
||||
}
|
|
@ -48,6 +48,7 @@ public class KeycloakDeploymentBuilder {
|
|||
deployment.setSslRequired(!adapterConfig.isSslNotRequired());
|
||||
deployment.setResourceCredentials(adapterConfig.getCredentials());
|
||||
deployment.setPublicClient(adapterConfig.isPublicClient());
|
||||
deployment.setUseResourceRoleMappings(adapterConfig.isUseResourceRoleMappings());
|
||||
|
||||
if (adapterConfig.isBearerOnly()) {
|
||||
deployment.setBearerOnly(true);
|
||||
|
|
|
@ -46,6 +46,10 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||
return this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
|
||||
}
|
||||
|
||||
public KeycloakDeployment getDeployment() {
|
||||
return deployment;
|
||||
}
|
||||
|
||||
public void setDeployment(KeycloakDeployment deployment) {
|
||||
this.deployment = deployment;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import org.keycloak.KeycloakPrincipal;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class RequestAuthenticator {
|
||||
protected Logger log = Logger.getLogger(RequestAuthenticator.class);
|
||||
protected static Logger log = Logger.getLogger(RequestAuthenticator.class);
|
||||
|
||||
protected HttpFacade facade;
|
||||
protected KeycloakDeployment deployment;
|
||||
|
@ -33,19 +33,25 @@ public abstract class RequestAuthenticator {
|
|||
public AuthOutcome authenticate() {
|
||||
log.info("--> authenticate()");
|
||||
BearerTokenRequestAuthenticator bearer = createBearerTokenAuthenticator();
|
||||
log.info("try bearer");
|
||||
AuthOutcome outcome = bearer.authenticate(facade);
|
||||
if (outcome == AuthOutcome.FAILED) {
|
||||
challenge = bearer.getChallenge();
|
||||
log.info("Bearer FAILED");
|
||||
return AuthOutcome.FAILED;
|
||||
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||
completeAuthentication(bearer);
|
||||
log.info("Bearer AUTHENTICATED");
|
||||
return AuthOutcome.AUTHENTICATED;
|
||||
} else if (deployment.isBearerOnly()) {
|
||||
challenge = bearer.getChallenge();
|
||||
log.info("NOT_ATTEMPTED: bearer only");
|
||||
return AuthOutcome.NOT_ATTEMPTED;
|
||||
}
|
||||
|
||||
log.info("try oauth");
|
||||
if (isCached()) {
|
||||
log.info("AUTHENTICATED: was cached");
|
||||
return AuthOutcome.AUTHENTICATED;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ public class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
|
|||
|
||||
private static final ModuleIdentifier KEYCLOAK_AS7_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-as7-adapter");
|
||||
private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-adapter-core");
|
||||
private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
|
||||
private static final ModuleIdentifier KEYCLOAK_CORE = ModuleIdentifier.create("org.keycloak.keycloak-core");
|
||||
//private static final ModuleIdentifier APACHE_HTTPCOMPONENTS = ModuleIdentifier.create("org.apache.httpcomponents");
|
||||
|
||||
|
@ -51,6 +52,7 @@ public class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
|
|||
final ModuleLoader moduleLoader = Module.getBootModuleLoader();
|
||||
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_AS7_ADAPTER, false, false, true, false));
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, false, false, false, false));
|
||||
//moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, APACHE_HTTPCOMPONENTS, false, false, true, false));
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jboss-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.apache.catalina.Session;
|
|||
import org.apache.catalina.authenticator.Constants;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.realm.GenericPrincipal;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
@ -22,6 +23,7 @@ import java.util.Set;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CatalinaRequestAuthenticator extends RequestAuthenticator {
|
||||
private static final Logger log = Logger.getLogger(CatalinaRequestAuthenticator.class);
|
||||
protected KeycloakAuthenticatorValve valve;
|
||||
protected CatalinaUserSessionManagement userSessionManagement;
|
||||
protected Request request;
|
||||
|
@ -53,7 +55,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
|
|||
@Override
|
||||
protected void completeOAuthAuthentication(KeycloakPrincipal skp, RefreshableKeycloakSecurityContext securityContext) {
|
||||
Set<String> roles = getRolesFromToken(securityContext);
|
||||
GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles);
|
||||
GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext);
|
||||
Session session = request.getSessionInternal(true);
|
||||
session.setPrincipal(principal);
|
||||
session.setAuthType("OAUTH");
|
||||
|
@ -66,7 +68,10 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
|
|||
@Override
|
||||
protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext securityContext) {
|
||||
Set<String> roles = getRolesFromToken(securityContext);
|
||||
Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles);
|
||||
for (String role : roles) {
|
||||
log.info("Bearer role: " + role);
|
||||
}
|
||||
Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext);
|
||||
request.setUserPrincipal(generalPrincipal);
|
||||
request.setAuthType("KEYCLOAK");
|
||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
||||
|
|
|
@ -9,6 +9,8 @@ import org.jboss.security.SecurityContext;
|
|||
import org.jboss.security.SecurityContextAssociation;
|
||||
import org.jboss.security.SimpleGroup;
|
||||
import org.jboss.security.SimplePrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import java.security.Principal;
|
||||
|
@ -25,9 +27,24 @@ import java.util.Set;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CatalinaSecurityContextHelper {
|
||||
public GenericPrincipal createPrincipal(Realm realm, Principal identity, Collection<String> roleSet) {
|
||||
public GenericPrincipal createPrincipal(Realm realm, final Principal identity, final Set<String> roleSet, final KeycloakSecurityContext securityContext) {
|
||||
KeycloakAccount account = new KeycloakAccount() {
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRoles() {
|
||||
return roleSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
return securityContext;
|
||||
}
|
||||
};
|
||||
Subject subject = new Subject();
|
||||
String credentials = "";
|
||||
Set<Principal> principals = subject.getPrincipals();
|
||||
principals.add(identity);
|
||||
Group[] roleSets = getRoleSets(roleSet);
|
||||
|
@ -56,11 +73,11 @@ public class CatalinaSecurityContextHelper {
|
|||
principals.add(callerGroup);
|
||||
SecurityContext sc = SecurityContextAssociation.getSecurityContext();
|
||||
Principal userPrincipal = getPrincipal(subject);
|
||||
sc.getUtil().createSubjectInfo(userPrincipal, credentials, subject);
|
||||
sc.getUtil().createSubjectInfo(userPrincipal, account, subject);
|
||||
List<String> rolesAsStringList = new ArrayList<String>();
|
||||
rolesAsStringList.addAll(roleSet);
|
||||
return new JBossGenericPrincipal(realm, userPrincipal.getName(), null, rolesAsStringList,
|
||||
userPrincipal, null, credentials, null, subject);
|
||||
userPrincipal, null, account, null, subject);
|
||||
|
||||
}
|
||||
|
||||
|
|
83
integration/jboss-adapter-core/pom.xml
Executable file
83
integration/jboss-adapter-core/pom.xml
Executable file
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-alpha-3-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-jboss-adapter-core</artifactId>
|
||||
<name>Common JBoss/Wildfly Core Classes</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>3.1.2.GA</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${keycloak.apache.httpcomponents.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.iharder</groupId>
|
||||
<artifactId>base64</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-xc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.picketbox</groupId>
|
||||
<artifactId>picketbox</artifactId>
|
||||
<version>4.0.20.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,100 @@
|
|||
package org.keycloak.adapters.jboss;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.security.SimpleGroup;
|
||||
import org.jboss.security.SimplePrincipal;
|
||||
import org.jboss.security.auth.callback.ObjectCallback;
|
||||
import org.jboss.security.auth.spi.AbstractServerLoginModule;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.NameCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.security.acl.Group;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakLoginModule extends AbstractServerLoginModule {
|
||||
protected static Logger log = Logger.getLogger(KeycloakLoginModule.class);
|
||||
protected Set<String> roleSet;
|
||||
protected Principal identity;
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean login() throws LoginException {
|
||||
log.info("KeycloakLoginModule.login()");
|
||||
if (super.login() == true) {
|
||||
log.info("super.login()==true");
|
||||
return true;
|
||||
}
|
||||
|
||||
Object credential = getCredential();
|
||||
if (credential != null && (credential instanceof KeycloakAccount)) {
|
||||
log.info("Found Account");
|
||||
KeycloakAccount account = (KeycloakAccount)credential;
|
||||
roleSet = account.getRoles();
|
||||
identity = account.getPrincipal();
|
||||
sharedState.put("javax.security.auth.login.name", identity);
|
||||
sharedState.put("javax.security.auth.login.password", credential);
|
||||
loginOk = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We return false to allow the next module to attempt authentication, maybe a
|
||||
// username and password has been supplied to a web auth.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Principal getIdentity() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
protected Group[] getRoleSets() throws LoginException {
|
||||
return new Group[0];
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected Group[] getRoleSets() throws LoginException {
|
||||
//log.info("getRoleSets");
|
||||
SimpleGroup roles = new SimpleGroup("Roles");
|
||||
Group[] roleSets = {roles};
|
||||
for (String role : roleSet) {
|
||||
//log.info(" adding role: " + role);
|
||||
roles.addMember(new SimplePrincipal(role));
|
||||
}
|
||||
return roleSets;
|
||||
}
|
||||
|
||||
protected Object getCredential() throws LoginException {
|
||||
NameCallback nc = new NameCallback("Alias: ");
|
||||
ObjectCallback oc = new ObjectCallback("Credential: ");
|
||||
Callback[] callbacks = { nc, oc };
|
||||
|
||||
try {
|
||||
callbackHandler.handle(callbacks);
|
||||
|
||||
return oc.getCredential();
|
||||
} catch (IOException ioe) {
|
||||
LoginException le = new LoginException();
|
||||
le.initCause(ioe);
|
||||
throw le;
|
||||
} catch (UnsupportedCallbackException uce) {
|
||||
LoginException le = new LoginException();
|
||||
le.initCause(uce);
|
||||
throw le;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -18,8 +18,10 @@
|
|||
<module>adapter-core</module>
|
||||
<module>jaxrs-oauth-client</module>
|
||||
<module>servlet-oauth-client</module>
|
||||
<module>jboss-adapter-core</module>
|
||||
<module>as7-eap6/adapter</module>
|
||||
<module>undertow</module>
|
||||
<module>wildfly-adapter</module>
|
||||
<module>wildfly-subsystem</module>
|
||||
<module>as7-eap-subsystem</module>
|
||||
<module>js</module>
|
||||
|
|
|
@ -65,7 +65,7 @@ public class KeycloakServletExtension implements ServletExtension {
|
|||
if (is == null) throw new RuntimeException("Unable to find realm config in /WEB-INF/keycloak.json or in keycloak subsystem.");
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
|
||||
UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement(deployment);
|
||||
final ServletKeycloakAuthMech mech = new ServletKeycloakAuthMech(deployment, userSessionManagement, deploymentInfo.getConfidentialPortManager());
|
||||
final ServletKeycloakAuthMech mech = createAuthenticationMechanism(deploymentInfo, deployment, userSessionManagement);
|
||||
|
||||
UndertowAuthenticatedActionsHandler.Wrapper actions = new UndertowAuthenticatedActionsHandler.Wrapper(deployment);
|
||||
|
||||
|
@ -102,4 +102,9 @@ public class KeycloakServletExtension implements ServletExtension {
|
|||
cookieConfig.setPath(deploymentInfo.getContextPath());
|
||||
deploymentInfo.setServletSessionConfig(cookieConfig);
|
||||
}
|
||||
|
||||
protected ServletKeycloakAuthMech createAuthenticationMechanism(DeploymentInfo deploymentInfo, KeycloakDeployment deployment, UndertowUserSessionManagement userSessionManagement) {
|
||||
log.info("creating ServletKeycloakAuthMech");
|
||||
return new ServletKeycloakAuthMech(deployment, userSessionManagement, deploymentInfo.getConfidentialPortManager());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.adapters.undertow;
|
|||
import io.undertow.security.idm.Account;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -16,7 +17,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakUndertowAccount implements Account, Serializable {
|
||||
public class KeycloakUndertowAccount implements Account, Serializable, KeycloakAccount {
|
||||
protected static Logger log = Logger.getLogger(KeycloakUndertowAccount.class);
|
||||
protected RefreshableKeycloakSecurityContext session;
|
||||
protected KeycloakPrincipal principal;
|
||||
|
@ -25,19 +26,27 @@ public class KeycloakUndertowAccount implements Account, Serializable {
|
|||
public KeycloakUndertowAccount(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session, KeycloakDeployment deployment) {
|
||||
this.principal = principal;
|
||||
this.session = session;
|
||||
setRoles(session.getToken(), deployment);
|
||||
setRoles(session.getToken());
|
||||
}
|
||||
|
||||
protected void setRoles(AccessToken accessToken, KeycloakDeployment deployment) {
|
||||
protected void setRoles(AccessToken accessToken) {
|
||||
Set<String> roles = null;
|
||||
if (deployment.isUseResourceRoleMappings()) {
|
||||
AccessToken.Access access = accessToken.getResourceAccess(deployment.getResourceName());
|
||||
if (session.getDeployment().isUseResourceRoleMappings()) {
|
||||
log.info("useResourceRoleMappings");
|
||||
AccessToken.Access access = accessToken.getResourceAccess(session.getDeployment().getResourceName());
|
||||
if (access != null) roles = access.getRoles();
|
||||
} else {
|
||||
log.info("use realm role mappings");
|
||||
AccessToken.Access access = accessToken.getRealmAccess();
|
||||
if (access != null) roles = access.getRoles();
|
||||
}
|
||||
if (roles == null) roles = Collections.emptySet();
|
||||
/*
|
||||
log.info("Setting roles: ");
|
||||
for (String role : roles) {
|
||||
log.info(" role: " + role);
|
||||
}
|
||||
*/
|
||||
this.accountRoles = roles;
|
||||
}
|
||||
|
||||
|
@ -51,22 +60,17 @@ public class KeycloakUndertowAccount implements Account, Serializable {
|
|||
return accountRoles;
|
||||
}
|
||||
|
||||
public AccessToken getAccessToken() {
|
||||
return session.getToken();
|
||||
}
|
||||
|
||||
public String getEncodedAccessToken() {
|
||||
return session.getTokenString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public boolean isActive(KeycloakDeployment deployment) {
|
||||
// this object may have been serialized, so we need to reset realm config/metadata
|
||||
public void setDeployment(KeycloakDeployment deployment) {
|
||||
session.setDeployment(deployment);
|
||||
log.info("realmConfig notBefore: " + deployment.getNotBefore());
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
// this object may have been serialized, so we need to reset realm config/metadata
|
||||
if (session.isActive()) {
|
||||
log.info("session is active");
|
||||
return true;
|
||||
|
@ -81,7 +85,7 @@ public class KeycloakUndertowAccount implements Account, Serializable {
|
|||
}
|
||||
log.info("refresh succeeded");
|
||||
|
||||
setRoles(session.getToken(), deployment);
|
||||
setRoles(session.getToken());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,7 @@ public class ServletKeycloakAuthMech implements AuthenticationMechanism {
|
|||
@Override
|
||||
public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
|
||||
UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
|
||||
ServletRequestAuthenticator authenticator = new ServletRequestAuthenticator(facade, deployment,
|
||||
portManager.getConfidentialPort(exchange), securityContext, exchange, userSessionManagement);
|
||||
ServletRequestAuthenticator authenticator = createRequestAuthenticator(exchange, securityContext, facade);
|
||||
AuthOutcome outcome = authenticator.authenticate();
|
||||
if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||
return AuthenticationMechanismOutcome.AUTHENTICATED;
|
||||
|
@ -46,6 +45,11 @@ public class ServletKeycloakAuthMech implements AuthenticationMechanism {
|
|||
return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
|
||||
}
|
||||
|
||||
protected ServletRequestAuthenticator createRequestAuthenticator(HttpServerExchange exchange, SecurityContext securityContext, UndertowHttpFacade facade) {
|
||||
return new ServletRequestAuthenticator(facade, deployment,
|
||||
portManager.getConfidentialPort(exchange), securityContext, exchange, userSessionManagement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
|
||||
AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.undertow.server.HttpServerExchange;
|
|||
import io.undertow.servlet.handlers.ServletRequestContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -39,7 +40,8 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
|
|||
log.info("Account was not in session, returning null");
|
||||
return false;
|
||||
}
|
||||
if (account.isActive(deployment)) {
|
||||
account.setDeployment(deployment);
|
||||
if (account.isActive()) {
|
||||
log.info("Cached account found");
|
||||
securityContext.authenticationComplete(account, "KEYCLOAK", false);
|
||||
propagateKeycloakContext( account);
|
||||
|
@ -59,7 +61,7 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void login(KeycloakUndertowAccount account) {
|
||||
protected void login(KeycloakAccount account) {
|
||||
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
|
||||
HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
|
||||
HttpSession session = req.getSession(true);
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.undertow.security.api.SecurityContext;
|
|||
import io.undertow.server.HttpServerExchange;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
|
@ -46,7 +47,7 @@ public class UndertowRequestAuthenticator extends RequestAuthenticator {
|
|||
login(account);
|
||||
}
|
||||
|
||||
protected void login(KeycloakUndertowAccount account) {
|
||||
protected void login(KeycloakAccount account) {
|
||||
|
||||
}
|
||||
|
||||
|
|
108
integration/wildfly-adapter/pom.xml
Executable file
108
integration/wildfly-adapter/pom.xml
Executable file
|
@ -0,0 +1,108 @@
|
|||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-alpha-3-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-wildfly-adapter</artifactId>
|
||||
<name>Keycloak Wildfly Integration</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>3.1.2.GA</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jboss-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${keycloak.apache.httpcomponents.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.iharder</groupId>
|
||||
<artifactId>base64</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-xc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.picketbox</groupId>
|
||||
<artifactId>picketbox</artifactId>
|
||||
<version>4.0.20.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
<artifactId>undertow-servlet</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
<artifactId>undertow-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,116 @@
|
|||
package org.keycloak.adapters.wildfly;
|
||||
|
||||
import org.jboss.security.NestableGroup;
|
||||
import org.jboss.security.SecurityConstants;
|
||||
import org.jboss.security.SecurityContextAssociation;
|
||||
import org.jboss.security.SimpleGroup;
|
||||
import org.jboss.security.SimplePrincipal;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import java.security.Principal;
|
||||
import java.security.acl.Group;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecurityInfoHelper {
|
||||
public static void propagateSessionInfo(KeycloakAccount account) {
|
||||
Subject subject = new Subject();
|
||||
Set<Principal> principals = subject.getPrincipals();
|
||||
principals.add(account.getPrincipal());
|
||||
Group[] roleSets = getRoleSets(account.getRoles());
|
||||
for (int g = 0; g < roleSets.length; g++) {
|
||||
Group group = roleSets[g];
|
||||
String name = group.getName();
|
||||
Group subjectGroup = createGroup(name, principals);
|
||||
if (subjectGroup instanceof NestableGroup) {
|
||||
/* A NestableGroup only allows Groups to be added to it so we
|
||||
need to add a SimpleGroup to subjectRoles to contain the roles
|
||||
*/
|
||||
SimpleGroup tmp = new SimpleGroup("Roles");
|
||||
subjectGroup.addMember(tmp);
|
||||
subjectGroup = tmp;
|
||||
}
|
||||
// Copy the group members to the Subject group
|
||||
Enumeration<? extends Principal> members = group.members();
|
||||
while (members.hasMoreElements()) {
|
||||
Principal role = (Principal) members.nextElement();
|
||||
subjectGroup.addMember(role);
|
||||
}
|
||||
}
|
||||
// add the CallerPrincipal group if none has been added in getRoleSets
|
||||
Group callerGroup = new SimpleGroup(SecurityConstants.CALLER_PRINCIPAL_GROUP);
|
||||
callerGroup.addMember(account.getPrincipal());
|
||||
principals.add(callerGroup);
|
||||
org.jboss.security.SecurityContext sc = SecurityContextAssociation.getSecurityContext();
|
||||
Principal userPrincipal = getPrincipal(subject);
|
||||
sc.getUtil().createSubjectInfo(userPrincipal, account, subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is
|
||||
* considered or the single subject inside the CallerPrincipal group.
|
||||
*
|
||||
* @param subject
|
||||
* @return the authenticated subject
|
||||
*/
|
||||
protected static Principal getPrincipal(Subject subject) {
|
||||
Principal principal = null;
|
||||
Principal callerPrincipal = null;
|
||||
if (subject != null) {
|
||||
Set<Principal> principals = subject.getPrincipals();
|
||||
if (principals != null && !principals.isEmpty()) {
|
||||
for (Principal p : principals) {
|
||||
if (!(p instanceof Group) && principal == null) {
|
||||
principal = p;
|
||||
}
|
||||
if (p instanceof Group) {
|
||||
Group g = Group.class.cast(p);
|
||||
if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
|
||||
Enumeration<? extends Principal> e = g.members();
|
||||
if (e.hasMoreElements())
|
||||
callerPrincipal = e.nextElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return callerPrincipal == null ? principal : callerPrincipal;
|
||||
}
|
||||
|
||||
protected static Group createGroup(String name, Set<Principal> principals) {
|
||||
Group roles = null;
|
||||
Iterator<Principal> iter = principals.iterator();
|
||||
while (iter.hasNext()) {
|
||||
Object next = iter.next();
|
||||
if ((next instanceof Group) == false)
|
||||
continue;
|
||||
Group grp = (Group) next;
|
||||
if (grp.getName().equals(name)) {
|
||||
roles = grp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If we did not find a group create one
|
||||
if (roles == null) {
|
||||
roles = new SimpleGroup(name);
|
||||
principals.add(roles);
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
protected static Group[] getRoleSets(Collection<String> roleSet) {
|
||||
SimpleGroup roles = new SimpleGroup("Roles");
|
||||
Group[] roleSets = {roles};
|
||||
for (String role : roleSet) {
|
||||
roles.addMember(new SimplePrincipal(role));
|
||||
}
|
||||
return roleSets;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.keycloak.adapters.wildfly;
|
||||
|
||||
import io.undertow.security.api.SecurityContext;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.servlet.api.ConfidentialPortManager;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.undertow.ServletKeycloakAuthMech;
|
||||
import org.keycloak.adapters.undertow.ServletRequestAuthenticator;
|
||||
import org.keycloak.adapters.undertow.UndertowHttpFacade;
|
||||
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class WildflyAuthenticationMechanism extends ServletKeycloakAuthMech {
|
||||
|
||||
public WildflyAuthenticationMechanism(KeycloakDeployment deployment,
|
||||
UndertowUserSessionManagement userSessionManagement,
|
||||
ConfidentialPortManager portManager) {
|
||||
super(deployment, userSessionManagement, portManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServletRequestAuthenticator createRequestAuthenticator(HttpServerExchange exchange, SecurityContext securityContext, UndertowHttpFacade facade) {
|
||||
return new WildflyRequestAuthenticator(facade, deployment,
|
||||
portManager.getConfidentialPort(exchange), securityContext, exchange, userSessionManagement);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.keycloak.adapters.wildfly;
|
||||
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.undertow.KeycloakServletExtension;
|
||||
import org.keycloak.adapters.undertow.ServletKeycloakAuthMech;
|
||||
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class WildflyKeycloakServletExtension extends KeycloakServletExtension {
|
||||
protected static Logger log = Logger.getLogger(WildflyKeycloakServletExtension.class);
|
||||
|
||||
@Override
|
||||
protected ServletKeycloakAuthMech createAuthenticationMechanism(DeploymentInfo deploymentInfo, KeycloakDeployment deployment,
|
||||
UndertowUserSessionManagement userSessionManagement) {
|
||||
log.info("creating WildflyAuthenticationMechanism");
|
||||
return new WildflyAuthenticationMechanism(deployment, userSessionManagement, deploymentInfo.getConfidentialPortManager());
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package org.keycloak.adapters.wildfly;
|
||||
|
||||
import io.undertow.security.api.SecurityContext;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.security.NestableGroup;
|
||||
import org.jboss.security.SecurityConstants;
|
||||
import org.jboss.security.SecurityContextAssociation;
|
||||
import org.jboss.security.SimpleGroup;
|
||||
import org.jboss.security.SimplePrincipal;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
|
||||
import org.keycloak.adapters.undertow.ServletRequestAuthenticator;
|
||||
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import java.security.Principal;
|
||||
import java.security.acl.Group;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class WildflyRequestAuthenticator extends ServletRequestAuthenticator {
|
||||
protected static Logger log = Logger.getLogger(WildflyRequestAuthenticator.class);
|
||||
|
||||
public WildflyRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
|
||||
SecurityContext securityContext, HttpServerExchange exchange,
|
||||
UndertowUserSessionManagement userSessionManagement) {
|
||||
super(facade, deployment, sslRedirectPort, securityContext, exchange, userSessionManagement);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
|
||||
super.propagateKeycloakContext(account);
|
||||
SecurityInfoHelper.propagateSessionInfo(account);
|
||||
log.info("propagate security context to wildfly");
|
||||
Subject subject = new Subject();
|
||||
Set<Principal> principals = subject.getPrincipals();
|
||||
principals.add(account.getPrincipal());
|
||||
Group[] roleSets = getRoleSets(account.getRoles());
|
||||
for (int g = 0; g < roleSets.length; g++) {
|
||||
Group group = roleSets[g];
|
||||
String name = group.getName();
|
||||
Group subjectGroup = createGroup(name, principals);
|
||||
if (subjectGroup instanceof NestableGroup) {
|
||||
/* A NestableGroup only allows Groups to be added to it so we
|
||||
need to add a SimpleGroup to subjectRoles to contain the roles
|
||||
*/
|
||||
SimpleGroup tmp = new SimpleGroup("Roles");
|
||||
subjectGroup.addMember(tmp);
|
||||
subjectGroup = tmp;
|
||||
}
|
||||
// Copy the group members to the Subject group
|
||||
Enumeration<? extends Principal> members = group.members();
|
||||
while (members.hasMoreElements()) {
|
||||
Principal role = (Principal) members.nextElement();
|
||||
subjectGroup.addMember(role);
|
||||
}
|
||||
}
|
||||
// add the CallerPrincipal group if none has been added in getRoleSets
|
||||
Group callerGroup = new SimpleGroup(SecurityConstants.CALLER_PRINCIPAL_GROUP);
|
||||
callerGroup.addMember(account.getPrincipal());
|
||||
principals.add(callerGroup);
|
||||
org.jboss.security.SecurityContext sc = SecurityContextAssociation.getSecurityContext();
|
||||
Principal userPrincipal = getPrincipal(subject);
|
||||
sc.getUtil().createSubjectInfo(userPrincipal, account, subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is
|
||||
* considered or the single subject inside the CallerPrincipal group.
|
||||
*
|
||||
* @param subject
|
||||
* @return the authenticated subject
|
||||
*/
|
||||
protected Principal getPrincipal(Subject subject) {
|
||||
Principal principal = null;
|
||||
Principal callerPrincipal = null;
|
||||
if (subject != null) {
|
||||
Set<Principal> principals = subject.getPrincipals();
|
||||
if (principals != null && !principals.isEmpty()) {
|
||||
for (Principal p : principals) {
|
||||
if (!(p instanceof Group) && principal == null) {
|
||||
principal = p;
|
||||
}
|
||||
if (p instanceof Group) {
|
||||
Group g = Group.class.cast(p);
|
||||
if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
|
||||
Enumeration<? extends Principal> e = g.members();
|
||||
if (e.hasMoreElements())
|
||||
callerPrincipal = e.nextElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return callerPrincipal == null ? principal : callerPrincipal;
|
||||
}
|
||||
|
||||
protected Group createGroup(String name, Set<Principal> principals) {
|
||||
Group roles = null;
|
||||
Iterator<Principal> iter = principals.iterator();
|
||||
while (iter.hasNext()) {
|
||||
Object next = iter.next();
|
||||
if ((next instanceof Group) == false)
|
||||
continue;
|
||||
Group grp = (Group) next;
|
||||
if (grp.getName().equals(name)) {
|
||||
roles = grp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If we did not find a group create one
|
||||
if (roles == null) {
|
||||
roles = new SimpleGroup(name);
|
||||
principals.add(roles);
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
protected Group[] getRoleSets(Collection<String> roleSet) {
|
||||
SimpleGroup roles = new SimpleGroup("Roles");
|
||||
Group[] roleSets = {roles};
|
||||
for (String role : roleSet) {
|
||||
roles.addMember(new SimplePrincipal(role));
|
||||
}
|
||||
return roleSets;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.adapters.wildfly.WildflyKeycloakServletExtension
|
|
@ -48,11 +48,30 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||
// Seems wise to have this run after INSTALL_WAR_DEPLOYMENT
|
||||
public static final int PRIORITY = Phase.INSTALL_WAR_DEPLOYMENT - 1;
|
||||
|
||||
// not sure if we need this yet, keeping here just in case
|
||||
protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) {
|
||||
String deploymentName = deploymentUnit.getName();
|
||||
if (!service.isKeycloakDeployment(deploymentName)) {
|
||||
return;
|
||||
}
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) return;
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) return;
|
||||
|
||||
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
|
||||
if (loginConfig == null || !loginConfig.getAuthMethod().equalsIgnoreCase("KEYCLOAK")) {
|
||||
return;
|
||||
}
|
||||
|
||||
webMetaData.setSecurityDomain("keycloak");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||
String deploymentName = deploymentUnit.getName();
|
||||
|
||||
String deploymentName = deploymentUnit.getName();
|
||||
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.find(phaseContext.getServiceRegistry());
|
||||
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: " + deploymentName);
|
||||
if (service.isKeycloakDeployment(deploymentName)) {
|
||||
|
@ -61,6 +80,9 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||
}
|
||||
|
||||
// FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
|
||||
|
||||
// todo notsure if we need this
|
||||
// addSecurityDomain(deploymentUnit, service);
|
||||
}
|
||||
|
||||
private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, String deploymentName, KeycloakAdapterConfigService service) {
|
||||
|
|
|
@ -36,7 +36,9 @@ import org.keycloak.subsystem.logging.KeycloakLogger;
|
|||
*/
|
||||
public class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
|
||||
|
||||
private static final ModuleIdentifier KEYCLOAK_WILDFLY_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-adapter");
|
||||
private static final ModuleIdentifier KEYCLOAK_UNDERTOW_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-undertow-adapter");
|
||||
private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
|
||||
private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-adapter-core");
|
||||
private static final ModuleIdentifier KEYCLOAK_CORE = ModuleIdentifier.create("org.keycloak.keycloak-core");
|
||||
//private static final ModuleIdentifier APACHE_HTTPCOMPONENTS = ModuleIdentifier.create("org.apache.httpcomponents");
|
||||
|
@ -51,7 +53,9 @@ public class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
|
|||
final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
|
||||
final ModuleLoader moduleLoader = Module.getBootModuleLoader();
|
||||
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, true, false));
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_WILDFLY_ADAPTER, false, false, true, false));
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, false, false));
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
|
||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, false, false, false, false));
|
||||
//moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, APACHE_HTTPCOMPONENTS, false, false, true, false));
|
||||
|
|
|
@ -218,7 +218,7 @@ public class AdminService {
|
|||
}
|
||||
}
|
||||
|
||||
@Path("isLoggedIn.js")
|
||||
@Path("isLoggedIn.js")
|
||||
@GET
|
||||
@Produces("application/javascript")
|
||||
@NoCache
|
||||
|
|
Loading…
Reference in a new issue