Skip to content

Commit

Permalink
Merge pull request #17842 from bstansberry/pulls
Browse files Browse the repository at this point in the history
[WFLY-19172][WFLY-19225][WFLY-17784] Aggregate PR
  • Loading branch information
bstansberry committed Apr 23, 2024
2 parents 6fc50fd + 7f4be8e commit ed4fede
Show file tree
Hide file tree
Showing 30 changed files with 1,788 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ ifdef::env-github[]
:warning-caption: :warning:
endif::[]

:auth-method: OpenID Connect

The ability to secure applications using https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect] is
provided by the _elytron-oidc-client_ subsystem.

Expand Down Expand Up @@ -263,78 +265,7 @@ your application's `web.xml` file, as shown in the example below:
</web-app>
----

[[identity_propagation]]
== Identity Propagation

When securing an application with OpenID Connect, the `elytron-oidc-client` subsystem will automatically
create a virtual security domain for you. If your application invokes an EJB, additional configuration
might be required to propagate the security identity from the virtual security domain depending on how
the EJB is being secured.

=== Securing an EJB using a different security domain

If your application secured with OpenID Connect invokes an EJB within the same deployment (e.g.,
within the same WAR or EAR) or invokes an EJB in a separate deployment (e.g., across EARs)
and you'd like to secure the EJB using a different security domain from your servlet,
additional configuration will be needed to outflow the security identities established by
the virtual security domain to another security domain.

The `virtual-security-domain` resource allows you to specify that security identities
established by a virtual security domain should automatically be outflowed to other
security domains. A `virtual-security-domain` resource has a few attributes, as
described below:

* `name` - This is the runtime name of a deployment associated with a virtual security domain (e.g.,
`DEPLOYMENT_NAME.ear`, a deployment that has a subdeployment that is secured using OpenID Connect).

* `outflow-security-domains` - This is the list of `security-domains` that security identities from
the virtual security domain should be automatically outflowed to.

* `outflow-anonymous` - When outflowing to a security domain, if outflow is not possible, should the
anonymous identity be used? Outflow to a security domain might not be possible if the domain does
not trust this domain or if the identity being outflowed to a domain does not exist in that domain.
Outflowing anonymous has the effect of clearing any identity already established for that domain.
This attribute defaults to `false`.

In addition to configuring a `virtual-security-domain` resource, you'll also need to update the
`security-domain` configuration for your EJB to indicate that it should trust security identities established
by the `virtual-security-domain`. This can be specified by configuring the `trusted-virtual-security-domains`
attribute for a `security-domain` (e.g., setting the `trusted-virtual-security-domains` attribute to
`DEPLOYMENT_NAME.ear` for a `security-domain` would indicate that this `security-domain`
should trust the virtual security domain associated with the `DEPLOYMENT_NAME.ear` deployment).

The `virtual-security-domain` configuration and `trusted-virtual-security-domains` configuration
will allow security identities established by a virtual security domain to be successfully
outflowed to a `security-domain` being used to secure the EJB.

=== Securing an EJB using the same virtual security domain

==== Within the same deployment

If your application secured with OpenID Connect invokes an EJB within the same deployment (e.g.,
within the same WAR or EAR), and you'd like to secure the EJB using the same virtual security
domain as your servlet, no additional configuration is required. This means that if no security
domain configuration has been explicitly specified for the EJB, the virtual security domain will
automatically be used to secure the EJB.

==== Across deployments

If your application secured with OpenID Connect invokes an EJB in a separate deployment (e.g., across EARs)
and you'd like to secure the EJB using the same virtual security domain as your servlet,
additional configuration will be needed. In particular, the EJB will need to reference the virtual
security domain explicitly.

The `virtual-security-domain` resource allows you to reference the virtual security domain
from the security domain configuration for the EJB. As an example, a `virtual-security-domain`
resource could be added as follows:

`/subsystem=elytron/virtual-security-domain=DEPLOYMENT_NAME.ear:add()`

An annotation like `@SecurityDomain(DEPLOYMENT_NAME.ear)` can then be added to the EJB,
where `DEPLOYMENT_NAME.ear` is a reference to the `virtual-security-domain` defined above.

