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>