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

Issue #9464 - Add optional configuration to log user out after OpenID idToken expires. (Jetty-10) #9528

Merged
merged 7 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions jetty-openid/src/main/config/etc/jetty-openid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
<Set name="authenticateNewUsers">
<Property name="jetty.openid.authenticateNewUsers" default="false"/>
</Set>
<Set name="logoutWhenIdTokenIsExpired">
<Property name="jetty.openid.logoutWhenIdTokenIsExpired" default="false"/>
</Set>
<Call name="addScopes">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
Expand Down
3 changes: 3 additions & 0 deletions jetty-openid/src/main/config/modules/openid.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ etc/jetty-openid.xml

## What authentication method to use with the Token Endpoint (client_secret_post, client_secret_basic).
# jetty.openid.authMethod=client_secret_post

## Whether the user should be logged out after the idToken expires.
# jetty.openid.logoutWhenIdTokenIsExpired=false
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ public UserIdentity login(String username, Object credentials, ServletRequest re
public void logout(ServletRequest request)
{
attemptLogoutRedirect(request);
logoutWithoutRedirect(request);
}

private void logoutWithoutRedirect(ServletRequest request)
{
super.logout(request);
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpSession session = httpRequest.getSession(false);
Expand All @@ -265,13 +270,13 @@ public void logout(ServletRequest request)
}

/**
* This will attempt to redirect the request to the end_session_endpoint, and finally to the {@link #REDIRECT_PATH}.
* <p>This will attempt to redirect the request to the end_session_endpoint, and finally to the {@link #REDIRECT_PATH}.</p>
*
* If end_session_endpoint is defined the request will be redirected to the end_session_endpoint, the optional
* post_logout_redirect_uri parameter will be set if {@link #REDIRECT_PATH} is non-null.
* <p>If end_session_endpoint is defined the request will be redirected to the end_session_endpoint, the optional
* post_logout_redirect_uri parameter will be set if {@link #REDIRECT_PATH} is non-null.</p>
*
* If the end_session_endpoint is not defined then the request will be redirected to {@link #REDIRECT_PATH} if it is a
* non-null value, otherwise no redirection will be done.
* <p>If the end_session_endpoint is not defined then the request will be redirected to {@link #REDIRECT_PATH} if it is a
* non-null value, otherwise no redirection will be done.</p>
*
* @param request the request to redirect.
*/
Expand Down Expand Up @@ -366,6 +371,17 @@ public void prepareRequest(ServletRequest request)
baseRequest.setMethod(method);
}

private boolean hasExpiredIdToken(HttpSession session)
{
if (session != null)
{
Map<String, Object> claims = (Map)session.getAttribute(CLAIMS);
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
if (claims != null)
return OpenIdCredentials.checkExpiry(claims);
}
return false;
}

@Override
public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
{
Expand All @@ -381,6 +397,17 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
if (uri == null)
uri = URIUtil.SLASH;

HttpSession session = request.getSession(false);
if (_openIdConfiguration.isLogoutWhenIdTokenIsExpired() && hasExpiredIdToken(session))
{
// After logout, fall through to the code below and send another login challenge.
logoutWithoutRedirect(request);
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved

// If we expired a valid authentication we do not want to defer authentication,
// we want to try re-authenticate the user.
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
mandatory = true;
}

mandatory |= isJSecurityCheck(uri);
if (!mandatory)
return new DeferredAuthentication(this);
Expand All @@ -391,7 +418,9 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
try
{
// Get the Session.
HttpSession session = request.getSession();
if (session == null)
session = request.getSession(true);

if (request.isRequestedSessionIdFromURL())
{
sendError(request, response, "Session ID must be a cookie to support OpenID authentication");
Expand Down Expand Up @@ -464,10 +493,7 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
{
if (LOG.isDebugEnabled())
LOG.debug("auth revoked {}", authentication);
synchronized (session)
{
session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
}
logoutWithoutRedirect(request);
}
else
{
Expand Down Expand Up @@ -499,10 +525,10 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
}
}
}
if (LOG.isDebugEnabled())
LOG.debug("auth {}", authentication);
return authentication;
}
if (LOG.isDebugEnabled())
LOG.debug("auth {}", authentication);
return authentication;
}

// If we can't send challenge.
Expand All @@ -513,12 +539,11 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
return Authentication.UNAUTHENTICATED;
}

// Send the the challenge.
// Send the challenge.
String challengeUri = getChallengeUri(baseRequest);
if (LOG.isDebugEnabled())
LOG.debug("challenge {}->{}", session.getId(), challengeUri);
baseResponse.sendRedirect(challengeUri, true);

return Authentication.SEND_CONTINUE;
}
catch (IOException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
private String tokenEndpoint;
private String endSessionEndpoint;
private boolean authenticateNewUsers = false;
private boolean logoutWhenIdTokenIsExpired = false;

/**
* Create an OpenID configuration for a specific OIDC provider.
Expand Down Expand Up @@ -275,6 +276,16 @@ public void setAuthenticateNewUsers(boolean authenticateNewUsers)
this.authenticateNewUsers = authenticateNewUsers;
}

public boolean isLogoutWhenIdTokenIsExpired()
{
return logoutWhenIdTokenIsExpired;
}

public void setLogoutWhenIdTokenIsExpired(boolean logoutWhenIdTokenIsExpired)
{
this.logoutWhenIdTokenIsExpired = logoutWhenIdTokenIsExpired;
}

private static HttpClient newHttpClient()
{
ClientConnector connector = new ClientConnector();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.io.Serializable;
import java.net.URI;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -137,12 +138,24 @@ private void validateClaims(OpenIdConfiguration configuration) throws Exception
throw new AuthenticationException("Authorized party claim value should be the client_id");

// Check that the ID token has not expired by checking the exp claim.
long expiry = (Long)claims.get("exp");
long currentTimeSeconds = (long)(System.currentTimeMillis() / 1000F);
if (currentTimeSeconds > expiry)
if (isExpired())
throw new AuthenticationException("ID Token has expired");
}

public boolean isExpired()
{
return checkExpiry(claims);
}

public static boolean checkExpiry(Map<String, Object> claims)
{
if (claims == null)
return true;

// Check that the ID token has not expired by checking the exp claim.
return Instant.ofEpochSecond((Long)claims.get("exp")).isBefore(Instant.now());
}

private void validateAudience(OpenIdConfiguration configuration) throws AuthenticationException
{
Object aud = claims.get("aud");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ public boolean validate(UserIdentity user)
{
if (!(user.getUserPrincipal() instanceof OpenIdUserPrincipal))
return false;

OpenIdUserPrincipal userPrincipal = (OpenIdUserPrincipal)user.getUserPrincipal();
if (configuration.isLogoutWhenIdTokenIsExpired() && userPrincipal.getCredentials().isExpired())
return false;
return loginService == null || loginService.validate(user);
}

Expand Down
Loading