From c4765df4de69c1b0dee596a38179b3363ceb3604 Mon Sep 17 00:00:00 2001 From: Lin Gao Date: Mon, 6 Mar 2023 11:22:43 +0800 Subject: [PATCH] [WFLY-17649] Adds a detailed quickstart for bearer authentication use cases --- ejb-security-jwt/README.adoc | 352 ++++++++++++++++++ ejb-security-jwt/app-one/ear/pom.xml | 105 ++++++ .../application/META-INF/jboss-ejb-client.xml | 24 ++ ejb-security-jwt/app-one/ejb/pom.xml | 94 +++++ .../security/jwt/appone/JWTSecurityEJBA.java | 95 +++++ .../jwt/appone/JWTSecurityEJBRemoteA.java | 29 ++ ejb-security-jwt/app-one/pom.xml | 44 +++ ejb-security-jwt/app-two/pom.xml | 89 +++++ .../security/jwt/apptwo/JWTSecurityEJBB.java | 49 +++ .../jwt/apptwo/JWTSecurityEJBRemoteB.java | 31 ++ ejb-security-jwt/client/pom.xml | 108 ++++++ .../security/jwt/client/RemoteEJBClient.java | 80 ++++ .../resources/META-INF/wildfly-config.xml | 40 ++ .../configure-ejb-outbound-connection.cli | 17 + ejb-security-jwt/configure-elytorn.cli | 21 ++ ejb-security-jwt/configure_server.adoc | 43 +++ ejb-security-jwt/install-role-decoder.cli | 7 + ejb-security-jwt/json-role-decoder/pom.xml | 57 +++ .../elytron/roledecoders/JsonRoleDecoder.java | 81 ++++ .../keycloak/realm/realm-import.json | 129 +++++++ ejb-security-jwt/pom.xml | 155 ++++++++ ejb-security-jwt/restore-configuration.cli | 51 +++ 22 files changed, 1701 insertions(+) create mode 100644 ejb-security-jwt/README.adoc create mode 100644 ejb-security-jwt/app-one/ear/pom.xml create mode 100644 ejb-security-jwt/app-one/ear/src/main/application/META-INF/jboss-ejb-client.xml create mode 100644 ejb-security-jwt/app-one/ejb/pom.xml create mode 100644 ejb-security-jwt/app-one/ejb/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/appone/JWTSecurityEJBA.java create mode 100644 ejb-security-jwt/app-one/ejb/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/appone/JWTSecurityEJBRemoteA.java create mode 100644 ejb-security-jwt/app-one/pom.xml create mode 100644 ejb-security-jwt/app-two/pom.xml create mode 100644 ejb-security-jwt/app-two/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/apptwo/JWTSecurityEJBB.java create mode 100644 ejb-security-jwt/app-two/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/apptwo/JWTSecurityEJBRemoteB.java create mode 100644 ejb-security-jwt/client/pom.xml create mode 100644 ejb-security-jwt/client/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/client/RemoteEJBClient.java create mode 100644 ejb-security-jwt/client/src/main/resources/META-INF/wildfly-config.xml create mode 100644 ejb-security-jwt/configure-ejb-outbound-connection.cli create mode 100644 ejb-security-jwt/configure-elytorn.cli create mode 100644 ejb-security-jwt/configure_server.adoc create mode 100644 ejb-security-jwt/install-role-decoder.cli create mode 100644 ejb-security-jwt/json-role-decoder/pom.xml create mode 100644 ejb-security-jwt/json-role-decoder/src/main/java/org/wildfly/security/elytron/roledecoders/JsonRoleDecoder.java create mode 100644 ejb-security-jwt/keycloak/realm/realm-import.json create mode 100644 ejb-security-jwt/pom.xml create mode 100644 ejb-security-jwt/restore-configuration.cli diff --git a/ejb-security-jwt/README.adoc b/ejb-security-jwt/README.adoc new file mode 100644 index 0000000000..5b9ea1c129 --- /dev/null +++ b/ejb-security-jwt/README.adoc @@ -0,0 +1,352 @@ +include::../shared-doc/attributes.adoc[] + += ejb-security-jwt: EJB Security with bearer token authentication and authorization +:author: Lin Gao +:level: Advanced +:technologies: EJB, Security, Bearer, OIDC, JWT +:requires-multiple-servers: true + +[abstract] +The `ejb-security-jwt` quickstart uses EJB and `OAUTHBEARER` SASL mechanism to demonstrate how to access a secured EJB deployed to {productNameFull} from a remote Java client application, and how an EJB deployed in one {productNameFull} calls a secured EJB deployed in another {productNameFull} using the same authentication context. + +:standalone-server-type: default +:archiveType: ear +:oidcIdp: KeyCloak +:oidcIdpLink: https://www.keycloak.org/ +:serverNameBase: {jbossHomeName} + +== What is it? + +The `ejb-security-jwt` quickstart shows how to access a remote secured EJB from a remote Java client application. It demonstrates the use of EJB and `OAUTHBEARER` SASL mechanism in {productNameFull}. It uses {oidcIdpLink}[{oidcIdp}] as the OIDC Identity Provider(IDP) with a predefined realm setup for this quickstart. + +This example consists of the following Maven projects, each with a shared parent: + +[cols="40%,60%",options="headers"] +|=== +|Project |Description + +a|`app-one` +a|An `EAR` application that can be called by the `client`. It shows current caller principal and if it has role of `user` and `admin`, it can also call the EJB deployed in a separate server from `app-two` using the same authentication context. + +[[ejba]] We can call the EJB in `app-one` as `EJBA` in this document, it will be deployed into {productName}_1 server. + +a|`app-two` +a|An `EJB` application that shows current caller principal and if it has role of `user` and `admin`. + +[[ejbb]] We can call the EJB in `app-two` as `EJBB` in this document, it will be deployed into {productName}_2 server. + +a|`json-role-decoder` +|An Elytron's https://wildfly-security.github.io/wildfly-elytron/documentation/api/current/org/wildfly/security/authz/RoleDecoder.html[RoleDecoder] implementation which extracts roles from a Json format information, like a JWT claim by specifying the https://www.rfc-editor.org/rfc/rfc6901[JsonPointer]. + +a|`client` +|This project builds the standalone client and executes it. +|=== + +The root `pom.xml` builds each of the subprojects in an appropriate order. + +The server configuration is done using CLI batch scripts located in the root of this quickstart folder. + +// System Requirements +include::../shared-doc/system-requirements.adoc[leveloffset=+1] +// Use of {jbossHomeName} +include::../shared-doc/use-of-jboss-home-name.adoc[leveloffset=+1] + +== Start A {oidcIdp} Server With Predefined Realm + +This quickstart needs an OIDC IDP from which to get the bearer token from. We use {oidcIdp} for this quickstart. + +{oidcIdp} supports starting a Docker container with a predefined realm setup + +.Start {oidcIdp} Server with realm setup using `keycloak/realm/realm-import.json`: +[source, subs="+quotes,attributes+"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt +$ docker run --rm -p 8180:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin -v $(pwd)/keycloak/realm:/opt/keycloak/data/import quay.io/keycloak/keycloak:21.0.0 start-dev --import-realm +---- + +This predefined realm has the following configuration: + +A realm called `jwt-realm` is created, there is a client called `app` created under this realm, and there are 2 users created: + +[cols="30%,30%,40%",options="headers"] +|=== +|UserName | Password | Realm Roles +|quickstartUser |quickstartPwd1! | user +|admin |admin | user, admin +|=== + +The `app` client in {oidcIdp} realm has `directAccessGrantsEnabled` to `true`, which allows regular account to log in instead of service account only. + +You can also set up the realm manually, please refer to {oidcIdpLink} for detail. + +== Build the Project +. Navigate to the quickstart directory to build the project ++ +[source, subs="+quotes,attributes+", options="nowrap"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt +---- + +. Build the project ++ +[source,options="nowrap"] +---- +$ mvn clean install +---- + +== Demonstrations + +There are 2 parts in this quickstart, the first part is showing how the remote EJB client calls EJB deployed in {productName}_1 server using `OAUTHBEARER` SASL mechanism, the second one is showing how the remote EJB client calls EJB deployed in {productName}_1 server which in turn invokes EJB deployed in {productName}_2 server with the authentication context propagated. + +Let's start the first part. + +=== Remote EJB Client calls EJB using `OAUTHBEARER` mechanism + +:jbossHomeName: {serverNameBase}_1 +[#start-server-1] +include::../shared-doc/start-the-standalone-server.adoc[leveloffset=+3] + +:hostController: --controller=localhost:9990 +include::./configure_server.adoc[] + +==== Deploy EJB in `app-one` to {productName}_1 Server +The EJB `JWTSecurityEJBRemoteA` in `app-one` has only one method declared: + +[source, java] +---- +String securityInfo(boolean recursive); +---- +which will return a String including the caller principal and the authorization check result if it has role of `user` and `admin`. + +Open a terminal, and use the following command to deploy EJB in `app-one` module to {productName}_1 Server: + +[source, bash, subs="+quotes,attributes+"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-one/ejb/ +$ mvn wildfly:deploy +---- + +==== Configure and run EJB client application + +The remote EJB client application will invoke link:#ejba[EJBA] twice. The first invocation reads the authentication configuration from `META-INF/wildfly-config.xml` where the `quickstartUser` and it's password is specified to get the bearer token from a specified token endpoint url. The second invocation uses programmatic authentication switching to use `admin` user. The only difference between 2 invocations is that they are using different user. + +It has a system property called `recursive` in the client to decide if link:#ejba[EJBA] should call link:#ejbb[EJBB], we won't call EJBB in the first part, so we leave it as the default value: `false`. + +Open a terminal, run the following command to run the remote EJB client application: + +[source, bash] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt/client +$ mvn exec:exec +---- + +==== Investigate the Console Output + +When the client application runs, it performs the following steps: + +. Obtains a stateless session bean instance. +. Sends method invocations to the stateless bean to get current security information from server side. + +The following output is displayed in the terminal window: + +[source, bash] +---- +* * * * * * * * * * * recursive: false * * * * * * * * * * * * * * * * * * * + +Security Info in JWTSecurityEJBA: + Caller: [quickstartuser] + quickstartuser has user role: (true) + quickstartuser has admin role: (false) + +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +* * * * * * * Below are invoked using admin account * * * * * * + +* * * * * * * * * * * recursive: false * * * * * * * * * * * * * * * * * * * + +Security Info in JWTSecurityEJBA: + Caller: [admin] + admin has user role: (true) + admin has admin role: (true) + +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +---- + +We can see that the user `quickstartUser` has the `user` role, but does not have `admin` role, the user `admin` has both roles. It can be confirmed in the {oidcIdp} server. + + +Now let's jump to the second part. + +=== Propagate the authentication context using `OAUTHBEARER` mechanism for EJB calls EJB. + +In this part, we will demonstrate how the link:#ejba[EJBA] calls link:#ejbb[EJBB], and how to configure the {productName}_1 to propagate the authentication context from remote client using `OAUTHBEARER` SASL mechanism. + +==== Configure remote outbound connection in {productName}_1 Server +We need to create a `remote-outbound-connection` in remoting subsystem of {productName}_1 server with the authentication context specified in elytron subsystem to propagate. + +Open a terminal, and run the following command: + +[source,subs="+quotes,attributes+",options="nowrap"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt +$ __${jbossHomeName}__/bin/jboss-cli.sh -c --file=configure-ejb-outbound-connection.cli +---- + +You will see the following configuration in standalone.xml of {productName}_1 server: + +[source, xml] +---- + + + + + + + + + + + + + + + + + +---- + +==== Redeploy EJB in `app-one` to {productName}_1 Server +In the first part, we deployed EJB `jar` to {productName}_1 server for the demonstration, to be able to make link:#ejba[EJBA] calls link:#ejbb[EJBB], we need to package the EJBs in `EAR` to have ejb client dependency of EJBB inside. + +Open a terminal, and run the following command to undeploy the link:#ejba[EJBA] in jar archive, and deploy it again using ear archive: + +[source, bash, subs="+quotes,attributes+"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-one/ejb +$ mvn wildfly:undeploy +$ cd ../ear +$ mvn wildfly:deploy +---- + +There is one more file `META-INF/jbos-ejb-client.xml` in the `EAR` deployment, which specifies the outbound-connection reference to `ejb-outbound-connection`, it matches what was defined above, and it points to the HTTP port: `8280` which is used by the {productName}_2 server. + +==== Starts {productName}_2 server + +:jbossHomeName: {serverNameBase}_2 +Now it's time to start {productName}_2 server. Open a terminal, and like what you did to start link:#start-server-1[{productName}_1 server], you need to specify a port offset to avoid port conflicts: `-Djboss.socket.binding.port-offset=200`, this makes the HTTP port opened by {productName}_2 server becomes `8280`: + +.Example of starting {productName}_2 server in Linux system: +[source,subs="+quotes,attributes+"] +---- +$ __{jbossHomeName}__/bin/standalone.sh -Djboss.socket.binding.port-offset=200 +---- + +:hostController: --controller=localhost:10190 +include::./configure_server.adoc[] + +==== Deploy EJB in `app-two` to {productName}_2 Server +The EJB `JWTSecurityEJBRemoteB` in `app-two` has only one method declared: + +[source, java] +---- +String securityInfo(); +---- +which will return a String including the caller principal and the authorization check result if it has role of `user` and `admin`. + +Open a terminal, and use the following command to deploy EJB in `app-two` module to {productName}_2 Server: + +[source, bash, subs="+quotes,attributes+"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-two +$ mvn wildfly:deploy -Dwildfly.port=10190 +---- + +==== Configure and run EJB client application +Now let's run the remote client again, we will specify `-Drecursive=true` to let EJBA calls EJBB this time. + +Open a terminal, and run the following command: + +[source, bash] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt/client +$ mvn exec:exec -Drecursive=true +---- + + +==== Investigate the Console Output +The following output is displayed in the terminal window: + +[source, bash] +---- +* * * * * * * * * * * recursive: true * * * * * * * * * * * * * * * * * * * + +Security Info in JWTSecurityEJBA: + Caller: [quickstartuser] + quickstartuser has user role: (true) + quickstartuser has admin role: (false) + +=========== Below are invocation from remote EJB in app-two =========== +Security Info in JWTSecurityEJBB: + Caller: [quickstartuser] + quickstartuser has user role: (true) + quickstartuser has admin role: (false) + +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +* * * * * * * Below are invoked using admin account * * * * * * + +* * * * * * * * * * * recursive: true * * * * * * * * * * * * * * * * * * * + +Security Info in JWTSecurityEJBA: + Caller: [admin] + admin has user role: (true) + admin has admin role: (true) + +=========== Below are invocation from remote EJB in app-two =========== +Security Info in JWTSecurityEJBB: + Caller: [admin] + admin has user role: (true) + admin has admin role: (true) + +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +---- + +We can see that the user `quickstartUser` has the `user` role, but does not have `admin` role, the user `admin` has both roles. It can be confirmed in the {oidcIdp} server. + +We can also see that the invocation from EJBA to EJBB uses the same authentication context as what is used in remote client calls EJBA. + +== Undeploy the Archives + +To undeploy the components from the {productName} servers: + +. Navigate to the `app-one/ear` subdirectory: ++ +[source,options="nowrap"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-one/ear/ +$ mvn wildfly:undeploy +---- + +. Navigate to the `app-two` subdirectory: ++ +[source,options="nowrap"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-two +$ mvn wildfly:undeploy -Dwildfly.port=10190 +---- + +== Restore the servers +After un-deployed the archives from both servers, you can restore the server configurations: + +[source,subs="+quotes,attributes+",options="nowrap"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt +$ __${serverNameBase}_1__/bin/jboss-cli.sh -c --file=restore-configuration.cli +$ __${serverNameBase}_2__/bin/jboss-cli.sh -c {hostController} --file=restore-configuration.cli +---- + + +// Debug the Application +include::../shared-doc/debug-the-application.adoc[leveloffset=+1] diff --git a/ejb-security-jwt/app-one/ear/pom.xml b/ejb-security-jwt/app-one/ear/pom.xml new file mode 100644 index 0000000000..de520d0a5f --- /dev/null +++ b/ejb-security-jwt/app-one/ear/pom.xml @@ -0,0 +1,105 @@ + + + + 4.0.0 + + + org.wildfly.quickstarts + ejb-security-jwt-app-one + 28.0.0.Beta1-SNAPSHOT + + ejb-security-jwt-app-one-ear + ear + Quickstart: ejb-security-jwt - app-one-ear + This project has the EJBA for the demonstration, this is the ear artifact. + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + + ${project.groupId} + ejb-security-jwt-app-one-ejb + ${project.version} + ejb + + + + + + ${project.parent.artifactId} + + + META-INF + src/main/resources/META-INF + + + jboss-ejb-client.xml + + + + + + + org.wildfly.plugins + wildfly-maven-plugin + + false + ${project.build.finalName}.ear + + + + maven-ear-plugin + + Application Main + A simple quickstart application to demonstrate the + server-server communication + 8 + lib + true + + true + + + ${project.groupId} + ejb-security-jwt-app-one-ejb + ejb.jar + + + + ${project.groupId} + ejb-security-jwt-app-two + lib + + + + false + + + + + + diff --git a/ejb-security-jwt/app-one/ear/src/main/application/META-INF/jboss-ejb-client.xml b/ejb-security-jwt/app-one/ear/src/main/application/META-INF/jboss-ejb-client.xml new file mode 100644 index 0000000000..8b9a0ea658 --- /dev/null +++ b/ejb-security-jwt/app-one/ear/src/main/application/META-INF/jboss-ejb-client.xml @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/ejb-security-jwt/app-one/ejb/pom.xml b/ejb-security-jwt/app-one/ejb/pom.xml new file mode 100644 index 0000000000..ac9c1a018d --- /dev/null +++ b/ejb-security-jwt/app-one/ejb/pom.xml @@ -0,0 +1,94 @@ + + + + 4.0.0 + + org.wildfly.quickstarts + ejb-security-jwt-app-one + 28.0.0.Beta1-SNAPSHOT + ../pom.xml + + ejb-security-jwt-app-one-ejb + ejb + Quickstart: ejb-security-jwt - app-one-ejb + This project has the EJBA for the demonstration, this is the EJB artifact. + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + + + jakarta.annotation + jakarta.annotation-api + provided + + + + + jakarta.ejb + jakarta.ejb-api + provided + + + org.jboss.ejb3 + jboss-ejb3-ext-api + provided + + + ${project.groupId} + ejb-security-jwt-app-two + ${project.version} + ejb-client + + + + + + + + org.wildfly.plugins + wildfly-maven-plugin + + false + + + + org.apache.maven.plugins + maven-ejb-plugin + + 3.2 + + true + + org/jboss/as/quickstarts/ejb/security/jwt/appone/JWTSecurityEJBA* + + + + + + + + diff --git a/ejb-security-jwt/app-one/ejb/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/appone/JWTSecurityEJBA.java b/ejb-security-jwt/app-one/ejb/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/appone/JWTSecurityEJBA.java new file mode 100644 index 0000000000..f908388c81 --- /dev/null +++ b/ejb-security-jwt/app-one/ejb/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/appone/JWTSecurityEJBA.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 The original author or authors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of Apache License v2.0 which + * accompanies this distribution. + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * 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.jboss.as.quickstarts.ejb.security.jwt.appone; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.ejb.Remote; +import jakarta.ejb.SessionContext; +import jakarta.ejb.Stateless; +import org.jboss.ejb3.annotation.SecurityDomain; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import java.lang.reflect.Method; +import java.util.Hashtable; + +/** + * @author Lin Gao + */ +@Stateless +@Remote(JWTSecurityEJBRemoteA.class) +@SecurityDomain("jwt-app-domain") +@PermitAll +public class JWTSecurityEJBA implements JWTSecurityEJBRemoteA { + + @Resource + private SessionContext context; + + private InitialContext ctx; + + @PostConstruct + public void setup() { + try { + final Hashtable p = new Hashtable<>(); + p.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); + this.ctx = new InitialContext(p); + } catch (NamingException e) { + throw new RuntimeException("Could not initialize context", e); + } + } + + @PreDestroy + public void shutdown() { + try { + if (this.ctx != null) this.ctx.close(); + } catch (NamingException e) { + throw new RuntimeException("Failed to close the context", e); + } + } + + @Override + public String securityInfo(boolean recursive) { + StringBuilder sb = new StringBuilder("Security Info in JWTSecurityEJBA: \n\tCaller: ["); + String caller = context.getCallerPrincipal() != null ? context.getCallerPrincipal().getName() : null; + sb.append(caller).append("]\n"); + sb.append("\t\t").append(caller).append(" has user role: (").append(context.isCallerInRole("user")).append(")\n"); + sb.append("\t\t").append(caller).append(" has admin role: (").append(context.isCallerInRole("admin")).append(")\n"); + System.out.println("\nSecurity Info(A): \n" + sb + "\n"); + if (recursive) { + sb.append("\n=========== Below are invocation from remote EJB in app-two ===========\n"); + sb.append(securityInfoFromB()); + } + return sb.toString(); + } + + private String securityInfoFromB() { + try { + String lookup = "ejb:/ejb-security-jwt-app-two/JWTSecurityEJBB!org.jboss.as.quickstarts.ejb.security.jwt.apptwo.JWTSecurityEJBRemoteB"; + // using reflection just for demonstration making it showing 2 steps easier + Object remote = ctx.lookup(lookup); + Method securityInfoMethod = remote.getClass().getDeclaredMethod("securityInfo"); + return securityInfoMethod.invoke(remote).toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/ejb-security-jwt/app-one/ejb/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/appone/JWTSecurityEJBRemoteA.java b/ejb-security-jwt/app-one/ejb/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/appone/JWTSecurityEJBRemoteA.java new file mode 100644 index 0000000000..9fd21f08f6 --- /dev/null +++ b/ejb-security-jwt/app-one/ejb/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/appone/JWTSecurityEJBRemoteA.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 The original author or authors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of Apache License v2.0 which + * accompanies this distribution. + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * 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.jboss.as.quickstarts.ejb.security.jwt.appone; + +/** + * @author Lin Gao + */ +public interface JWTSecurityEJBRemoteA { + + /** + * @return the security information in the current security context + */ + String securityInfo(boolean recursive); + +} diff --git a/ejb-security-jwt/app-one/pom.xml b/ejb-security-jwt/app-one/pom.xml new file mode 100644 index 0000000000..ca774c6783 --- /dev/null +++ b/ejb-security-jwt/app-one/pom.xml @@ -0,0 +1,44 @@ + + + + 4.0.0 + + + org.wildfly.quickstarts + ejb-security-jwt + 28.0.0.Beta1-SNAPSHOT + ../pom.xml + + ejb-security-jwt-app-one + pom + Quickstart: ejb-security-jwt - app-one + A project has EJBA for the demonstration, this is the POM artifact. + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + ejb + ear + + diff --git a/ejb-security-jwt/app-two/pom.xml b/ejb-security-jwt/app-two/pom.xml new file mode 100644 index 0000000000..a842c7671b --- /dev/null +++ b/ejb-security-jwt/app-two/pom.xml @@ -0,0 +1,89 @@ + + + + 4.0.0 + + org.wildfly.quickstarts + ejb-security-jwt + 28.0.0.Beta1-SNAPSHOT + ../pom.xml + + ejb-security-jwt-app-two + ejb + Quickstart: ejb-security-jwt - app-two + This project has the EJBB for the demonstration + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + + + jakarta.annotation + jakarta.annotation-api + provided + + + + + jakarta.ejb + jakarta.ejb-api + provided + + + org.jboss.ejb3 + jboss-ejb3-ext-api + provided + + + + + + + + org.wildfly.plugins + wildfly-maven-plugin + + false + ${project.build.finalName}.jar + + + + org.apache.maven.plugins + maven-ejb-plugin + + 3.2 + + true + + org/jboss/as/quickstarts/ejb/security/jwt/apptwo/JWTSecurityEJBB* + + + + + + + + diff --git a/ejb-security-jwt/app-two/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/apptwo/JWTSecurityEJBB.java b/ejb-security-jwt/app-two/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/apptwo/JWTSecurityEJBB.java new file mode 100644 index 0000000000..3e96ebe7ac --- /dev/null +++ b/ejb-security-jwt/app-two/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/apptwo/JWTSecurityEJBB.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 The original author or authors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of Apache License v2.0 which + * accompanies this distribution. + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * 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.jboss.as.quickstarts.ejb.security.jwt.apptwo; + +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.ejb.Remote; +import jakarta.ejb.SessionContext; +import jakarta.ejb.Stateless; +import org.jboss.ejb3.annotation.SecurityDomain; + +/** + * @author Lin Gao + */ +@Stateless +@Remote(JWTSecurityEJBRemoteB.class) +@SecurityDomain("jwt-app-domain") +@PermitAll +public class JWTSecurityEJBB implements JWTSecurityEJBRemoteB { + + @Resource + private SessionContext context; + + @Override + public String securityInfo() { + StringBuilder sb = new StringBuilder("Security Info in JWTSecurityEJBB: \n\tCaller: ["); + String caller = context.getCallerPrincipal() != null ? context.getCallerPrincipal().getName() : null; + sb.append(caller).append("]\n"); + sb.append("\t\t").append(caller).append(" has user role: (").append(context.isCallerInRole("user")).append(")\n"); + sb.append("\t\t").append(caller).append(" has admin role: (").append(context.isCallerInRole("admin")).append(")\n"); + System.out.println("\nSecurity Info(B): \n" + sb + "\n"); + return sb.toString(); + } + +} diff --git a/ejb-security-jwt/app-two/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/apptwo/JWTSecurityEJBRemoteB.java b/ejb-security-jwt/app-two/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/apptwo/JWTSecurityEJBRemoteB.java new file mode 100644 index 0000000000..11eb1ff6c1 --- /dev/null +++ b/ejb-security-jwt/app-two/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/apptwo/JWTSecurityEJBRemoteB.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 The original author or authors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of Apache License v2.0 which + * accompanies this distribution. + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * 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.jboss.as.quickstarts.ejb.security.jwt.apptwo; + +/** + * @author Lin Gao + */ +public interface JWTSecurityEJBRemoteB { + + /** + * The security information contains the principal and if it has role of user and admin. + * + * @return the security information in the current security context + */ + String securityInfo(); + +} diff --git a/ejb-security-jwt/client/pom.xml b/ejb-security-jwt/client/pom.xml new file mode 100644 index 0000000000..07521117d4 --- /dev/null +++ b/ejb-security-jwt/client/pom.xml @@ -0,0 +1,108 @@ + + + + 4.0.0 + + + org.wildfly.quickstarts + ejb-security-jwt + 28.0.0.Beta1-SNAPSHOT + ../pom.xml + + ejb-security-jwt-client + jar + Quickstart: ejb-security-jwt - client + This project is the remote EJB client application. + + + false + + + + + + org.wildfly + wildfly-ejb-client-bom + pom + compile + + + jakarta.json + jakarta.json-api + + + org.eclipse.parsson + parsson + + + + ${project.groupId} + ejb-security-jwt-app-one + ${project.version} + ejb-client + + + + + + + + + + org.codehaus.mojo + exec-maven-plugin + + java + ${project.build.directory}/exec-working-directory + + -Drecursive=${recursive} + -classpath + + org.jboss.as.quickstarts.ejb.security.jwt.client.RemoteEJBClient + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + jar-with-dependencies + + + + org.jboss.as.quickstarts.ejb.security.jwt.client.RemoteEJBClient + + + true + + + + + + + + + diff --git a/ejb-security-jwt/client/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/client/RemoteEJBClient.java b/ejb-security-jwt/client/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/client/RemoteEJBClient.java new file mode 100644 index 0000000000..8ec20a2524 --- /dev/null +++ b/ejb-security-jwt/client/src/main/java/org/jboss/as/quickstarts/ejb/security/jwt/client/RemoteEJBClient.java @@ -0,0 +1,80 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2015, Red Hat, Inc. and/or its affiliates, and individual + * contributors by the @authors tag. See the copyright.txt in the + * distribution for a full listing of individual contributors. + * + * 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.jboss.as.quickstarts.ejb.security.jwt.client; + +import org.jboss.as.quickstarts.ejb.security.jwt.appone.JWTSecurityEJBRemoteA; +import org.wildfly.security.auth.client.AuthenticationConfiguration; +import org.wildfly.security.auth.client.AuthenticationContext; +import org.wildfly.security.auth.client.MatchRule; +import org.wildfly.security.credential.source.OAuth2CredentialSource; +import org.wildfly.security.sasl.SaslMechanismSelector; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import java.net.URL; +import java.util.Hashtable; + +/** + * + */ +public class RemoteEJBClient { + + private static final String CLIENT_ID = "app"; + private static final String CLIENT_SECRET = "secret"; + private static final String TOKEN_URL = "http://localhost:8180/realms/jwt-realm/protocol/openid-connect/token"; + private static final String ADMIN_NAME = "admin"; + private static final String ADMIN_PASS = "admin"; + + public static void main(String[] args) throws Exception { + boolean recursive = Boolean.getBoolean("recursive"); + invokeAppOneOnly(recursive); + + System.out.println("\n\n* * * * * * * Below are invoked using admin account * * * * * *\n"); + + // now lets programmatically set up an authentication context to switch to admin user + AuthenticationConfiguration superUser = AuthenticationConfiguration.empty() + .setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism("OAUTHBEARER")) + .useCredentials(OAuth2CredentialSource + .builder(new URL(TOKEN_URL)) + .clientCredentials(CLIENT_ID, CLIENT_SECRET) + .useResourceOwnerPassword(ADMIN_NAME, ADMIN_PASS) + .build()); + final AuthenticationContext authCtx = AuthenticationContext.empty().with(MatchRule.ALL, superUser); + AuthenticationContext.getContextManager().setThreadDefault(authCtx); + invokeAppOneOnly(recursive); + } + + private static void invokeAppOneOnly(boolean recursive) throws NamingException { + InitialContext context = jndiContext(); + final String jndiName = recursive ? + "ejb:ejb-security-jwt-app-one/ejb/JWTSecurityEJBA!" + JWTSecurityEJBRemoteA.class.getName() + : "ejb:/ejb-security-jwt-app-one-ejb/JWTSecurityEJBA!" + JWTSecurityEJBRemoteA.class.getName(); + JWTSecurityEJBRemoteA ejbRemoteA = (JWTSecurityEJBRemoteA)context.lookup(jndiName); + System.out.println("\n\n* * * * * * * * * * * recursive: " + recursive + " * * * * * * * * * * * * * * * * * * *\n"); + System.out.println(ejbRemoteA.securityInfo(recursive)); + System.out.println("\n* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n\n"); + context.close(); + } + + private static InitialContext jndiContext() throws NamingException { + final Hashtable jndiProperties = new Hashtable<>(); + jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory"); + jndiProperties.put(Context.PROVIDER_URL, "remote+http://localhost:8080"); + return new InitialContext(jndiProperties); + } +} diff --git a/ejb-security-jwt/client/src/main/resources/META-INF/wildfly-config.xml b/ejb-security-jwt/client/src/main/resources/META-INF/wildfly-config.xml new file mode 100644 index 0000000000..5139f875c0 --- /dev/null +++ b/ejb-security-jwt/client/src/main/resources/META-INF/wildfly-config.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ejb-security-jwt/configure-ejb-outbound-connection.cli b/ejb-security-jwt/configure-ejb-outbound-connection.cli new file mode 100644 index 0000000000..58e40b3e89 --- /dev/null +++ b/ejb-security-jwt/configure-ejb-outbound-connection.cli @@ -0,0 +1,17 @@ +batch + +# The following part is needed for ejb from server a to server b +# Add the authentication configuration and authentication context that will be used for outbound connections into server A +/subsystem=elytron/authentication-configuration=ejb-outbound-configuration:add(security-domain=jwt-domain,sasl-mechanism-selector="OAUTHBEARER") +/subsystem=elytron/authentication-context=ejb-outbound-context:add(match-rules=[{authentication-configuration=ejb-outbound-configuration}]) + +# Add the outbound socket binding +/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=ejb-outbound:add(host=localhost,port=8280) + +# Add the remote outbound connection +/subsystem=remoting/remote-outbound-connection=ejb-outbound-connection:add(outbound-socket-binding-ref=ejb-outbound, authentication-context=ejb-outbound-context) + +run-batch + +reload + diff --git a/ejb-security-jwt/configure-elytorn.cli b/ejb-security-jwt/configure-elytorn.cli new file mode 100644 index 0000000000..47a1f2ce63 --- /dev/null +++ b/ejb-security-jwt/configure-elytorn.cli @@ -0,0 +1,21 @@ +batch + +# Add a new token security realm to elytron for authentication using oauth2 introspection endpoint +/subsystem=elytron/token-realm=jwt-realm:add(principal-claim="username", oauth2-introspection={client-id=app, client-secret=secret, introspection-url="http://localhost:8180/realms/jwt-realm/protocol/openid-connect/token/introspect"}) + +# Add a new security domain, which uses the jwt security realm +/subsystem=elytron/security-domain=jwt-domain:add(realms=[{realm=jwt-realm,role-decoder=crd}],permission-mapper=default-permission-mapper,default-realm=jwt-realm) + +# Create sasl authentication factory that uses SASL OAUTHBEARER +/subsystem=elytron/sasl-authentication-factory=jwt-sasl-authentication:add(security-domain=jwt-domain, sasl-server-factory=configured,mechanism-configurations=[{mechanism-name="OAUTHBEARER",mechanism-realm-configurations=[{realm-name="jwt-realm"}]}]) + +# Update remoting subsystem to use jwt-sasl-authentication created above +/subsystem=remoting/http-connector=http-remoting-connector:write-attribute(name=sasl-authentication-factory,value=jwt-sasl-authentication) + +# Configure the EJB subsystem to use jwt-domain +/subsystem=ejb3/application-security-domain=jwt-app-domain:add(security-domain=jwt-domain) + +run-batch + +reload + diff --git a/ejb-security-jwt/configure_server.adoc b/ejb-security-jwt/configure_server.adoc new file mode 100644 index 0000000000..b0502715f9 --- /dev/null +++ b/ejb-security-jwt/configure_server.adoc @@ -0,0 +1,43 @@ +==== Install the RoleDecoder + +The custom RoleDecoder needs to be added to the server as a JBoss module first before it can be added to the elytron subsystem, use the `install-role-decoder.cli` for it. + +[source,subs="+quotes,attributes+",options="nowrap"] +---- +$ cd ${QUICKSTART_HOME}/ejb-security-jwt +$ __${jbossHomeName}__/bin/jboss-cli.sh -c {hostController} --file=install-role-decoder.cli +---- + +It adds a new JBoss module called `org.wildfly.security.elytron.roledecoders` with the resource of `json-role-decoder/target/ejb-security-jwt-json-role-decoder.jar`, and a new custom-role-decoder called `crd` with `json-path=>"/realm_access/roles"` configuration to extract realm roles from the JWT claim. + +==== Configure the {productName} Server +Use the following CLI to configure the {productName} Server: + +[source,subs="+quotes,attributes+",options="nowrap"] +---- +$ __${jbossHomeName}__/bin/jboss-cli.sh -c {hostController} --file=configure-elytron.cli +---- + +It creates an elytron security realm called `jwt-realm` with the following information: + +.The `jwt-realm` information in standalone.xml +[source, xml] +---- + + + +---- + +It creates an elytron security domain called `jwt-domain` with the default realm `jwt-realm` and role-decoder to `crd`. It also creates a `jwt-sasl-authentication` sasl-authentication-factory which uses the `jwt-domain` and `OAUTHBEARER` SASL mechanism. It updates remoting subsystem to use `jwt-sasl-authentication` created in previous step, and then creates a `application-security-domain` called `jwt-app-domain` in ejb3 subsystem, the latter matches the `@org.jboss.ejb3.annotation.SecurityDomain("jwt-app-domain")` in the EJB. + +.The `jwt-sasl-authentication` information in standalone.xml +[source, xml] +---- + + + + + + + +---- \ No newline at end of file diff --git a/ejb-security-jwt/install-role-decoder.cli b/ejb-security-jwt/install-role-decoder.cli new file mode 100644 index 0000000000..75b31e50ca --- /dev/null +++ b/ejb-security-jwt/install-role-decoder.cli @@ -0,0 +1,7 @@ +# Adds role decoder module into server +module add --name=org.wildfly.security.elytron.roledecoders --absolute-resources=json-role-decoder/target/ejb-security-jwt-json-role-decoder.jar --dependencies=org.wildfly.security.elytron-base,jakarta.json.api + +# Create a custom-role-decoder to decode the rols from jwt claim. +/subsystem=elytron/custom-role-decoder=crd:add(module=org.wildfly.security.elytron.roledecoders, class-name=org.wildfly.security.elytron.roledecoders.JsonRoleDecoder,configuration={json-path=>"/realm_access/roles"}) + + diff --git a/ejb-security-jwt/json-role-decoder/pom.xml b/ejb-security-jwt/json-role-decoder/pom.xml new file mode 100644 index 0000000000..6ba5553890 --- /dev/null +++ b/ejb-security-jwt/json-role-decoder/pom.xml @@ -0,0 +1,57 @@ + + + + 4.0.0 + + + org.wildfly.quickstarts + ejb-security-jwt + 28.0.0.Beta1-SNAPSHOT + ../pom.xml + + ejb-security-jwt-json-role-decoder + jar + Quickstart: ejb-security-jwt - json-role-decoder + This project is a customized Elytron RoleDecoder based on attribute in a json object + + + + + org.wildfly.bom + wildfly-ee-with-tools + import + pom + ${version.server.bom} + + + + + + + jakarta.json + jakarta.json-api + provided + + + org.wildfly.security + wildfly-elytron-auth-server + provided + + + + diff --git a/ejb-security-jwt/json-role-decoder/src/main/java/org/wildfly/security/elytron/roledecoders/JsonRoleDecoder.java b/ejb-security-jwt/json-role-decoder/src/main/java/org/wildfly/security/elytron/roledecoders/JsonRoleDecoder.java new file mode 100644 index 0000000000..f1c2015165 --- /dev/null +++ b/ejb-security-jwt/json-role-decoder/src/main/java/org/wildfly/security/elytron/roledecoders/JsonRoleDecoder.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 The original author or authors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of Apache License v2.0 which + * accompanies this distribution. + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * 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.wildfly.security.elytron.roledecoders; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonString; +import jakarta.json.JsonStructure; +import org.wildfly.security.authz.Attributes; +import org.wildfly.security.authz.AuthorizationIdentity; +import org.wildfly.security.authz.RoleDecoder; +import org.wildfly.security.authz.Roles; + +import java.io.StringReader; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A RoleDecoder to extract Roles from a JSON format data, normally comes from a JWT claim. + * + * @author Lin Gao + */ +public class JsonRoleDecoder implements RoleDecoder { + + private static final String JSON_PATH_KEY = "json-path"; + private static final String DEFAULT_JSON_PATH = "/realm_access/roles"; + + /** + * The Json path to locate the roles. It follows JsonPointer spec + * The first segment is used to retrieve from Elytron AuthorizationIdentity attributes. + */ + private String jsonPath = DEFAULT_JSON_PATH; + + /** + * This method is called by WildFly Elytron subsystem with specified configuration. + * + * @param configuration the configuration + */ + public void initialize(Map configuration) { + jsonPath = configuration.getOrDefault(JSON_PATH_KEY, DEFAULT_JSON_PATH); + if (!jsonPath.startsWith("/")) { + throw new IllegalArgumentException("jsonPath must start with '/'"); + } + } + + @Override + public Roles decodeRoles(AuthorizationIdentity identity) { + int idx = jsonPath.indexOf('/', 1); + String key = idx > 0 ? jsonPath.substring(1, idx) : jsonPath.substring(1); + Attributes.Entry entry = identity.getAttributes().get(key); + if (entry != null && entry.size() > 0) { + // if only one segment, key is the leaf + try { + JsonStructure jsonStructure = (JsonStructure)Json.createReader(new StringReader(entry.get(0))).readValue(); + String jsonPointer = jsonPath.substring(key.length() + 1); + JsonArray jsonArray = (JsonArray)jsonStructure.getValue(jsonPointer); + Set roles = new HashSet<>(); + jsonArray.forEach(jv -> roles.add(((JsonString)jv).getString())); + return Roles.fromSet(roles); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return Roles.NONE; + } +} diff --git a/ejb-security-jwt/keycloak/realm/realm-import.json b/ejb-security-jwt/keycloak/realm/realm-import.json new file mode 100644 index 0000000000..517e29ffc7 --- /dev/null +++ b/ejb-security-jwt/keycloak/realm/realm-import.json @@ -0,0 +1,129 @@ +{ + "realm": "jwt-realm", + "enabled": true, + "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", + "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "requiredCredentials": [ + "password" + ], + "users": [ + { + "username": "quickstartUser", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "quickstartPwd1!" + } + ], + "realmRoles": [ + "user" + ] + }, + { + "username": "admin", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "admin" + } + ], + "realmRoles": [ + "user", + "admin" + ], + "clientRoles": { + "realm-management": [ + "realm-admin" + ] + } + }, + { + "username": "service-account-app", + "enabled": true, + "serviceAccountClientId": "app", + "clientRoles": { + "app" : ["uma_protection"] + } + } + ], + "roles": { + "realm": [ + { + "name": "user", + "description": "User privileges" + }, + { + "name": "admin", + "description": "Administrator privileges" + }, + { + "name": "user_premium", + "description": "User Premium privileges" + } + ] + }, + "clients": [ + { + "clientId": "app", + "enabled": true, + "baseUrl": "http://localhost:8080/app", + "adminUrl": "http://localhost:8080/app", + "bearerOnly": false, + "redirectUris": [ + "http://localhost:8080/app/*" + ], + "webOrigins": [ + "+" + ], + "secret": "secret", + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Default Resource", + "type": "urn:test:resources:default", + "ownerManagedAccess": false, + "attributes": {}, + "_id": "175c35de-cd3d-4abb-a114-d8230aecab76", + "uris": [ + "/*" + ] + } + ], + "policies": [ + { + "name" : "Default Policy", + "description" : "A policy that grants access to any user", + "type" : "role", + "logic" : "POSITIVE", + "decisionStrategy" : "AFFIRMATIVE", + "config" : { + "roles": "[{\"id\":\"user\",\"required\":false}]" + } + }, + { + "id": "e7b698eb-1684-4f92-a07e-1bc0dfde5570", + "name": "Default Permission", + "description": "A permission that applies to the default resource type", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "defaultResourceType": "urn:test:resources:default", + "applyPolicies": "[\"Default Policy\"]" + } + } + ], + "scopes": [] + } + } + ] +} diff --git a/ejb-security-jwt/pom.xml b/ejb-security-jwt/pom.xml new file mode 100644 index 0000000000..78615bf1b3 --- /dev/null +++ b/ejb-security-jwt/pom.xml @@ -0,0 +1,155 @@ + + + + 4.0.0 + + org.wildfly.quickstarts + wildfly-quickstart-parent + + 2 + + + ejb-security-jwt + 28.0.0.Beta1-SNAPSHOT + pom + Quickstart: ejb-remote + This project demonstrates how to access an EJB from a remote client using OAUTHBEARER SASL mechanism + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + 27.0.0.Final + + + + + jboss-public-maven-repository + JBoss Public Maven Repository + https://repository.jboss.org/nexus/content/groups/public/ + + true + never + + + true + never + + default + + + redhat-ga-maven-repository + Red Hat GA Maven Repository + https://maven.repository.redhat.com/ga/ + + true + never + + + true + never + + default + + + + + jboss-public-maven-repository + JBoss Public Maven Repository + https://repository.jboss.org/nexus/content/groups/public/ + + true + + + true + + + + redhat-ga-maven-repository + Red Hat GA Maven Repository + https://maven.repository.redhat.com/ga/ + + true + + + true + + + + + + + + + org.wildfly.bom + wildfly-ee-with-tools + ${version.server.bom} + pom + import + + + org.wildfly.quickstarts + ejb-security-jwt-client + ${project.version} + + + org.wildfly.quickstarts + ejb-security-jwt-app-one + ${project.version} + + + org.wildfly.quickstarts + ejb-security-jwt-app-two + ${project.version} + + + + + + + json-role-decoder + client + app-one + app-two + + + + + + org.wildfly.plugins + wildfly-maven-plugin + + true + + + + + diff --git a/ejb-security-jwt/restore-configuration.cli b/ejb-security-jwt/restore-configuration.cli new file mode 100644 index 0000000000..71f1644ce8 --- /dev/null +++ b/ejb-security-jwt/restore-configuration.cli @@ -0,0 +1,51 @@ +# Batch script to restore the configuration that was modified to run the quickstart + +# Remove the remote outbound connection +if (outcome == success) of /subsystem=remoting/remote-outbound-connection=ejb-outbound-connection:read-resource + /subsystem=remoting/remote-outbound-connection=ejb-outbound-connection:remove +end-if + +# Remove the outbound socket binding +if (outcome == success) of /socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=ejb-outbound:read-resource + /socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=ejb-outbound:remove +end-if + +# Remove the authentication context +if (outcome == success) of /subsystem=elytron/authentication-context=ejb-outbound-context:read-resource + /subsystem=elytron/authentication-context=ejb-outbound-context:remove +end-if + +# Remove authentication configuration +if (outcome == success) of /subsystem=elytron/authentication-configuration=ejb-outbound-configuration:read-resource + /subsystem=elytron/authentication-configuration=ejb-outbound-configuration:remove +end-if + +batch + +# Remove ejb3 application-security-domain +/subsystem=ejb3/application-security-domain=jwt-app-domain:remove + +# Sets sasl-authentication-factory of http-remoting-connector back to application-sasl-authentication +/subsystem=remoting/http-connector=http-remoting-connector:write-attribute(name=sasl-authentication-factory,value=application-sasl-authentication) + +# Remove jwt-sasl-authentication +/subsystem=elytron/sasl-authentication-factory=jwt-sasl-authentication:remove + +# Remove the security domain +/subsystem=elytron/security-domain=jwt-domain:remove + +# Remove the security realm +/subsystem=elytron/token-realm=jwt-realm:remove + +# Remove the role-decoder module +/subsystem=elytron/custom-role-decoder=crd:remove + +module remove --name=org.wildfly.security.elytron.roledecoders + +# Run the batch commands +run-batch + +# Reload the server configuration +reload + +