Skip to content

Commit 24b7d06

Browse files
Issue #9464 - Add optional configuration to log user out after OpenID 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>
1 parent 81efae2 commit 24b7d06

9 files changed

Lines changed: 365 additions & 47 deletions

File tree

jetty-openid/src/main/config/etc/jetty-openid.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
<Set name="authenticateNewUsers">
3939
<Property name="jetty.openid.authenticateNewUsers" default="false"/>
4040
</Set>
41+
<Set name="logoutWhenIdTokenIsExpired">
42+
<Property name="jetty.openid.logoutWhenIdTokenIsExpired" default="false"/>
43+
</Set>
4144
<Call name="addScopes">
4245
<Arg>
4346
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">

jetty-openid/src/main/config/modules/openid.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@ etc/jetty-openid.xml
4545

4646
## What authentication method to use with the Token Endpoint (client_secret_post, client_secret_basic).
4747
# jetty.openid.authMethod=client_secret_post
48+
49+
## Whether the user should be logged out after the idToken expires.
50+
# jetty.openid.logoutWhenIdTokenIsExpired=false

jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,11 @@ public UserIdentity login(String username, Object credentials, ServletRequest re
248248
public void logout(ServletRequest request)
249249
{
250250
attemptLogoutRedirect(request);
251+
logoutWithoutRedirect(request);
252+
}
253+
254+
private void logoutWithoutRedirect(ServletRequest request)
255+
{
251256
super.logout(request);
252257
HttpServletRequest httpRequest = (HttpServletRequest)request;
253258
HttpSession session = httpRequest.getSession(false);
@@ -265,13 +270,13 @@ public void logout(ServletRequest request)
265270
}
266271

267272
/**
268-
* This will attempt to redirect the request to the end_session_endpoint, and finally to the {@link #REDIRECT_PATH}.
273+
* <p>This will attempt to redirect the request to the end_session_endpoint, and finally to the {@link #REDIRECT_PATH}.</p>
269274
*
270-
* If end_session_endpoint is defined the request will be redirected to the end_session_endpoint, the optional
271-
* post_logout_redirect_uri parameter will be set if {@link #REDIRECT_PATH} is non-null.
275+
* <p>If end_session_endpoint is defined the request will be redirected to the end_session_endpoint, the optional
276+
* post_logout_redirect_uri parameter will be set if {@link #REDIRECT_PATH} is non-null.</p>
272277
*
273-
* If the end_session_endpoint is not defined then the request will be redirected to {@link #REDIRECT_PATH} if it is a
274-
* non-null value, otherwise no redirection will be done.
278+
* <p>If the end_session_endpoint is not defined then the request will be redirected to {@link #REDIRECT_PATH} if it is a
279+
* non-null value, otherwise no redirection will be done.</p>
275280
*
276281
* @param request the request to redirect.
277282
*/
@@ -366,6 +371,17 @@ public void prepareRequest(ServletRequest request)
366371
baseRequest.setMethod(method);
367372
}
368373

