diff --git a/.gitignore b/.gitignore
index 1da6f15..53ab1b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,8 @@ hs_err_pid*
# macOS
.DS_Store
+.vscode/
+
# IntelliJ
.idea/
*.iml
@@ -25,4 +27,4 @@ out/
**/build/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
-!gradle-wrapper.jar
\ No newline at end of file
+!gradle-wrapper.jar
diff --git a/.version b/.version
index 169f19b..32bd932 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-1.11.0
\ No newline at end of file
+1.12.0
\ No newline at end of file
diff --git a/README.md b/README.md
index 1ee3030..3ce6e97 100644
--- a/README.md
+++ b/README.md
@@ -24,26 +24,49 @@
### Requirements
-Java 8 or above and `javax.servlet` version 3.
+Java 8 or above and `javax.servlet` version 3, **or** `jakarta.servlet` version 5.0 or above for Jakarta EE environments.
> If you are using Spring, we recommend leveraging Spring's OIDC and OAuth2 support, as demonstrated by the [Spring Boot Quickstart](https://auth0.com/docs/quickstart/webapp/java-spring-boot).
### Installation
+#### For traditional Java EE / javax.servlet environments:
+
+Add the dependency via Maven:
+
+```xml
+
+ com.auth0
+ mvc-auth-commons
+ 1.12.0
+
+```
+
+or Gradle:
+
+```gradle
+implementation 'com.auth0:mvc-auth-commons:1.12.0'
+```
+
+#### For Jakarta EE / jakarta.servlet environments:
+
+Starting from version 1.12.0, this library provides dual support for both javax.servlet and jakarta.servlet environments through bytecode transformation. For Jakarta EE environments (Tomcat 10+ etc.), use the Jakarta-compatible version:
+
Add the dependency via Maven:
```xml
com.auth0
mvc-auth-commons
- 1.11.0
+ 1.12.0
+ jakarta
```
or Gradle:
```gradle
-implementation 'com.auth0:mvc-auth-commons:1.11.0'
+implementation 'com.auth0:mvc-auth-commons:1.12.0:jakarta'
```
### Configure Auth0
diff --git a/build.gradle b/build.gradle
index 47632fa..8dabe94 100644
--- a/build.gradle
+++ b/build.gradle
@@ -26,8 +26,6 @@ version = getVersionFromFile()
group = GROUP
logger.lifecycle("Using version ${version} for ${name} group $group")
-import me.champeau.gradle.japicmp.JapicmpTask
-
project.afterEvaluate {
def versions = project.ext.testInJavaVersions
for (pluginJavaTestVersion in versions) {
@@ -49,7 +47,7 @@ project.afterEvaluate {
project.configure(project) {
def baselineVersion = project.ext.baselineCompareVersion
- task('apiDiff', type: JapicmpTask, dependsOn: 'jar') {
+ task('apiDiff', type: me.champeau.gradle.japicmp.JapicmpTask, dependsOn: 'jar') {
oldClasspath.from(files(getBaselineJar(project, baselineVersion)))
newClasspath.from(files(jar.archiveFile))
onlyModified = true
@@ -106,6 +104,20 @@ java {
}
}
+// Jakarta test source set configuration
+sourceSets {
+ testJakarta {
+ java {
+ srcDirs = ['src/testJakarta/java']
+ }
+ resources {
+ srcDirs = ['src/testJakarta/resources']
+ }
+ compileClasspath += sourceSets.main.output + configurations.testRuntimeClasspath
+ runtimeClasspath += output + compileClasspath
+ }
+}
+
compileJava {
sourceCompatibility '1.8'
targetCompatibility '1.8'
@@ -136,6 +148,146 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
testImplementation 'org.springframework:spring-test:4.3.14.RELEASE'
testImplementation 'com.squareup.okhttp3:okhttp:4.11.0'
+
+ // Jakarta test dependencies (Java 8 compatible)
+ testJakartaImplementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'
+ testJakartaImplementation 'org.apache.commons:commons-lang3:3.12.0'
+ testJakartaImplementation 'com.google.guava:guava-annotations:r03'
+ testJakartaImplementation 'commons-codec:commons-codec:1.15'
+ testJakartaImplementation 'com.auth0:auth0:1.45.1'
+ testJakartaImplementation 'com.auth0:java-jwt:3.19.4'
+ testJakartaImplementation 'com.auth0:jwks-rsa:0.22.1'
+ testJakartaImplementation 'org.bouncycastle:bcprov-jdk15on:1.64'
+ testJakartaImplementation 'org.hamcrest:java-hamcrest:2.0.0.0'
+ testJakartaImplementation 'org.hamcrest:hamcrest-core:1.3'
+ testJakartaImplementation 'org.mockito:mockito-core:2.8.9'
+ testJakartaImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
+ testJakartaImplementation 'org.springframework:spring-test:4.3.14.RELEASE'
+ testJakartaImplementation 'com.squareup.okhttp3:okhttp:4.11.0'
+ testJakartaRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
+}
+
+// Task to perform javax to jakarta transformation using Eclipse Transformer
+tasks.register('performJakartaTransformation') {
+ dependsOn tasks.named('classes')
+
+ def transformedClassesDir = file("$buildDir/transformed-classes")
+
+ outputs.dir transformedClassesDir
+ inputs.files sourceSets.main.output.classesDirs
+
+ doLast {
+ // Eclipse Transformer CLI configuration
+ def transformerVersion = '0.5.0'
+ def transformerConfig = project.configurations.detachedConfiguration(
+ project.dependencies.create("org.eclipse.transformer:org.eclipse.transformer.cli:${transformerVersion}")
+ )
+
+ // Clean and create output directory
+ delete transformedClassesDir
+ transformedClassesDir.mkdirs()
+
+ // Transform each classes directory directly to the root of transformedClassesDir
+ sourceSets.main.output.classesDirs.each { classDir ->
+ if (classDir.exists()) {
+ project.javaexec {
+ classpath = transformerConfig
+ main = 'org.eclipse.transformer.cli.JakartaTransformerCLI'
+ args = [
+ classDir.absolutePath,
+ transformedClassesDir.absolutePath,
+ '-q' // quiet mode
+ ]
+ }
+ }
+ }
+
+ // Also transform resources if needed
+ sourceSets.main.output.resourcesDir.with { resourceDir ->
+ if (resourceDir.exists()) {
+ copy {
+ from resourceDir
+ into transformedClassesDir
+ }
+ }
+ }
+
+ println "Jakarta transformation completed: ${transformedClassesDir.absolutePath}"
+ }
+}
+
+// Task to create Jakarta JAR from transformed classes
+tasks.register('transformJarToJakarta', Jar) {
+ dependsOn tasks.named('performJakartaTransformation')
+
+ archiveClassifier = 'jakarta'
+
+ def transformedClassesDir = file("$buildDir/transformed-classes")
+
+ from transformedClassesDir
+
+ // Include original manifest with modifications
+ manifest {
+ from(tasks.jar.manifest) {
+ attributes.remove('Implementation-Title')
+ attributes.remove('Implementation-Version')
+ }
+ attributes(
+ 'Implementation-Title': "${project.name}-jakarta",
+ 'Implementation-Version': project.version,
+ 'Jakarta-Transformed': 'true'
+ )
+ }
+
+ doLast {
+ println "Jakarta JAR file generated: ${archiveFile.get().asFile.absolutePath}"
+ }
+}
+
+// Jakarta test task (updated to use transformed JAR)
+tasks.register('testJakarta', Test) {
+ description = "Runs Jakarta EE specific tests using transformed jakarta classes"
+ group = "verification"
+ testClassesDirs = sourceSets.testJakarta.output.classesDirs
+
+ // Build classpath with Jakarta dependencies and transformed JAR
+ def jakartaClasspath = sourceSets.testJakarta.runtimeClasspath
+
+ shouldRunAfter tasks.named('test')
+ dependsOn tasks.named('transformJarToJakarta')
+
+ // Enabled for Jakarta testing
+ enabled = true
+
+ useJUnitPlatform()
+ testLogging {
+ events "skipped", "failed"
+ exceptionFormat "short"
+ }
+
+ doFirst {
+ def jakartaJar = tasks.transformJarToJakarta.archiveFile.get().asFile
+ if (jakartaJar.exists()) {
+ // Replace main classes with jakarta-transformed JAR in classpath
+ classpath = jakartaClasspath + files(jakartaJar) - sourceSets.main.output
+ } else {
+ throw new GradleException("Jakarta JAR not found: ${jakartaJar.absolutePath}")
+ }
+ }
+}
+
+// Enable Jakarta tests in check task
+tasks.named('check').configure {
+ dependsOn tasks.named('testJakarta')
+}
+
+// Ensure Jakarta JAR is built when running build task
+tasks.named('build').configure {
+ dependsOn tasks.named('transformJarToJakarta')
}
apply from: rootProject.file('gradle/maven-publish.gradle')
+
+
+
+
diff --git a/gradle/maven-publish.gradle b/gradle/maven-publish.gradle
index 206a581..4e6d31f 100644
--- a/gradle/maven-publish.gradle
+++ b/gradle/maven-publish.gradle
@@ -37,8 +37,18 @@ publishing {
artifactId = POM_ARTIFACT_ID
version = getVersionName()
+ // 1. Main artifact (javax version) - no classifier
artifact("$buildDir/libs/${project.name}-${version}.jar")
+
+ // 2. Jakarta EE version artifact - with 'jakarta' classifier
+ artifact("$buildDir/libs/${project.name}-${version}-jakarta.jar") {
+ classifier 'jakarta'
+ }
+
+ // 3. Sources JAR
artifact sourcesJar
+
+ // 4. Javadoc JAR
artifact javadocJar
pom {
@@ -94,6 +104,15 @@ publishing {
}
}
+// Ensure Jakarta JAR is built before publishing
+tasks.named('publishMavenJavaPublicationToSonatypeRepository').configure {
+ dependsOn tasks.named('transformJarToJakarta')
+}
+
+tasks.named('publishToMavenLocal').configure {
+ dependsOn tasks.named('transformJarToJakarta')
+}
+
signing {
def signingKey = System.getenv("SIGNING_KEY")
def signingPassword = System.getenv("SIGNING_PASSWORD")
diff --git a/src/testJakarta/java/com/auth0/JakartaCompatibilityTest.java b/src/testJakarta/java/com/auth0/JakartaCompatibilityTest.java
new file mode 100644
index 0000000..451e296
--- /dev/null
+++ b/src/testJakarta/java/com/auth0/JakartaCompatibilityTest.java
@@ -0,0 +1,85 @@
+package com.auth0;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import jakarta.servlet.http.Cookie;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests to verify Jakarta EE compatibility.
+ * These tests ensure Jakarta servlet API is available and working.
+ */
+public class JakartaCompatibilityTest {
+
+ @Test
+ public void testJakartaServletRequestAvailable() {
+ // Verify Jakarta HttpServletRequest can be mocked and used
+ HttpServletRequest mockRequest = Mockito.mock(HttpServletRequest.class);
+ assertNotNull(mockRequest, "Jakarta HttpServletRequest should be available");
+
+ // Verify it's from Jakarta package
+ String className = mockRequest.getClass().getName();
+ assertTrue(className.contains("jakarta") || className.contains("HttpServletRequest"),
+ "Should be Jakarta servlet type");
+ }
+
+ @Test
+ public void testJakartaServletResponseAvailable() {
+ // Verify Jakarta HttpServletResponse can be mocked and used
+ HttpServletResponse mockResponse = Mockito.mock(HttpServletResponse.class);
+ assertNotNull(mockResponse, "Jakarta HttpServletResponse should be available");
+ }
+
+ @Test
+ public void testJakartaHttpSessionAvailable() {
+ // Test Jakarta HttpSession
+ HttpSession mockSession = Mockito.mock(HttpSession.class);
+ assertNotNull(mockSession, "Jakarta HttpSession should be available");
+
+ // Test basic session operations
+ Mockito.when(mockSession.getAttribute("test")).thenReturn("value");
+ Mockito.doNothing().when(mockSession).setAttribute("test", "value");
+
+ mockSession.setAttribute("test", "value");
+ Mockito.verify(mockSession).setAttribute("test", "value");
+ }
+
+ @Test
+ public void testJakartaCookieAvailable() {
+ // Test Jakarta Cookie
+ Cookie cookie = new Cookie("testCookie", "testValue");
+ assertNotNull(cookie, "Jakarta Cookie should be available");
+
+ cookie.setPath("/");
+ cookie.setSecure(true);
+ cookie.setHttpOnly(true);
+
+ // Verify basic cookie properties
+ assertNotNull(cookie.getName());
+ assertNotNull(cookie.getValue());
+ }
+
+ @Test
+ public void testJakartaServletIntegration() {
+ // Test integration between Jakarta servlet components
+ HttpServletRequest mockRequest = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse mockResponse = Mockito.mock(HttpServletResponse.class);
+ HttpSession mockSession = Mockito.mock(HttpSession.class);
+
+ // Setup mock behavior
+ Mockito.when(mockRequest.getSession(true)).thenReturn(mockSession);
+ Mockito.when(mockRequest.getContextPath()).thenReturn("/test");
+
+ // Test session retrieval
+ HttpSession session = mockRequest.getSession(true);
+ assertNotNull(session, "Session should be retrieved successfully");
+
+ String contextPath = mockRequest.getContextPath();
+ assertNotNull(contextPath, "Context path should be available");
+ }
+}
\ No newline at end of file