This configuration indicates that the virtual security domain associated with `DEPLOYMENT_NAME.ear`
should be used to secure the EJB.
include::Identity_Propagation.adoc[]

== Securing the management console with OpenID Connect

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
[[Identity_Propagation]]
== Identity Propagation

When securing an application with {auth-method}, a virtual security domain
will be created automatically for you. If your application invokes an EJB, additional configuration
might be required to propagate the security identity from the virtual security domain depending on how
the EJB is being secured.

=== Securing an EJB using a different security domain

If your application secured with {auth-method} invokes an EJB within the
same deployment (e.g., within the same WAR or EAR) or invokes an EJB in a separate deployment
(e.g., across EARs) and you'd like to secure the EJB using a different security domain from
your web component, additional configuration will be needed to outflow the security identities
established by the virtual security domain to another security domain.

The `virtual-security-domain` resource allows you to specify that security identities
established by a virtual security domain should automatically be outflowed to other
security domains. A `virtual-security-domain` resource has a few attributes, as
described below:

* `name` - This is the runtime name of a deployment associated with a virtual security domain (e.g.,
`DEPLOYMENT_NAME.ear`, a deployment that has a subdeployment that is secured using {auth-method}).

ifeval::["{auth-method}" == "MicroProfile JWT"]
* `auth-method` - The authentication mechanism that will be used with the virtual security domain.
If your application is secured with MicroProfile JWT, the `auth-method` should be set to `MP-JWT`.
endif::[]

* `outflow-security-domains` - This is the list of `security-domains` that security identities from
the virtual security domain should be automatically outflowed to.

* `outflow-anonymous` - When outflowing to a security domain, if outflow is not possible, should the
anonymous identity be used? Outflow to a security domain might not be possible if the domain does
not trust this domain or if the identity being outflowed to a domain does not exist in that domain.
Outflowing anonymous has the effect of clearing any identity already established for that domain.
This attribute defaults to `false`.

In addition to configuring a `virtual-security-domain` resource, you'll also need to update the
`security-domain` configuration for your EJB to indicate that it should trust security identities established
by the `virtual-security-domain`. This can be specified by configuring the `trusted-virtual-security-domains`
attribute for a `security-domain` (e.g., setting the `trusted-virtual-security-domains` attribute to
`DEPLOYMENT_NAME.ear` for a `security-domain` would indicate that this `security-domain`
should trust the virtual security domain associated with the `DEPLOYMENT_NAME.ear` deployment).

The `virtual-security-domain` configuration and `trusted-virtual-security-domains` configuration
will allow security identities established by a virtual security domain to be successfully
outflowed to a `security-domain` being used to secure the EJB.

=== Securing an EJB using the same virtual security domain

==== Within the same deployment

If your application secured with {auth-method} invokes an EJB within the same deployment (e.g.,
within the same WAR or EAR), and you'd like to secure the EJB using the same virtual security
domain as your web component, no additional configuration is required. This means that if no security
domain configuration has been explicitly specified for the EJB, the virtual security domain will
automatically be used to secure the EJB.

==== Across deployments

If your application secured with {auth-method} invokes an EJB in a separate deployment (e.g., across EARs)
and you'd like to secure the EJB using the same virtual security domain as your web component,
additional configuration will be needed. In particular, the EJB will need to reference the virtual
security domain explicitly.

The `virtual-security-domain` resource allows you to reference the virtual security domain
from the security domain configuration for the EJB. As an example, a `virtual-security-domain`
resource could be added as follows:

`/subsystem=elytron/virtual-security-domain=DEPLOYMENT_NAME.ear:add()`

An annotation like `@SecurityDomain(DEPLOYMENT_NAME.ear)` can then be added to the EJB,
where `DEPLOYMENT_NAME.ear` is a reference to the `virtual-security-domain` defined above.

This configuration indicates that the virtual security domain associated with `DEPLOYMENT_NAME.ear`
should be used to secure the EJB.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ ifdef::env-github[]
:warning-caption: :warning:
endif::[]

:auth-method: MicroProfile JWT

Support for https://microprofile.io/project/eclipse/microprofile-jwt-auth[MicroProfile JWT RBAC] is provided by the _microprofile-jwt-smallrye_ subsystem.

