From 32cad53c55d597c8f7bdc1b5b74fb69490d09f86 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 22 Nov 2024 17:04:36 +1100 Subject: [PATCH 1/6] Add documentation for Jetty OpenID Connect support Signed-off-by: Lachlan Roberts --- .../jetty/modules/code/examples/pom.xml | 4 + .../docs/programming/security/OpenIdDocs.java | 107 ++++++++++++++++++ .../jetty/modules/operations-guide/nav.adoc | 6 +- .../pages/modules/standard.adoc | 11 ++ .../index.adoc => security/jaas-support.adoc} | 0 .../jaspi-support.adoc} | 0 .../pages/security/openid-support.adoc | 54 +++++++++ .../jetty/modules/programming-guide/nav.adoc | 2 + .../pages/security/openid-support.adoc | 92 +++++++++++++++ .../src/main/config/modules/openid.mod | 2 + 10 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java rename documentation/jetty/modules/operations-guide/pages/{jaas/index.adoc => security/jaas-support.adoc} (100%) rename documentation/jetty/modules/operations-guide/pages/{jaspi/index.adoc => security/jaspi-support.adoc} (100%) create mode 100644 documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc create mode 100644 documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc diff --git a/documentation/jetty/modules/code/examples/pom.xml b/documentation/jetty/modules/code/examples/pom.xml index d42c00e80dcb..b24afc396b69 100644 --- a/documentation/jetty/modules/code/examples/pom.xml +++ b/documentation/jetty/modules/code/examples/pom.xml @@ -42,6 +42,10 @@ org.eclipse.jetty jetty-nosql + + org.eclipse.jetty + jetty-openid + org.eclipse.jetty jetty-rewrite diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java new file mode 100644 index 000000000000..c1e2e3e026cf --- /dev/null +++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java @@ -0,0 +1,107 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.docs.programming.security; + +import java.util.Map; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.HashLoginService; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.security.UserStore; +import org.eclipse.jetty.security.openid.OpenIdAuthenticator; +import org.eclipse.jetty.security.openid.OpenIdConfiguration; +import org.eclipse.jetty.security.openid.OpenIdLoginService; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.security.Credential; + +public class OpenIdDocs +{ + private static final String ISSUER = ""; + private static final String CLIENT_ID = ""; + private static final String CLIENT_SECRET = ""; + private static final String TOKEN_ENDPOINT = ""; + private static final String AUTH_ENDPOINT = ""; + private static final String END_SESSION_ENDPOINT = ""; + private static final String AUTH_METHOD = ""; + private static final HttpClient httpClient = new HttpClient(); + + private OpenIdConfiguration openIdConfig; + private SecurityHandler securityHandler; + + public void createConfigurationWithDiscovery() + { + // tag::createConfigurationWithDiscovery[] + OpenIdConfiguration openIdConfig = new OpenIdConfiguration(ISSUER, CLIENT_ID, CLIENT_SECRET); + // end::createConfigurationWithDiscovery[] + } + + public void createConfiguration() + { + // tag::createConfiguration[] + OpenIdConfiguration openIdConfig = new OpenIdConfiguration(ISSUER, TOKEN_ENDPOINT, AUTH_ENDPOINT, END_SESSION_ENDPOINT, + CLIENT_ID, CLIENT_SECRET, AUTH_METHOD, httpClient); + // end::createConfiguration[] + } + + public void configureLoginService() + { + // tag::configureLoginService[] + LoginService loginService = new OpenIdLoginService(openIdConfig); + securityHandler.setLoginService(loginService); + // end::configureLoginService[] + } + + public void configureAuthenticator() + { + // tag::configureAuthenticator[] + Authenticator authenticator = new OpenIdAuthenticator(openIdConfig, "/error"); + securityHandler.setAuthenticator(authenticator); + // end::configureAuthenticator[] + } + + @SuppressWarnings("unchecked") + public void accessClaims() + { + Request request = new Request.Wrapper(null); + + // tag::accessClaims[] + Map claims = (Map)request.getSession(true).getAttribute("org.eclipse.jetty.security.openid.claims"); + String userId = (String)claims.get("sub"); + + Map response = (Map)request.getSession(true).getAttribute("org.eclipse.jetty.security.openid.response"); + String accessToken = (String)response.get("access_token"); + // tag::accessClaims[] + } + + public void wrappedLoginService() + { + // tag::wrappedLoginService[] + // Use the optional LoginService for Roles. + LoginService wrappedLoginService = createWrappedLoginService(); + LoginService loginService = new OpenIdLoginService(openIdConfig, wrappedLoginService); + // end::wrappedLoginService[] + } + + private LoginService createWrappedLoginService() + { + HashLoginService loginService = new HashLoginService(); + UserStore userStore = new UserStore(); + userStore.addUser("admin", Credential.getCredential("password"), new String[]{"admin"}); + loginService.setUserStore(userStore); + loginService.setName(ISSUER); + return loginService; + } +} \ No newline at end of file diff --git a/documentation/jetty/modules/operations-guide/nav.adoc b/documentation/jetty/modules/operations-guide/nav.adoc index eb03e27de1f4..3f154b9da86c 100644 --- a/documentation/jetty/modules/operations-guide/nav.adoc +++ b/documentation/jetty/modules/operations-guide/nav.adoc @@ -32,8 +32,10 @@ * xref:jstl/index.adoc[] * xref:jsf-taglibs/index.adoc[] * xref:jndi/index.adoc[] -* xref:jaas/index.adoc[] -* xref:jaspi/index.adoc[] +* Jetty Security +** xref:security/jaas-support.adoc[] +** xref:security/jaspi-support.adoc[] +** xref:security/openid-support.adoc[] * xref:jmx/index.adoc[] * xref:tools/index.adoc[] * xref:troubleshooting/index.adoc[] diff --git a/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc index f5e1a1192019..af0dfb3d2124 100644 --- a/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc +++ b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc @@ -858,3 +858,14 @@ The module properties are: ---- include::{jetty-home}/modules/well-known.mod[tags=documentation] ---- + +[[openid]] +== Module `openid` + +The openid module enables support for OpenID Connect (OIDC) authentication in Jetty. +It allows Jetty to authenticate users via an OpenID Connect identity provider, making it possible to integrate features like "Sign in with Google" or "Sign in with Microsoft," among others. +This simplifies user authentication while leveraging the security and convenience of external identity providers. + +---- +include::{jetty-home}/modules/openid.mod[tags=documentation] +---- \ No newline at end of file diff --git a/documentation/jetty/modules/operations-guide/pages/jaas/index.adoc b/documentation/jetty/modules/operations-guide/pages/security/jaas-support.adoc similarity index 100% rename from documentation/jetty/modules/operations-guide/pages/jaas/index.adoc rename to documentation/jetty/modules/operations-guide/pages/security/jaas-support.adoc diff --git a/documentation/jetty/modules/operations-guide/pages/jaspi/index.adoc b/documentation/jetty/modules/operations-guide/pages/security/jaspi-support.adoc similarity index 100% rename from documentation/jetty/modules/operations-guide/pages/jaspi/index.adoc rename to documentation/jetty/modules/operations-guide/pages/security/jaspi-support.adoc diff --git a/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc b/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc new file mode 100644 index 000000000000..ed4871408db8 --- /dev/null +++ b/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc @@ -0,0 +1,54 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[openid-support]] += OpenID Support + +For more information about Jetty OpenID configuration also see the xref:programming-guide:security/openid-support.adoc[OpenID Support] section in the programming guide. + +== OpenID Provider Configuration +To enable OpenID support, you first need to activate the `openid` module in your implementation. + +---- +$ java -jar $JETTY_HOME/start.jar --add-to-start=openid +---- + +To configure OpenID Authentication with Jetty you will need to specify the OpenID Provider's issuer identifier (case-sensitive URL using the `https` scheme) and the OAuth 2.0 Client ID and Client Secret. +If the OpenID Provider does not allow metadata discovery you will also need to specify the token endpoint and authorization endpoint of the OpenID Provider. +These can be set as properties in the `start.ini` or `start.d/openid.ini` files. + +== WebApp Specific Configuration in web.xml + +The `web.xml` file needs some specific configuration to use OpenID. +There must be a `login-config` element with an `auth-method` value of `OPENID`, and a `realm-name` value of the exact URL string used to set the OpenID Provider. + +To set the error page, an init param is set at `"org.eclipse.jetty.security.openid.error_page"`, its value should be a path relative to the webapp where authentication errors should be redirected. + +Example: + +[,xml,subs=attributes+] +---- + + OPENID + https://accounts.google.com + + + org.eclipse.jetty.security.openid.error_page + /error + +---- + +== Supporting Multiple OpenID Providers. + +You may override the `jetty-openid.xml` file in `$JETTY_BASE/etc/jetty-openid.xml` to add additional `OpenIdConfiguration` instances as beans on the server. +If there are multiple OpenID configuration instances found on the server then the `OpenIdAuthenticationFactory` will select the one with an `issuer` matching the `` of the `web.xml` for a given web app. \ No newline at end of file diff --git a/documentation/jetty/modules/programming-guide/nav.adoc b/documentation/jetty/modules/programming-guide/nav.adoc index ac50c0d7f133..efdbbed8c289 100644 --- a/documentation/jetty/modules/programming-guide/nav.adoc +++ b/documentation/jetty/modules/programming-guide/nav.adoc @@ -43,6 +43,8 @@ ** xref:troubleshooting/state-tracking.adoc[] ** xref:troubleshooting/component-dump.adoc[] ** xref:troubleshooting/debugging.adoc[] +* Jetty Security +** xref:security/openid-support.adoc[] * Migration Guides ** xref:migration/94-to-10.adoc[] ** xref:migration/11-to-12.adoc[] diff --git a/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc b/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc new file mode 100644 index 000000000000..d550ee3ef15a --- /dev/null +++ b/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc @@ -0,0 +1,92 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[openid-support]] += OpenID Support + +== External Setup + +=== Registering an App with OpenID Provider +You must register the app with an OpenID Provider such as link:https://developers.google.com/identity/protocols/OpenIDConnect[Google] or link:https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html[Amazon]. +This will give you a Client ID and Client Secret. +Once set up you must also register all the possible URI's for your webapp with the path `/j_security_check` so that the OpenId Provider will allow redirection back to the webapp. + +These may look like + + * `http://localhost:8080/openid-webapp/j_security_check` + + * `https://example.com/j_security_check` + + +== Embedded Configuration + +=== Define the `OpenIdConfiguration` for a specific OpenID Provider. + +If the OpenID Provider allows metadata discovery then you can use. + +[,java,indent=0] +---- +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=createConfigurationWithDiscovery] +---- + +Otherwise, you can manually enter the necessary information: + +[,java,indent=0] +---- +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=createConfiguration] +---- + +=== Configuring an `OpenIdLoginService` +[,java,indent=0] +---- +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=configureLoginService] +---- + +=== Configuring an `OpenIdAuthenticator` with `OpenIdConfiguration` and Error Page Redirect +[,java,indent=0] +---- +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=configureAuthenticator] +---- + +=== Usage + +==== Claims and Access Token +Claims about the user can be found using attributes on the session attribute `"org.eclipse.jetty.security.openid.claims"`, and the full response containing the OAuth 2.0 Access Token can be found with the session attribute `"org.eclipse.jetty.security.openid.response"`. + +Example: +[,java,indent=0] +---- +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=configureAuthenticator] +---- + +== Scopes +The OpenID scope is always used but additional scopes can be requested which can give you additional resources or privileges. +For the Google OpenID Provider it can be useful to request the scopes `profile` and `email` which will give you additional user claims. + +Additional scopes can be requested through the `start.ini` or `start.d/openid.ini` files, or with `OpenIdConfiguration.addScopes(...);` in embedded code. + +== Authorization + +If security roles are required they can be configured through a wrapped `LoginService` which is deferred to for role information by the `OpenIdLoginService`. + +This can be configured in XML through `etc/openid-baseloginservice.xml` in the Distribution, or in embedded code using the constructor for the `OpenIdLoginService`. + +[,java,indent=0] +---- +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=wrappedLoginService] +---- + +When using authorization roles, the setting `authenticateNewUsers` becomes significant. +If set to `true` users not found by the wrapped `LoginService` will still be authenticated but will have no roles. +If set to `false` those users will be not be allowed to authenticate and are redirected to the error page. +This setting is configured through the property `jetty.openid.authenticateNewUsers` in the `start.ini` or `start.d/openid.ini` file, or with `OpenIdLoginService.setAuthenticateNewUsers(...);` in embedded code. diff --git a/jetty-core/jetty-openid/src/main/config/modules/openid.mod b/jetty-core/jetty-openid/src/main/config/modules/openid.mod index 1f75f0478a23..4bbb408cc96c 100644 --- a/jetty-core/jetty-openid/src/main/config/modules/openid.mod +++ b/jetty-core/jetty-openid/src/main/config/modules/openid.mod @@ -19,6 +19,7 @@ etc/jetty-openid-baseloginservice.xml etc/jetty-openid.xml [ini-template] +# tag::documentation[] ## The OpenID Identity Provider's issuer ID (the entire URL *before* ".well-known/openid-configuration") # jetty.openid.provider=https://id.example.com/ @@ -48,3 +49,4 @@ etc/jetty-openid.xml ## Whether the user should be logged out after the idToken expires. # jetty.openid.logoutWhenIdTokenIsExpired=false +# end::documentation[] \ No newline at end of file From ba65f9e838dc5a13322a32d8deec52eaf3b5758b Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 25 Nov 2024 20:02:26 +0100 Subject: [PATCH 2/6] Cleanups after review. Signed-off-by: Simone Bordet --- .../pages/modules/standard.adoc | 26 +++++----- .../pages/security/openid-support.adoc | 47 ++++++++++++------- .../pages/security/openid-support.adoc | 18 +++---- .../src/main/config/modules/openid.mod | 2 +- 4 files changed, 55 insertions(+), 38 deletions(-) diff --git a/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc index af0dfb3d2124..abcb2e8fbd2e 100644 --- a/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc +++ b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc @@ -328,6 +328,21 @@ However, we the RMI server is configured to bind to `localhost`, i.e. `127.0.0.1 If the system property `java.rmi.server.hostname` is not specified, the RMI client will try to connect to `127.0.1.1` (because that's what in the RMI stub) and fail because nothing is listening on that address. +[[openid]] +== Module `openid` + +The `openid` module enables support for OpenID Connect (OIDC) authentication in Jetty, as detailed in xref:security/openid-support.adoc#openid-support[this section]. + +This module allows Jetty to authenticate users via an OpenID Connect identity provider, making possible to integrate features like "Sign in with Google" or "Sign in with Microsoft", among others. + +This simplifies user authentication while leveraging the security and convenience of external identity providers. + +The module properties are: + +---- +include::{jetty-home}/modules/openid.mod[tags=documentation] +---- + [[qos]] == Module `qos` @@ -858,14 +873,3 @@ The module properties are: ---- include::{jetty-home}/modules/well-known.mod[tags=documentation] ---- - -[[openid]] -== Module `openid` - -The openid module enables support for OpenID Connect (OIDC) authentication in Jetty. -It allows Jetty to authenticate users via an OpenID Connect identity provider, making it possible to integrate features like "Sign in with Google" or "Sign in with Microsoft," among others. -This simplifies user authentication while leveraging the security and convenience of external identity providers. - ----- -include::{jetty-home}/modules/openid.mod[tags=documentation] ----- \ No newline at end of file diff --git a/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc b/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc index ed4871408db8..fe93505f99bc 100644 --- a/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc +++ b/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc @@ -14,41 +14,54 @@ [[openid-support]] = OpenID Support -For more information about Jetty OpenID configuration also see the xref:programming-guide:security/openid-support.adoc[OpenID Support] section in the programming guide. +A more general discussion about OpenID and its support in Jetty is available in xref:programming-guide:security/openid-support.adoc[this section]. == OpenID Provider Configuration -To enable OpenID support, you first need to activate the `openid` module in your implementation. + +To enable OpenID support, you need to enable the `openid` module: ---- -$ java -jar $JETTY_HOME/start.jar --add-to-start=openid +$ java -jar $JETTY_HOME/start.jar --add-modules=openid ---- To configure OpenID Authentication with Jetty you will need to specify the OpenID Provider's issuer identifier (case-sensitive URL using the `https` scheme) and the OAuth 2.0 Client ID and Client Secret. + If the OpenID Provider does not allow metadata discovery you will also need to specify the token endpoint and authorization endpoint of the OpenID Provider. -These can be set as properties in the `start.ini` or `start.d/openid.ini` files. +These values can be set as properties in `$JETTY_BASE/start.d/openid.ini` file. -== WebApp Specific Configuration in web.xml +== Web Application Specific Configuration in `web.xml` The `web.xml` file needs some specific configuration to use OpenID. + There must be a `login-config` element with an `auth-method` value of `OPENID`, and a `realm-name` value of the exact URL string used to set the OpenID Provider. -To set the error page, an init param is set at `"org.eclipse.jetty.security.openid.error_page"`, its value should be a path relative to the webapp where authentication errors should be redirected. +To set the error page, you must set an `init-param` named `org.eclipse.jetty.security.openid.error_page` whose value should be a path relative to the webapp where authentication errors should be redirected. -Example: +For example: [,xml,subs=attributes+] ---- - - OPENID - https://accounts.google.com - - - org.eclipse.jetty.security.openid.error_page - /error - + + + ... + + OPENID + https://accounts.google.com + + + + org.eclipse.jetty.security.openid.error_page + /error + + ... + ---- -== Supporting Multiple OpenID Providers. +== Supporting Multiple OpenID Providers You may override the `jetty-openid.xml` file in `$JETTY_BASE/etc/jetty-openid.xml` to add additional `OpenIdConfiguration` instances as beans on the server. -If there are multiple OpenID configuration instances found on the server then the `OpenIdAuthenticationFactory` will select the one with an `issuer` matching the `` of the `web.xml` for a given web app. \ No newline at end of file + +If there are multiple OpenID configuration instances found on the server then the `OpenIdAuthenticationFactory` will select the one with an `issuer` matching the `` of the `web.xml` for a given web app. diff --git a/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc b/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc index d550ee3ef15a..0746c24ffae1 100644 --- a/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc +++ b/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc @@ -19,20 +19,19 @@ === Registering an App with OpenID Provider You must register the app with an OpenID Provider such as link:https://developers.google.com/identity/protocols/OpenIDConnect[Google] or link:https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html[Amazon]. This will give you a Client ID and Client Secret. -Once set up you must also register all the possible URI's for your webapp with the path `/j_security_check` so that the OpenId Provider will allow redirection back to the webapp. -These may look like +Once obtained the Client ID and the Client Secret, you must also register all the possible URIs for your web application with the path `/j_security_check` so that the OpenId Provider will allow redirection back to the webapp. - * `http://localhost:8080/openid-webapp/j_security_check` +These may look like: - * `https://example.com/j_security_check` + * `+http://localhost:8080/openid-webapp/j_security_check+` + * `+https://example.com/j_security_check+` - -== Embedded Configuration +== Code Setup === Define the `OpenIdConfiguration` for a specific OpenID Provider. -If the OpenID Provider allows metadata discovery then you can use. +If the OpenID Provider allows metadata discovery then you can use: [,java,indent=0] ---- @@ -61,7 +60,7 @@ include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/ === Usage ==== Claims and Access Token -Claims about the user can be found using attributes on the session attribute `"org.eclipse.jetty.security.openid.claims"`, and the full response containing the OAuth 2.0 Access Token can be found with the session attribute `"org.eclipse.jetty.security.openid.response"`. +Claims about the user can be found using attributes in the HTTP session attribute `org.eclipse.jetty.security.openid.claims`, and the full response containing the OAuth 2.0 Access Token can be found in the HTTP session attribute `org.eclipse.jetty.security.openid.response`. Example: [,java,indent=0] @@ -71,9 +70,10 @@ include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/ == Scopes The OpenID scope is always used but additional scopes can be requested which can give you additional resources or privileges. + For the Google OpenID Provider it can be useful to request the scopes `profile` and `email` which will give you additional user claims. -Additional scopes can be requested through the `start.ini` or `start.d/openid.ini` files, or with `OpenIdConfiguration.addScopes(...);` in embedded code. +Additional scopes can be specified using `OpenIdConfiguration.addScopes(\...);`. == Authorization diff --git a/jetty-core/jetty-openid/src/main/config/modules/openid.mod b/jetty-core/jetty-openid/src/main/config/modules/openid.mod index 4bbb408cc96c..612d39b814d8 100644 --- a/jetty-core/jetty-openid/src/main/config/modules/openid.mod +++ b/jetty-core/jetty-openid/src/main/config/modules/openid.mod @@ -49,4 +49,4 @@ etc/jetty-openid.xml ## Whether the user should be logged out after the idToken expires. # jetty.openid.logoutWhenIdTokenIsExpired=false -# end::documentation[] \ No newline at end of file +# end::documentation[] From b89affd92fb5aa9a660f984f7d6f452fd2a28b60 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 3 Dec 2024 16:57:29 +1100 Subject: [PATCH 3/6] wip Signed-off-by: Lachlan Roberts --- .../jetty/modules/code/examples/pom.xml | 8 + .../docs/programming/security/OpenIdDocs.java | 102 ++++ jetty-core/jetty-openid/pom.xml | 5 + .../jetty/security/openid/JwtDecoderTest.java | 1 + .../openid/OpenIdAuthenticationTest.java | 1 + .../jetty/security/openid/OpenIdProvider.java | 445 ------------------ jetty-ee9/jetty-ee9-openid/pom.xml | 5 + .../jetty/ee9/security/openid/JwtEncoder.java | 53 --- .../openid/OpenIdAuthenticationTest.java | 1 + .../ee9/security/openid/OpenIdProvider.java | 402 ---------------- pom.xml | 5 + tests/jetty-test-common/pom.xml | 29 ++ .../org/eclipse/jetty/tests}/JwtEncoder.java | 2 +- .../eclipse/jetty/tests/OpenIdProvider.java | 421 +++++++++++++++++ tests/pom.xml | 1 + 15 files changed, 580 insertions(+), 901 deletions(-) delete mode 100644 jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java delete mode 100644 jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/JwtEncoder.java delete mode 100644 jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdProvider.java create mode 100644 tests/jetty-test-common/pom.xml rename {jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid => tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests}/JwtEncoder.java (97%) create mode 100644 tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java diff --git a/documentation/jetty/modules/code/examples/pom.xml b/documentation/jetty/modules/code/examples/pom.xml index b24afc396b69..0400f131e201 100644 --- a/documentation/jetty/modules/code/examples/pom.xml +++ b/documentation/jetty/modules/code/examples/pom.xml @@ -117,6 +117,14 @@ org.eclipse.jetty.websocket jetty-websocket-jetty-server + + org.eclipse.jetty + jetty-slf4j-impl + + + org.eclipse.jetty.tests + jetty-test-common + diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java index c1e2e3e026cf..55aae620d666 100644 --- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java +++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java @@ -13,10 +13,13 @@ package org.eclipse.jetty.docs.programming.security; +import java.io.PrintStream; import java.util.Map; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.io.Content; import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.Constraint; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.SecurityHandler; @@ -24,7 +27,14 @@ import org.eclipse.jetty.security.openid.OpenIdAuthenticator; import org.eclipse.jetty.security.openid.OpenIdConfiguration; import org.eclipse.jetty.security.openid.OpenIdLoginService; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.session.SessionHandler; +import org.eclipse.jetty.tests.OpenIdProvider; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.security.Credential; public class OpenIdDocs @@ -37,6 +47,7 @@ public class OpenIdDocs private static final String END_SESSION_ENDPOINT = ""; private static final String AUTH_METHOD = ""; private static final HttpClient httpClient = new HttpClient(); + private static Server server = new Server(); private OpenIdConfiguration openIdConfig; private SecurityHandler securityHandler; @@ -104,4 +115,95 @@ private LoginService createWrappedLoginService() loginService.setName(ISSUER); return loginService; } + + public static void main(String[] args) throws Exception + { + new OpenIdDocs().combinedExample(); + } + + public void combinedExample() throws Exception + { + OpenIdProvider openIdProvider = new OpenIdProvider("my-client-id", "my-client-secret"); + openIdProvider.addRedirectUri("http://localhost:8080/j_security_check"); + openIdProvider.start(); + + server = new Server(8080); + server.setHandler(new Handler.Abstract() + { + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception + { + PrintStream writer = new PrintStream(Content.Sink.asOutputStream(response)); + + String pathInContext = Request.getPathInContext(request); + if (pathInContext.startsWith("/error")) + { + Fields parameters = Request.getParameters(request); + writer.println("error: " + parameters.get("error_description")); + } + else + { + writer.println("hello world"); + } + + writer.close(); + callback.succeeded(); + return true; + } + }); + + // To create an OpenIdConfiguration you can rely on discovery of the OIDC metadata. + OpenIdConfiguration openIdConfig = new OpenIdConfiguration( + "https://example.com/issuer", // ISSUER + "my-client-id", // CLIENT_ID + "my-client-secret" // CLIENT_SECRET + ); + + // Or you can specify the full OpenID configuration manually. + openIdConfig = new OpenIdConfiguration( + "https://example.com/issuer", // ISSUER + "https://example.com/token", // TOKEN_ENDPOINT + "https://example.com/auth", // AUTH_ENDPOINT + "https://example.com/logout", // END_SESSION_ENDPOINT + "my-client-id", // CLIENT_ID + "my-client-secret", // CLIENT_SECRET + "client_secret_post", // AUTH_METHOD (e.g., client_secret_post, client_secret_basic) + httpClient // HttpClient instance + ); + + openIdConfig = new OpenIdConfiguration( + openIdProvider.getProvider(), + openIdProvider.getClientId(), + openIdProvider.getClientSecret() + ); + + // The specific security handler implementation will change depending on whether you are using EE8/EE9/EE10/EE11 or Jetty Core API. + SecurityHandler.PathMapped securityHandler = new SecurityHandler.PathMapped(); + server.insertHandler(securityHandler); + securityHandler.put("/*", Constraint.ANY_USER); + + // A nested LoginService is optional and used to specify known users with defined roles. + // This can be any instance of LoginService and is not restricted to be a HashLoginService. + HashLoginService nestedLoginService = new HashLoginService(); + UserStore userStore = new UserStore(); + userStore.addUser("admin", null, new String[]{"admin"}); + nestedLoginService.setUserStore(userStore); + + // An OpenIdLoginService should be used which can optionally wrap the nestedLoginService to support roles. + LoginService loginService = new OpenIdLoginService(openIdConfig, nestedLoginService); + securityHandler.setLoginService(loginService); + + // Configure an OpenIdAuthenticator. + Authenticator authenticator = new OpenIdAuthenticator(openIdConfig, + "/j_security_check", // The path where the OIDC provider redirects back to Jetty. + "/error", // The error page where authentication errors are redirected. + "/logoutRedirect" // After logout the user is redirected to this page. + ); + securityHandler.setAuthenticator(authenticator); + + server.insertHandler(new SessionHandler()); + server.setDumpAfterStart(true); + server.start(); + server.join(); + } } \ No newline at end of file diff --git a/jetty-core/jetty-openid/pom.xml b/jetty-core/jetty-openid/pom.xml index 106c1ac250ac..80728823a0d5 100644 --- a/jetty-core/jetty-openid/pom.xml +++ b/jetty-core/jetty-openid/pom.xml @@ -47,6 +47,11 @@ jetty-slf4j-impl test + + org.eclipse.jetty.tests + jetty-test-common + test + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java b/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java index b343b222e52e..32a377d313cd 100644 --- a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java +++ b/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java @@ -16,6 +16,7 @@ import java.util.Map; import java.util.stream.Stream; +import org.eclipse.jetty.tests.JwtEncoder; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java b/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java index 79baf390aea4..e122d9b8e5d6 100644 --- a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java +++ b/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.session.FileSessionDataStoreFactory; import org.eclipse.jetty.session.SessionHandler; +import org.eclipse.jetty.tests.OpenIdProvider; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java b/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java deleted file mode 100644 index 512237b8c6ec..000000000000 --- a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java +++ /dev/null @@ -1,445 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.security.openid; - -import java.io.IOException; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -import org.eclipse.jetty.http.BadMessageException; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.ContextHandlerCollection; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.Fields; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.statistic.CounterStatistic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenIdProvider extends ContainerLifeCycle -{ - private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class); - - private static final String CONFIG_PATH = "/.well-known/openid-configuration"; - private static final String AUTH_PATH = "/auth"; - private static final String TOKEN_PATH = "/token"; - private static final String END_SESSION_PATH = "/end_session"; - private final Map issuedAuthCodes = new HashMap<>(); - - protected final String clientId; - protected final String clientSecret; - protected final List redirectUris = new ArrayList<>(); - private final ServerConnector connector; - private final Server server; - private int port = 0; - private String provider; - private User preAuthedUser; - private final CounterStatistic loggedInUsers = new CounterStatistic(); - private long _idTokenDuration = Duration.ofSeconds(10).toMillis(); - - public static void main(String[] args) throws Exception - { - String clientId = "CLIENT_ID123"; - String clientSecret = "PASSWORD123"; - int port = 5771; - String redirectUri = "http://localhost:8080/j_security_check"; - - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - openIdProvider.addRedirectUri(redirectUri); - openIdProvider.setPort(port); - openIdProvider.start(); - try - { - openIdProvider.join(); - } - finally - { - openIdProvider.stop(); - } - } - - public OpenIdProvider(String clientId, String clientSecret) - { - this.clientId = clientId; - this.clientSecret = clientSecret; - - server = new Server(); - connector = new ServerConnector(server); - server.addConnector(connector); - - ContextHandlerCollection contexts = new ContextHandlerCollection(); - contexts.addHandler(new ConfigServlet(CONFIG_PATH)); - contexts.addHandler(new AuthEndpoint(AUTH_PATH)); - contexts.addHandler(new TokenEndpoint(TOKEN_PATH)); - contexts.addHandler(new EndSessionEndpoint(END_SESSION_PATH)); - server.setHandler(contexts); - - addBean(server); - } - - public void setIdTokenDuration(long duration) - { - _idTokenDuration = duration; - } - - public long getIdTokenDuration() - { - return _idTokenDuration; - } - - public void join() throws InterruptedException - { - server.join(); - } - - public OpenIdConfiguration getOpenIdConfiguration() - { - String provider = getProvider(); - String authEndpoint = provider + AUTH_PATH; - String tokenEndpoint = provider + TOKEN_PATH; - return new OpenIdConfiguration(provider, authEndpoint, tokenEndpoint, clientId, clientSecret, null); - } - - public CounterStatistic getLoggedInUsers() - { - return loggedInUsers; - } - - @Override - protected void doStart() throws Exception - { - connector.setPort(port); - super.doStart(); - provider = "http://localhost:" + connector.getLocalPort(); - } - - public void setPort(int port) - { - if (isStarted()) - throw new IllegalStateException(); - this.port = port; - } - - public void setUser(User user) - { - this.preAuthedUser = user; - } - - public String getProvider() - { - if (!isStarted() && port == 0) - throw new IllegalStateException("Port of OpenIdProvider not configured"); - return provider; - } - - public void addRedirectUri(String uri) - { - redirectUris.add(uri); - } - - public class AuthEndpoint extends ContextHandler - { - public AuthEndpoint(String contextPath) - { - super(contextPath); - } - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - switch (request.getMethod()) - { - case "GET": - doGet(request, response, callback); - break; - case "POST": - doPost(request, response, callback); - break; - default: - throw new BadMessageException("Unsupported HTTP Method"); - } - - return true; - } - - protected void doGet(Request request, Response response, Callback callback) throws Exception - { - Fields parameters = Request.getParameters(request); - if (!clientId.equals(parameters.getValue("client_id"))) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid client_id"); - return; - } - - String redirectUri = parameters.getValue("redirect_uri"); - if (!redirectUris.contains(redirectUri)) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid redirect_uri"); - return; - } - - String scopeString = parameters.getValue("scope"); - List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(StringUtil.csvSplit(scopeString)); - if (!scopes.contains("openid")) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no openid scope"); - return; - } - - if (!"code".equals(parameters.getValue("response_type"))) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "response_type must be code"); - return; - } - - String state = parameters.getValue("state"); - if (state == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no state param"); - return; - } - - if (preAuthedUser == null) - { - response.getHeaders().add(HttpHeader.CONTENT_TYPE, "text/html"); - - String content = - "

Login to OpenID Connect Provider

" + - "
" + - "" + - "" + - "" + - "" + - "
"; - response.write(true, BufferUtil.toBuffer(content), callback); - } - else - { - redirectUser(request, response, callback, preAuthedUser, redirectUri, state); - } - } - - protected void doPost(Request request, Response response, Callback callback) throws Exception - { - Fields parameters = Request.getParameters(request); - - String redirectUri = parameters.getValue("redirectUri"); - if (!redirectUris.contains(redirectUri)) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid redirect_uri"); - return; - } - - String state = parameters.getValue("state"); - if (state == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no state param"); - return; - } - - String username = parameters.getValue("username"); - if (username == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no username"); - return; - } - - User user = new User(username); - redirectUser(request, response, callback, user, redirectUri, state); - } - - public void redirectUser(Request request, Response response, Callback callback, User user, String redirectUri, String state) throws IOException - { - String authCode = UUID.randomUUID().toString().replace("-", ""); - issuedAuthCodes.put(authCode, user); - - try - { - redirectUri += "?code=" + authCode + "&state=" + state; - Response.sendRedirect(request, response, callback, redirectUri); - } - catch (Throwable t) - { - issuedAuthCodes.remove(authCode); - throw t; - } - } - } - - private class TokenEndpoint extends ContextHandler - { - public TokenEndpoint(String contextPath) - { - super(contextPath); - } - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - Fields parameters = Request.getParameters(request); - - String code = parameters.getValue("code"); - - if (!clientId.equals(parameters.getValue("client_id")) || - !clientSecret.equals(parameters.getValue("client_secret")) || - !redirectUris.contains(parameters.getValue("redirect_uri")) || - !"authorization_code".equals(parameters.getValue("grant_type")) || - code == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "bad auth request"); - return true; - } - - User user = issuedAuthCodes.remove(code); - if (user == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid auth code"); - return true; - } - - String accessToken = "ABCDEFG"; - long accessTokenDuration = Duration.ofMinutes(10).toSeconds(); - String content = "{" + - "\"access_token\": \"" + accessToken + "\"," + - "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId, _idTokenDuration)) + "\"," + - "\"expires_in\": " + accessTokenDuration + "," + - "\"token_type\": \"Bearer\"" + - "}"; - - loggedInUsers.increment(); - response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain"); - response.write(true, BufferUtil.toBuffer(content), callback); - return true; - } - } - - private class EndSessionEndpoint extends ContextHandler - { - public EndSessionEndpoint(String contextPath) - { - super(contextPath); - } - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - Fields parameters = Request.getParameters(request); - - String idToken = parameters.getValue("id_token_hint"); - if (idToken == null) - { - Response.writeError(request, response, callback, HttpStatus.BAD_REQUEST_400, "no id_token_hint"); - return true; - } - - String logoutRedirect = parameters.getValue("post_logout_redirect_uri"); - if (logoutRedirect == null) - { - response.setStatus(HttpStatus.OK_200); - response.write(true, BufferUtil.toBuffer("logout success on end_session_endpoint"), callback); - return true; - } - - loggedInUsers.decrement(); - response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain"); - Response.sendRedirect(request, response, callback, logoutRedirect); - return true; - } - } - - private class ConfigServlet extends ContextHandler - { - public ConfigServlet(String contextPath) - { - super(contextPath); - } - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - String discoveryDocument = "{" + - "\"issuer\": \"" + provider + "\"," + - "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + - "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + - "\"end_session_endpoint\": \"" + provider + END_SESSION_PATH + "\"," + - "}"; - - response.write(true, BufferUtil.toBuffer(discoveryDocument), callback); - return true; - } - } - - public static class User - { - private final String subject; - private final String name; - - public User(String name) - { - this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); - } - - public User(String subject, String name) - { - this.subject = subject; - this.name = name; - } - - public String getName() - { - return name; - } - - public String getSubject() - { - return subject; - } - - public String getIdToken(String provider, String clientId, long duration) - { - long expiryTime = Instant.now().plusMillis(duration).getEpochSecond(); - return JwtEncoder.createIdToken(provider, clientId, subject, name, expiryTime); - } - - @Override - public boolean equals(Object obj) - { - if (!(obj instanceof User)) - return false; - return Objects.equals(subject, ((User)obj).subject) && Objects.equals(name, ((User)obj).name); - } - - @Override - public int hashCode() - { - return Objects.hash(subject, name); - } - } -} diff --git a/jetty-ee9/jetty-ee9-openid/pom.xml b/jetty-ee9/jetty-ee9-openid/pom.xml index 45f0c469ebb0..3cae7afd7f16 100644 --- a/jetty-ee9/jetty-ee9-openid/pom.xml +++ b/jetty-ee9/jetty-ee9-openid/pom.xml @@ -55,6 +55,11 @@ jetty-ee9-servlet test
+ + org.eclipse.jetty.tests + jetty-test-common + test + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/JwtEncoder.java b/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/JwtEncoder.java deleted file mode 100644 index 6de75b9e4ac1..000000000000 --- a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/JwtEncoder.java +++ /dev/null @@ -1,53 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.ee9.security.openid; - -import java.util.Base64; - -/** - * A basic JWT encoder for testing purposes. - */ -public class JwtEncoder -{ - private static final Base64.Encoder ENCODER = Base64.getUrlEncoder(); - private static final String DEFAULT_HEADER = "{\"INFO\": \"this is not used or checked in our implementation\"}"; - private static final String DEFAULT_SIGNATURE = "we do not validate signature as we use the authorization code flow"; - - public static String encode(String idToken) - { - return stripPadding(ENCODER.encodeToString(DEFAULT_HEADER.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(idToken.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(DEFAULT_SIGNATURE.getBytes())); - } - - private static String stripPadding(String paddedBase64) - { - return paddedBase64.split("=")[0]; - } - - /** - * Create a basic JWT for testing using argument supplied attributes. - */ - public static String createIdToken(String provider, String clientId, String subject, String name, long expiry) - { - return "{" + - "\"iss\": \"" + provider + "\"," + - "\"sub\": \"" + subject + "\"," + - "\"aud\": \"" + clientId + "\"," + - "\"exp\": " + expiry + "," + - "\"name\": \"" + name + "\"," + - "\"email\": \"" + name + "@example.com" + "\"" + - "}"; - } -} diff --git a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticationTest.java b/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticationTest.java index dfa5d06537b4..b98937811847 100644 --- a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticationTest.java +++ b/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticationTest.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.session.FileSessionDataStoreFactory; +import org.eclipse.jetty.tests.OpenIdProvider; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.security.Password; diff --git a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdProvider.java b/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdProvider.java deleted file mode 100644 index 21f1ff6b7cb8..000000000000 --- a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdProvider.java +++ /dev/null @@ -1,402 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.ee9.security.openid; - -import java.io.IOException; -import java.io.PrintWriter; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.ee9.servlet.ServletContextHandler; -import org.eclipse.jetty.ee9.servlet.ServletHolder; -import org.eclipse.jetty.security.openid.OpenIdConfiguration; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.statistic.CounterStatistic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenIdProvider extends ContainerLifeCycle -{ - private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class); - - private static final String CONFIG_PATH = "/.well-known/openid-configuration"; - private static final String AUTH_PATH = "/auth"; - private static final String TOKEN_PATH = "/token"; - private static final String END_SESSION_PATH = "/end_session"; - private final Map issuedAuthCodes = new HashMap<>(); - - protected final String clientId; - protected final String clientSecret; - protected final List redirectUris = new ArrayList<>(); - private final ServerConnector connector; - private final Server server; - private int port = 0; - private String provider; - private User preAuthedUser; - private final CounterStatistic loggedInUsers = new CounterStatistic(); - private long _idTokenDuration = Duration.ofSeconds(10).toMillis(); - - public static void main(String[] args) throws Exception - { - String clientId = "CLIENT_ID123"; - String clientSecret = "PASSWORD123"; - int port = 5771; - String redirectUri = "http://localhost:8080/j_security_check"; - - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - openIdProvider.addRedirectUri(redirectUri); - openIdProvider.setPort(port); - openIdProvider.start(); - try - { - openIdProvider.join(); - } - finally - { - openIdProvider.stop(); - } - } - - public OpenIdProvider(String clientId, String clientSecret) - { - this.clientId = clientId; - this.clientSecret = clientSecret; - - server = new Server(); - connector = new ServerConnector(server); - server.addConnector(connector); - - ServletContextHandler contextHandler = new ServletContextHandler(); - contextHandler.setContextPath("/"); - contextHandler.addServlet(new ServletHolder(new ConfigServlet()), CONFIG_PATH); - contextHandler.addServlet(new ServletHolder(new AuthEndpoint()), AUTH_PATH); - contextHandler.addServlet(new ServletHolder(new TokenEndpoint()), TOKEN_PATH); - contextHandler.addServlet(new ServletHolder(new EndSessionEndpoint()), END_SESSION_PATH); - server.setHandler(contextHandler); - - addBean(server); - } - - public void setIdTokenDuration(long duration) - { - _idTokenDuration = duration; - } - - public long getIdTokenDuration() - { - return _idTokenDuration; - } - - public void join() throws InterruptedException - { - server.join(); - } - - public OpenIdConfiguration getOpenIdConfiguration() - { - String provider = getProvider(); - String authEndpoint = provider + AUTH_PATH; - String tokenEndpoint = provider + TOKEN_PATH; - return new OpenIdConfiguration(provider, authEndpoint, tokenEndpoint, clientId, clientSecret, null); - } - - public CounterStatistic getLoggedInUsers() - { - return loggedInUsers; - } - - @Override - protected void doStart() throws Exception - { - connector.setPort(port); - super.doStart(); - provider = "http://localhost:" + connector.getLocalPort(); - } - - public void setPort(int port) - { - if (isStarted()) - throw new IllegalStateException(); - this.port = port; - } - - public void setUser(User user) - { - this.preAuthedUser = user; - } - - public String getProvider() - { - if (!isStarted() && port == 0) - throw new IllegalStateException("Port of OpenIdProvider not configured"); - return provider; - } - - public void addRedirectUri(String uri) - { - redirectUris.add(uri); - } - - public class AuthEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - if (!clientId.equals(req.getParameter("client_id"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid client_id"); - return; - } - - String redirectUri = req.getParameter("redirect_uri"); - if (!redirectUris.contains(redirectUri)) - { - LOG.warn("invalid redirectUri {}", redirectUri); - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String scopeString = req.getParameter("scope"); - List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(scopeString.split(" ")); - if (!scopes.contains("openid")) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no openid scope"); - return; - } - - if (!"code".equals(req.getParameter("response_type"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "response_type must be code"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - if (preAuthedUser == null) - { - PrintWriter writer = resp.getWriter(); - resp.setContentType("text/html"); - writer.println("

Login to OpenID Connect Provider

"); - writer.println("
"); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println("
"); - } - else - { - redirectUser(resp, preAuthedUser, redirectUri, state); - } - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String redirectUri = req.getParameter("redirectUri"); - if (!redirectUris.contains(redirectUri)) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - String username = req.getParameter("username"); - if (username == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no username"); - return; - } - - User user = new User(username); - redirectUser(resp, user, redirectUri, state); - } - - public void redirectUser(HttpServletResponse response, User user, String redirectUri, String state) throws IOException - { - String authCode = UUID.randomUUID().toString().replace("-", ""); - issuedAuthCodes.put(authCode, user); - - try - { - redirectUri += "?code=" + authCode + "&state=" + state; - response.sendRedirect(response.encodeRedirectURL(redirectUri)); - } - catch (Throwable t) - { - issuedAuthCodes.remove(authCode); - throw t; - } - } - } - - private class TokenEndpoint extends HttpServlet - { - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - String code = req.getParameter("code"); - - if (!clientId.equals(req.getParameter("client_id")) || - !clientSecret.equals(req.getParameter("client_secret")) || - !redirectUris.contains(req.getParameter("redirect_uri")) || - !"authorization_code".equals(req.getParameter("grant_type")) || - code == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "bad auth request"); - return; - } - - User user = issuedAuthCodes.remove(code); - if (user == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid auth code"); - return; - } - - String accessToken = "ABCDEFG"; - long accessTokenDuration = Duration.ofMinutes(10).toSeconds(); - String response = "{" + - "\"access_token\": \"" + accessToken + "\"," + - "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId, _idTokenDuration)) + "\"," + - "\"expires_in\": " + accessTokenDuration + "," + - "\"token_type\": \"Bearer\"" + - "}"; - - loggedInUsers.increment(); - resp.setContentType("text/plain"); - resp.getWriter().print(response); - } - } - - private class EndSessionEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - doPost(req, resp); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String idToken = req.getParameter("id_token_hint"); - if (idToken == null) - { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "no id_token_hint"); - return; - } - - String logoutRedirect = req.getParameter("post_logout_redirect_uri"); - if (logoutRedirect == null) - { - resp.setStatus(HttpServletResponse.SC_OK); - resp.getWriter().println("logout success on end_session_endpoint"); - return; - } - - loggedInUsers.decrement(); - resp.setContentType("text/plain"); - resp.sendRedirect(logoutRedirect); - } - } - - private class ConfigServlet extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String discoveryDocument = "{" + - "\"issuer\": \"" + provider + "\"," + - "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + - "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + - "\"end_session_endpoint\": \"" + provider + END_SESSION_PATH + "\"," + - "}"; - - resp.getWriter().write(discoveryDocument); - } - } - - public static class User - { - private final String subject; - private final String name; - - public User(String name) - { - this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); - } - - public User(String subject, String name) - { - this.subject = subject; - this.name = name; - } - - public String getName() - { - return name; - } - - public String getSubject() - { - return subject; - } - - public String getIdToken(String provider, String clientId, long duration) - { - long expiryTime = Instant.now().plusMillis(duration).getEpochSecond(); - return JwtEncoder.createIdToken(provider, clientId, subject, name, expiryTime); - } - - @Override - public boolean equals(Object obj) - { - if (!(obj instanceof User)) - return false; - return Objects.equals(subject, ((User)obj).subject) && Objects.equals(name, ((User)obj).name); - } - - @Override - public int hashCode() - { - return Objects.hash(subject, name); - } - } -} diff --git a/pom.xml b/pom.xml index 0c6ff417b416..25e3d4d3b503 100644 --- a/pom.xml +++ b/pom.xml @@ -942,6 +942,11 @@ jetty-quic-server ${project.version}
+ + org.eclipse.jetty.tests + jetty-test-common + ${project.version} + org.eclipse.jetty.tests jetty-test-session-common diff --git a/tests/jetty-test-common/pom.xml b/tests/jetty-test-common/pom.xml new file mode 100644 index 000000000000..233f05a24662 --- /dev/null +++ b/tests/jetty-test-common/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + org.eclipse.jetty.tests + tests + 12.0.16-SNAPSHOT + + jetty-test-common + jar + Tests :: Test Utilities + + + ${project.groupId}.testers + + + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty + jetty-util + + + + diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java b/tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java similarity index 97% rename from jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java rename to tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java index 928a51fd8224..d4611f07a774 100644 --- a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java +++ b/tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.security.openid; +package org.eclipse.jetty.tests; import java.util.Base64; diff --git a/tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java b/tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java new file mode 100644 index 000000000000..aba79c2a0c39 --- /dev/null +++ b/tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java @@ -0,0 +1,421 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.tests; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.statistic.CounterStatistic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OpenIdProvider extends ContainerLifeCycle +{ + private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class); + + private static final String CONFIG_PATH = "/.well-known/openid-configuration"; + private static final String AUTH_PATH = "/auth"; + private static final String TOKEN_PATH = "/token"; + private static final String END_SESSION_PATH = "/end_session"; + private final Map issuedAuthCodes = new HashMap<>(); + + protected final String clientId; + protected final String clientSecret; + protected final List redirectUris = new ArrayList<>(); + private final ServerConnector connector; + private final Server server; + private int port = 0; + private String provider; + private User preAuthedUser; + private final CounterStatistic loggedInUsers = new CounterStatistic(); + private long _idTokenDuration = Duration.ofSeconds(10).toMillis(); + + public static void main(String[] args) throws Exception + { + String clientId = "CLIENT_ID123"; + String clientSecret = "PASSWORD123"; + int port = 5771; + String redirectUri = "http://localhost:8080/j_security_check"; + + OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); + openIdProvider.addRedirectUri(redirectUri); + openIdProvider.setPort(port); + openIdProvider.start(); + try + { + openIdProvider.join(); + } + finally + { + openIdProvider.stop(); + } + } + + public OpenIdProvider() + { + this("clientId" + StringUtil.randomAlphaNumeric(4), StringUtil.randomAlphaNumeric(10)); + } + + public OpenIdProvider(String clientId, String clientSecret) + { + this.clientId = clientId; + this.clientSecret = clientSecret; + + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + + server.setHandler(new OpenIdProviderHandler()); + addBean(server); + } + + public String getClientId() + { + return clientId; + } + + public String getClientSecret() + { + return clientSecret; + } + + public void setIdTokenDuration(long duration) + { + _idTokenDuration = duration; + } + + public long getIdTokenDuration() + { + return _idTokenDuration; + } + + public void join() throws InterruptedException + { + server.join(); + } + + public CounterStatistic getLoggedInUsers() + { + return loggedInUsers; + } + + @Override + protected void doStart() throws Exception + { + connector.setPort(port); + super.doStart(); + provider = "http://localhost:" + connector.getLocalPort(); + } + + public void setPort(int port) + { + if (isStarted()) + throw new IllegalStateException(); + this.port = port; + } + + public void setUser(User user) + { + this.preAuthedUser = user; + } + + public String getProvider() + { + if (!isStarted() && port == 0) + throw new IllegalStateException("Port of OpenIdProvider not configured"); + return provider; + } + + public void addRedirectUri(String uri) + { + redirectUris.add(uri); + } + + public class OpenIdProviderHandler extends Handler.Abstract + { + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception + { + String pathInContext = Request.getPathInContext(request); + switch (pathInContext) + { + case CONFIG_PATH -> doGetConfigServlet(request, response, callback); + case AUTH_PATH -> doAuthEndpoint(request, response, callback); + case TOKEN_PATH -> doTokenEndpoint(request, response, callback); + case END_SESSION_PATH -> doEndSessionEndpoint(request, response, callback); + default -> Response.writeError(request, response, callback, HttpStatus.NOT_FOUND_404); + } + + return true; + } + } + + protected void doAuthEndpoint(Request request, Response response, Callback callback) throws Exception + { + String method = request.getMethod(); + switch (method) + { + case "GET" -> doGetAuthEndpoint(request, response, callback); + case "POST" -> doPostAuthEndpoint(request, response, callback); + default -> throw new BadMessageException("Unsupported HTTP method: " + method); + } + } + + protected void doGetAuthEndpoint(Request request, Response response, Callback callback) throws Exception + { + Fields parameters = Request.getParameters(request); + + if (!clientId.equals(parameters.getValue("client_id"))) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid client_id"); + return; + } + + String redirectUri = parameters.getValue("redirect_uri"); + if (!redirectUris.contains(redirectUri)) + { + LOG.warn("invalid redirectUri {}", redirectUri); + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid redirect_uri"); + return; + } + + String scopeString = parameters.getValue("scope"); + List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(scopeString.split(" ")); + if (!scopes.contains("openid")) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no openid scope"); + return; + } + + if (!"code".equals(parameters.getValue("response_type"))) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "response_type must be code"); + return; + } + + String state = parameters.getValue("state"); + if (state == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no state param"); + return; + } + + if (preAuthedUser == null) + { + String responseContent = String.format(""" +

Login to OpenID Connect Provider

+
+ + + + +
+ """, AUTH_PATH, redirectUri, state); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/html"); + response.write(true, BufferUtil.toBuffer(responseContent), callback); + } + else + { + redirectUser(request, response, callback, preAuthedUser, redirectUri, state); + } + } + + protected void doPostAuthEndpoint(Request request, Response response, Callback callback) throws Exception + { + Fields parameters = Request.getParameters(request); + String redirectUri = parameters.getValue("redirectUri"); + if (!redirectUris.contains(redirectUri)) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid redirect_uri"); + return; + } + + String state = parameters.getValue("state"); + if (state == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no state param"); + return; + } + + String username = parameters.getValue("username"); + if (username == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no username"); + return; + } + + User user = new User(username); + redirectUser(request, response, callback, user, redirectUri, state); + } + + public void redirectUser(Request request, Response response, Callback callback, User user, String redirectUri, String state) throws IOException + { + String authCode = UUID.randomUUID().toString().replace("-", ""); + issuedAuthCodes.put(authCode, user); + + try + { + redirectUri += "?code=" + authCode + "&state=" + state; + Response.sendRedirect(request, response, callback, redirectUri); + } + catch (Throwable t) + { + issuedAuthCodes.remove(authCode); + throw t; + } + } + + protected void doTokenEndpoint(Request request, Response response, Callback callback) throws Exception + { + if (!HttpMethod.POST.is(request.getMethod())) + throw new BadMessageException("Unsupported HTTP method for token Endpoint: " + request.getMethod()); + + Fields parameters = Request.getParameters(request); + String code = parameters.getValue("code"); + + if (!clientId.equals(parameters.getValue("client_id")) || + !clientSecret.equals(parameters.getValue("client_secret")) || + !redirectUris.contains(parameters.getValue("redirect_uri")) || + !"authorization_code".equals(parameters.getValue("grant_type")) || + code == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "bad auth request"); + return; + } + + User user = issuedAuthCodes.remove(code); + if (user == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid auth code"); + return; + } + + String accessToken = "ABCDEFG"; + long accessTokenDuration = Duration.ofMinutes(10).toSeconds(); + String responseContent = "{" + + "\"access_token\": \"" + accessToken + "\"," + + "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId, _idTokenDuration)) + "\"," + + "\"expires_in\": " + accessTokenDuration + "," + + "\"token_type\": \"Bearer\"" + + "}"; + + loggedInUsers.increment(); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain"); + response.write(true, BufferUtil.toBuffer(responseContent), callback); + } + + protected void doEndSessionEndpoint(Request request, Response response, Callback callback) throws Exception + { + Fields parameters = Request.getParameters(request); + String idToken = parameters.getValue("id_token_hint"); + if (idToken == null) + { + Response.writeError(request, response, callback, HttpStatus.BAD_REQUEST_400, "no id_token_hint"); + return; + } + + String logoutRedirect = parameters.getValue("post_logout_redirect_uri"); + if (logoutRedirect == null) + { + response.setStatus(HttpStatus.OK_200); + response.write(true, BufferUtil.toBuffer("logout success on end_session_endpoint"), callback); + return; + } + + loggedInUsers.decrement(); + Response.sendRedirect(request, response, callback, logoutRedirect); + } + + protected void doGetConfigServlet(Request request, Response response, Callback callback) throws IOException + { + String discoveryDocument = "{" + + "\"issuer\": \"" + provider + "\"," + + "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + + "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + + "\"end_session_endpoint\": \"" + provider + END_SESSION_PATH + "\"," + + "}"; + + response.write(true, BufferUtil.toBuffer(discoveryDocument), callback); + } + + public static class User + { + private final String subject; + private final String name; + + public User(String name) + { + this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); + } + + public User(String subject, String name) + { + this.subject = subject; + this.name = name; + } + + public String getName() + { + return name; + } + + public String getSubject() + { + return subject; + } + + public String getIdToken(String provider, String clientId, long duration) + { + long expiryTime = Instant.now().plusMillis(duration).getEpochSecond(); + return JwtEncoder.createIdToken(provider, clientId, subject, name, expiryTime); + } + + @Override + public boolean equals(Object obj) + { + if (!(obj instanceof User)) + return false; + return Objects.equals(subject, ((User)obj).subject) && Objects.equals(name, ((User)obj).name); + } + + @Override + public int hashCode() + { + return Objects.hash(subject, name); + } + } +} diff --git a/tests/pom.xml b/tests/pom.xml index 51c21ba95489..c71e40886a2f 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -13,6 +13,7 @@ jetty-testers jetty-jmh + jetty-test-common jetty-test-multipart jetty-test-session-common test-cross-context-dispatch From 10444e0473ac8edd5686fdd9c5fa03ec6a703ba0 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 16 Dec 2024 17:10:22 +1100 Subject: [PATCH 4/6] PR #12559 - changes to OpenID docs from review Signed-off-by: Lachlan Roberts --- .../docs/programming/security/OpenIdDocs.java | 140 +++++------------- .../pages/modules/standard.adoc | 9 ++ .../pages/security/openid-support.adoc | 31 +++- .../pages/security/openid-support.adoc | 76 ++++------ 4 files changed, 95 insertions(+), 161 deletions(-) diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java index 55aae620d666..fb33bf6298cd 100644 --- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java +++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java @@ -14,11 +14,12 @@ package org.eclipse.jetty.docs.programming.security; import java.io.PrintStream; +import java.security.Principal; import java.util.Map; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.io.Content; -import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.AuthenticationState; import org.eclipse.jetty.security.Constraint; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.security.LoginService; @@ -31,103 +32,17 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.Session; import org.eclipse.jetty.session.SessionHandler; -import org.eclipse.jetty.tests.OpenIdProvider; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Fields; -import org.eclipse.jetty.util.security.Credential; public class OpenIdDocs { - private static final String ISSUER = ""; - private static final String CLIENT_ID = ""; - private static final String CLIENT_SECRET = ""; - private static final String TOKEN_ENDPOINT = ""; - private static final String AUTH_ENDPOINT = ""; - private static final String END_SESSION_ENDPOINT = ""; - private static final String AUTH_METHOD = ""; - private static final HttpClient httpClient = new HttpClient(); - private static Server server = new Server(); - - private OpenIdConfiguration openIdConfig; - private SecurityHandler securityHandler; - - public void createConfigurationWithDiscovery() - { - // tag::createConfigurationWithDiscovery[] - OpenIdConfiguration openIdConfig = new OpenIdConfiguration(ISSUER, CLIENT_ID, CLIENT_SECRET); - // end::createConfigurationWithDiscovery[] - } - - public void createConfiguration() - { - // tag::createConfiguration[] - OpenIdConfiguration openIdConfig = new OpenIdConfiguration(ISSUER, TOKEN_ENDPOINT, AUTH_ENDPOINT, END_SESSION_ENDPOINT, - CLIENT_ID, CLIENT_SECRET, AUTH_METHOD, httpClient); - // end::createConfiguration[] - } - - public void configureLoginService() - { - // tag::configureLoginService[] - LoginService loginService = new OpenIdLoginService(openIdConfig); - securityHandler.setLoginService(loginService); - // end::configureLoginService[] - } - - public void configureAuthenticator() - { - // tag::configureAuthenticator[] - Authenticator authenticator = new OpenIdAuthenticator(openIdConfig, "/error"); - securityHandler.setAuthenticator(authenticator); - // end::configureAuthenticator[] - } - - @SuppressWarnings("unchecked") - public void accessClaims() - { - Request request = new Request.Wrapper(null); - - // tag::accessClaims[] - Map claims = (Map)request.getSession(true).getAttribute("org.eclipse.jetty.security.openid.claims"); - String userId = (String)claims.get("sub"); - - Map response = (Map)request.getSession(true).getAttribute("org.eclipse.jetty.security.openid.response"); - String accessToken = (String)response.get("access_token"); - // tag::accessClaims[] - } - - public void wrappedLoginService() - { - // tag::wrappedLoginService[] - // Use the optional LoginService for Roles. - LoginService wrappedLoginService = createWrappedLoginService(); - LoginService loginService = new OpenIdLoginService(openIdConfig, wrappedLoginService); - // end::wrappedLoginService[] - } - - private LoginService createWrappedLoginService() - { - HashLoginService loginService = new HashLoginService(); - UserStore userStore = new UserStore(); - userStore.addUser("admin", Credential.getCredential("password"), new String[]{"admin"}); - loginService.setUserStore(userStore); - loginService.setName(ISSUER); - return loginService; - } - - public static void main(String[] args) throws Exception - { - new OpenIdDocs().combinedExample(); - } - public void combinedExample() throws Exception { - OpenIdProvider openIdProvider = new OpenIdProvider("my-client-id", "my-client-secret"); - openIdProvider.addRedirectUri("http://localhost:8080/j_security_check"); - openIdProvider.start(); - - server = new Server(8080); + Server server = new Server(8080); + // tag::openIdUsageExample[] server.setHandler(new Handler.Abstract() { @Override @@ -138,12 +53,24 @@ public boolean handle(Request request, Response response, Callback callback) thr String pathInContext = Request.getPathInContext(request); if (pathInContext.startsWith("/error")) { + // Handle requests to the error page which may have an error description parameter. Fields parameters = Request.getParameters(request); - writer.println("error: " + parameters.get("error_description")); + writer.println("error_description: " + parameters.get("error_description_jetty") + "
"); } else { - writer.println("hello world"); + Principal userPrincipal = AuthenticationState.getUserPrincipal(request); + writer.println("userPrincipal: " + userPrincipal); + if (userPrincipal != null) + { + // You can access the full openid claims for an authenticated session. + Session session = request.getSession(false); + @SuppressWarnings("unchecked") + Map claims = (Map)session.getAttribute("org.eclipse.jetty.security.openid.claims"); + writer.println("claims: " + claims); + writer.println("name: " + claims.get("name")); + writer.println("sub: " + claims.get("sub")); + } } writer.close(); @@ -151,7 +78,9 @@ public boolean handle(Request request, Response response, Callback callback) thr return true; } }); + // end::openIdUsageExample[] + // tag::openIdConfigExample[] // To create an OpenIdConfiguration you can rely on discovery of the OIDC metadata. OpenIdConfiguration openIdConfig = new OpenIdConfiguration( "https://example.com/issuer", // ISSUER @@ -168,41 +97,38 @@ public boolean handle(Request request, Response response, Callback callback) thr "my-client-id", // CLIENT_ID "my-client-secret", // CLIENT_SECRET "client_secret_post", // AUTH_METHOD (e.g., client_secret_post, client_secret_basic) - httpClient // HttpClient instance - ); - - openIdConfig = new OpenIdConfiguration( - openIdProvider.getProvider(), - openIdProvider.getClientId(), - openIdProvider.getClientSecret() + new HttpClient() // HttpClient instance ); // The specific security handler implementation will change depending on whether you are using EE8/EE9/EE10/EE11 or Jetty Core API. SecurityHandler.PathMapped securityHandler = new SecurityHandler.PathMapped(); server.insertHandler(securityHandler); - securityHandler.put("/*", Constraint.ANY_USER); + securityHandler.put("/auth/*", Constraint.ANY_USER); // A nested LoginService is optional and used to specify known users with defined roles. // This can be any instance of LoginService and is not restricted to be a HashLoginService. HashLoginService nestedLoginService = new HashLoginService(); UserStore userStore = new UserStore(); - userStore.addUser("admin", null, new String[]{"admin"}); + userStore.addUser("", null, new String[]{"admin"}); nestedLoginService.setUserStore(userStore); + // Optional configuration to allow new users not listed in the nested LoginService to be authenticated. + openIdConfig.setAuthenticateNewUsers(true); + // An OpenIdLoginService should be used which can optionally wrap the nestedLoginService to support roles. LoginService loginService = new OpenIdLoginService(openIdConfig, nestedLoginService); securityHandler.setLoginService(loginService); // Configure an OpenIdAuthenticator. - Authenticator authenticator = new OpenIdAuthenticator(openIdConfig, + securityHandler.setAuthenticator(new OpenIdAuthenticator(openIdConfig, "/j_security_check", // The path where the OIDC provider redirects back to Jetty. - "/error", // The error page where authentication errors are redirected. - "/logoutRedirect" // After logout the user is redirected to this page. - ); - securityHandler.setAuthenticator(authenticator); + "/error", // Optional page where authentication errors are redirected. + "/logoutRedirect" // Optional page where the user is redirected to this page after logout. + )); + // Session handler is required for OpenID authentication. server.insertHandler(new SessionHandler()); - server.setDumpAfterStart(true); + // end::openIdConfigExample[] server.start(); server.join(); } diff --git a/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc index 129b0110fc8e..0195b9644c70 100644 --- a/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc +++ b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc @@ -356,6 +356,15 @@ The module properties are: include::{jetty-home}/modules/openid.mod[tags=documentation] ---- +Among the configurable properties, the only required properties are: + +`jetty.openid.provider`:: +The issuer identifier of the OpenID Provider, which is the base URL before .well-known/openid-configuration. It must match the issuer field in the provider's configuration and is case-sensitive. +`jetty.openid.clientId`:: +The Client Identifier assigned to your application by the OpenID Provider, used to uniquely identify your app during authentication requests. +`jetty.openid.clientSecret`:: +The Client Secret issued by the OpenID Provider, used to verify your application's identity during the authentication process. This must be kept confidential. + [[qos]] == Module `qos` diff --git a/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc b/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc index fe93505f99bc..4bd428999ec8 100644 --- a/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc +++ b/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc @@ -16,6 +16,8 @@ A more general discussion about OpenID and its support in Jetty is available in xref:programming-guide:security/openid-support.adoc[this section]. +Also see xref:operations-guide:modules/standard.adoc#openid[OpenID Module] for more information about the module configuration. + == OpenID Provider Configuration To enable OpenID support, you need to enable the `openid` module: @@ -24,18 +26,31 @@ To enable OpenID support, you need to enable the `openid` module: $ java -jar $JETTY_HOME/start.jar --add-modules=openid ---- -To configure OpenID Authentication with Jetty you will need to specify the OpenID Provider's issuer identifier (case-sensitive URL using the `https` scheme) and the OAuth 2.0 Client ID and Client Secret. +To configure OpenID Authentication with Jetty you will need to specify the OpenID Provider's issuer identifier (case-sensitive URL) and the OAuth 2.0 Client ID and Client Secret. If the OpenID Provider does not allow metadata discovery you will also need to specify the token endpoint and authorization endpoint of the OpenID Provider. These values can be set as properties in `$JETTY_BASE/start.d/openid.ini` file. +This is an example of an openid.ini file which uses discovery of the OpenID endpoints: +[source] +---- +## The OpenID Identity Provider's issuer ID (the entire URL *before* ".well-known/openid-configuration") +jetty.openid.provider=https://id.example.com/ + +## The Client Identifier +jetty.openid.clientId=test1234 + +## The Client Secret +jetty.openid.clientSecret=XT_Mafv_aUCGheuCaKY8P +---- + == Web Application Specific Configuration in `web.xml` The `web.xml` file needs some specific configuration to use OpenID. There must be a `login-config` element with an `auth-method` value of `OPENID`, and a `realm-name` value of the exact URL string used to set the OpenID Provider. -To set the error page, you must set an `init-param` named `org.eclipse.jetty.security.openid.error_page` whose value should be a path relative to the webapp where authentication errors should be redirected. +To set the error page, you must set an `context-param` named `org.eclipse.jetty.security.openid.error_page` whose value should be a path relative to the web application where authentication errors should be redirected. For example: @@ -60,8 +75,16 @@ For example: ---- +== Authorization + +A nested `LoginService` can be added in order to specify known users by their SUB (subject identifier) with an empty credential. +This can be used to assign security roles to users, and you can configure `openid.ini` to only authenticate users known to the nested `LoginService`. + +This nested `LoginService`can be configured in XML through `$JETTY_BASE/etc/openid-baseloginservice.xml`. + == Supporting Multiple OpenID Providers -You may override the `jetty-openid.xml` file in `$JETTY_BASE/etc/jetty-openid.xml` to add additional `OpenIdConfiguration` instances as beans on the server. +It is possible to support multiple OpenID Providers on the same server, where the web application selects the provider it wants to use through the `` in its `` element of `web.xml`. -If there are multiple OpenID configuration instances found on the server then the `OpenIdAuthenticationFactory` will select the one with an `issuer` matching the `` of the `web.xml` for a given web app. +You may create a custom module to add additional `OpenIdConfiguration` instances as beans on the server. +See the xref:operations-guide:modules/custom.adoc[Custom Module] section as well as `$JETTY_HOME/etc/jetty-openid.xml` for information on how to do this. diff --git a/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc b/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc index 0746c24ffae1..bccae48a83b1 100644 --- a/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc +++ b/documentation/jetty/modules/programming-guide/pages/security/openid-support.adoc @@ -14,79 +14,55 @@ [[openid-support]] = OpenID Support -== External Setup +Jetty supports authentication using the OpenID Connect protocol (see link:https://openid.net/specs/openid-connect-core-1_0-final.html[OpenID Connect Core 1.0]). +This allows users to authenticate with third party OpenID Providers, making possible to integrate features like "Sign in with Google" or "Sign in with Microsoft", among others. +With OpenID Connect, Jetty applications can offload the responsibility of managing user credentials while enabling features like single sign-on (SSO). -=== Registering an App with OpenID Provider -You must register the app with an OpenID Provider such as link:https://developers.google.com/identity/protocols/OpenIDConnect[Google] or link:https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html[Amazon]. -This will give you a Client ID and Client Secret. +== External Configuration -Once obtained the Client ID and the Client Secret, you must also register all the possible URIs for your web application with the path `/j_security_check` so that the OpenId Provider will allow redirection back to the webapp. +To use OpenID Connect authentication with Jetty you are required to set up an external OpenID Provider, some examples of this are link:https://developers.google.com/identity/protocols/OpenIDConnect[Google] and link:https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html[Amazon]. +Once you have set up your OpenID Provider you will have access to a Client ID and Client Secret which you can use to configure Jetty. -These may look like: +You must also configure your OpenID Provider to recognize the redirect URIs that your application will use to handle authentication responses. +By default, the redirect path is `/j_security_check`, but this can be customized through the `OpenIdAuthenticator` configuration if needed. - * `+http://localhost:8080/openid-webapp/j_security_check+` - * `+https://example.com/j_security_check+` +For example, you may wish to register the following URIs: -== Code Setup + * For a deployed application, `+https://example.com/j_security_check+`. + * For local development, `+http://localhost:8080/j_security_check+`. -=== Define the `OpenIdConfiguration` for a specific OpenID Provider. +Ensure that all relevant environments where your application is deployed have their corresponding URIs registered with the OpenID Provider to avoid authentication errors. -If the OpenID Provider allows metadata discovery then you can use: +== Jetty Configuration -[,java,indent=0] ----- -include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=createConfigurationWithDiscovery] ----- +=== Code Example -Otherwise, you can manually enter the necessary information: +This is an example of how you can configure OpenID Connect authentication with an embedded Jetty environment with the Jetty Core API. [,java,indent=0] ---- -include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=createConfiguration] +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=openIdConfigExample] ---- -=== Configuring an `OpenIdLoginService` -[,java,indent=0] ----- -include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=configureLoginService] ----- +=== Application Usage + +Here is an example of a Jetty Core `Handler` which handles authenticated requests by accessing the OpenID claims, and also handles authentication errors at `/error`. -=== Configuring an `OpenIdAuthenticator` with `OpenIdConfiguration` and Error Page Redirect [,java,indent=0] ---- -include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=configureAuthenticator] +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=openIdUsageExample] ---- -=== Usage - ==== Claims and Access Token Claims about the user can be found using attributes in the HTTP session attribute `org.eclipse.jetty.security.openid.claims`, and the full response containing the OAuth 2.0 Access Token can be found in the HTTP session attribute `org.eclipse.jetty.security.openid.response`. -Example: -[,java,indent=0] ----- -include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=configureAuthenticator] ----- - -== Scopes -The OpenID scope is always used but additional scopes can be requested which can give you additional resources or privileges. - -For the Google OpenID Provider it can be useful to request the scopes `profile` and `email` which will give you additional user claims. - -Additional scopes can be specified using `OpenIdConfiguration.addScopes(\...);`. +=== Authorization -== Authorization +If security roles are required they can be configured through a nested `LoginService` which is deferred to for user roles. -If security roles are required they can be configured through a wrapped `LoginService` which is deferred to for role information by the `OpenIdLoginService`. +The nested `LoginService` be configured through the constructor arguments of `OpenIdLoginService`, or left `null` if no user roles are required. -This can be configured in XML through `etc/openid-baseloginservice.xml` in the Distribution, or in embedded code using the constructor for the `OpenIdLoginService`. - -[,java,indent=0] ----- -include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=wrappedLoginService] ----- +When using authorization roles, the `authenticateNewUsers` setting becomes significant, which can be configured through the `OpenIdConfiguration`, or directly on the `OpenIdLoginService`. -When using authorization roles, the setting `authenticateNewUsers` becomes significant. -If set to `true` users not found by the wrapped `LoginService` will still be authenticated but will have no roles. -If set to `false` those users will be not be allowed to authenticate and are redirected to the error page. -This setting is configured through the property `jetty.openid.authenticateNewUsers` in the `start.ini` or `start.d/openid.ini` file, or with `OpenIdLoginService.setAuthenticateNewUsers(...);` in embedded code. + * If set to `true`, users not known by the nested `LoginService` will still be authenticated but will have no roles. + * If set to `false`, users not known by the nested `LoginService` will be not be allowed to authenticate and are redirected to the error page. From 6e4f78694bba4e9e40055a87ea8570024ec26953 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 16 Dec 2024 17:23:33 +1100 Subject: [PATCH 5/6] PR #12559 - changes to OpenID docs from review Signed-off-by: Lachlan Roberts --- .../jetty/modules/code/examples/pom.xml | 16 ++++++++-------- .../operations-guide/pages/features/index.adoc | 2 +- .../pages/security/openid-support.adoc | 12 ++++++------ jetty-ee8/jetty-ee8-openid/pom.xml | 5 +++++ 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/documentation/jetty/modules/code/examples/pom.xml b/documentation/jetty/modules/code/examples/pom.xml index 0400f131e201..45de1c409180 100644 --- a/documentation/jetty/modules/code/examples/pom.xml +++ b/documentation/jetty/modules/code/examples/pom.xml @@ -58,6 +58,10 @@ org.eclipse.jetty jetty-session
+ + org.eclipse.jetty + jetty-slf4j-impl + org.eclipse.jetty jetty-unixdomain-server @@ -109,6 +113,10 @@ org.eclipse.jetty.memcached jetty-memcached-sessions + + org.eclipse.jetty.tests + jetty-test-common + org.eclipse.jetty.websocket jetty-websocket-jetty-client @@ -117,14 +125,6 @@ org.eclipse.jetty.websocket jetty-websocket-jetty-server - - org.eclipse.jetty - jetty-slf4j-impl - - - org.eclipse.jetty.tests - jetty-test-common - diff --git a/documentation/jetty/modules/operations-guide/pages/features/index.adoc b/documentation/jetty/modules/operations-guide/pages/features/index.adoc index 083fe540aa3a..532db21ab833 100644 --- a/documentation/jetty/modules/operations-guide/pages/features/index.adoc +++ b/documentation/jetty/modules/operations-guide/pages/features/index.adoc @@ -23,7 +23,7 @@ Protocols:: Technologies:: * xref:annotations/index.adoc[Servlet Annotations] -* xref:jaas/index.adoc[JAAS] +* xref:security/jaas-support.adoc[JAAS] * xref:jndi/index.adoc[JNDI] * xref:jsp/index.adoc[JSP] * xref:jmx/index.adoc[JMX Monitoring & Management] diff --git a/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc b/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc index 4bd428999ec8..3f8fc1ff1c72 100644 --- a/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc +++ b/documentation/jetty/modules/operations-guide/pages/security/openid-support.adoc @@ -14,7 +14,7 @@ [[openid-support]] = OpenID Support -A more general discussion about OpenID and its support in Jetty is available in xref:programming-guide:security/openid-support.adoc[this section]. +A more general discussion about OpenID and its support in Jetty is available in the xref:programming-guide:security/openid-support.adoc[programming guide section]. Also see xref:operations-guide:modules/standard.adoc#openid[OpenID Module] for more information about the module configuration. @@ -31,7 +31,7 @@ To configure OpenID Authentication with Jetty you will need to specify the OpenI If the OpenID Provider does not allow metadata discovery you will also need to specify the token endpoint and authorization endpoint of the OpenID Provider. These values can be set as properties in `$JETTY_BASE/start.d/openid.ini` file. -This is an example of an openid.ini file which uses discovery of the OpenID endpoints: +This is an example of an `openid.ini` file which uses discovery of the OpenID endpoints: [source] ---- ## The OpenID Identity Provider's issuer ID (the entire URL *before* ".well-known/openid-configuration") @@ -48,7 +48,7 @@ jetty.openid.clientSecret=XT_Mafv_aUCGheuCaKY8P The `web.xml` file needs some specific configuration to use OpenID. -There must be a `login-config` element with an `auth-method` value of `OPENID`, and a `realm-name` value of the exact URL string used to set the OpenID Provider. +There must be a `` element with an `` value of `OPENID`, and a `` value of the exact URL string used to set the OpenID Provider. To set the error page, you must set an `context-param` named `org.eclipse.jetty.security.openid.error_page` whose value should be a path relative to the web application where authentication errors should be redirected. @@ -77,14 +77,14 @@ For example: == Authorization -A nested `LoginService` can be added in order to specify known users by their SUB (subject identifier) with an empty credential. +A nested `LoginService` can be added in order to specify known users by their subject identifier with an empty credential. This can be used to assign security roles to users, and you can configure `openid.ini` to only authenticate users known to the nested `LoginService`. -This nested `LoginService`can be configured in XML through `$JETTY_BASE/etc/openid-baseloginservice.xml`. +This nested `LoginService` can be configured in XML through the `$JETTY_BASE/etc/openid-baseloginservice.xml` file. == Supporting Multiple OpenID Providers -It is possible to support multiple OpenID Providers on the same server, where the web application selects the provider it wants to use through the `` in its `` element of `web.xml`. +It is possible to support multiple OpenID Providers on the same server, where the web application selects the provider to use through the `` in its `` element of `web.xml`. You may create a custom module to add additional `OpenIdConfiguration` instances as beans on the server. See the xref:operations-guide:modules/custom.adoc[Custom Module] section as well as `$JETTY_HOME/etc/jetty-openid.xml` for information on how to do this. diff --git a/jetty-ee8/jetty-ee8-openid/pom.xml b/jetty-ee8/jetty-ee8-openid/pom.xml index 75b97503964a..5d0ce88c25ed 100644 --- a/jetty-ee8/jetty-ee8-openid/pom.xml +++ b/jetty-ee8/jetty-ee8-openid/pom.xml @@ -52,6 +52,11 @@ jetty-ee8-servlet test + + org.eclipse.jetty.tests + jetty-test-common + test + org.eclipse.jetty.toolchain jetty-test-helper From 5195d7a9be864527ae8bbe42fc8137052e7eb522 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 3 Jan 2025 11:26:41 +1100 Subject: [PATCH 6/6] Update pom.xml in jetty-test-common Signed-off-by: Lachlan Roberts --- tests/jetty-test-common/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/jetty-test-common/pom.xml b/tests/jetty-test-common/pom.xml index 233f05a24662..3f26bcd929c5 100644 --- a/tests/jetty-test-common/pom.xml +++ b/tests/jetty-test-common/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.tests tests - 12.0.16-SNAPSHOT + 12.0.17-SNAPSHOT jetty-test-common jar