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.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.LinkedHashMap; 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 J_SECURITY_CHECK = "/j_security_check";
public static final String ERROR_PARAMETER = "error_description_jetty"; public static final String ERROR_PARAMETER = "error_description_jetty";
private static final String CSRF_MAP = "org.eclipse.jetty.security.openid.csrf_map"; 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 @Deprecated
public static final String CSRF_TOKEN = "org.eclipse.jetty.security.openid.csrf_token"; 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 _errorPath;
private String _errorQuery; private String _errorQuery;
private boolean _alwaysSaveUri; private boolean _alwaysSaveUri;
private String _logoutPage;
private String _redirectPathAfterLogout;
public OpenIdAuthenticator() public OpenIdAuthenticator()
{ {
this(null, J_SECURITY_CHECK, null); this(null, J_SECURITY_CHECK, (String) null);
} }
public OpenIdAuthenticator(OpenIdConfiguration configuration) public OpenIdAuthenticator(OpenIdConfiguration configuration)
@ -101,13 +106,32 @@ public class OpenIdAuthenticator extends LoginAuthenticator
{ {
this(configuration, J_SECURITY_CHECK, errorPage); 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) 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; _openIdConfiguration = configuration;
setRedirectPath(redirectPath); setRedirectPath(redirectPath);
if (errorPage != null) if (errorPage != null)
setErrorPage(errorPage); setErrorPage(errorPage);
_logoutPage = logoutPage;
_redirectPathAfterLogout = redirectPathAfterLogout;
} }
@Override @Override
@ -128,6 +152,14 @@ public class OpenIdAuthenticator extends LoginAuthenticator
String error = authConfig.getInitParameter(ERROR_PAGE); String error = authConfig.getInitParameter(ERROR_PAGE);
if (error != null) if (error != null)
setErrorPage(error); 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)); super.setConfiguration(new OpenIdAuthConfiguration(_openIdConfiguration, authConfig));
} }
@ -414,6 +446,10 @@ public class OpenIdAuthenticator extends LoginAuthenticator
} }
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("auth {}", authentication); LOG.debug("auth {}", authentication);
if (isLogoutPage(request.getRequestURI()) && isAuthenticatedUser(authentication))
return this.logout(request, response);
return authentication; return authentication;
} }
@ -538,6 +574,73 @@ public class OpenIdAuthenticator extends LoginAuthenticator
{ {
return req.isSecure(); 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) private UriRedirectInfo removeAndClearCsrfMap(HttpSession session, String csrf)
{ {

View File

@ -52,6 +52,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
private final String authMethod; private final String authMethod;
private String authEndpoint; private String authEndpoint;
private String tokenEndpoint; private String tokenEndpoint;
private String endSessionEndpoint;
private boolean authenticateNewUsers = false; private boolean authenticateNewUsers = false;
/** /**
@ -77,9 +78,26 @@ public class OpenIdConfiguration extends ContainerLifeCycle
public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint, public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint,
String clientId, String clientSecret, HttpClient httpClient) 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. * Create an OpenID configuration for a specific OIDC provider.
* @param issuer The URL of the OpenID provider. * @param issuer The URL of the OpenID provider.
@ -97,11 +115,35 @@ public class OpenIdConfiguration extends ContainerLifeCycle
@Name("clientSecret") String clientSecret, @Name("clientSecret") String clientSecret,
@Name("authMethod") String authMethod, @Name("authMethod") String authMethod,
@Name("httpClient") HttpClient httpClient) @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.issuer = issuer;
this.clientId = clientId; this.clientId = clientId;
this.clientSecret = clientSecret; this.clientSecret = clientSecret;
this.authEndpoint = authorizationEndpoint; this.authEndpoint = authorizationEndpoint;
this.endSessionEndpoint = endSessionEndpoint;
this.tokenEndpoint = tokenEndpoint; this.tokenEndpoint = tokenEndpoint;
this.httpClient = httpClient != null ? httpClient : newHttpClient(); this.httpClient = httpClient != null ? httpClient : newHttpClient();
this.authMethod = authMethod; this.authMethod = authMethod;
@ -139,7 +181,11 @@ public class OpenIdConfiguration extends ContainerLifeCycle
tokenEndpoint = (String)discoveryDocument.get(TOKEN_ENDPOINT); tokenEndpoint = (String)discoveryDocument.get(TOKEN_ENDPOINT);
if (tokenEndpoint == null) if (tokenEndpoint == null)
throw new IllegalStateException(TOKEN_ENDPOINT); 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. // We are lenient and not throw here as some major OIDC providers do not conform to this.
if (!Objects.equals(discoveryDocument.get(ISSUER), issuer)) if (!Objects.equals(discoveryDocument.get(ISSUER), issuer))
LOG.warn("The issuer in the metadata is not correct."); LOG.warn("The issuer in the metadata is not correct.");
@ -213,6 +259,11 @@ public class OpenIdConfiguration extends ContainerLifeCycle
{ {
return tokenEndpoint; return tokenEndpoint;
} }
public String getEndSessionEndpoint()
{
return endSessionEndpoint;
}
public String getAuthMethod() public String getAuthMethod()
{ {