I realize that the most likely explanation is that I must have made an error, but after extensive research it seems prudent to ask: Is Azure AD authentication for Spring Boot webapps still compatible with WAR deployment in Tomcat 10? Or perhaps "not yet"?
My trivial Hello World app used to work in Tomcat 9, this is the code after migrating it to Spring Boot 3 / Spring 6 / Spring Security 6 / Tomcat 10.
This error is thrown during WAR deploy to Tomcat 10:
Cannot cast ch.qos.logback.classic.servlet.LogbackServletContainerInitializer to jakarta.servlet.ServletContainerInitializer
In case it is relevant, Tomcat 10 is configured for log4j 2.20.0 logging with a log4j file similar to the Tomcat 9 one, and includes log4j-api, log4j-appserver and log4j-core JARs.
The most relevant information I found is: https://learn.microsoft.com/en-us/azure/developer/java/spring-framework/spring-boot-starter-for-azure-active-directory-developer-guide?tabs=SpringCloudAzure5x but I fail to see anything there that would indicate what is wrong with my setup.
Application:
@EnableWebSecurity
@EnableMethodSecurity
@SpringBootApplication
public class WebSbAzureHelloApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(WebSbAzureHelloApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(WebSbAzureHelloApplication.class, args);
System.out.println("Hello World");
}
}
Controller:
@RestController
public class HelloController {
@GetMapping("Admin")
@ResponseBody
@PreAuthorize("hasAuthority('APPROLE_Admin')")
public String Admin() {
return "Admin message";
}
}
POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.4</version>
<relativePath/>
</parent>
<groupId>net.cndc</groupId>
<artifactId>webSbAzureHello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>webSbAzureHello</name>
<description>SpringBoot test for Azure AD authentication</description>
<packaging>war</packaging>
<properties>
<java.version>17</java.version>
<start-class>net.cndc.webSbAzureHello.WebSbAzureHelloApplication</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties:
azure.activedirectory.tenant-id= my tenant ID from Azure here
azure.activedirectory.client-id= my client ID from Azure here
azure.activedirectory.client-secret= my client secret from Azure here
azure.activedirectory.redirect-uri-template=https://localhost/webSbAzureHello/login/oauth2/code/
spring.main.allow-bean-definition-overriding=true
Configuring a Spring Boot app without
spring-cloud-azure-starter-active-directoryis actually quite simple.OAuth2 Client
For the server-side rendered UI with login and logout, use just the
spring-boot-starter-oauth2-clientyou already depend on. Requests from the browser to this client will be secured with sessions (not access tokens).The following properties should be enough:
Azure AD implements strictly the RP-Initiated Logout and exposes an
end_session_endpointin its OpenID configuration (at least, it does on my test tenant). This means you can useOidcClientInitiatedLogoutSuccessHandlerto invalidate user session on Azure AD too when terminating his session on your client, but for that, you'll have to provide with a complete client security filter-chain configuration:OAuth2 Resource Server
Applications secured with OAuth2 access tokens are resource servers. The dependency to use is
spring-boot-starter-oauth2-resource-server.The following properties should be enough to configure a single tenant resource server with authorities mapped from
scopeclaim:Stateless resource server, Roles mapping, multi-tenancy,...
For more advanced configuration, please refer to my tutorials. It cover subjects like