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