Merge commit '5282ca37ef7a81789b3c1d0a41854259898bce96' into jetty-10.0.x-8216-openid-logout
This commit is contained in:
commit
92cf466801
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue