Issue #8216 - Use HttpServletRequest.logout() for openid end_session_endpoint redirect

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2022-07-12 12:40:45 +10:00
parent 92cf466801
commit 26732c90a0
2 changed files with 69 additions and 122 deletions

View File

@ -16,7 +16,6 @@ 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;
@ -69,6 +68,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
public static final String RESPONSE = "org.eclipse.jetty.security.openid.response";
public static final String ISSUER = "org.eclipse.jetty.security.openid.issuer";
public static final String REDIRECT_PATH = "org.eclipse.jetty.security.openid.redirect_path";
public static final String LOGOUT_REDIRECT_PATH = "org.eclipse.jetty.security.openid.logout_redirect_path";
public static final String ERROR_PAGE = "org.eclipse.jetty.security.openid.error_page";
public static final String J_URI = "org.eclipse.jetty.security.openid.URI";
public static final String J_POST = "org.eclipse.jetty.security.openid.POST";
@ -76,8 +76,6 @@ 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";
@ -85,16 +83,15 @@ public class OpenIdAuthenticator extends LoginAuthenticator
private final SecureRandom _secureRandom = new SecureRandom();
private OpenIdConfiguration _openIdConfiguration;
private String _redirectPath;
private String _logoutRedirectPath = "/";
private String _errorPage;
private String _errorPath;
private String _errorQuery;
private boolean _alwaysSaveUri;
private String _logoutPage;
private String _redirectPathAfterLogout;
public OpenIdAuthenticator()
{
this(null, J_SECURITY_CHECK, (String) null);
this(null, J_SECURITY_CHECK, null);
}
public OpenIdAuthenticator(OpenIdConfiguration configuration)
@ -106,32 +103,20 @@ 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);
this(configuration, redirectPath, errorPage, null);
}
public OpenIdAuthenticator(OpenIdConfiguration configuration, String redirectPath, String errorPage,
String logoutPage, String redirectPathAfterLogout)
public OpenIdAuthenticator(OpenIdConfiguration configuration, String redirectPath, String errorPage, String logoutRedirectPath)
{
_openIdConfiguration = configuration;
setRedirectPath(redirectPath);
if (errorPage != null)
setErrorPage(errorPage);
_logoutPage = logoutPage;
_redirectPathAfterLogout = redirectPathAfterLogout;
if (logoutRedirectPath != null)
setLogoutRedirectPath(logoutRedirectPath);
}
@Override
@ -147,19 +132,15 @@ public class OpenIdAuthenticator extends LoginAuthenticator
String redirectPath = authConfig.getInitParameter(REDIRECT_PATH);
if (redirectPath != null)
_redirectPath = redirectPath;
setRedirectPath(redirectPath);
String error = authConfig.getInitParameter(ERROR_PAGE);
if (error != null)
setErrorPage(error);
String logout = authConfig.getInitParameter(LOGOUT_PAGE);
String logout = authConfig.getInitParameter(LOGOUT_REDIRECT_PATH);
if (logout != null)
_logoutPage = logout;
String redirectAfterLogoutPath = authConfig.getInitParameter(REDIRECT_PATH_AFTER_LOGOUT);
if (redirectAfterLogoutPath != null)
_redirectPathAfterLogout = redirectAfterLogoutPath;
setLogoutRedirectPath(logout);
super.setConfiguration(new OpenIdAuthConfiguration(_openIdConfiguration, authConfig));
}
@ -198,6 +179,22 @@ public class OpenIdAuthenticator extends LoginAuthenticator
_redirectPath = redirectPath;
}
public void setLogoutRedirectPath(String logoutRedirectPath)
{
if (logoutRedirectPath == null)
{
LOG.warn("redirect path must not be null, defaulting to /");
logoutRedirectPath = "/";
}
else if (!logoutRedirectPath.startsWith("/"))
{
LOG.warn("redirect path must start with /");
logoutRedirectPath = "/" + logoutRedirectPath;
}
_logoutRedirectPath = logoutRedirectPath;
}
public void setErrorPage(String path)
{
if (path == null || path.trim().length() == 0)
@ -250,6 +247,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
@Override
public void logout(ServletRequest request)
{
attemptLogoutRedirect(request);
super.logout(request);
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpSession session = httpRequest.getSession(false);
@ -265,6 +263,43 @@ public class OpenIdAuthenticator extends LoginAuthenticator
}
}
private void attemptLogoutRedirect(ServletRequest request)
{
try
{
Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
Response baseResponse = baseRequest.getResponse();
String endSessionEndpoint = _openIdConfiguration.getEndSessionEndpoint();
if (endSessionEndpoint == null)
return;
StringBuilder redirectUri = new StringBuilder(128);
URIUtil.appendSchemeHostPort(redirectUri, request.getScheme(), request.getServerName(), request.getServerPort());
redirectUri.append(baseRequest.getContextPath());
redirectUri.append(_logoutRedirectPath);
HttpSession session = baseRequest.getSession(false);
if (session == null)
return;
Object openIdResponse = session.getAttribute(OpenIdAuthenticator.RESPONSE);
if (openIdResponse instanceof Map)
{
@SuppressWarnings("rawtypes")
String idToken = (String)((Map)openIdResponse).get("id_token");
baseResponse.sendRedirect(endSessionEndpoint +
"?id_token_hint=" + UrlEncoded.encodeString(idToken, StandardCharsets.UTF_8) +
"&post_logout_redirect_uri=" + UrlEncoded.encodeString(redirectUri.toString(), StandardCharsets.UTF_8),
true);
}
}
catch (Throwable t)
{
LOG.warn("failed to redirect to end_session_endpoint", t);
}
}
@Override
public void prepareRequest(ServletRequest request)
{
@ -446,10 +481,6 @@ 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;
}
@ -574,73 +605,6 @@ 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

@ -78,26 +78,9 @@ public class OpenIdConfiguration extends ContainerLifeCycle
public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint,
String clientId, String clientSecret, HttpClient httpClient)
{
this(issuer, authorizationEndpoint, tokenEndpoint, null, clientId, clientSecret, "client_secret_post", httpClient);
this(issuer, authorizationEndpoint, tokenEndpoint, 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.
@ -116,7 +99,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
@Name("authMethod") String authMethod,
@Name("httpClient") HttpClient httpClient)
{
this(issuer, authorizationEndpoint, tokenEndpoint, null, clientId, clientSecret, authMethod, httpClient);
this(issuer, authorizationEndpoint, tokenEndpoint, null, clientId, clientSecret, authorizationEndpoint, httpClient);
}
/**
@ -146,7 +129,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
this.endSessionEndpoint = endSessionEndpoint;
this.tokenEndpoint = tokenEndpoint;
this.httpClient = httpClient != null ? httpClient : newHttpClient();
this.authMethod = authMethod;
this.authMethod = authMethod == null ? "client_secret_post" : authMethod;
if (this.issuer == null)
throw new IllegalArgumentException("Issuer was not configured");
@ -185,7 +168,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
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.");