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