Merge commit '5282ca37ef7a81789b3c1d0a41854259898bce96' into jetty-10.0.x-8216-openid-logout

This commit is contained in:
Lachlan Roberts 2022-07-12 11:52:09 +10:00
commit 92cf466801
2 changed files with 158 additions and 4 deletions

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.security.openid;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.LinkedHashMap;
@ -75,6 +76,8 @@ public class OpenIdAuthenticator extends LoginAuthenticator
public static final String J_SECURITY_CHECK = "/j_security_check";
public static final String ERROR_PARAMETER = "error_description_jetty";
private static final String CSRF_MAP = "org.eclipse.jetty.security.openid.csrf_map";
public static final String LOGOUT_PAGE = "org.eclipse.jetty.security.openid.logout_page";
public static final String REDIRECT_PATH_AFTER_LOGOUT = "org.eclipse.jetty.security.openid.redirect_after_logout";
@Deprecated
public static final String CSRF_TOKEN = "org.eclipse.jetty.security.openid.csrf_token";
@ -86,10 +89,12 @@ public class OpenIdAuthenticator extends LoginAuthenticator
private String _errorPath;
private String _errorQuery;
private boolean _alwaysSaveUri;
private String _logoutPage;
private String _redirectPathAfterLogout;
public OpenIdAuthenticator()
{
this(null, J_SECURITY_CHECK, null);
this(null, J_SECURITY_CHECK, (String) null);
}
public OpenIdAuthenticator(OpenIdConfiguration configuration)
@ -101,13 +106,32 @@ public class OpenIdAuthenticator extends LoginAuthenticator
{
this(configuration, J_SECURITY_CHECK, errorPage);
}
public OpenIdAuthenticator(String errorPage, String logoutPage, OpenIdConfiguration configuration)
{
this(configuration, J_SECURITY_CHECK, errorPage, logoutPage, null);
}
public OpenIdAuthenticator(String errorPage, String logoutPage, String redirectAfterLogout, OpenIdConfiguration configuration)
{
this(configuration, J_SECURITY_CHECK, errorPage, logoutPage, redirectAfterLogout);
}
public OpenIdAuthenticator(OpenIdConfiguration configuration, String redirectPath, String errorPage)
{
this(configuration, redirectPath, errorPage, null, null);
}
public OpenIdAuthenticator(OpenIdConfiguration configuration, String redirectPath, String errorPage,
String logoutPage, String redirectPathAfterLogout)
{
_openIdConfiguration = configuration;
setRedirectPath(redirectPath);
if (errorPage != null)
setErrorPage(errorPage);
_logoutPage = logoutPage;
_redirectPathAfterLogout = redirectPathAfterLogout;
}
@Override
@ -128,6 +152,14 @@ public class OpenIdAuthenticator extends LoginAuthenticator
String error = authConfig.getInitParameter(ERROR_PAGE);
if (error != null)
setErrorPage(error);
String logout = authConfig.getInitParameter(LOGOUT_PAGE);
if (logout != null)
_logoutPage = logout;
String redirectAfterLogoutPath = authConfig.getInitParameter(REDIRECT_PATH_AFTER_LOGOUT);
if (redirectAfterLogoutPath != null)
_redirectPathAfterLogout = redirectAfterLogoutPath;
super.setConfiguration(new OpenIdAuthConfiguration(_openIdConfiguration, authConfig));
}
@ -414,6 +446,10 @@ public class OpenIdAuthenticator extends LoginAuthenticator
}
if (LOG.isDebugEnabled())
LOG.debug("auth {}", authentication);
if (isLogoutPage(request.getRequestURI()) && isAuthenticatedUser(authentication))
return this.logout(request, response);
return authentication;
}
@ -538,6 +574,73 @@ public class OpenIdAuthenticator extends LoginAuthenticator
{
return req.isSecure();
}
public String getEndSessionEndpoint()
{
return _openIdConfiguration.getEndSessionEndpoint();
}
public boolean isLogoutPage(String contextPath)
{
return _logoutPage != null && (_logoutPage.equals(contextPath));
}
protected boolean isAuthenticatedUser(Authentication authentication) {
return authentication != null && authentication instanceof Authentication.User
&& ((Authentication.User) authentication).getUserIdentity() != null;
}
protected Authentication logout(HttpServletRequest request, HttpServletResponse response) throws ServerAuthException {
try
{
if (_redirectPathAfterLogout == null || (_redirectPathAfterLogout != null && _redirectPathAfterLogout.isEmpty()))
{
final Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
final Response baseResponse = baseRequest.getResponse();
// simple redirect without id_token_hint
baseResponse.sendRedirect(getEndSessionEndpoint(), true);
} else
{
// redirect with id_token_hint
OpenIdAuthenticator.logout(request, response, getEndSessionEndpoint(), _redirectPathAfterLogout);
}
return Authentication.SEND_CONTINUE;
} catch (IOException e)
{
throw new ServerAuthException(e);
}
}
public static void logout(HttpServletRequest request, HttpServletResponse response, String endSessionEndpoint, String redirectPath) throws IOException {
HttpSession session = request != null? request.getSession(false): null;
if (request != null && response != null && endSessionEndpoint != null)
{
final Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
final Response baseResponse = baseRequest.getResponse();
StringBuffer redirectUri = new StringBuffer();
if (redirectPath != null && !redirectPath.isEmpty())
{
URIUtil.appendSchemeHostPort(redirectUri, request.getScheme(), request.getServerName(), request.getServerPort());
redirectUri.append(redirectPath);
}
if (session != null)
{
Object openIdResponse = session.getAttribute(OpenIdAuthenticator.RESPONSE);
if (openIdResponse != null && openIdResponse instanceof Map)
{
Map<?, ?> responseMap = (Map<?, ?>) openIdResponse;
String idToken = (String) responseMap.get("id_token");
baseResponse.sendRedirect(endSessionEndpoint + "?id_token_hint=" + URLEncoder.encode(idToken, StandardCharsets.UTF_8.toString())
+ (redirectPath != null? ("&post_logout_redirect_uri=" + URLEncoder.encode(redirectUri.toString(), StandardCharsets.UTF_8.toString())): "" ), true);
}
}
}
}
private UriRedirectInfo removeAndClearCsrfMap(HttpSession session, String csrf)
{

View File

@ -52,6 +52,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
private final String authMethod;
private String authEndpoint;
private String tokenEndpoint;
private String endSessionEndpoint;
private boolean authenticateNewUsers = false;
/**
@ -77,9 +78,26 @@ public class OpenIdConfiguration extends ContainerLifeCycle
public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint,
String clientId, String clientSecret, HttpClient httpClient)
{
this(issuer, authorizationEndpoint, tokenEndpoint, clientId, clientSecret, "client_secret_post", httpClient);
this(issuer, authorizationEndpoint, tokenEndpoint, null, clientId, clientSecret, "client_secret_post", httpClient);
}
/**
* Create an OpenID configuration for a specific OIDC provider.
* @param issuer The URL of the OpenID provider.
* @param authorizationEndpoint the URL of the OpenID provider's authorization endpoint if configured.
* @param tokenEndpoint the URL of the OpenID provider's token endpoint if configured.
* @param endSessionEndpoint the URL of the OpdnID provider's end session endpoint if configured.
* @param httpClient The {@link HttpClient} instance to use.
* @param clientId OAuth 2.0 Client Identifier valid at the Authorization Server.
* @param clientSecret The client secret known only by the Client and the Authorization Server.
*/
public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint, String endSesseionEndpoint,
HttpClient httpClient, String clientId, String clientSecret)
{
this(issuer, authorizationEndpoint, tokenEndpoint, endSesseionEndpoint, clientId, clientSecret, "client_secret_post",
httpClient);
}
/**
* Create an OpenID configuration for a specific OIDC provider.
* @param issuer The URL of the OpenID provider.
@ -97,11 +115,35 @@ public class OpenIdConfiguration extends ContainerLifeCycle
@Name("clientSecret") String clientSecret,
@Name("authMethod") String authMethod,
@Name("httpClient") HttpClient httpClient)
{
this(issuer, authorizationEndpoint, tokenEndpoint, null, clientId, clientSecret, authMethod, httpClient);
}
/**
* Create an OpenID configuration for a specific OIDC provider.
* @param issuer The URL of the OpenID provider.
* @param authorizationEndpoint the URL of the OpenID provider's authorization endpoint if configured.
* @param tokenEndpoint the URL of the OpenID provider's token endpoint if configured.
* @param endSessionEndpoint the URL of the OpdnID provider's end session endpoint if configured.
* @param clientId OAuth 2.0 Client Identifier valid at the Authorization Server.
* @param clientSecret The client secret known only by the Client and the Authorization Server.
* @param authMethod Authentication method to use with the Token Endpoint.
* @param httpClient The {@link HttpClient} instance to use.
*/
public OpenIdConfiguration(@Name("issuer") String issuer,
@Name("authorizationEndpoint") String authorizationEndpoint,
@Name("tokenEndpoint") String tokenEndpoint,
@Name("endSessionEndpoint") String endSessionEndpoint,
@Name("clientId") String clientId,
@Name("clientSecret") String clientSecret,
@Name("authMethod") String authMethod,
@Name("httpClient") HttpClient httpClient)
{
this.issuer = issuer;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.authEndpoint = authorizationEndpoint;
this.endSessionEndpoint = endSessionEndpoint;
this.tokenEndpoint = tokenEndpoint;
this.httpClient = httpClient != null ? httpClient : newHttpClient();
this.authMethod = authMethod;
@ -139,7 +181,11 @@ public class OpenIdConfiguration extends ContainerLifeCycle
tokenEndpoint = (String)discoveryDocument.get(TOKEN_ENDPOINT);
if (tokenEndpoint == null)
throw new IllegalStateException(TOKEN_ENDPOINT);
endSessionEndpoint = (String)discoveryDocument.get("end_session_endpoint");
if (endSessionEndpoint == null)
throw new IllegalArgumentException("end_session_endpoint");
// We are lenient and not throw here as some major OIDC providers do not conform to this.
if (!Objects.equals(discoveryDocument.get(ISSUER), issuer))
LOG.warn("The issuer in the metadata is not correct.");
@ -213,6 +259,11 @@ public class OpenIdConfiguration extends ContainerLifeCycle
{
return tokenEndpoint;
}
public String getEndSessionEndpoint()
{
return endSessionEndpoint;
}
public String getAuthMethod()
{