From acabf67eb5e47697ee0b352339f9ec67ff0255c6 Mon Sep 17 00:00:00 2001 From: Nathan Shaw Date: Wed, 5 Oct 2022 17:14:44 -0400 Subject: [PATCH 1/2] ENDOC-578 Update the RBAC tutorial for Entando 7.1 --- .../create/ms/add-access-controls.md | 121 ++++++++---------- 1 file changed, 50 insertions(+), 71 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index e7a234adf9..94e8316ea9 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -17,7 +17,7 @@ The basic security setup for a blueprint-generated application allows any authen The list of Conferences must be visible to only the `conference-user` and `conference-admin` user roles. -1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory +1. Go to `microservices/conference-ms/src/main/java/com/YOUR-ORG/YOUR-APP-NAME/web/rest` 2. Open `ConferenceResource.java` 3. Add the following to the list of imports: ```java @@ -30,80 +30,43 @@ The list of Conferences must be visible to only the `conference-user` and `confe ``` This confines use of the `getAllConferences` method to users who are assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. -> Note: In local testing, the default client is `internal`. Refer to the [Spring Security documentation](https://spring.io/projects/spring-security) for more information. - -We also need to modify the blueprint JWT handling to deal with a recent change to the Spring libraries. - -5. Edit `src/main/java/com/mycompany/myapp/config/SecurityConfiguration.java` and make two changes. - * Add this code after the other @Value fields -``` java -@Value("${spring.security.oauth2.client.registration.oidc.client-id}") -private String clientId; -``` - * Modify the following call in the `authenticationConverter` method to provide the clientId field -``` java -jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter(clientId)); - ``` -6. Now modify - `src/main/java/com/mycompany/myapp/security/oauth2/JwtGrantedAuthorityConverter.java` to accept the clientId. Three changes are required. - * Remove the @Component annotation on the class definition - ```java{1} - @Component -public class JwtGrantedAuthorityConverter implements Converter> { - ``` - * Remove the @Value annotation on the clientId field -```java{1} -@Value("${spring.security.oauth2.client.registration.oidc.client-id}") -private String clientId; -``` - * Modify the constructor to accept the clientId -```java - public JwtGrantedAuthorityConverter(String clientId) { - this.clientId = clientId; - } -``` - ### Step 2: Run your project in a local developer environment -The following commands must be run from your project directory. They leverage the [ent CLI](../../../docs/getting-started/entando-cli.md). +The following commands must be run from your bundle project directory. They leverage the [ent CLI](../../../docs/getting-started/entando-cli.md). -> Note: Refer to the [Run Blueprint-generated Microservices and Micro Frontends in Dev Mode tutorial](./run-local.md) for details. +> Note: Refer to the [Run Blueprint-generated Microservices and Micro Frontends in Dev Mode tutorial](./run-local.md) for more details. 1. Start up your Keycloak instance ``` sh -ent prj ext-keycloak start +ent bundle svc start keycloak ``` 2. Start the microservice in another shell ``` sh -ent prj be-test-run +ent bundle run conference-ms ``` -3. Start the tableWidget MFE in a third shell +3. Start the conference-table MFE in a third shell ``` sh -ent prj fe-test-run +ent bundle run conference-table ``` -4. When prompted to select a widget to run, choose the option corresponding to the tableWidget, e.g. `ui/widgets/conference/tableWidget` -### Step 3: Access the tableWidget MFE +### Step 3: Access the conference-table MFE -1. In your browser, go to . This is typically the location of the tableWidget MFE. -2. Access the tableWidget MFE with the default credentials of `username: admin`, `password: admin` +1. In your browser, go to . +2. Access the conference-table MFE with the default credentials of `username: admin`, `password: admin` -> Note: Once authenticated, the message "No conferences are available" is generated. If you check your browser -console, you should see a `403 (Forbidden)` error for the request made to `localhost:8080/services/conference/api/conferences`. This is expected because the admin user has not yet been granted the new role. +> Note: Once authenticated, the message "No conferences are available" is generated. If you check your browser console, you should see a `403 (Forbidden)` error for the request made to `localhost:8080/services/conference/api/conferences`. This is expected because the admin user has not yet been granted the new role. ### Step 4: Login to Keycloak 1. Go to -2. Login using the the default credentials of `username: admin`, `password: admin` +2. Login using the default credentials of `username: admin`, `password: admin` ### Step 5: Create the `conference-user` and `conference-admin` roles -Add the `conference-user` and `conference-admin` roles to the `internal` client. - 1. Go to `Clients` → `internal` → `Roles` 2. Click `Add Role` -3. Fill in the `Role Name` with `conference-admin` +3. Fill in the `Role Name` with `conference-user` 4. Click `Save` -5. Repeat these steps to create the `conference-user` role +5. Repeat these steps to create the `conference-admin` role > Note: The `internal` client is configured by default in the Spring Boot `application.yml`. @@ -114,13 +77,13 @@ To grant access to the `getAllConferences` API: 1. Go to `Users` → `View all users` → `admin` → `Role Mappings` 2. Select `internal` for the `Client Roles` 3. Move `conference-user` from `Available Roles` to `Assigned Roles` -4. Return to the MFE to confirm you see the full list of Conferences +4. Return to the MFE to confirm you can now see the full list of Conferences ### Step 7: Restrict the ability to delete Conferences The `conference-admin` role should grant a user permission to delete Conferences. To restrict the delete method to the `conference-admin` role: -1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory +1. Go to the `src/main/java/com/YOUR-ORG/YOUR-APP-NAME/web/rest` directory 2. Open `ConferenceResource.java` 3. Modify the `deleteConference` method by preceding it with the following annotation: ```java{1} @@ -130,9 +93,9 @@ The `conference-admin` role should grant a user permission to delete Conferences To verify that a user without the `conference-admin` role is unable to call the delete API: -1. Restart the microservice. By default this includes rebuilding any changed source files. -2. Once the microservice is available, return to the MFE and try deleting one of the Conferences in the list -3. Verify that attempting to delete via the UI generates a `403 error` in the browser console and an error in the service logs similar to the following: +1. Restart the microservice. By default, this includes rebuilding any changed source files. +2. Return to the MFE and try deleting one of the Conferences in the list +3. Verify that attempting to delete a conference via the UI generates a `403 error` in the browser console. There should be an error in the service logs similar to the following: ``` WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbidden: Access is denied ``` @@ -141,7 +104,7 @@ WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbi The MFE UI can be updated to hide the delete button from a user without the `conference-admin` authority. The key logic checks whether the `internal` client role `conference-admin` is mapped to the current user via the hasResourceRole call. -1. Go to the `ui/widgets/conference/tableWidget/src/components` directory +1. Go to the `microfrontends/conference-table/src/components` directory 2. Open `ConferenceTableContainer.js` 3. Replace the `onDelete` logic with an additional user permission: ```javascript @@ -164,32 +127,39 @@ Promote the admin user to a full `conference-admin` to reinstate the ability to 5. Confirm the delete icon is visible 6. Confirm a Conference can be successfully deleted from the list -## Notes -### Realm Roles versus Client Authorities -This tutorial utilizes authorities. In Keycloak, authorities are roles mapped to a user for a specific client. It is possible to assign higher-level Realm Roles directly to users, e.g. `ROLE_ADMIN`, but this can result in collisions between applications using the same roles. +### Step 10. Configure the roles in `entando.json` +Entando can automatically add roles to your client (see [the notes below](#notes) for different client options) when your microservice is deployed. -To implement Realm-assigned roles, the code above must be modified: -- In the backend, use the annotation `@Secured('ROLE_ADMIN)` or `@PreAuthorize(hasRole('ROLE_ADMIN'))` -- In the frontend, use `keycloak.hasRealmRole` instead of `keycloak.hasResourceRole` +1. Modify the `entando.json` by adding the following line to the `microservices/conference-ms`: +```json + "roles": ["conference-admin","conference-user"] +``` -See the [Spring Security page](https://www.baeldung.com/spring-security-check-user-role) for more examples. +## Next Steps +Follow one of the links below to run the bundle components locally, or build and publish the bundle into an Entando Application: + +- [Run Blueprint-generated components locally in dev mode](./run-local.md) +- [Build and publish a project bundle](../pb/publish-project-bundle.md) to deploy your microservice and micro frontends to Entando +- [Iterate on your data model](./update-data-model.md) using the JHipster Domain Language (JDL) + +## Notes -### Local vs. Kubernetes Testing +### Local vs. Entando Application Testing This tutorial leverages the `internal` client, which is configured in the microservice via the `application.yml`. Client roles are manually created and assigned in Keycloak. -In Kubernetes, Entando will automatically create client roles per the bundle plugin definition (see the [plugin definition](../../../docs/curate/bundle-details.md) for more information). These roles are created for the client specific to the microservice, e.g. `-conference-server`. The client name is injected as an environment variable into the plugin container, so the annotations noted above will work in both local and Kubernetes environments. +In Kubernetes, Entando will automatically create client roles per the bundle plugin definition (see the [plugin definition](../../../docs/curate/bundle-details.md) for more information). These roles are created for the client specific to the microservice, e.g. `pn-YOUR-SERVICE-ID-conference-ms`. The client name is injected as an environment variable into the plugin container, so the annotations noted above will work in both local and Kubernetes environments. -#### Modify Security Checks for Kubernetes +#### Keycloak Client options in an Entando Application -In this tutorial, the MFE authorization checks explicitly note the client ID, e.g. `internal`. The following options modify the checks to work in Kubernetes: +In this tutorial, the MFE authorization checks explicitly note the client ID, e.g. `internal`. The following options modify the checks to work in an Entando Application: 1) Change the `application.yml` client ID under `security.oauth2.client.registration.oidc` to match the Kubernetes client ID. - This is the most secure option and allows the MFE checks to work identically in both local and Kubernetes environments. However, you may not be be able to use the same clientId, depending on how the microservice is deployed. + This is the most secure option and allows the MFE checks to work identically in both local and Kubernetes environments. However, you may not be able to use the same clientId, depending on how the microservice is deployed. 2) Broaden the MFE authorization check to look for a named role on any client. - This could result in overlap with other clients, but this is the most flexible option when using appropriately named roles (e.g. with a bundle or feature prefix like `conference-` in `conference-admin`). It can be achieved via a helper function, e.g. `api/helpers.js`, and results in a simpler role check: + This could result in overlap with roles created for other clients, but this is the most flexible option when using appropriately named roles (e.g. with a bundle or feature prefix like `conference-` in `conference-admin`). It can be achieved via a helper function, e.g. `api/helpers.js`, and results in a simpler role check: ```javascript // Add helper function // Check if the authenticated user has the clientRole for any Keycloak clients @@ -209,9 +179,18 @@ export const hasKeycloakClientRole = clientRole => { return false; }; -// Perform role check +// Update the role check in ConferenceTableContainer.js using the new helper function const isAdmin = hasKeycloakClientRole('conference-admin'); ``` + +### Realm Roles versus Client Authorities +This tutorial utilizes authorities. In Keycloak, authorities are roles mapped to a user for a specific client. It is possible to assign higher-level Realm Roles directly to users, e.g. `ROLE_ADMIN`, but this can result in collisions between applications using the same roles. + +To implement Realm-assigned roles, the code above must be modified: +- In the backend, use the annotation `@Secured('ROLE_ADMIN)` or `@PreAuthorize(hasRole('ROLE_ADMIN'))` +- In the frontend, use `keycloak.hasRealmRole` instead of `keycloak.hasResourceRole` + +See the [Spring Security page](https://www.baeldung.com/spring-security-check-user-role) for more examples. ### Troubleshooting In both local and Kubernetes environments, the default Blueprint Javascript provides a global variable in the browser, e.g. `window.entando.keycloak`. Examining this variable can help diagnose issues with assigned roles and authorities. In some cases, you may need to logout of Entando and reauthenticate for the latest role assignments to be applied. \ No newline at end of file From 567376a5c04ae3ecaca573547c11166ee109e533 Mon Sep 17 00:00:00 2001 From: Nathan Shaw Date: Thu, 6 Oct 2022 16:59:39 -0400 Subject: [PATCH 2/2] ENDOC-578 Refine RBAC tutorial per PR feedback --- .../tutorials/create/ms/add-access-controls.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index 94e8316ea9..a055eecae8 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -30,7 +30,7 @@ The list of Conferences must be visible to only the `conference-user` and `confe ``` This confines use of the `getAllConferences` method to users who are assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. -### Step 2: Run your project in a local developer environment +### Step 2: Run your project locally The following commands must be run from your bundle project directory. They leverage the [ent CLI](../../../docs/getting-started/entando-cli.md). > Note: Refer to the [Run Blueprint-generated Microservices and Micro Frontends in Dev Mode tutorial](./run-local.md) for more details. @@ -50,10 +50,10 @@ ent bundle run conference-table ### Step 3: Access the conference-table MFE -1. In your browser, go to . +1. In your browser, go to 2. Access the conference-table MFE with the default credentials of `username: admin`, `password: admin` -> Note: Once authenticated, the message "No conferences are available" is generated. If you check your browser console, you should see a `403 (Forbidden)` error for the request made to `localhost:8080/services/conference/api/conferences`. This is expected because the admin user has not yet been granted the new role. +> Note: Once authenticated, the message "No conferences are available" is generated. If you check your browser console, you should see a `403 (Forbidden)` error for the request made to `localhost:8080/services/conference/api/conferences`. This is expected because the `admin` user has not yet been granted the new role. ### Step 4: Login to Keycloak @@ -70,7 +70,7 @@ ent bundle run conference-table > Note: The `internal` client is configured by default in the Spring Boot `application.yml`. -### Step 6: Map the `conference-user` role to the admin user +### Step 6: Map the `conference-user` role to the `admin` user To grant access to the `getAllConferences` API: @@ -95,7 +95,7 @@ To verify that a user without the `conference-admin` role is unable to call the 1. Restart the microservice. By default, this includes rebuilding any changed source files. 2. Return to the MFE and try deleting one of the Conferences in the list -3. Verify that attempting to delete a conference via the UI generates a `403 error` in the browser console. There should be an error in the service logs similar to the following: +3. Verify that attempting to delete a Conference via the UI generates a `403 error` in the browser console. There should be an error in the service logs similar to the following: ``` WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbidden: Access is denied ``` @@ -130,7 +130,7 @@ Promote the admin user to a full `conference-admin` to reinstate the ability to ### Step 10. Configure the roles in `entando.json` Entando can automatically add roles to your client (see [the notes below](#notes) for different client options) when your microservice is deployed. -1. Modify the `entando.json` by adding the following line to the `microservices/conference-ms`: +1. Modify `entando.json` by adding the following line to the `microservices/conference-ms`: ```json "roles": ["conference-admin","conference-user"] ``` @@ -149,7 +149,7 @@ This tutorial leverages the `internal` client, which is configured in the micros In Kubernetes, Entando will automatically create client roles per the bundle plugin definition (see the [plugin definition](../../../docs/curate/bundle-details.md) for more information). These roles are created for the client specific to the microservice, e.g. `pn-YOUR-SERVICE-ID-conference-ms`. The client name is injected as an environment variable into the plugin container, so the annotations noted above will work in both local and Kubernetes environments. -#### Keycloak Client options in an Entando Application +#### Keycloak Client Options in an Entando Application In this tutorial, the MFE authorization checks explicitly note the client ID, e.g. `internal`. The following options modify the checks to work in an Entando Application: