Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation for Jetty OpenID Connect support #12559

Open
wants to merge 9 commits into
base: jetty-12.0.x
Choose a base branch
from
12 changes: 12 additions & 0 deletions documentation/jetty/modules/code/examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-nosql</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-openid</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-rewrite</artifactId>
Expand All @@ -54,6 +58,10 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-session</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-unixdomain-server</artifactId>
Expand Down Expand Up @@ -105,6 +113,10 @@
<groupId>org.eclipse.jetty.memcached</groupId>
<artifactId>jetty-memcached-sessions</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-test-common</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-jetty-client</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//
// ========================================================================
// 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.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.AuthenticationState;
import org.eclipse.jetty.security.Constraint;
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.Handler;
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.util.Callback;
import org.eclipse.jetty.util.Fields;

public class OpenIdDocs
{
public void combinedExample() throws Exception
{
Server server = new Server(8080);
// tag::openIdUsageExample[]
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"))
{
// Handle requests to the error page which may have an error description parameter.
Fields parameters = Request.getParameters(request);
writer.println("error_description: " + parameters.get("error_description_jetty") + "<br>");
}
else
{
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<String, String> claims = (Map<String, String>)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();
callback.succeeded();
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
"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)
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("/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-user-subject-identifier>", 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.
securityHandler.setAuthenticator(new OpenIdAuthenticator(openIdConfig,
"/j_security_check", // The path where the OIDC provider redirects back to Jetty.
"/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());
// end::openIdConfigExample[]
server.start();
server.join();
}
}
6 changes: 4 additions & 2 deletions documentation/jetty/modules/operations-guide/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,30 @@ 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]
----

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`

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// ========================================================================
// 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

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.

== OpenID Provider Configuration

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) 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 `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:

[,xml,subs=attributes+]
----
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
...
<login-config>
<auth-method>OPENID</auth-method>
<realm-name>https://accounts.google.com</realm-name>
</login-config>

<context-param>
<param-name>org.eclipse.jetty.security.openid.error_page</param-name>
<param-value>/error</param-value>
</context-param>
...
</web-app>
----

== Authorization

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 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 to use through the `<realm-name>` in its `<login-config>` 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.
2 changes: 2 additions & 0 deletions documentation/jetty/modules/programming-guide/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// ========================================================================
// 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

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

== External Configuration

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.

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.

For example, you may wish to register the following URIs:

* For a deployed application, `+https://example.com/j_security_check+`.
* For local development, `+http://localhost:8080/j_security_check+`.

Ensure that all relevant environments where your application is deployed have their corresponding URIs registered with the OpenID Provider to avoid authentication errors.

== Jetty Configuration

=== Code Example

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=openIdConfigExample]
----

=== 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`.

[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/OpenIdDocs.java[tags=openIdUsageExample]
----

==== 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`.

=== Authorization

If security roles are required they can be configured through a nested `LoginService` which is deferred to for user roles.

The nested `LoginService` be configured through the constructor arguments of `OpenIdLoginService`, or left `null` if no user roles are required.

When using authorization roles, the `authenticateNewUsers` setting becomes significant, which can be configured through the `OpenIdConfiguration`, or directly on the `OpenIdLoginService`.

* 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.
Loading
Loading