Skip to content

Commit f16491c

Browse files
OIDC Improvements and bug fixes (#8761) (#8767)
Added pre-login filter to enable autologin, and restrict guest users Added configuration for a OAuth2AuthorizedClientManager, which enables refreshing access token Fixed the check for setting the userNameAttribute in GeonetworkOidcUserService.java and the Client Registration UserNameAttribute configuration in GeonetworkClientRegistrationProvider.java to based on OIDCConfig Update the postLogoutRedirect endpoint to exclude default port numbers * Update AudienceAccessTokenValidator.java Fix logic * oidc fix * remove @configuration in OAuth2Config * add license file header Co-authored-by: xiechangning20 <59653172+xiechangning20@users.noreply.github.com>
1 parent 63a8172 commit f16491c

7 files changed

Lines changed: 224 additions & 24 deletions

File tree

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ else if (!StringUtils.isBlank(serverMetadataJsonText) && (serverMetadataJsonText
120120
public GeonetworkClientRegistrationProvider(InputStream inputStream,
121121
OIDCConfiguration oidcConfiguration) throws IOException, ParseException {
122122
this.oidcConfiguration = oidcConfiguration;
123-
this.oidcConfiguration = oidcConfiguration;
124123
String clientId = oidcConfiguration.clientId;
125124
String clientSecret = oidcConfiguration.clientSecret;
126125
clientRegistration = createClientRegistration(inputStream, clientId, clientSecret);
@@ -246,7 +245,7 @@ ClientRegistration createClientRegistration(String jsonServerConfig,
246245
Map<String, Object> configurationMetadata = new LinkedHashMap<>(oidcMetadata.toJSONObject());
247246

248247
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(CLIENTREGISTRATION_NAME)
249-
.userNameAttributeName(IdTokenClaimNames.SUB)
248+
.userNameAttributeName(this.oidcConfiguration.getUserNameAttribute())
250249
.scope(scopes)
251250
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
252251
.clientAuthenticationMethod(method)

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ private URI createPostLogoutRedirectUri(HttpServletRequest request) {
7171
String host = request.getServerName();
7272
int port = request.getServerPort();
7373
String path = servletContext.getContextPath();
74-
uri = protocol + "://" + host + ":" + port + path;
74+
// Only include the port if it's not the default for the scheme
75+
boolean isDefaultPort = ("https".equalsIgnoreCase(protocol) && port == 443)
76+
|| ("http".equalsIgnoreCase(protocol) && port == 80);
77+
78+
uri = protocol + "://" + host + (isDefaultPort ? "" : ":" + port) + path;
7579
return new URI(uri);
7680
} catch (URISyntaxException e) {
7781
Log.debug(Geonet.SECURITY,"OIDC Post Logout Redirect Uri is invalid. Likely you can ignore this -"
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
25+
import org.fao.geonet.Constants;
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.security.authentication.AnonymousAuthenticationToken;
28+
import org.springframework.security.core.context.SecurityContextHolder;
29+
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
30+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
31+
32+
import javax.servlet.*;
33+
import javax.servlet.http.HttpServletRequest;
34+
import javax.servlet.http.HttpServletResponse;
35+
import java.io.IOException;
36+
import java.net.URLEncoder;
37+
38+
/**
39+
* Filter implementation for handling pre-authentication actions for OpenID Connect (OIDC) login.
40+
* This filter checks if the user is authenticated and redirects unauthenticated users to the login page.
41+
*/
42+
public class GeonetworkOidcPreAuthActionsLoginFilter implements Filter {
43+
44+
/**
45+
* Repository for managing client registrations for OpenID Connect.
46+
* This is used to retrieve client registration details such as registration ID.
47+
*/
48+
@Autowired
49+
private ClientRegistrationRepository clientRegistrationRepository;
50+
51+
52+
53+
@Override
54+
public void init(FilterConfig filterConfig) throws ServletException {
55+
56+
}
57+
58+
/**
59+
* Filter implementation for handling pre-authentication actions for OpenID Connect login.
60+
* This filter checks if the user is authenticated and redirects unauthenticated users to the login page.
61+
* It ensures secure access to protected resources by handling login redirection and bypassing public endpoints.
62+
*/
63+
@Override
64+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
65+
throws IOException, ServletException {
66+
67+
HttpServletRequest servletRequest = (HttpServletRequest) request;
68+
HttpServletResponse servletResponse = (HttpServletResponse) response;
69+
70+
String requestUri = servletRequest.getRequestURI();
71+
String contextPath = servletRequest.getContextPath();
72+
String clientRegistrationId = GeonetworkClientRegistrationProvider.CLIENTREGISTRATION_NAME;
73+
// Grab the first (or default) registrationId from repository
74+
String registrationId = clientRegistrationRepository.findByRegistrationId(clientRegistrationId).getRegistrationId();
75+
String loginPath = contextPath + OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/" + registrationId;
76+
77+
// Avoid infinite loop and skip API or OIDC system endpoints or bearer token access
78+
boolean isLoginRequest = requestUri.equals(loginPath);
79+
boolean isBearerTokenAccess = servletRequest.getHeader("Authorization") != null &&
80+
servletRequest.getHeader("Authorization").startsWith("Bearer ");
81+
boolean isAuthenticated = SecurityContextHolder.getContext().getAuthentication() != null &&
82+
SecurityContextHolder.getContext().getAuthentication().isAuthenticated() &&
83+
!(SecurityContextHolder.getContext().getAuthentication() instanceof AnonymousAuthenticationToken);
84+
85+
boolean isPublicEndpoint =
86+
requestUri.endsWith("/.well-known/jwks.json");
87+
88+
if (!isAuthenticated && !isLoginRequest && !isPublicEndpoint && !isBearerTokenAccess) {
89+
String returningUrl = requestUri +
90+
(servletRequest.getQueryString() == null ? "" : "?" + servletRequest.getQueryString());
91+
92+
String redirectUrl = loginPath + "?redirectUrl=" + URLEncoder.encode(returningUrl, Constants.ENCODING);
93+
servletResponse.sendRedirect(redirectUrl);
94+
return;
95+
}
96+
97+
chain.doFilter(servletRequest, servletResponse);
98+
}
99+
100+
@Override
101+
public void destroy() {
102+
103+
}
104+
105+
106+
107+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio
8989
String userNameAttributeName = userRequest.getClientRegistration()
9090
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
9191

92-
if (!StringUtils.hasText(userNameAttributeName)) {
92+
if (StringUtils.hasText(userNameAttributeName)) {
9393
user = new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName);
9494
} else {
9595
user = new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
25+
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.security.oauth2.client.*;
29+
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
30+
31+
public class OAuth2Configuration {
32+
33+
/**
34+
* Client registration repository used to set authorized client manager
35+
*/
36+
@Autowired
37+
private ClientRegistrationRepository clientRegistrationRepository;
38+
39+
@Autowired
40+
private OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
41+
42+
/**
43+
* This method is used to obtain the OAuth2 authorized clients
44+
* which is used to obtaining OAuth2 Access Token, and refresh token.
45+
*/
46+
@Bean
47+
public OAuth2AuthorizedClientManager authorizedClientManager() {
48+
OAuth2AuthorizedClientProvider authorizedClientProvider =
49+
OAuth2AuthorizedClientProviderBuilder.builder()
50+
.clientCredentials()
51+
.refreshToken()
52+
.build();
53+
54+
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
55+
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
56+
clientRegistrationRepository, oAuth2AuthorizedClientService);
57+
58+
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
59+
60+
return authorizedClientManager;
61+
}
62+
63+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,4 @@ openidconnectConfiguration.clientId=${OPENIDCONNECT_CLIENTID}
9494
openidconnectConfiguration.clientSecret=${OPENIDCONNECT_CLIENTSECRET}
9595

9696
openidconnectConfiguration.userNameAttribute=${OPENIDCONNECT_USERNAME_ATTRIBUTE:#{'email'}}
97+
openidconnectConfiguration.loginType=${OPENIDCONNECT_LOGINTYPE:#{'LINK'}}

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

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -199,30 +199,56 @@
199199
</constructor-arg>
200200
<property name="filterProcessesUrl" value="/signout"/>
201201
</bean>
202+
<bean id="geonetworkOidcPreAuthActionsLoginFilter" class="org.fao.geonet.kernel.security.openidconnect.GeonetworkOidcPreAuthActionsLoginFilter"/>
202203

204+
<bean id="oAuth2Configuration" class="org.fao.geonet.kernel.security.openidconnect.OAuth2Configuration"/>
203205

204206
<bean id="filterChainFilters" class="java.util.ArrayList">
205-
<constructor-arg>
206-
<list>
207-
<ref bean="securityContextPersistenceFilter"/>
208-
<!-- To disable csrf security (not recommended) comment the following line -->
209-
<ref bean="csrfFilter" />
210-
<!-- To disable csrf security (not recommended) comment the upper line -->
211-
212-
<ref bean="openidconnectOAuth2AuthorizationRequestRedirectFilter"/>
213-
<ref bean="openidconnectOAuth2LoginAuthenticationFilter"/>
214-
<ref bean="logoutFilter"/>
215-
216-
<ref bean="openidconnectBearerTokenAuthenticationFilter"/>
217-
<!-- <ref bean="openidconnect_GeonetworkBearerTokenAuthenticationFilter"/>-->
218-
219-
<ref bean="requestCacheFilter"/>
220-
<ref bean="anonymousFilter"/>
221-
<ref bean="sessionMgmtFilter"/>
222-
<ref bean="exceptionTranslationFilter"/>
223-
<ref bean="filterSecurityInterceptor"/>
224-
</list>
207+
<constructor-arg
208+
ref="#{ openidconnectConfiguration.loginType == 'autologin' ? 'openidConnectFilterChanFiltersInclusive' : 'openidConnectFilterChanFiltersExclusive' }">
209+
225210
</constructor-arg>
226211
</bean>
212+
<util:list id="openidConnectFilterChanFiltersExclusive">
213+
214+
<ref bean="securityContextPersistenceFilter"/>
215+
<!-- To disable csrf security (not recommended) comment the following line -->
216+
<ref bean="csrfFilter" />
217+
<!-- To disable csrf security (not recommended) comment the upper line -->
218+
219+
<ref bean="openidconnectOAuth2AuthorizationRequestRedirectFilter"/>
220+
<ref bean="openidconnectOAuth2LoginAuthenticationFilter"/>
221+
<ref bean="logoutFilter"/>
222+
223+
<ref bean="openidconnectBearerTokenAuthenticationFilter"/>
224+
<!-- <ref bean="openidconnect_GeonetworkBearerTokenAuthenticationFilter"/>-->
225+
226+
<ref bean="requestCacheFilter"/>
227+
<ref bean="anonymousFilter"/>
228+
<ref bean="sessionMgmtFilter"/>
229+
<ref bean="exceptionTranslationFilter"/>
230+
<ref bean="filterSecurityInterceptor"/>
231+
232+
</util:list>
233+
234+
<util:list id="openidConnectFilterChanFiltersInclusive">
235+
236+
<ref bean="securityContextPersistenceFilter"/>
237+
<ref bean="csrfFilter" />
238+
239+
<ref bean="openidconnectOAuth2AuthorizationRequestRedirectFilter"/>
240+
<ref bean="openidconnectOAuth2LoginAuthenticationFilter"/>
241+
<ref bean="logoutFilter"/>
242+
<!-- include a pre login filter-->
243+
<ref bean="geonetworkOidcPreAuthActionsLoginFilter"/>
244+
245+
<ref bean="openidconnectBearerTokenAuthenticationFilter"/>
246+
247+
<ref bean="requestCacheFilter"/>
248+
<ref bean="anonymousFilter"/>
249+
<ref bean="sessionMgmtFilter"/>
250+
<ref bean="exceptionTranslationFilter"/>
251+
<ref bean="filterSecurityInterceptor"/>
227252

253+
</util:list>
228254
</beans>

0 commit comments

Comments
 (0)