Skip to content

Commit

Permalink
Issue #9464 - Add optional configuration to log user out after OpenID…
Browse files Browse the repository at this point in the history
… idToken expires. (Jetty-10) (#9528)

* improvements to logout from the OpenIdLoginService validate
* respect idToken expiry for lifetime of login
* fix checkstyle error
* Add respectIdTokenExpiry configuration
* changes from review
* rename respectIdTokenExpiry to logoutWhenIdTokenIsExpired
* changes from review

---------

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
  • Loading branch information
lachlan-roberts committed Apr 11, 2023
1 parent 81efae2 commit 24b7d06
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 47 deletions.
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);
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);

// If we expired a valid authentication we do not want to defer authentication,
// we want to try re-authenticate the user.
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

0 comments on commit 24b7d06

Please sign in to comment.