This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/3_0_X by this push:
     new 99c5aa5c33 [SYNCOPE-1805] Upgrading Spring Framework and Security to 
latest versions compatible with Spring Boot 2.7 (#623)
99c5aa5c33 is described below

commit 99c5aa5c3306d9c99675c167fd26955e98cca777
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Mon Feb 19 14:24:53 2024 +0100

    [SYNCOPE-1805] Upgrading Spring Framework and Security to latest versions 
compatible with Spring Boot 2.7 (#623)
---
 .../main/resources/archetype-resources/wa/pom.xml  | 27 +++++++++
 .../implementation/ImplementationManager.java      |  9 +--
 .../spring/security/JWTAuthenticationFilter.java   | 28 ++++++++-
 .../spring/security/JWTAuthenticationProvider.java | 66 ----------------------
 .../core/spring/security/SecureRandomUtils.java    |  4 ++
 .../core/spring/security/WebSecurityContext.java   | 60 ++++++++------------
 .../jws/MSEntraAccessTokenJWSVerifier.java         |  2 +-
 fit/wa-reference/pom.xml                           | 27 +++++++++
 pom.xml                                            | 19 ++++++-
 wa/pom.xml                                         | 27 +++++++++
 10 files changed, 158 insertions(+), 111 deletions(-)

diff --git a/archetype/src/main/resources/archetype-resources/wa/pom.xml 
b/archetype/src/main/resources/archetype-resources/wa/pom.xml
index a005c5223c..960599dbef 100644
--- a/archetype/src/main/resources/archetype-resources/wa/pom.xml
+++ b/archetype/src/main/resources/archetype-resources/wa/pom.xml
@@ -34,6 +34,27 @@ under the License.
 
   <dependencyManagement>
     <dependencies>
+      <dependency>
+        <groupId>org.springframework</groupId>
+        <artifactId>spring-framework-bom</artifactId>
+        <version>${spring-framework.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.security</groupId>
+        <artifactId>spring-security-bom</artifactId>
+        <version>${spring-security.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-dependencies</artifactId>
+        <version>${spring-boot.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
       <dependency>
         <groupId>org.apereo.cas</groupId>
         <artifactId>cas-server-support-bom</artifactId>
@@ -42,6 +63,12 @@ under the License.
         <scope>import</scope>
       </dependency>
 
+      <!-- Spring Boot 2.7.x pulls Thymeleaf Layout Dialect 3.0.0 -->
+      <dependency>
+        <groupId>nz.net.ultraq.thymeleaf</groupId>
+        <artifactId>thymeleaf-layout-dialect</artifactId>
+        <version>${thymeleaf-layout-dialect.version}</version>
+      </dependency>
       <!-- CAS 6.6.x pulls CXF 3.5.3 -->
       <dependency>
         <groupId>org.apache.cxf</groupId>
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
index 820142f644..f86cdacb6b 100644
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
@@ -46,7 +46,6 @@ import 
org.apache.syncope.core.provisioning.api.rules.PullCorrelationRule;
 import org.apache.syncope.core.provisioning.api.rules.PushCorrelationRule;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
-import org.springframework.beans.factory.support.AbstractBeanDefinition;
 
 public final class ImplementationManager {
 
@@ -192,7 +191,7 @@ public final class ImplementationManager {
         }
     }
 
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     private static Class<? extends CommandArgs> findCommandArgsClass(final 
Type type) {
         if (type.getTypeName().startsWith(
                 
ImplementationTypesHolder.getInstance().getValues().get(IdRepoImplementationType.COMMAND)
 + "<")) {
@@ -254,8 +253,7 @@ public final class ImplementationManager {
 
     @SuppressWarnings("unchecked")
     public static <T> T build(final Implementation impl) throws 
ClassNotFoundException {
-        return (T) ApplicationContextProvider.getBeanFactory().
-                createBean(getClass(impl).getLeft(), 
AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+        return (T) 
ApplicationContextProvider.getBeanFactory().createBean(getClass(impl).getLeft());
     }
 
     @SuppressWarnings("unchecked")
@@ -273,8 +271,7 @@ public final class ImplementationManager {
             instance = cacheGetter.get();
         }
         if (instance == null) {
-            instance = (T) ApplicationContextProvider.getBeanFactory().
-                    createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, 
false);
+            instance = 
ApplicationContextProvider.getBeanFactory().createBean(clazz);
 
             if (perContext) {
                 cachePutter.accept(instance);
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java
index 9a6eef9883..0a1231f4f6 100644
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java
@@ -19,21 +19,24 @@
 package org.apache.syncope.core.spring.security;
 
 import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import java.io.IOException;
 import java.text.ParseException;
+import java.util.Date;
 import java.util.Optional;
 import java.util.Set;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.core.HttpHeaders;
 import org.apache.commons.lang3.tuple.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.CredentialsExpiredException;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.web.AuthenticationEntryPoint;
@@ -89,14 +92,35 @@ public class JWTAuthenticationFilter extends 
BasicAuthenticationFilter {
         try {
             credentialChecker.checkIsDefaultJWSKeyInUse();
 
+            // 0. parse JWT
             SignedJWT jwt = SignedJWT.parse(stringToken);
+
+            // 1. check signature
             JWTSSOProvider jwtSSOProvider = 
dataAccessor.getJWTSSOProvider(jwt.getJWTClaimsSet().getIssuer());
             if (!jwt.verify(jwtSSOProvider)) {
                 throw new BadCredentialsException("Invalid signature found in 
JWT");
             }
 
+            JWTClaimsSet claims = jwt.getJWTClaimsSet();
+            long referenceTime = System.currentTimeMillis();
+
+            // 2. check expiration
+            Date expirationTime = claims.getExpirationTime();
+            if (expirationTime != null && expirationTime.getTime() < 
referenceTime) {
+                dataAccessor.removeExpired(claims.getJWTID());
+                throw new CredentialsExpiredException("JWT is expired");
+            }
+
+            // 3. check not before
+            Date notBefore = claims.getNotBeforeTime();
+            if (notBefore != null && notBefore.getTime() > referenceTime) {
+                throw new CredentialsExpiredException("JWT not valid yet");
+            }
+
+            // 4. generate and set the authentication object
             JWTAuthentication jwtAuthentication =
-                    new JWTAuthentication(jwt.getJWTClaimsSet(), 
authenticationDetailsSource.buildDetails(request));
+                    new JWTAuthentication(claims, 
authenticationDetailsSource.buildDetails(request));
+            jwtAuthentication.setAuthenticated(true);
             
AuthContextUtils.callAsAdmin(jwtAuthentication.getDetails().getDomain(), () -> {
                 Pair<String, Set<SyncopeGrantedAuthority>> authenticated = 
dataAccessor.authenticate(jwtAuthentication);
                 jwtAuthentication.setUsername(authenticated.getLeft());
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
deleted file mode 100644
index 797ca25acb..0000000000
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.core.spring.security;
-
-import com.nimbusds.jwt.JWTClaimsSet;
-import java.util.Date;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authentication.CredentialsExpiredException;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-
-/**
- * Attempts to authenticate the passed {@link JWTAuthentication} object, 
returning a fully populated
- * {@link Authentication} object (including granted authorities) if successful.
- */
-public class JWTAuthenticationProvider implements AuthenticationProvider {
-
-    protected final AuthDataAccessor dataAccessor;
-
-    public JWTAuthenticationProvider(final AuthDataAccessor dataAccessor) {
-        this.dataAccessor = dataAccessor;
-    }
-
-    @Override
-    public Authentication authenticate(final Authentication authentication) 
throws AuthenticationException {
-        JWTAuthentication jwtAuthentication = (JWTAuthentication) 
authentication;
-
-        JWTClaimsSet claims = jwtAuthentication.getClaims();
-        long referenceTime = System.currentTimeMillis();
-
-        Date expirationTime = claims.getExpirationTime();
-        if (expirationTime != null && expirationTime.getTime() < 
referenceTime) {
-            dataAccessor.removeExpired(claims.getJWTID());
-            throw new CredentialsExpiredException("JWT is expired");
-        }
-
-        Date notBefore = claims.getNotBeforeTime();
-        if (notBefore != null && notBefore.getTime() > referenceTime) {
-            throw new CredentialsExpiredException("JWT not valid yet");
-        }
-
-        jwtAuthentication.setAuthenticated(true);
-        return jwtAuthentication;
-    }
-
-    @Override
-    public boolean supports(final Class<?> authentication) {
-        return JWTAuthentication.class.isAssignableFrom(authentication);
-    }
-}
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
index ceb4fbcaa3..6b69e2da86 100644
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
@@ -78,6 +78,10 @@ public final class SecureRandomUtils {
                 }).build().generate(1);
     }
 
+    public static int generateRandomInt(final int startInclusive, final int 
endExclusive) {
+        return startInclusive + RANDOM.nextInt(endExclusive - startInclusive);
+    }
+
     public static UUID generateRandomUUID() {
         return UUID_GENERATOR.generate();
     }
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/WebSecurityContext.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/WebSecurityContext.java
index 76382801e7..0b6bb0ba87 100644
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/WebSecurityContext.java
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/WebSecurityContext.java
@@ -38,29 +38,24 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.authentication.AuthenticationManager;
 import 
org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import 
org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import 
org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
 import 
org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import 
org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import 
org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
-import org.springframework.security.config.http.SessionCreationPolicy;
-import org.springframework.security.core.context.SecurityContextHolder;
+import 
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.access.AccessDeniedHandler;
-import 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
+import org.springframework.security.web.access.intercept.AuthorizationFilter;
 import 
org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
-import org.springframework.security.web.context.NullSecurityContextRepository;
 import org.springframework.security.web.firewall.DefaultHttpFirewall;
 import org.springframework.security.web.firewall.HttpFirewall;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 
 @EnableWebSecurity
-@EnableGlobalMethodSecurity(prePostEnabled = true)
+@EnableMethodSecurity(prePostEnabled = true)
 @Configuration(proxyBeanMethods = false)
 public class WebSecurityContext {
 
-    public WebSecurityContext() {
-        
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
-    }
-
     @Bean
     public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
         DefaultHttpFirewall firewall = new DefaultHttpFirewall();
@@ -77,15 +72,15 @@ public class WebSecurityContext {
     public SecurityFilterChain filterChain(
             final HttpSecurity http,
             final UsernamePasswordAuthenticationProvider 
usernamePasswordAuthenticationProvider,
-            final JWTAuthenticationProvider jwtAuthenticationProvider,
-            final SecurityProperties securityProperties,
-            final AuthDataAccessor authDataAccessor,
+            final AccessDeniedHandler accessDeniedHandler,
+            final AuthDataAccessor dataAccessor,
             final DefaultCredentialChecker defaultCredentialChecker) throws 
Exception {
 
         AuthenticationManager authenticationManager = 
http.getSharedObject(AuthenticationManagerBuilder.class).
+                parentAuthenticationManager(null).
                 authenticationProvider(usernamePasswordAuthenticationProvider).
-                authenticationProvider(jwtAuthenticationProvider).
                 build();
+        http.authenticationManager(authenticationManager);
 
         SyncopeAuthenticationDetailsSource authenticationDetailsSource =
                 new SyncopeAuthenticationDetailsSource();
@@ -93,29 +88,30 @@ public class WebSecurityContext {
         SyncopeBasicAuthenticationEntryPoint basicAuthenticationEntryPoint =
                 new SyncopeBasicAuthenticationEntryPoint();
         basicAuthenticationEntryPoint.setRealmName("Apache Syncope 
authentication");
+        http.httpBasic(customizer -> customizer.
+                authenticationEntryPoint(basicAuthenticationEntryPoint).
+                authenticationDetailsSource(authenticationDetailsSource));
 
         JWTAuthenticationFilter jwtAuthenticationFilter = new 
JWTAuthenticationFilter(
                 authenticationManager,
                 basicAuthenticationEntryPoint,
                 authenticationDetailsSource,
-                authDataAccessor,
+                dataAccessor,
                 defaultCredentialChecker);
+        http.addFilterBefore(jwtAuthenticationFilter, 
BasicAuthenticationFilter.class);
 
         MustChangePasswordFilter mustChangePasswordFilter = new 
MustChangePasswordFilter();
-
-        http.authenticationManager(authenticationManager).
-                authorizeRequests().
-                
antMatchers("/actuator/**").hasRole(IdRepoEntitlement.ANONYMOUS).
-                antMatchers("/**").permitAll().and().
-                
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
-                securityContext().securityContextRepository(new 
NullSecurityContextRepository()).and().
-                
httpBasic().authenticationEntryPoint(basicAuthenticationEntryPoint).
-                authenticationDetailsSource(authenticationDetailsSource).and().
-                
exceptionHandling().accessDeniedHandler(accessDeniedHandler()).and().
-                addFilterBefore(jwtAuthenticationFilter, 
BasicAuthenticationFilter.class).
-                addFilterBefore(mustChangePasswordFilter, 
FilterSecurityInterceptor.class).
-                headers().disable().
-                csrf().disable();
+        http.addFilterBefore(mustChangePasswordFilter, 
AuthorizationFilter.class);
+
+        http.authorizeHttpRequests(customizer -> customizer.
+                
requestMatchers(AntPathRequestMatcher.antMatcher("/actuator/**")).
+                hasAuthority(IdRepoEntitlement.ANONYMOUS).
+                
requestMatchers(AntPathRequestMatcher.antMatcher("/**")).permitAll());
+        http.securityContext(AbstractHttpConfigurer::disable);
+        http.sessionManagement(AbstractHttpConfigurer::disable);
+        http.headers(AbstractHttpConfigurer::disable);
+        http.csrf(AbstractHttpConfigurer::disable);
+        http.exceptionHandling(customizer -> 
customizer.accessDeniedHandler(accessDeniedHandler));
 
         return http.build();
     }
@@ -137,12 +133,6 @@ public class WebSecurityContext {
                 securityProperties);
     }
 
-    @ConditionalOnMissingBean
-    @Bean
-    public JWTAuthenticationProvider jwtAuthenticationProvider(final 
AuthDataAccessor authDataAccessor) {
-        return new JWTAuthenticationProvider(authDataAccessor);
-    }
-
     @Bean
     public AccessDeniedHandler accessDeniedHandler() {
         return new SyncopeAccessDeniedHandler();
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/jws/MSEntraAccessTokenJWSVerifier.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/jws/MSEntraAccessTokenJWSVerifier.java
index dc1c4dfe5f..76eaa3f940 100644
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/jws/MSEntraAccessTokenJWSVerifier.java
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/jws/MSEntraAccessTokenJWSVerifier.java
@@ -95,7 +95,7 @@ public class MSEntraAccessTokenJWSVerifier implements 
JWSVerifier {
 
                     @Override
                     public JWSVerifier load(final String key) {
-                        return loadAll(List.of(key)).get(key);
+                        return loadAll(Set.of(key)).get(key);
                     }
 
                     @Override
diff --git a/fit/wa-reference/pom.xml b/fit/wa-reference/pom.xml
index 3fdc5234e3..990c0b8ef8 100644
--- a/fit/wa-reference/pom.xml
+++ b/fit/wa-reference/pom.xml
@@ -41,6 +41,27 @@ under the License.
 
   <dependencyManagement>
     <dependencies>
+      <dependency>
+        <groupId>org.springframework</groupId>
+        <artifactId>spring-framework-bom</artifactId>
+        <version>${spring-framework.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.security</groupId>
+        <artifactId>spring-security-bom</artifactId>
+        <version>${spring-security.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-dependencies</artifactId>
+        <version>${spring-boot.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
       <dependency>
         <groupId>org.apereo.cas</groupId>
         <artifactId>cas-server-support-bom</artifactId>
@@ -49,6 +70,12 @@ under the License.
         <scope>import</scope>
       </dependency>
 
+      <!-- Spring Boot 2.7.x pulls Thymeleaf Layout Dialect 3.0.0 -->
+      <dependency>
+        <groupId>nz.net.ultraq.thymeleaf</groupId>
+        <artifactId>thymeleaf-layout-dialect</artifactId>
+        <version>${thymeleaf-layout-dialect.version}</version>
+      </dependency>
       <!-- CAS 6.6.x pulls CXF 3.5.3 -->
       <dependency>
         <groupId>org.apache.cxf</groupId>
diff --git a/pom.xml b/pom.xml
index 2e98d5f1a9..a1c4190130 100644
--- a/pom.xml
+++ b/pom.xml
@@ -413,6 +413,8 @@ under the License.
     <bouncycastle.version>1.70</bouncycastle.version>
     <nimbus-jose-jwt.version>9.37.3</nimbus-jose-jwt.version>
 
+    <spring-framework.version>5.3.32</spring-framework.version>
+    <spring-security.version>5.8.10</spring-security.version>
     <spring-boot.version>2.7.18</spring-boot.version>
     <spring-cloud-gateway.version>3.1.9</spring-cloud-gateway.version>
 
@@ -445,6 +447,7 @@ under the License.
 
     <cas.version>6.6.15</cas.version>
     <cas-client.version>3.6.4</cas-client.version>
+    <thymeleaf-layout-dialect.version>3.1.0</thymeleaf-layout-dialect.version>
 
     <h2.version>2.2.224</h2.version>
 
@@ -497,7 +500,7 @@ under the License.
     <cargo.rmi.port>9805</cargo.rmi.port>
     <cargo.deployable.ping.timeout>60000</cargo.deployable.ping.timeout>
 
-    <tomcat.version>9.0.85</tomcat.version>
+    <tomcat.version>9.0.86</tomcat.version>
     <wildfly.version>26.1.3.Final</wildfly.version>
     <payara.version>5.2022.5</payara.version>
     <javax.faces.version>2.3.14</javax.faces.version>
@@ -705,6 +708,20 @@ under the License.
         <version>6.6.0</version>
       </dependency>
 
+      <dependency>
+        <groupId>org.springframework</groupId>
+        <artifactId>spring-framework-bom</artifactId>
+        <version>${spring-framework.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.security</groupId>
+        <artifactId>spring-security-bom</artifactId>
+        <version>${spring-security.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
       <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-dependencies</artifactId>
diff --git a/wa/pom.xml b/wa/pom.xml
index 0176ecd4ff..4ec07cafe1 100644
--- a/wa/pom.xml
+++ b/wa/pom.xml
@@ -38,6 +38,27 @@ under the License.
 
   <dependencyManagement>
     <dependencies>
+      <dependency>
+        <groupId>org.springframework</groupId>
+        <artifactId>spring-framework-bom</artifactId>
+        <version>${spring-framework.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.security</groupId>
+        <artifactId>spring-security-bom</artifactId>
+        <version>${spring-security.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-dependencies</artifactId>
+        <version>${spring-boot.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
       <dependency>
         <groupId>org.apereo.cas</groupId>
         <artifactId>cas-server-support-bom</artifactId>
@@ -46,6 +67,12 @@ under the License.
         <scope>import</scope>
       </dependency>
 
+      <!-- Spring Boot 2.7.x pulls Thymeleaf Layout Dialect 3.0.0 -->
+      <dependency>
+        <groupId>nz.net.ultraq.thymeleaf</groupId>
+        <artifactId>thymeleaf-layout-dialect</artifactId>
+        <version>${thymeleaf-layout-dialect.version}</version>
+      </dependency>
       <!-- CAS 6.6.x pulls CXF 3.5.3 -->
       <dependency>
         <groupId>org.apache.cxf</groupId>

Reply via email to