374+
private boolean hasExpiredIdToken(HttpSession session)
375+
{
376+
if (session != null)
377+
{
378+
Map<String, Object> claims = (Map)session.getAttribute(CLAIMS);
379+
if (claims != null)
380+
return OpenIdCredentials.checkExpiry(claims);
381+
}
382+
return false;
383+
}
384+
369385
@Override
370386
public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
371387
{
@@ -381,6 +397,17 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
381397
if (uri == null)
382398
uri = URIUtil.SLASH;
383399

400+
HttpSession session = request.getSession(false);
401+
if (_openIdConfiguration.isLogoutWhenIdTokenIsExpired() && hasExpiredIdToken(session))
402+
{
403+
// After logout, fall through to the code below and send another login challenge.
404+
logoutWithoutRedirect(request);
405+
406+
// If we expired a valid authentication we do not want to defer authentication,
407+
// we want to try re-authenticate the user.
408+
mandatory = true;
409+
}
410+
384411
mandatory |= isJSecurityCheck(uri);
385412
if (!mandatory)
386413
return new DeferredAuthentication(this);
@@ -391,7 +418,9 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
391418
try
392419
{
393420
// Get the Session.
394-
HttpSession session = request.getSession();
421+
if (session == null)
422+
session = request.getSession(true);
423+
395424
if (request.isRequestedSessionIdFromURL())
396425
{
397426
sendError(request, response, "Session ID must be a cookie to support OpenID authentication");
@@ -464,10 +493,7 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
464493
{
465494
if (LOG.isDebugEnabled())
466495
LOG.debug("auth revoked {}", authentication);
467-
synchronized (session)
468-
{
469-
session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
470-
}
496+
logoutWithoutRedirect(request);
471497
}
472498
else
473499
{
@@ -499,10 +525,10 @@ public Authentication validateRequest(ServletRequest req, ServletResponse res, b
499525
}
500526
}
501527
}
528+
if (LOG.isDebugEnabled())
529+
LOG.debug("auth {}", authentication);
530+
return authentication;
502531
}
503-
if (LOG.isDebugEnabled())
504-
LOG.debug("auth {}", authentication);
505-
return authentication;
506532
}
507533

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

516-
// Send the the challenge.
542+
// Send the challenge.
517543
String challengeUri = getChallengeUri(baseRequest);
518544
if (LOG.isDebugEnabled())
519545
LOG.debug("challenge {}->{}", session.getId(), challengeUri);
520546
baseResponse.sendRedirect(challengeUri, true);
521-
522547
return Authentication.SEND_CONTINUE;
523548
}
524549
catch (IOException e)

jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
5555
private String tokenEndpoint;
5656
private String endSessionEndpoint;
5757
private boolean authenticateNewUsers = false;
58+
private boolean logoutWhenIdTokenIsExpired = false;
5859

5960
/**
6061
* Create an OpenID configuration for a specific OIDC provider.
@@ -275,6 +276,16 @@ public void setAuthenticateNewUsers(boolean authenticateNewUsers)
275276
this.authenticateNewUsers = authenticateNewUsers;
276277
}
277278

279+
public boolean isLogoutWhenIdTokenIsExpired()
280+
{
281+
return logoutWhenIdTokenIsExpired;
282+
}
283+
284+
public void setLogoutWhenIdTokenIsExpired(boolean logoutWhenIdTokenIsExpired)
285+
{
286+
this.logoutWhenIdTokenIsExpired = logoutWhenIdTokenIsExpired;
287+
}
288+
278289
private static HttpClient newHttpClient()
279290
{
280291
ClientConnector connector = new ClientConnector();

jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import java.io.Serializable;
1717
import java.net.URI;
18+
import java.time.Instant;
1819
import java.util.Arrays;
1920
import java.util.List;
2021
import java.util.Map;
@@ -137,12 +138,24 @@ private void validateClaims(OpenIdConfiguration configuration) throws Exception
137138
throw new AuthenticationException("Authorized party claim value should be the client_id");
138139

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

145+
public boolean isExpired()
146+
{
147+
return checkExpiry(claims);
148+
}
149+
150+
public static boolean checkExpiry(Map<String, Object> claims)
151+
{
152+
if (claims == null)
153+
return true;
154+
155+
// Check that the ID token has not expired by checking the exp claim.
156+
return Instant.ofEpochSecond((Long)claims.get("exp")).isBefore(Instant.now());
157+
}
158+
146159
private void validateAudience(OpenIdConfiguration configuration) throws AuthenticationException
147160
{
148161
Object aud = claims.get("aud");

jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ public boolean validate(UserIdentity user)
136136
{
137137
if (!(user.getUserPrincipal() instanceof OpenIdUserPrincipal))
138138
return false;
139-
139+
OpenIdUserPrincipal userPrincipal = (OpenIdUserPrincipal)user.getUserPrincipal();
140+
if (configuration.isLogoutWhenIdTokenIsExpired() && userPrincipal.getCredentials().isExpired())
141+
return false;
140142
return loginService == null || loginService.validate(user);
141143
}
142144

0 commit comments

Comments
 (0)