Skip to content

Commit 696699c

Browse files
Fix issue with oidc invalid session by adding session filter (#8875)
* add session filter for oidc * add more steps to clean up authentication and logout request in filter
1 parent 35433a7 commit 696699c

File tree

3 files changed

+123
-1
lines changed

3 files changed

+123
-1
lines changed

core/src/main/java/org/fao/geonet/kernel/security/openidconnect/GeonetworkOidcPreAuthActionsLoginFilter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import javax.servlet.*;
3333
import javax.servlet.http.HttpServletRequest;
3434
import javax.servlet.http.HttpServletResponse;
35+
import javax.servlet.http.HttpSession;
3536
import java.io.IOException;
3637
import java.net.URLEncoder;
3738
import org.apache.commons.lang3.StringUtils;
@@ -105,6 +106,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
105106
HttpServletRequest servletRequest = (HttpServletRequest) request;
106107
HttpServletResponse servletResponse = (HttpServletResponse) response;
107108

109+
final HttpSession httpSession = servletRequest.getSession(false);
110+
108111
String requestUri = servletRequest.getRequestURI();
109112
String contextPath = servletRequest.getContextPath();
110113
String clientRegistrationId = GeonetworkClientRegistrationProvider.CLIENTREGISTRATION_NAME;
@@ -118,7 +121,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
118121
servletRequest.getHeader("Authorization").startsWith("Bearer ");
119122
boolean isAuthenticated = SecurityContextHolder.getContext().getAuthentication() != null &&
120123
SecurityContextHolder.getContext().getAuthentication().isAuthenticated() &&
121-
!(SecurityContextHolder.getContext().getAuthentication() instanceof AnonymousAuthenticationToken);
124+
!(SecurityContextHolder.getContext().getAuthentication() instanceof AnonymousAuthenticationToken) && httpSession != null;
122125

123126
boolean isPublicEndpoint = excludedPathsMatchers.stream()
124127
.anyMatch(matcher -> matcher.matches(servletRequest));
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright (C) 2025 Food and Agriculture Organization of the
3+
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
4+
* and United Nations Environment Programme (UNEP)
5+
*
6+
* This program is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 2 of the License, or (at
9+
* your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful, but
12+
* WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19+
*
20+
* Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
21+
* Rome - Italy. email: geonetwork@osgeo.org
22+
*/
23+
package org.fao.geonet.kernel.security.openidconnect;
24+
import javax.servlet.*;
25+
import javax.servlet.http.HttpServletRequest;
26+
import javax.servlet.http.HttpServletResponse;
27+
import javax.servlet.http.HttpSession;
28+
29+
import org.slf4j.LoggerFactory;
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.security.core.Authentication;
32+
import org.springframework.security.core.context.SecurityContextHolder;
33+
import org.springframework.security.oauth2.client.ClientAuthorizationException;
34+
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
35+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
36+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
37+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
38+
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
39+
import java.io.IOException;
40+
import java.time.Instant;
41+
42+
/**
43+
* A fix for the token life span issue based on
44+
* https://stackoverflow.com/questions/77438484/solved-keycloak-spring-security-oidc-backchannel-logout-unable-to-trigger-re
45+
*
46+
* This resolved the issue where keycloak or OIDC session is killed therefor when detected, we also need to kill the spring sessions as well.
47+
*/
48+
49+
public class SessionExpirationFilter implements Filter {
50+
/**
51+
* Service to retrieve OAuth2 authorized clients inorder to get access token.
52+
*/
53+
@Autowired(required = false)
54+
private OAuth2AuthorizedClientManager authorizedClientManager;
55+
56+
private static final org.slf4j.Logger log = LoggerFactory.getLogger(SessionExpirationFilter.class);
57+
58+
@Override
59+
public void init(FilterConfig filterConfig) throws ServletException {
60+
61+
}
62+
63+
/**
64+
* Token filter to check and invalidate the expired token
65+
*
66+
* @param request http request object
67+
* @param response http response
68+
* @param filterChain Servlet filterChain
69+
*/
70+
@Override
71+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
72+
final HttpServletRequest httpRequest = (HttpServletRequest) request;
73+
final HttpServletResponse httpResponse = (HttpServletResponse) response;
74+
// Get the session without creating a new one.
75+
final HttpSession httpSession = httpRequest.getSession(false);
76+
// Only proceed if
77+
// the current session is not null - otherwise there is nothing to expire
78+
// the response is not committed - otherwise we may get an error indicating that the response is already committed when attempting to expire the session.
79+
if (httpSession != null && !response.isCommitted()) {
80+
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
81+
if (authentication instanceof OAuth2AuthenticationToken) {
82+
OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;
83+
OidcUser principal = (OidcUser) oauth2Token.getPrincipal();
84+
85+
if (principal.getExpiresAt() != null && principal.getExpiresAt().isBefore(Instant.now())) {
86+
try {
87+
OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(
88+
OAuth2AuthorizeRequest.withClientRegistrationId(oauth2Token.getAuthorizedClientRegistrationId())
89+
.principal(oauth2Token)
90+
.build()
91+
);
92+
93+
if (authorizedClient == null || authorizedClient.getAccessToken() == null) {
94+
log.warn("Session '{}' for subject '{}', user '{}' is expired due to terminated/expired authentication server session.", httpSession.getId(), principal.getSubject(), principal.getPreferredUsername());
95+
httpRequest.logout();
96+
SecurityContextHolder.getContext().setAuthentication(null);
97+
httpSession.invalidate();
98+
}
99+
} catch (ClientAuthorizationException e) {
100+
log.warn("Authorization exception occurred for session '{}' for user '{}'.", httpSession.getId(), principal.getPreferredUsername(), e);
101+
httpRequest.logout();
102+
SecurityContextHolder.getContext().setAuthentication(null);
103+
httpSession.invalidate();
104+
}
105+
}
106+
}
107+
}
108+
filterChain.doFilter(httpRequest, httpResponse);
109+
}
110+
111+
@Override
112+
public void destroy() {
113+
114+
}
115+
}

web/src/main/webapp/WEB-INF/config-security/config-security-openidconnectbearer.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@
199199
</constructor-arg>
200200
<property name="filterProcessesUrl" value="/signout"/>
201201
</bean>
202+
203+
<bean id="sessionExpirationFilter" class="org.fao.geonet.kernel.security.openidconnect.SessionExpirationFilter"/>
204+
202205
<bean id="geonetworkOidcPreAuthActionsLoginFilter" class="org.fao.geonet.kernel.security.openidconnect.GeonetworkOidcPreAuthActionsLoginFilter"/>
203206

204207
<bean id="oAuth2Configuration" class="org.fao.geonet.kernel.security.openidconnect.OAuth2Configuration"/>
@@ -239,6 +242,7 @@
239242
<ref bean="openidconnectOAuth2AuthorizationRequestRedirectFilter"/>
240243
<ref bean="openidconnectOAuth2LoginAuthenticationFilter"/>
241244
<ref bean="logoutFilter"/>
245+
<ref bean="sessionExpirationFilter"/>
242246
<!-- include a pre login filter-->
243247
<ref bean="geonetworkOidcPreAuthActionsLoginFilter"/>
244248

0 commit comments

Comments
 (0)