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
+
+