Skip to content

Commit c4765df

Browse files
committed
[WFLY-17649] Adds a detailed quickstart for bearer authentication use cases
1 parent 5fbdacc commit c4765df

File tree

22 files changed

+1701
-0
lines changed

22 files changed

+1701
-0
lines changed

ejb-security-jwt/README.adoc

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
include::../shared-doc/attributes.adoc[]
2+
3+
= ejb-security-jwt: EJB Security with bearer token authentication and authorization
4+
:author: Lin Gao
5+
:level: Advanced
6+
:technologies: EJB, Security, Bearer, OIDC, JWT
7+
:requires-multiple-servers: true
8+
9+
[abstract]
10+
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.
11+
12+
:standalone-server-type: default
13+
:archiveType: ear
14+
:oidcIdp: KeyCloak
15+
:oidcIdpLink: https://www.keycloak.org/
16+
:serverNameBase: {jbossHomeName}
17+
18+
== What is it?
19+
20+
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.
21+
22+
This example consists of the following Maven projects, each with a shared parent:
23+
24+
[cols="40%,60%",options="headers"]
25+
|===
26+
|Project |Description
27+
28+
a|`app-one`
29+
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.
30+
31+
[[ejba]] We can call the EJB in `app-one` as `EJBA` in this document, it will be deployed into {productName}_1 server.
32+
33+
a|`app-two`
34+
a|An `EJB` application that shows current caller principal and if it has role of `user` and `admin`.
35+
36+
[[ejbb]] We can call the EJB in `app-two` as `EJBB` in this document, it will be deployed into {productName}_2 server.
37+
38+
a|`json-role-decoder`
39+
|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].
40+
41+
a|`client`
42+
|This project builds the standalone client and executes it.
43+
|===
44+
45+
The root `pom.xml` builds each of the subprojects in an appropriate order.
46+
47+
The server configuration is done using CLI batch scripts located in the root of this quickstart folder.
48+
49+
// System Requirements
50+
include::../shared-doc/system-requirements.adoc[leveloffset=+1]
51+
// Use of {jbossHomeName}
52+
include::../shared-doc/use-of-jboss-home-name.adoc[leveloffset=+1]
53+
54+
== Start A {oidcIdp} Server With Predefined Realm
55+
56+
This quickstart needs an OIDC IDP from which to get the bearer token from. We use {oidcIdp} for this quickstart.
57+
58+
{oidcIdp} supports starting a Docker container with a predefined realm setup
59+
60+
.Start {oidcIdp} Server with realm setup using `keycloak/realm/realm-import.json`:
61+
[source, subs="+quotes,attributes+"]
62+
----
63+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt
64+
$ 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
65+
----
66+
67+
This predefined realm has the following configuration:
68+
69+
A realm called `jwt-realm` is created, there is a client called `app` created under this realm, and there are 2 users created:
70+
71+
[cols="30%,30%,40%",options="headers"]
72+
|===
73+
|UserName | Password | Realm Roles
74+
|quickstartUser |quickstartPwd1! | user
75+
|admin |admin | user, admin
76+
|===
77+
78+
The `app` client in {oidcIdp} realm has `directAccessGrantsEnabled` to `true`, which allows regular account to log in instead of service account only.
79+
80+
You can also set up the realm manually, please refer to {oidcIdpLink} for detail.
81+
82+
== Build the Project
83+
. Navigate to the quickstart directory to build the project
84+
+
85+
[source, subs="+quotes,attributes+", options="nowrap"]
86+
----
87+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt
88+
----
89+
90+
. Build the project
91+
+
92+
[source,options="nowrap"]
93+
----
94+
$ mvn clean install
95+
----
96+
97+
== Demonstrations
98+
99+
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.
100+
101+
Let's start the first part.
102+
103+
=== Remote EJB Client calls EJB using `OAUTHBEARER` mechanism
104+
105+
:jbossHomeName: {serverNameBase}_1
106+
[#start-server-1]
107+
include::../shared-doc/start-the-standalone-server.adoc[leveloffset=+3]
108+
109+
:hostController: --controller=localhost:9990
110+
include::./configure_server.adoc[]
111+
112+
==== Deploy EJB in `app-one` to {productName}_1 Server
113+
The EJB `JWTSecurityEJBRemoteA` in `app-one` has only one method declared:
114+
115+
[source, java]
116+
----
117+
String securityInfo(boolean recursive);
118+
----
119+
which will return a String including the caller principal and the authorization check result if it has role of `user` and `admin`.
120+
121+
Open a terminal, and use the following command to deploy EJB in `app-one` module to {productName}_1 Server:
122+
123+
[source, bash, subs="+quotes,attributes+"]
124+
----
125+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-one/ejb/
126+
$ mvn wildfly:deploy
127+
----
128+
129+
==== Configure and run EJB client application
130+
131+
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.
132+
133+
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`.
134+
135+
Open a terminal, run the following command to run the remote EJB client application:
136+
137+
[source, bash]
138+
----
139+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt/client
140+
$ mvn exec:exec
141+
----
142+
143+
==== Investigate the Console Output
144+
145+
When the client application runs, it performs the following steps:
146+
147+
. Obtains a stateless session bean instance.
148+
. Sends method invocations to the stateless bean to get current security information from server side.
149+
150+
The following output is displayed in the terminal window:
151+
152+
[source, bash]
153+
----
154+
* * * * * * * * * * * recursive: false * * * * * * * * * * * * * * * * * * *
155+
156+
Security Info in JWTSecurityEJBA:
157+
Caller: [quickstartuser]
158+
quickstartuser has user role: (true)
159+
quickstartuser has admin role: (false)
160+
161+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
162+
163+
* * * * * * * Below are invoked using admin account * * * * * *
164+
165+
* * * * * * * * * * * recursive: false * * * * * * * * * * * * * * * * * * *
166+
167+
Security Info in JWTSecurityEJBA:
168+
Caller: [admin]
169+
admin has user role: (true)
170+
admin has admin role: (true)
171+
172+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
173+
174+
----
175+
176+
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.
177+
178+
179+
Now let's jump to the second part.
180+
181+
=== Propagate the authentication context using `OAUTHBEARER` mechanism for EJB calls EJB.
182+
183+
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.
184+
185+
==== Configure remote outbound connection in {productName}_1 Server
186+
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.
187+
188+
Open a terminal, and run the following command:
189+
190+
[source,subs="+quotes,attributes+",options="nowrap"]
191+
----
192+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt
193+
$ __${jbossHomeName}__/bin/jboss-cli.sh -c --file=configure-ejb-outbound-connection.cli
194+
----
195+
196+
You will see the following configuration in standalone.xml of {productName}_1 server:
197+
198+
[source, xml]
199+
----
200+
<!-- in elytron subsystem -->
201+
<authentication-client>
202+
<authentication-configuration name="ejb-outbound-configuration" security-domain="jwt-domain" sasl-mecha
203+
nism-selector="OAUTHBEARER"/>
204+
<authentication-context name="ejb-outbound-context">
205+
<match-rule authentication-configuration="ejb-outbound-configuration"/>
206+
</authentication-context>
207+
</authentication-client>
208+
209+
<!-- in remoting subsystem -->
210+
<outbound-connections>
211+
<remote-outbound-connection name="ejb-outbound-connection" outbound-socket-binding-ref="ejb-outbound" authentication-context="ejb-outbound-context"/>
212+
</outbound-connections>
213+
214+
<!-- in socket-binding-group -->
215+
<outbound-socket-binding name="ejb-outbound">
216+
<remote-destination host="localhost" port="8280"/>
217+
</outbound-socket-binding>
218+
----
219+
220+
==== Redeploy EJB in `app-one` to {productName}_1 Server
221+
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.
222+
223+
Open a terminal, and run the following command to undeploy the link:#ejba[EJBA] in jar archive, and deploy it again using ear archive:
224+
225+
[source, bash, subs="+quotes,attributes+"]
226+
----
227+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-one/ejb
228+
$ mvn wildfly:undeploy
229+
$ cd ../ear
230+
$ mvn wildfly:deploy
231+
----
232+
233+
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.
234+
235+
==== Starts {productName}_2 server
236+
237+
:jbossHomeName: {serverNameBase}_2
238+
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`:
239+
240+
.Example of starting {productName}_2 server in Linux system:
241+
[source,subs="+quotes,attributes+"]
242+
----
243+
$ __{jbossHomeName}__/bin/standalone.sh -Djboss.socket.binding.port-offset=200
244+
----
245+
246+
:hostController: --controller=localhost:10190
247+
include::./configure_server.adoc[]
248+
249+
==== Deploy EJB in `app-two` to {productName}_2 Server
250+
The EJB `JWTSecurityEJBRemoteB` in `app-two` has only one method declared:
251+
252+
[source, java]
253+
----
254+
String securityInfo();
255+
----
256+
which will return a String including the caller principal and the authorization check result if it has role of `user` and `admin`.
257+
258+
Open a terminal, and use the following command to deploy EJB in `app-two` module to {productName}_2 Server:
259+
260+
[source, bash, subs="+quotes,attributes+"]
261+
----
262+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-two
263+
$ mvn wildfly:deploy -Dwildfly.port=10190
264+
----
265+
266+
==== Configure and run EJB client application
267+
Now let's run the remote client again, we will specify `-Drecursive=true` to let EJBA calls EJBB this time.
268+
269+
Open a terminal, and run the following command:
270+
271+
[source, bash]
272+
----
273+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt/client
274+
$ mvn exec:exec -Drecursive=true
275+
----
276+
277+
278+
==== Investigate the Console Output
279+
The following output is displayed in the terminal window:
280+
281+
[source, bash]
282+
----
283+
* * * * * * * * * * * recursive: true * * * * * * * * * * * * * * * * * * *
284+
285+
Security Info in JWTSecurityEJBA:
286+
Caller: [quickstartuser]
287+
quickstartuser has user role: (true)
288+
quickstartuser has admin role: (false)
289+
290+
=========== Below are invocation from remote EJB in app-two ===========
291+
Security Info in JWTSecurityEJBB:
292+
Caller: [quickstartuser]
293+
quickstartuser has user role: (true)
294+
quickstartuser has admin role: (false)
295+
296+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
297+
298+
* * * * * * * Below are invoked using admin account * * * * * *
299+
300+
* * * * * * * * * * * recursive: true * * * * * * * * * * * * * * * * * * *
301+
302+
Security Info in JWTSecurityEJBA:
303+
Caller: [admin]
304+
admin has user role: (true)
305+
admin has admin role: (true)
306+
307+
=========== Below are invocation from remote EJB in app-two ===========
308+
Security Info in JWTSecurityEJBB:
309+
Caller: [admin]
310+
admin has user role: (true)
311+
admin has admin role: (true)
312+
313+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
314+
----
315+
316+
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.
317+
318+
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.
319+
320+
== Undeploy the Archives
321+
322+
To undeploy the components from the {productName} servers:
323+
324+
. Navigate to the `app-one/ear` subdirectory:
325+
+
326+
[source,options="nowrap"]
327+
----
328+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-one/ear/
329+
$ mvn wildfly:undeploy
330+
----
331+
332+
. Navigate to the `app-two` subdirectory:
333+
+
334+
[source,options="nowrap"]
335+
----
336+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt/app-two
337+
$ mvn wildfly:undeploy -Dwildfly.port=10190
338+
----
339+
340+
== Restore the servers
341+
After un-deployed the archives from both servers, you can restore the server configurations:
342+
343+
[source,subs="+quotes,attributes+",options="nowrap"]
344+
----
345+
$ cd ${QUICKSTART_HOME}/ejb-security-jwt
346+
$ __${serverNameBase}_1__/bin/jboss-cli.sh -c --file=restore-configuration.cli
347+
$ __${serverNameBase}_2__/bin/jboss-cli.sh -c {hostController} --file=restore-configuration.cli
348+
----
349+
350+
351+
// Debug the Application
352+
include::../shared-doc/debug-the-application.adoc[leveloffset=+1]

0 commit comments

Comments
 (0)