The MicroProfile JWT specification describes how authentication can be performed using cryptographically signed JWT tokens and the contents of the token to be used to establish a resuting identity without relying on access to external repositories of identities such as databases or directory servers.
Expand Down Expand Up @@ -122,5 +124,11 @@ One of the main motivations for using MicroProfile JWT is the ability to describ

As the deployment is configured entirely within the MicroProfile Config properties other than the presence of the _microprofile-jwt-smallrye_ subsystem the virtual `SecurityDomain` means no other managed configuration is required for the deployment.

[NOTE]
====
To propagate an identity from a virtual security domain, additional configuration might be required
depending on your use case. See <<identity_propagation, Identity Propagation>> for more details.
====

include::Identity_Propagation.adoc[]

Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ public class KeycloakConfiguration {
public static final String CHARLIE_PASSWORD = "charlie123+";
public static final String ALLOWED_ORIGIN = "http://somehost";

// the users below are for multi-tenancy tests specifically
public static final String TENANT1_USER = "tenant1_user";
public static final String TENANT1_PASSWORD = "tenant1_password";
public static final String TENANT2_USER = "tenant2_user";
public static final String TENANT2_PASSWORD = "tenant2_password";
public static final String CHARLOTTE = "charlotte";
public static final String CHARLOTTE_PASSWORD =" charlotte123+";
public static final String DAN = "dan";
public static final String DAN_PASSWORD =" dan123+";
public static final String TENANT1_REALM = "tenant1";
public static final String TENANT2_REALM = "tenant2";
public static final String TENANT1_ENDPOINT = "/tenant1";
public static final String TENANT2_ENDPOINT = "/tenant2";

public enum ClientAppType {
OIDC_CLIENT,
DIRECT_ACCESS_GRANT_OIDC_CLIENT,
Expand All @@ -60,7 +74,13 @@ public enum ClientAppType {
*/
public static RealmRepresentation getRealmRepresentation(final String realmName, String clientSecret,
String clientHostName, int clientPort, Map<String, ClientAppType> clientApps) {
return createRealm(realmName, clientSecret, clientHostName, clientPort, clientApps);
return createRealm(realmName, clientSecret, clientHostName, clientPort, clientApps, 3, 3, false);
}

public static RealmRepresentation getRealmRepresentation(final String realmName, String clientSecret,
String clientHostName, int clientPort, Map<String, ClientAppType> clientApps,
int accessTokenLifespan, int ssoSessionMaxLifespan, boolean multiTenancyApp) {
return createRealm(realmName, clientSecret, clientHostName, clientPort, clientApps, accessTokenLifespan, ssoSessionMaxLifespan, multiTenancyApp);
}

public static String getAdminAccessToken(String authServerUrl) {
Expand Down Expand Up @@ -101,14 +121,20 @@ public static String getAccessToken(String authServerUrl, String realmName, Stri

private static RealmRepresentation createRealm(String name, String clientSecret,
String clientHostName, int clientPort, Map<String, ClientAppType> clientApps) {
return createRealm(name, clientSecret, clientHostName, clientPort, clientApps, 3, 3, false);
}

private static RealmRepresentation createRealm(String name, String clientSecret,
String clientHostName, int clientPort, Map<String, ClientAppType> clientApps,
int accessTokenLifespan, int ssoSessionMaxLifespan, boolean multiTenancyApp) {
RealmRepresentation realm = new RealmRepresentation();

realm.setRealm(name);
realm.setEnabled(true);
realm.setUsers(new ArrayList<>());
realm.setClients(new ArrayList<>());
realm.setAccessTokenLifespan(3);
realm.setSsoSessionMaxLifespan(3);
realm.setAccessTokenLifespan(accessTokenLifespan);
realm.setSsoSessionMaxLifespan(ssoSessionMaxLifespan);

RolesRepresentation roles = new RolesRepresentation();
List<RoleRepresentation> realmRoles = new ArrayList<>();
Expand All @@ -121,40 +147,63 @@ private static RealmRepresentation createRealm(String name, String clientSecret,

for (Map.Entry<String, ClientAppType> entry : clientApps.entrySet()) {
String clientApp = entry.getKey();
String multiTenancyRedirectUri = null;
if (multiTenancyApp) {
if (name.equals(TENANT1_REALM)) {
multiTenancyRedirectUri = "http://" + clientHostName + ":" + clientPort + "/" + clientApp + TENANT1_ENDPOINT;
} else if (name.equals(TENANT2_REALM)) {
multiTenancyRedirectUri = "http://" + clientHostName + ":" + clientPort + "/" + clientApp + TENANT2_ENDPOINT;
}
}

switch (entry.getValue()) {
case DIRECT_ACCESS_GRANT_OIDC_CLIENT:
realm.getClients().add(createWebAppClient(clientApp, clientSecret, clientHostName, clientPort, clientApp, true));
realm.getClients().add(createWebAppClient(clientApp, clientSecret, clientHostName, clientPort, clientApp, true, multiTenancyRedirectUri));
break;
case BEARER_ONLY_CLIENT:
realm.getClients().add(createBearerOnlyClient(clientApp));
break;
case CORS_CLIENT:
realm.getClients().add(createWebAppClient(clientApp, clientSecret, clientHostName, clientPort, clientApp, true, ALLOWED_ORIGIN));
realm.getClients().add(createWebAppClient(clientApp, clientSecret, clientHostName, clientPort, clientApp, true, ALLOWED_ORIGIN, multiTenancyRedirectUri));
break;
default:
realm.getClients().add(createWebAppClient(clientApp, clientSecret, clientHostName, clientPort, clientApp, false));
realm.getClients().add(createWebAppClient(clientApp, clientSecret, clientHostName, clientPort, clientApp, false, multiTenancyRedirectUri));
}
}

realm.getUsers().add(createUser(ALICE, ALICE_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
realm.getUsers().add(createUser(BOB, BOB_PASSWORD, Arrays.asList(USER_ROLE)));
realm.getUsers().add(createUser(CHARLIE, CHARLIE_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
if (name.equals(TENANT1_REALM)) {
realm.getUsers().add(createUser(TENANT1_USER, TENANT1_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
realm.getUsers().add(createUser(CHARLOTTE, CHARLOTTE_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
realm.getUsers().add(createUser(DAN, DAN_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
} else if (name.equals(TENANT2_REALM)) {
realm.getUsers().add(createUser(TENANT2_USER, TENANT2_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
realm.getUsers().add(createUser(CHARLOTTE, CHARLOTTE_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
realm.getUsers().add(createUser(DAN, DAN_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
} else {
realm.getUsers().add(createUser(ALICE, ALICE_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
realm.getUsers().add(createUser(BOB, BOB_PASSWORD, Arrays.asList(USER_ROLE)));
realm.getUsers().add(createUser(CHARLIE, CHARLIE_PASSWORD, Arrays.asList(USER_ROLE, JBOSS_ADMIN_ROLE)));
}
return realm;
}

private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort,
String clientApp, boolean directAccessGrantEnabled) {
return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, null);
String clientApp, boolean directAccessGrantEnabled, String multiTenancyRedirectUri) {
return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, null, multiTenancyRedirectUri);
}

private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort,
String clientApp, boolean directAccessGrantEnabled, String allowedOrigin) {
String clientApp, boolean directAccessGrantEnabled, String allowedOrigin, String multiTenancyRedirectUri) {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(clientId);
client.setPublicClient(false);
client.setSecret(clientSecret);
//client.setRedirectUris(Arrays.asList("*"));
client.setRedirectUris(Arrays.asList("http://" + clientHostName + ":" + clientPort + "/" + clientApp + "/*"));
if (multiTenancyRedirectUri != null) {
client.setRedirectUris(Arrays.asList(multiTenancyRedirectUri));
} else {
client.setRedirectUris(Arrays.asList("http://" + clientHostName + ":" + clientPort + "/" + clientApp + "/*"));
}
client.setEnabled(true);
client.setDirectAccessGrantsEnabled(directAccessGrantEnabled);
if (allowedOrigin != null) {
Expand Down

0 comments on commit ed4fede

Please sign in to comment.