From 0d754c97a4971dd119aa4dbb9799194b61d02281 Mon Sep 17 00:00:00 2001 From: HTHou Date: Tue, 17 Mar 2026 10:02:50 +0800 Subject: [PATCH 1/9] Reject expired OpenID tokens and add regression test --- iotdb-core/node-commons/pom.xml | 10 ++ .../auth/authorizer/OpenIdAuthorizer.java | 4 +- .../auth/authorizer/OpenIdAuthorizerTest.java | 113 ++++++++++++++++++ 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java diff --git a/iotdb-core/node-commons/pom.xml b/iotdb-core/node-commons/pom.xml index 24f33d0de8bec..9cae757055c32 100644 --- a/iotdb-core/node-commons/pom.xml +++ b/iotdb-core/node-commons/pom.xml @@ -149,6 +149,16 @@ io.jsonwebtoken jjwt-api + + io.jsonwebtoken + jjwt-impl + test + + + io.jsonwebtoken + jjwt-jackson + test + com.nimbusds oauth2-oidc-sdk diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java index ee66ee5bced95..f5f74a87b392f 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java @@ -55,6 +55,7 @@ public class OpenIdAuthorizer extends BasicAuthorizer { private static final Logger logger = LoggerFactory.getLogger(OpenIdAuthorizer.class); + private static final long MAX_CLOCK_SKEW_SECONDS = 300; public static final String IOTDB_ADMIN_ROLE_NAME = "iotdb_admin"; public static final String OPENID_USER_PREFIX = "openid-"; @@ -194,8 +195,7 @@ public String getIoTDBUserName(String token) { private Claims validateToken(String token) { return Jwts.parser() - // Basically ignore the Expiration Date, if there is any??? - .clockSkewSeconds(Long.MAX_VALUE / 1000) + .clockSkewSeconds(MAX_CLOCK_SKEW_SECONDS) .verifyWith(providerKey) .build() .parseSignedClaims(token) diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java new file mode 100644 index 0000000000000..f41645998fe04 --- /dev/null +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java @@ -0,0 +1,113 @@ +/* + * 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.iotdb.commons.auth.authorizer; + +import org.apache.iotdb.commons.conf.CommonConfig; +import org.apache.iotdb.commons.conf.CommonDescriptor; + +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import io.jsonwebtoken.Jwts; +import net.minidev.json.JSONObject; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPublicKey; +import java.time.Instant; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; + +public class OpenIdAuthorizerTest { + + private final CommonConfig config = CommonDescriptor.getInstance().getConfig(); + + private String originalUserFolder; + private String originalRoleFolder; + private Path baseDir; + + @Before + public void setUp() throws IOException { + originalUserFolder = config.getUserFolder(); + originalRoleFolder = config.getRoleFolder(); + + baseDir = Files.createTempDirectory("openid-authorizer-test-"); + config.setUserFolder(Files.createDirectories(baseDir.resolve("users")).toString()); + config.setRoleFolder(Files.createDirectories(baseDir.resolve("roles")).toString()); + } + + @After + public void tearDown() throws IOException { + config.setUserFolder(originalUserFolder); + config.setRoleFolder(originalRoleFolder); + + if (baseDir != null) { + try (java.util.stream.Stream stream = Files.walk(baseDir)) { + stream.sorted(Comparator.reverseOrder()).forEach(this::deleteIfExists); + } + } + } + + @Test + public void testExpiredTokenRejectedByLoginAndIsAdmin() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + JSONObject jwk = + new JSONObject( + new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) + .privateKey(keyPair.getPrivate()) + .keyUse(KeyUse.SIGNATURE) + .keyID("expired-token-test-key") + .build() + .toJSONObject()); + + OpenIdAuthorizer authorizer = new OpenIdAuthorizer(jwk); + String expiredToken = + Jwts.builder() + .subject("attacker") + .expiration(Date.from(Instant.now().minusSeconds(3600))) + .claim( + "realm_access", + Collections.singletonMap( + "roles", Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME))) + .signWith(keyPair.getPrivate(), Jwts.SIG.RS256) + .compact(); + + Assert.assertFalse(authorizer.login(expiredToken, "", false)); + Assert.assertFalse(authorizer.isAdmin(expiredToken)); + } + + private void deleteIfExists(Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + throw new RuntimeException("Failed to delete test path " + path, e); + } + } +} From df788e0773f6a0ac268eb680f6d619b8c2af1e85 Mon Sep 17 00:00:00 2001 From: HTHou Date: Tue, 17 Mar 2026 10:19:06 +0800 Subject: [PATCH 2/9] Validate OpenID issuer and audience claims --- .../conf/iotdb-system.properties.template | 6 + .../auth/authorizer/OpenIdAuthorizer.java | 101 ++++++++++++++-- .../iotdb/commons/conf/CommonConfig.java | 9 ++ .../iotdb/commons/conf/CommonDescriptor.java | 2 + .../auth/authorizer/OpenIdAuthorizerTest.java | 112 ++++++++++++++++++ 5 files changed, 221 insertions(+), 9 deletions(-) diff --git a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template index 3f1c4ab41ffaf..5d5cce4123f8c 100644 --- a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template +++ b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template @@ -1753,6 +1753,12 @@ authorizer_provider_class=org.apache.iotdb.commons.auth.authorizer.LocalFileAuth # Privilege: SECURITY openID_url= +# If OpenIdAuthorizer is enabled, then openID_audience must contain the IoTDB client ID +# or a comma-separated allowlist of accepted audiences. +# effectiveMode: restart +# Privilege: SECURITY +openID_audience= + # encryption provider class # effectiveMode: first_start iotdb_server_encrypt_decrypt_provider=org.apache.iotdb.commons.security.encrypt.MessageDigestEncrypt diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java index f5f74a87b392f..2ff888fa4aeaa 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java @@ -45,11 +45,16 @@ import java.net.URISyntaxException; import java.net.URL; import java.security.interfaces.RSAPublicKey; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Scanner; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; /** Uses an OpenID Connect provider for Authorization / Authentication. */ public class OpenIdAuthorizer extends BasicAuthorizer { @@ -62,6 +67,8 @@ public class OpenIdAuthorizer extends BasicAuthorizer { private static final CommonConfig config = CommonDescriptor.getInstance().getConfig(); private final RSAPublicKey providerKey; + private final String expectedIssuer; + private final Set acceptedAudiences; /** Stores all claims to the respective user */ private final Map loggedClaims = new HashMap<>(); @@ -71,6 +78,11 @@ public OpenIdAuthorizer() throws AuthException, ParseException, IOException, URI } public OpenIdAuthorizer(JSONObject jwk) throws AuthException { + this(jwk, null, Collections.emptySet()); + } + + public OpenIdAuthorizer(JSONObject jwk, String expectedIssuer, Set acceptedAudiences) + throws AuthException { super( new LocalFileUserManager(config.getUserFolder()), new LocalFileRoleManager(config.getRoleFolder())); @@ -80,15 +92,21 @@ public OpenIdAuthorizer(JSONObject jwk) throws AuthException { throw new AuthException( TSStatusCode.INIT_AUTH_ERROR, "Unable to get OIDC Provider Key from JWK " + jwk, e); } + this.expectedIssuer = expectedIssuer; + this.acceptedAudiences = Collections.unmodifiableSet(new HashSet<>(acceptedAudiences)); logger.info("Initialized with providerKey: {}", providerKey); } public OpenIdAuthorizer(String providerUrl) throws AuthException, URISyntaxException, ParseException, IOException { - this(getJwkFromProvider(providerUrl)); + this(loadProviderContext(providerUrl)); } - private static JSONObject getJwkFromProvider(String providerUrl) + private OpenIdAuthorizer(ProviderContext providerContext) throws AuthException { + this(providerContext.jwk, providerContext.issuer, providerContext.acceptedAudiences); + } + + private static ProviderContext loadProviderContext(String providerUrl) throws URISyntaxException, IOException, ParseException, AuthException { if (providerUrl == null) { throw new IllegalArgumentException("OpenID Connect Provider URI must be given!"); @@ -99,10 +117,24 @@ private static JSONObject getJwkFromProvider(String providerUrl) logger.debug("Using Provider Metadata: {}", providerMetadata); + Set acceptedAudiences = parseAudiences(config.getOpenIdAudience()); + if (acceptedAudiences.isEmpty()) { + throw new AuthException( + TSStatusCode.INIT_AUTH_ERROR, + "openID_audience must be configured when OpenIdAuthorizer is enabled"); + } + + String issuer = + providerMetadata.getIssuer() == null ? null : providerMetadata.getIssuer().getValue(); + if (issuer == null || issuer.isEmpty()) { + throw new AuthException( + TSStatusCode.INIT_AUTH_ERROR, "OIDC provider metadata does not contain an issuer"); + } + try { URL url = new URI(providerMetadata.getJWKSetURI().toString()).toURL(); logger.debug("Using url {}", url); - return getProviderRsaJwk(url.openStream()); + return new ProviderContext(getProviderRsaJwk(url.openStream()), issuer, acceptedAudiences); } catch (IOException e) { throw new AuthException(TSStatusCode.INIT_AUTH_ERROR, "Unable to start the Auth", e); } @@ -194,12 +226,51 @@ public String getIoTDBUserName(String token) { } private Claims validateToken(String token) { - return Jwts.parser() - .clockSkewSeconds(MAX_CLOCK_SKEW_SECONDS) - .verifyWith(providerKey) - .build() - .parseSignedClaims(token) - .getPayload(); + Claims claims = + Jwts.parser() + .clockSkewSeconds(MAX_CLOCK_SKEW_SECONDS) + .verifyWith(providerKey) + .build() + .parseSignedClaims(token) + .getPayload(); + validateClaims(claims); + return claims; + } + + private void validateClaims(Claims claims) { + if (expectedIssuer != null && !expectedIssuer.equals(claims.getIssuer())) { + throw new JwtException( + String.format("Unexpected issuer %s, expected %s", claims.getIssuer(), expectedIssuer)); + } + if (!acceptedAudiences.isEmpty() && !hasAcceptedAudience(claims.get("aud"))) { + throw new JwtException( + String.format( + "Unexpected audience %s, expected one of %s", claims.get("aud"), acceptedAudiences)); + } + } + + private boolean hasAcceptedAudience(Object audienceClaim) { + if (audienceClaim instanceof String) { + return acceptedAudiences.contains(audienceClaim); + } + if (audienceClaim instanceof List) { + for (Object audience : (List) audienceClaim) { + if (audience instanceof String && acceptedAudiences.contains(audience)) { + return true; + } + } + } + return false; + } + + private static Set parseAudiences(String configuredAudiences) { + if (configuredAudiences == null || configuredAudiences.trim().isEmpty()) { + return Collections.emptySet(); + } + return Arrays.stream(configuredAudiences.split(",")) + .map(String::trim) + .filter(audience -> !audience.isEmpty()) + .collect(Collectors.toCollection(HashSet::new)); } private String getUsername(Claims claims) { @@ -258,6 +329,18 @@ public boolean checkUserPrivileges(String userName, PrivilegeUnion union) throws return isAdmin(userName); } + private static class ProviderContext { + private final JSONObject jwk; + private final String issuer; + private final Set acceptedAudiences; + + private ProviderContext(JSONObject jwk, String issuer, Set acceptedAudiences) { + this.jwk = jwk; + this.issuer = issuer; + this.acceptedAudiences = acceptedAudiences; + } + } + @Override public void updateUserPassword(String userName, String newPassword) { throwUnsupportedOperationException(); diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java index a490107ded324..760cccb973baf 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java @@ -60,6 +60,7 @@ public class CommonConfig { // Open ID Secret private String openIdProviderUrl = ""; + private String openIdAudience = ""; // The authorizer provider class which extends BasicAuthorizer private String authorizerProvider = @@ -543,6 +544,14 @@ public void setOpenIdProviderUrl(String openIdProviderUrl) { this.openIdProviderUrl = openIdProviderUrl; } + public String getOpenIdAudience() { + return openIdAudience; + } + + public void setOpenIdAudience(String openIdAudience) { + this.openIdAudience = openIdAudience; + } + public String getAuthorizerProvider() { return authorizerProvider; } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java index 8483d1425cfec..004b147938c6c 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java @@ -80,6 +80,8 @@ public void loadCommonProps(TrimProperties properties) throws IOException { // if using org.apache.iotdb.db.auth.authorizer.OpenIdAuthorizer, openID_url is needed. config.setOpenIdProviderUrl( properties.getProperty("openID_url", config.getOpenIdProviderUrl()).trim()); + config.setOpenIdAudience( + properties.getProperty("openID_audience", config.getOpenIdAudience()).trim()); config.setEncryptDecryptProvider( properties .getProperty( diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java index f41645998fe04..b0f2b797f35c2 100644 --- a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java @@ -24,6 +24,8 @@ import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpServer; import io.jsonwebtoken.Jwts; import net.minidev.json.JSONObject; import org.junit.After; @@ -32,6 +34,8 @@ import org.junit.Test; import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyPair; @@ -48,12 +52,14 @@ public class OpenIdAuthorizerTest { private String originalUserFolder; private String originalRoleFolder; + private String originalOpenIdAudience; private Path baseDir; @Before public void setUp() throws IOException { originalUserFolder = config.getUserFolder(); originalRoleFolder = config.getRoleFolder(); + originalOpenIdAudience = config.getOpenIdAudience(); baseDir = Files.createTempDirectory("openid-authorizer-test-"); config.setUserFolder(Files.createDirectories(baseDir.resolve("users")).toString()); @@ -64,6 +70,7 @@ public void setUp() throws IOException { public void tearDown() throws IOException { config.setUserFolder(originalUserFolder); config.setRoleFolder(originalRoleFolder); + config.setOpenIdAudience(originalOpenIdAudience); if (baseDir != null) { try (java.util.stream.Stream stream = Files.walk(baseDir)) { @@ -103,6 +110,111 @@ public void testExpiredTokenRejectedByLoginAndIsAdmin() throws Exception { Assert.assertFalse(authorizer.isAdmin(expiredToken)); } + @Test + public void testWrongIssuerRejected() throws Exception { + config.setOpenIdAudience("iotdb"); + KeyPair keyPair = generateKeyPair(); + HttpServer server = startProviderServer(keyPair); + String issuer = "http://127.0.0.1:" + server.getAddress().getPort() + "/"; + + try { + OpenIdAuthorizer authorizer = new OpenIdAuthorizer(issuer); + String token = + Jwts.builder() + .subject("attacker") + .issuer("https://evil.example/issuer") + .claim("aud", "iotdb") + .expiration(Date.from(Instant.now().plusSeconds(3600))) + .claim( + "realm_access", + Collections.singletonMap( + "roles", Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME))) + .signWith(keyPair.getPrivate(), Jwts.SIG.RS256) + .compact(); + + Assert.assertFalse(authorizer.login(token, "", false)); + Assert.assertFalse(authorizer.isAdmin(token)); + } finally { + server.stop(0); + } + } + + @Test + public void testWrongAudienceRejected() throws Exception { + config.setOpenIdAudience("iotdb"); + KeyPair keyPair = generateKeyPair(); + HttpServer server = startProviderServer(keyPair); + String issuer = "http://127.0.0.1:" + server.getAddress().getPort() + "/"; + + try { + OpenIdAuthorizer authorizer = new OpenIdAuthorizer(issuer); + String token = + Jwts.builder() + .subject("attacker") + .issuer(issuer) + .claim("aud", "unrelated-client") + .expiration(Date.from(Instant.now().plusSeconds(3600))) + .claim( + "realm_access", + Collections.singletonMap( + "roles", Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME))) + .signWith(keyPair.getPrivate(), Jwts.SIG.RS256) + .compact(); + + Assert.assertFalse(authorizer.login(token, "", false)); + Assert.assertFalse(authorizer.isAdmin(token)); + } finally { + server.stop(0); + } + } + + private KeyPair generateKeyPair() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + return keyPairGenerator.generateKeyPair(); + } + + private HttpServer startProviderServer(KeyPair keyPair) throws Exception { + JSONObject publicJwk = + new JSONObject( + new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) + .keyUse(KeyUse.SIGNATURE) + .keyID("openid-provider-test-key") + .build() + .toJSONObject()); + + HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + String issuer = "http://127.0.0.1:" + server.getAddress().getPort() + "/"; + String metadata = + "{" + + "\"issuer\":\"" + + issuer + + "\"," + + "\"jwks_uri\":\"" + + issuer + + "jwks.json\"," + + "\"subject_types_supported\":[\"public\"]," + + "\"response_types_supported\":[\"code\"]," + + "\"id_token_signing_alg_values_supported\":[\"RS256\"]" + + "}"; + String jwks = "{\"keys\":[" + publicJwk.toJSONString() + "]}"; + + server.createContext( + "/.well-known/openid-configuration", exchange -> writeJson(exchange, metadata)); + server.createContext("/jwks.json", exchange -> writeJson(exchange, jwks)); + server.start(); + return server; + } + + private void writeJson(HttpExchange exchange, String json) throws IOException { + byte[] response = json.getBytes(java.nio.charset.StandardCharsets.UTF_8); + exchange.getResponseHeaders().set("Content-Type", "application/json"); + exchange.sendResponseHeaders(200, response.length); + try (OutputStream outputStream = exchange.getResponseBody()) { + outputStream.write(response); + } + } + private void deleteIfExists(Path path) { try { Files.deleteIfExists(path); From 8d826b458b792158dbdfd42240d89626c93548a7 Mon Sep 17 00:00:00 2001 From: HTHou Date: Tue, 17 Mar 2026 10:33:09 +0800 Subject: [PATCH 3/9] Replace JJWT test token generation with Nimbus JWT --- iotdb-core/node-commons/pom.xml | 10 --- .../auth/authorizer/OpenIdAuthorizerTest.java | 80 +++++++++++-------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/iotdb-core/node-commons/pom.xml b/iotdb-core/node-commons/pom.xml index 9cae757055c32..24f33d0de8bec 100644 --- a/iotdb-core/node-commons/pom.xml +++ b/iotdb-core/node-commons/pom.xml @@ -149,16 +149,6 @@ io.jsonwebtoken jjwt-api - - io.jsonwebtoken - jjwt-impl - test - - - io.jsonwebtoken - jjwt-jackson - test - com.nimbusds oauth2-oidc-sdk diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java index b0f2b797f35c2..1951a6893c20d 100644 --- a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java @@ -22,11 +22,16 @@ import org.apache.iotdb.commons.conf.CommonConfig; import org.apache.iotdb.commons.conf.CommonDescriptor; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; -import io.jsonwebtoken.Jwts; import net.minidev.json.JSONObject; import org.junit.After; import org.junit.Assert; @@ -40,6 +45,7 @@ import java.nio.file.Path; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.PrivateKey; import java.security.interfaces.RSAPublicKey; import java.time.Instant; import java.util.Collections; @@ -96,15 +102,16 @@ public void testExpiredTokenRejectedByLoginAndIsAdmin() throws Exception { OpenIdAuthorizer authorizer = new OpenIdAuthorizer(jwk); String expiredToken = - Jwts.builder() - .subject("attacker") - .expiration(Date.from(Instant.now().minusSeconds(3600))) - .claim( - "realm_access", - Collections.singletonMap( - "roles", Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME))) - .signWith(keyPair.getPrivate(), Jwts.SIG.RS256) - .compact(); + createSignedToken( + keyPair.getPrivate(), + new JWTClaimsSet.Builder() + .subject("attacker") + .expirationTime(Date.from(Instant.now().minusSeconds(3600))) + .claim( + "realm_access", + Collections.singletonMap( + "roles", + Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME)))); Assert.assertFalse(authorizer.login(expiredToken, "", false)); Assert.assertFalse(authorizer.isAdmin(expiredToken)); @@ -120,17 +127,18 @@ public void testWrongIssuerRejected() throws Exception { try { OpenIdAuthorizer authorizer = new OpenIdAuthorizer(issuer); String token = - Jwts.builder() - .subject("attacker") - .issuer("https://evil.example/issuer") - .claim("aud", "iotdb") - .expiration(Date.from(Instant.now().plusSeconds(3600))) - .claim( - "realm_access", - Collections.singletonMap( - "roles", Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME))) - .signWith(keyPair.getPrivate(), Jwts.SIG.RS256) - .compact(); + createSignedToken( + keyPair.getPrivate(), + new JWTClaimsSet.Builder() + .subject("attacker") + .issuer("https://evil.example/issuer") + .audience("iotdb") + .expirationTime(Date.from(Instant.now().plusSeconds(3600))) + .claim( + "realm_access", + Collections.singletonMap( + "roles", + Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME)))); Assert.assertFalse(authorizer.login(token, "", false)); Assert.assertFalse(authorizer.isAdmin(token)); @@ -149,17 +157,18 @@ public void testWrongAudienceRejected() throws Exception { try { OpenIdAuthorizer authorizer = new OpenIdAuthorizer(issuer); String token = - Jwts.builder() - .subject("attacker") - .issuer(issuer) - .claim("aud", "unrelated-client") - .expiration(Date.from(Instant.now().plusSeconds(3600))) - .claim( - "realm_access", - Collections.singletonMap( - "roles", Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME))) - .signWith(keyPair.getPrivate(), Jwts.SIG.RS256) - .compact(); + createSignedToken( + keyPair.getPrivate(), + new JWTClaimsSet.Builder() + .subject("attacker") + .issuer(issuer) + .audience("unrelated-client") + .expirationTime(Date.from(Instant.now().plusSeconds(3600))) + .claim( + "realm_access", + Collections.singletonMap( + "roles", + Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME)))); Assert.assertFalse(authorizer.login(token, "", false)); Assert.assertFalse(authorizer.isAdmin(token)); @@ -174,6 +183,13 @@ private KeyPair generateKeyPair() throws Exception { return keyPairGenerator.generateKeyPair(); } + private String createSignedToken(PrivateKey privateKey, JWTClaimsSet.Builder claimsBuilder) + throws JOSEException { + SignedJWT signedJwt = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsBuilder.build()); + signedJwt.sign(new RSASSASigner(privateKey)); + return signedJwt.serialize(); + } + private HttpServer startProviderServer(KeyPair keyPair) throws Exception { JSONObject publicJwk = new JSONObject( From 7231e1c37bcc5de2d11ba690c7d89195463ebc8d Mon Sep 17 00:00:00 2001 From: HTHou Date: Tue, 17 Mar 2026 10:44:51 +0800 Subject: [PATCH 4/9] Restore JWT test runtime dependencies --- iotdb-core/node-commons/pom.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/iotdb-core/node-commons/pom.xml b/iotdb-core/node-commons/pom.xml index 24f33d0de8bec..7c54c64b32873 100644 --- a/iotdb-core/node-commons/pom.xml +++ b/iotdb-core/node-commons/pom.xml @@ -149,6 +149,16 @@ io.jsonwebtoken jjwt-api + + io.jsonwebtoken + jjwt-impl + test + + + io.jsonwebtoken + jjwt-jackson + test + com.nimbusds oauth2-oidc-sdk @@ -267,6 +277,16 @@ + + org.apache.maven.plugins + maven-dependency-plugin + + + io.jsonwebtoken:jjwt-impl + io.jsonwebtoken:jjwt-jackson + + + org.apache.maven.plugins maven-surefire-plugin From e3f43ad0af4fb811c2ef466dc9987b059a773837 Mon Sep 17 00:00:00 2001 From: HTHou Date: Tue, 17 Mar 2026 11:35:58 +0800 Subject: [PATCH 5/9] fix ut --- .../auth/authorizer/OpenIdAuthorizerTest.java | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java index 196cc80e5b6da..bfefed7b9778d 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java @@ -24,8 +24,17 @@ import org.apache.iotdb.commons.conf.CommonDescriptor; import org.apache.iotdb.db.utils.EnvironmentUtils; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; +import net.minidev.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -33,6 +42,13 @@ import java.io.IOException; import java.net.URISyntaxException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.time.Instant; +import java.util.Collections; +import java.util.Date; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -42,11 +58,24 @@ public class OpenIdAuthorizerTest { private static final String OPEN_ID_PUBLIC_JWK = "{\"kty\":\"RSA\",\"x5t#S256\":\"TZFbbj6HsRU28HYvrcVnDs03KreV3DE24-Cxb9EPdS4\",\"e\":\"AQAB\",\"use\":\"sig\",\"x5t\":\"l_N2UlC_a624iu5eYFypnB1Wr20\",\"kid\":\"q1-Wm0ozQ5O0mQH8-SJap2ZcN4MmucWwnQWKYxZJ4ow\",\"x5c\":[\"MIICmTCCAYECBgFyRdXW2DANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQDDAVJb1REQjAeFw0yMDA1MjQwODM3MjJaFw0zMDA1MjQwODM5MDJaMBAxDjAMBgNVBAMMBUlvVERCMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAozDCZTVc9946VvhZ6E\\/OP8Yx6tJe0i9GR2Q9jR9S3jQoo0haT\\/P1b\\/zvQK52qA1xj6tBVg64xl3+LUxtCvh3HfAM5Q3PeSa0e2MkZaKCt335lKnKCSuaQGYoHULmg\\/FDOgCA0wJYOonGGJkgWmkzSAzdnHmBATosTl0XkBXHTdFOq5HaKw+bfghYp5097Gkl\\/Dp4sixVjIWLTh5l9diy4D\\/XKxadGumPCmTOS5E7y92jiHE64XFe1Q7v1qD+qKJKFvamAMIFPGBKegIajt42IcOIcIaJZnM1lBZApq1a\\/E6oL24QnP\\/j2e9coseDtGNywaADQdO8PaJadH\\/BV4aPCwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBX4rsWPIAwgSK6BEZmtEkh\\/FMfZtkvCFANpwkCX5Pph8yuk\\/8xrvx30yb4fIgqsxxQk6H+Q1qptm1cXs0tNu1yft+t+B2VuVjrWtkCkV0hAy6eZcdW411Pt523pHoOTxg6ehQd5DsvCIlsvWo83ePTKME+092vfs3irfQcRzc5xINdpopSvZlZuQ83tNEJY8gWvspQZr+uj8AP2x6w0BOrPJIiLlV+peNJuD3UgJKlSfOueKbKeM1kIVOG\\/a2AoEkBgqktnaIWzkXbk475\\/0xfGegsSZrxGR3\\/SA3jegS0sHFCY7\\/Ie\\/UvDgqMjd207oT64jxEGrd4mObxOx7aS0tp\"],\"alg\":\"RS256\",\"n\":\"ozDCZTVc9946VvhZ6E_OP8Yx6tJe0i9GR2Q9jR9S3jQoo0haT_P1b_zvQK52qA1xj6tBVg64xl3-LUxtCvh3HfAM5Q3PeSa0e2MkZaKCt335lKnKCSuaQGYoHULmg_FDOgCA0wJYOonGGJkgWmkzSAzdnHmBATosTl0XkBXHTdFOq5HaKw-bfghYp5097Gkl_Dp4sixVjIWLTh5l9diy4D_XKxadGumPCmTOS5E7y92jiHE64XFe1Q7v1qD-qKJKFvamAMIFPGBKegIajt42IcOIcIaJZnM1lBZApq1a_E6oL24QnP_j2e9coseDtGNywaADQdO8PaJadH_BV4aPCw\"}"; private static CommonConfig config; + private PrivateKey privateKey; + private JSONObject publicJwk; @Before public void setUp() throws Exception { EnvironmentUtils.envSetUp(); config = CommonDescriptor.getInstance().getConfig(); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + privateKey = keyPair.getPrivate(); + publicJwk = + new JSONObject( + new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) + .keyUse(KeyUse.SIGNATURE) + .keyID("datanode-openid-test-key") + .build() + .toJSONObject()); } @After @@ -55,35 +84,30 @@ public void tearDown() throws Exception { } @Test - public void loginWithJWT() throws AuthException, ParseException { - String jwt = - "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxMS1XbTBvelE1TzBtUUg4LVNKYXAyWmNONE1tdWNXd25RV0tZeFpKNG93In0.eyJleHAiOjE1OTAzMTcxNzYsImlhdCI6MTU5MDMxNjg3NiwianRpIjoiY2MyNWQ3MDAtYjc5NC00OTA4LTg0OGUtOTRhNzYzNmM5YzQxIiwiaXNzIjoiaHR0cDovL2F1dGguZGVtby5wcmFnbWF0aWNpbmR1c3RyaWVzLmRlL2F1dGgvcmVhbG1zL0lvVERCIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6Ijg2YWRmNGIzLWE4ZTUtNDc1NC1iNWEwLTQ4OGI0OWY0M2VkMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImlvdGRiIiwic2Vzc2lvbl9zdGF0ZSI6Ijk0ZmI5NGZjLTg3YTMtNDg4Ny04M2Q3LWE5MmQ1MzMzOTMzMCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImNsaWVudEhvc3QiOiIxOTIuMTY4LjE2OS4yMSIsImNsaWVudElkIjoiaW90ZGIiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1pb3RkYiIsImNsaWVudEFkZHJlc3MiOiIxOTIuMTY4LjE2OS4yMSJ9.GxQFltm1PrZzVL7rR6K-GpQINFLymjqAxxoDt_DGfQEMt61M6ebmx2oHiP_3G0HDSl7sbamajQbbRrfyTg--emBC2wfhdZ7v_7O0qWC60Yd8cWZ9qxwqwTFKYb8a0Z6_TeH9-vUmsy6kp2BfJZXq3mSy0My21VGUAXRmWTbghiM4RFoHKjAZVhsPHWelFmtLftYPdOGxv-7c9iUOVh_W-nOcCNRJpYY7BEjUYN24TsjvCEwWDQWD9E29LMYfA6LNeG0KdL9Jvqad4bc2FTJn9TaCnJMCiAJ7wEEiotqhXn70uEBWYxGXIVlm3vn3MDe3pTKA2TZy7U5xcrE7S8aGMg"; + public void loginWithJWT() throws Exception { + String jwt = createJwt(false); - OpenIdAuthorizer authorizer = new OpenIdAuthorizer(JSONObjectUtils.parse(OPEN_ID_PUBLIC_JWK)); + OpenIdAuthorizer authorizer = new OpenIdAuthorizer(publicJwk); boolean login = authorizer.login(jwt, null, false); assertTrue(login); } @Test - public void isAdmin_hasAccess() throws AuthException, ParseException { - // IOTDB_ADMIN = true - String jwt = - "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxMS1XbTBvelE1TzBtUUg4LVNKYXAyWmNONE1tdWNXd25RV0tZeFpKNG93In0.eyJleHAiOjE1OTAzMjM5MjgsImlhdCI6MTU5MDMyMzYyOCwianRpIjoiZGQ5ZDZhNmItZjgzOC00Mjk3LTg5YWUtMjdlZTgxNzVhMThiIiwiaXNzIjoiaHR0cDovL2F1dGguZGVtby5wcmFnbWF0aWNpbmR1c3RyaWVzLmRlL2F1dGgvcmVhbG1zL0lvVERCIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJhMzJlNDcxLWM3NzItNGIzMy04ZGE2LTZmZThhY2RhMDA3MyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImlvdGRiIiwic2Vzc2lvbl9zdGF0ZSI6IjViZDRhNmM5LTBmYzItNGIxMy05Y2QxLTFhN2NjMzk3NjVhNyIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImlvdGRiX2FkbWluIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyIn0.LthDI93A3jqATc_8Fm0ismqQSN62TUknD6ara6w43eao2hY6KBoMXVY1c6SXSy8hhQeHLiWpopjJE6fsG9xbaV2Gs24SJYnP4DkHvhULlBJ_PUjFy18QxzeexwYK358a99eVHG_8yu-f2kN3mJslOSrlny8oZDxeSxUi9wYNIuQFeLPmGfSISVFn_5V8lpoUAHeENmf9h8mSyEcUHGqtZfVm5zEYIbPPSBqvNei2NvKAFle6qoaJ1l13dpbw39KkOtIUF8dJ7v8XY_xgO2GXCJCvZ5YGr-q4UnA9v_GM3h3vSa5dyCuG0HXBmAujxSxywzPl5RB_QCTiYcTm7MGKLg"; + public void isAdmin_hasAccess() throws Exception { + String jwt = createJwt(true); - OpenIdAuthorizer authorizer = new OpenIdAuthorizer(JSONObjectUtils.parse(OPEN_ID_PUBLIC_JWK)); + OpenIdAuthorizer authorizer = new OpenIdAuthorizer(publicJwk); boolean admin = authorizer.isAdmin(jwt); assertTrue(admin); } @Test - public void isAdmin_noAdminClaim() throws AuthException, ParseException { - // IOTDB_ADMIN = false - String jwt = - "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxMS1XbTBvelE1TzBtUUg4LVNKYXAyWmNONE1tdWNXd25RV0tZeFpKNG93In0.eyJleHAiOjE1OTAzMTcxNzYsImlhdCI6MTU5MDMxNjg3NiwianRpIjoiY2MyNWQ3MDAtYjc5NC00OTA4LTg0OGUtOTRhNzYzNmM5YzQxIiwiaXNzIjoiaHR0cDovL2F1dGguZGVtby5wcmFnbWF0aWNpbmR1c3RyaWVzLmRlL2F1dGgvcmVhbG1zL0lvVERCIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6Ijg2YWRmNGIzLWE4ZTUtNDc1NC1iNWEwLTQ4OGI0OWY0M2VkMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImlvdGRiIiwic2Vzc2lvbl9zdGF0ZSI6Ijk0ZmI5NGZjLTg3YTMtNDg4Ny04M2Q3LWE5MmQ1MzMzOTMzMCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImNsaWVudEhvc3QiOiIxOTIuMTY4LjE2OS4yMSIsImNsaWVudElkIjoiaW90ZGIiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1pb3RkYiIsImNsaWVudEFkZHJlc3MiOiIxOTIuMTY4LjE2OS4yMSJ9.GxQFltm1PrZzVL7rR6K-GpQINFLymjqAxxoDt_DGfQEMt61M6ebmx2oHiP_3G0HDSl7sbamajQbbRrfyTg--emBC2wfhdZ7v_7O0qWC60Yd8cWZ9qxwqwTFKYb8a0Z6_TeH9-vUmsy6kp2BfJZXq3mSy0My21VGUAXRmWTbghiM4RFoHKjAZVhsPHWelFmtLftYPdOGxv-7c9iUOVh_W-nOcCNRJpYY7BEjUYN24TsjvCEwWDQWD9E29LMYfA6LNeG0KdL9Jvqad4bc2FTJn9TaCnJMCiAJ7wEEiotqhXn70uEBWYxGXIVlm3vn3MDe3pTKA2TZy7U5xcrE7S8aGMg"; + public void isAdmin_noAdminClaim() throws Exception { + String jwt = createJwt(false); - OpenIdAuthorizer authorizer = new OpenIdAuthorizer(JSONObjectUtils.parse(OPEN_ID_PUBLIC_JWK)); + OpenIdAuthorizer authorizer = new OpenIdAuthorizer(publicJwk); boolean admin = authorizer.isAdmin(jwt); assertFalse(admin); @@ -111,4 +135,21 @@ public void fetchMetadata() false); assertTrue(login); } + + private String createJwt(boolean hasAdminRole) throws JOSEException { + JWTClaimsSet.Builder claimsBuilder = + new JWTClaimsSet.Builder() + .subject("datanode-test-user") + .expirationTime(Date.from(Instant.now().plusSeconds(3600))) + .claim( + "realm_access", + Collections.singletonMap( + "roles", + hasAdminRole + ? Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME) + : Collections.singletonList("offline_access"))); + SignedJWT signedJwt = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsBuilder.build()); + signedJwt.sign(new RSASSASigner(privateKey)); + return signedJwt.serialize(); + } } From c1bc077e025b6298ca05812a98c65c5fdda75f39 Mon Sep 17 00:00:00 2001 From: HTHou Date: Tue, 17 Mar 2026 11:42:09 +0800 Subject: [PATCH 6/9] spotless --- .../apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java index bfefed7b9778d..61d61c75f1844 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java @@ -33,7 +33,6 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import com.nimbusds.oauth2.sdk.ParseException; -import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; import net.minidev.json.JSONObject; import org.junit.After; import org.junit.Before; From 531a7c9e1c1be0aecca8f98a6e9f029e5cf31201 Mon Sep 17 00:00:00 2001 From: HTHou Date: Tue, 17 Mar 2026 11:55:14 +0800 Subject: [PATCH 7/9] Add test-scoped Nimbus JWT dependency for datanode tests --- iotdb-core/datanode/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml index 8b5302048964f..0484076bf620b 100644 --- a/iotdb-core/datanode/pom.xml +++ b/iotdb-core/datanode/pom.xml @@ -304,6 +304,11 @@ oauth2-oidc-sdk test + + com.nimbusds + nimbus-jose-jwt + test + org.powermock powermock-core From 9e09e7b8bc9ecb074a0119be8a9bbf948f7265a9 Mon Sep 17 00:00:00 2001 From: HTHou Date: Tue, 17 Mar 2026 15:10:37 +0800 Subject: [PATCH 8/9] Move authorizer tests to node-commons --- iotdb-core/datanode/pom.xml | 15 -- .../auth/authorizer/OpenIdAuthorizerTest.java | 154 ------------------ .../auth/authorizer/OpenIdAuthorizer.java | 13 +- .../authorizer/LocalFileAuthorizerTest.java | 10 +- .../auth/authorizer/OpenIdAuthorizerTest.java | 98 ++++++----- 5 files changed, 63 insertions(+), 227 deletions(-) delete mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java rename iotdb-core/{datanode/src/test/java/org/apache/iotdb/db => node-commons/src/test/java/org/apache/iotdb/commons}/auth/authorizer/LocalFileAuthorizerTest.java (96%) diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml index 0484076bf620b..141b3f7e8ad69 100644 --- a/iotdb-core/datanode/pom.xml +++ b/iotdb-core/datanode/pom.xml @@ -289,26 +289,11 @@ jjwt-jackson test - - net.minidev - json-smart - test - org.apache.ratis ratis-thirdparty-misc runtime - - com.nimbusds - oauth2-oidc-sdk - test - - - com.nimbusds - nimbus-jose-jwt - test - org.powermock powermock-core diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java deleted file mode 100644 index 61d61c75f1844..0000000000000 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java +++ /dev/null @@ -1,154 +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.iotdb.db.auth.authorizer; - -import org.apache.iotdb.commons.auth.AuthException; -import org.apache.iotdb.commons.auth.authorizer.OpenIdAuthorizer; -import org.apache.iotdb.commons.conf.CommonConfig; -import org.apache.iotdb.commons.conf.CommonDescriptor; -import org.apache.iotdb.db.utils.EnvironmentUtils; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.jwk.KeyUse; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; -import com.nimbusds.oauth2.sdk.ParseException; -import net.minidev.json.JSONObject; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.time.Instant; -import java.util.Collections; -import java.util.Date; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class OpenIdAuthorizerTest { - - private static final String OPEN_ID_PUBLIC_JWK = - "{\"kty\":\"RSA\",\"x5t#S256\":\"TZFbbj6HsRU28HYvrcVnDs03KreV3DE24-Cxb9EPdS4\",\"e\":\"AQAB\",\"use\":\"sig\",\"x5t\":\"l_N2UlC_a624iu5eYFypnB1Wr20\",\"kid\":\"q1-Wm0ozQ5O0mQH8-SJap2ZcN4MmucWwnQWKYxZJ4ow\",\"x5c\":[\"MIICmTCCAYECBgFyRdXW2DANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQDDAVJb1REQjAeFw0yMDA1MjQwODM3MjJaFw0zMDA1MjQwODM5MDJaMBAxDjAMBgNVBAMMBUlvVERCMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAozDCZTVc9946VvhZ6E\\/OP8Yx6tJe0i9GR2Q9jR9S3jQoo0haT\\/P1b\\/zvQK52qA1xj6tBVg64xl3+LUxtCvh3HfAM5Q3PeSa0e2MkZaKCt335lKnKCSuaQGYoHULmg\\/FDOgCA0wJYOonGGJkgWmkzSAzdnHmBATosTl0XkBXHTdFOq5HaKw+bfghYp5097Gkl\\/Dp4sixVjIWLTh5l9diy4D\\/XKxadGumPCmTOS5E7y92jiHE64XFe1Q7v1qD+qKJKFvamAMIFPGBKegIajt42IcOIcIaJZnM1lBZApq1a\\/E6oL24QnP\\/j2e9coseDtGNywaADQdO8PaJadH\\/BV4aPCwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBX4rsWPIAwgSK6BEZmtEkh\\/FMfZtkvCFANpwkCX5Pph8yuk\\/8xrvx30yb4fIgqsxxQk6H+Q1qptm1cXs0tNu1yft+t+B2VuVjrWtkCkV0hAy6eZcdW411Pt523pHoOTxg6ehQd5DsvCIlsvWo83ePTKME+092vfs3irfQcRzc5xINdpopSvZlZuQ83tNEJY8gWvspQZr+uj8AP2x6w0BOrPJIiLlV+peNJuD3UgJKlSfOueKbKeM1kIVOG\\/a2AoEkBgqktnaIWzkXbk475\\/0xfGegsSZrxGR3\\/SA3jegS0sHFCY7\\/Ie\\/UvDgqMjd207oT64jxEGrd4mObxOx7aS0tp\"],\"alg\":\"RS256\",\"n\":\"ozDCZTVc9946VvhZ6E_OP8Yx6tJe0i9GR2Q9jR9S3jQoo0haT_P1b_zvQK52qA1xj6tBVg64xl3-LUxtCvh3HfAM5Q3PeSa0e2MkZaKCt335lKnKCSuaQGYoHULmg_FDOgCA0wJYOonGGJkgWmkzSAzdnHmBATosTl0XkBXHTdFOq5HaKw-bfghYp5097Gkl_Dp4sixVjIWLTh5l9diy4D_XKxadGumPCmTOS5E7y92jiHE64XFe1Q7v1qD-qKJKFvamAMIFPGBKegIajt42IcOIcIaJZnM1lBZApq1a_E6oL24QnP_j2e9coseDtGNywaADQdO8PaJadH_BV4aPCw\"}"; - private static CommonConfig config; - private PrivateKey privateKey; - private JSONObject publicJwk; - - @Before - public void setUp() throws Exception { - EnvironmentUtils.envSetUp(); - config = CommonDescriptor.getInstance().getConfig(); - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); - KeyPair keyPair = keyPairGenerator.generateKeyPair(); - privateKey = keyPair.getPrivate(); - publicJwk = - new JSONObject( - new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) - .keyUse(KeyUse.SIGNATURE) - .keyID("datanode-openid-test-key") - .build() - .toJSONObject()); - } - - @After - public void tearDown() throws Exception { - EnvironmentUtils.cleanEnv(); - } - - @Test - public void loginWithJWT() throws Exception { - String jwt = createJwt(false); - - OpenIdAuthorizer authorizer = new OpenIdAuthorizer(publicJwk); - boolean login = authorizer.login(jwt, null, false); - - assertTrue(login); - } - - @Test - public void isAdmin_hasAccess() throws Exception { - String jwt = createJwt(true); - - OpenIdAuthorizer authorizer = new OpenIdAuthorizer(publicJwk); - boolean admin = authorizer.isAdmin(jwt); - - assertTrue(admin); - } - - @Test - public void isAdmin_noAdminClaim() throws Exception { - String jwt = createJwt(false); - - OpenIdAuthorizer authorizer = new OpenIdAuthorizer(publicJwk); - boolean admin = authorizer.isAdmin(jwt); - - assertFalse(admin); - } - - /** Can be run manually as long as the site below is active... */ - @Test - @Ignore("We have to find a way to test this against a defined OIDC Provider") - public void fetchMetadata() - throws ParseException, IOException, URISyntaxException, AuthException { - OpenIdAuthorizer openIdAuthorizer = - new OpenIdAuthorizer("https://auth.demo.pragmaticindustries.de/auth/realms/IoTDB/"); - boolean login = - openIdAuthorizer.login( - "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxMS1XbTBvelE1TzBtUUg4LVNKYXAyWmNONE1tdWNXd25RV0tZeFpKNG93In0.eyJleHAiOjE1OTAzMTcxNzYsImlhdCI6MTU5MDMxNjg3NiwianRpIjoiY2MyNWQ3MDAtYjc5NC00OTA4LTg0OGUtOTRhNzYzNmM5YzQxIiwiaXNzIjoiaHR0cDovL2F1dGguZGVtby5wcmFnbWF0aWNpbmR1c3RyaWVzLmRlL2F1dGgvcmVhbG1zL0lvVERCIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6Ijg2YWRmNGIzLWE4ZTUtNDc1NC1iNWEwLTQ4OGI0OWY0M2VkMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImlvdGRiIiwic2Vzc2lvbl9zdGF0ZSI6Ijk0ZmI5NGZjLTg3YTMtNDg4Ny04M2Q3LWE5MmQ1MzMzOTMzMCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImNsaWVudEhvc3QiOiIxOTIuMTY4LjE2OS4yMSIsImNsaWVudElkIjoiaW90ZGIiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1pb3RkYiIsImNsaWVudEFkZHJlc3MiOiIxOTIuMTY4LjE2OS4yMSJ9.GxQFltm1PrZzVL7rR6K-GpQINFLymjqAxxoDt_DGfQEMt61M6ebmx2oHiP_3G0HDSl7sbamajQbbRrfyTg--emBC2wfhdZ7v_7O0qWC60Yd8cWZ9qxwqwTFKYb8a0Z6_TeH9-vUmsy6kp2BfJZXq3mSy0My21VGUAXRmWTbghiM4RFoHKjAZVhsPHWelFmtLftYPdOGxv-7c9iUOVh_W-nOcCNRJpYY7BEjUYN24TsjvCEwWDQWD9E29LMYfA6LNeG0KdL9Jvqad4bc2FTJn9TaCnJMCiAJ7wEEiotqhXn70uEBWYxGXIVlm3vn3MDe3pTKA2TZy7U5xcrE7S8aGMg", - "", - false); - assertTrue(login); - config.setOpenIdProviderUrl("https://auth.demo.pragmaticindustries.de/auth/realms/IoTDB/"); - OpenIdAuthorizer openIdAuthorizer1 = new OpenIdAuthorizer(); - login = - openIdAuthorizer1.login( - "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxMS1XbTBvelE1TzBtUUg4LVNKYXAyWmNONE1tdWNXd25RV0tZeFpKNG93In0.eyJleHAiOjE1OTAzMTcxNzYsImlhdCI6MTU5MDMxNjg3NiwianRpIjoiY2MyNWQ3MDAtYjc5NC00OTA4LTg0OGUtOTRhNzYzNmM5YzQxIiwiaXNzIjoiaHR0cDovL2F1dGguZGVtby5wcmFnbWF0aWNpbmR1c3RyaWVzLmRlL2F1dGgvcmVhbG1zL0lvVERCIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6Ijg2YWRmNGIzLWE4ZTUtNDc1NC1iNWEwLTQ4OGI0OWY0M2VkMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImlvdGRiIiwic2Vzc2lvbl9zdGF0ZSI6Ijk0ZmI5NGZjLTg3YTMtNDg4Ny04M2Q3LWE5MmQ1MzMzOTMzMCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImNsaWVudEhvc3QiOiIxOTIuMTY4LjE2OS4yMSIsImNsaWVudElkIjoiaW90ZGIiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1pb3RkYiIsImNsaWVudEFkZHJlc3MiOiIxOTIuMTY4LjE2OS4yMSJ9.GxQFltm1PrZzVL7rR6K-GpQINFLymjqAxxoDt_DGfQEMt61M6ebmx2oHiP_3G0HDSl7sbamajQbbRrfyTg--emBC2wfhdZ7v_7O0qWC60Yd8cWZ9qxwqwTFKYb8a0Z6_TeH9-vUmsy6kp2BfJZXq3mSy0My21VGUAXRmWTbghiM4RFoHKjAZVhsPHWelFmtLftYPdOGxv-7c9iUOVh_W-nOcCNRJpYY7BEjUYN24TsjvCEwWDQWD9E29LMYfA6LNeG0KdL9Jvqad4bc2FTJn9TaCnJMCiAJ7wEEiotqhXn70uEBWYxGXIVlm3vn3MDe3pTKA2TZy7U5xcrE7S8aGMg", - "", - false); - assertTrue(login); - } - - private String createJwt(boolean hasAdminRole) throws JOSEException { - JWTClaimsSet.Builder claimsBuilder = - new JWTClaimsSet.Builder() - .subject("datanode-test-user") - .expirationTime(Date.from(Instant.now().plusSeconds(3600))) - .claim( - "realm_access", - Collections.singletonMap( - "roles", - hasAdminRole - ? Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME) - : Collections.singletonList("offline_access"))); - SignedJWT signedJwt = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsBuilder.build()); - signedJwt.sign(new RSASSASigner(privateKey)); - return signedJwt.serialize(); - } -} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java index 2ff888fa4aeaa..f073984060999 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizer.java @@ -94,7 +94,6 @@ public OpenIdAuthorizer(JSONObject jwk, String expectedIssuer, Set accep } this.expectedIssuer = expectedIssuer; this.acceptedAudiences = Collections.unmodifiableSet(new HashSet<>(acceptedAudiences)); - logger.info("Initialized with providerKey: {}", providerKey); } public OpenIdAuthorizer(String providerUrl) @@ -115,8 +114,6 @@ private static ProviderContext loadProviderContext(String providerUrl) // Fetch Metadata OIDCProviderMetadata providerMetadata = fetchMetadata(providerUrl); - logger.debug("Using Provider Metadata: {}", providerMetadata); - Set acceptedAudiences = parseAudiences(config.getOpenIdAudience()); if (acceptedAudiences.isEmpty()) { throw new AuthException( @@ -133,7 +130,6 @@ private static ProviderContext loadProviderContext(String providerUrl) try { URL url = new URI(providerMetadata.getJWKSetURI().toString()).toURL(); - logger.debug("Using url {}", url); return new ProviderContext(getProviderRsaJwk(url.openStream()), issuer, acceptedAudiences); } catch (IOException e) { throw new AuthException(TSStatusCode.INIT_AUTH_ERROR, "Unable to start the Auth", e); @@ -194,14 +190,9 @@ public boolean login(String token, String password, final boolean useEncryptedPa try { claims = validateToken(token); } catch (JwtException e) { - logger.error("Unable to login the user with Username (token) {}", token, e); + logger.error("Unable to login the user with Username (token), {}", e.getMessage()); return false; } - logger.debug("JWT was validated successfully!"); - logger.debug("ID: {}", claims.getId()); - logger.debug("Subject: {}", claims.getSubject()); - logger.debug("Issuer: {}", claims.getIssuer()); - logger.debug("Expiration: {}", claims.getExpiration()); // Create User if not exists String iotdbUsername = getUsername(claims); if (!super.listAllUsers().contains(iotdbUsername)) { @@ -308,7 +299,7 @@ public boolean isAdmin(String token) { try { claims = validateToken(token); } catch (JwtException e) { - logger.warn("Unable to validate token {}!", token, e); + logger.warn("Unable to validate token! {}", e.getMessage()); return false; } } diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/LocalFileAuthorizerTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/LocalFileAuthorizerTest.java similarity index 96% rename from iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/LocalFileAuthorizerTest.java rename to iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/LocalFileAuthorizerTest.java index 194f33e8d67b3..90ee3afd86b85 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/authorizer/LocalFileAuthorizerTest.java +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/LocalFileAuthorizerTest.java @@ -16,16 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.iotdb.db.auth.authorizer; +package org.apache.iotdb.commons.auth.authorizer; import org.apache.iotdb.commons.auth.AuthException; -import org.apache.iotdb.commons.auth.authorizer.BasicAuthorizer; -import org.apache.iotdb.commons.auth.authorizer.IAuthorizer; import org.apache.iotdb.commons.auth.entity.PrivilegeType; import org.apache.iotdb.commons.auth.entity.PrivilegeUnion; import org.apache.iotdb.commons.conf.CommonDescriptor; import org.apache.iotdb.commons.path.PartialPath; -import org.apache.iotdb.db.utils.EnvironmentUtils; import org.junit.After; import org.junit.Assert; @@ -51,16 +48,13 @@ public class LocalFileAuthorizerTest { @Before public void setUp() throws Exception { - EnvironmentUtils.envSetUp(); authorizer = BasicAuthorizer.getInstance(); authorizer.reset(); nodeName = new PartialPath("root.laptop.d1"); } @After - public void tearDown() throws Exception { - EnvironmentUtils.cleanEnv(); - } + public void tearDown() throws Exception {} @Test public void testLogin() throws AuthException { diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java index 1951a6893c20d..cf7fb53ae12b2 100644 --- a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/auth/authorizer/OpenIdAuthorizerTest.java @@ -34,55 +34,66 @@ import com.sun.net.httpserver.HttpServer; import net.minidev.json.JSONObject; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; -import java.nio.file.Files; -import java.nio.file.Path; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.interfaces.RSAPublicKey; import java.time.Instant; import java.util.Collections; -import java.util.Comparator; import java.util.Date; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + public class OpenIdAuthorizerTest { private final CommonConfig config = CommonDescriptor.getInstance().getConfig(); - - private String originalUserFolder; - private String originalRoleFolder; - private String originalOpenIdAudience; - private Path baseDir; + private PrivateKey privateKey; + private JSONObject publicJwk; @Before - public void setUp() throws IOException { - originalUserFolder = config.getUserFolder(); - originalRoleFolder = config.getRoleFolder(); - originalOpenIdAudience = config.getOpenIdAudience(); - - baseDir = Files.createTempDirectory("openid-authorizer-test-"); - config.setUserFolder(Files.createDirectories(baseDir.resolve("users")).toString()); - config.setRoleFolder(Files.createDirectories(baseDir.resolve("roles")).toString()); + public void setUp() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + privateKey = keyPair.getPrivate(); + publicJwk = + new JSONObject( + new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) + .keyUse(KeyUse.SIGNATURE) + .keyID("datanode-openid-test-key") + .build() + .toJSONObject()); } @After - public void tearDown() throws IOException { - config.setUserFolder(originalUserFolder); - config.setRoleFolder(originalRoleFolder); - config.setOpenIdAudience(originalOpenIdAudience); - - if (baseDir != null) { - try (java.util.stream.Stream stream = Files.walk(baseDir)) { - stream.sorted(Comparator.reverseOrder()).forEach(this::deleteIfExists); - } - } + public void tearDown() throws IOException {} + + @Test + public void loginWithJWT() throws Exception { + String jwt = createJwt(false); + OpenIdAuthorizer authorizer = new OpenIdAuthorizer(publicJwk); + assertTrue(authorizer.login(jwt, null, false)); + } + + @Test + public void isAdmin_hasAccess() throws Exception { + String jwt = createJwt(true); + OpenIdAuthorizer authorizer = new OpenIdAuthorizer(publicJwk); + assertTrue(authorizer.isAdmin(jwt)); + } + + @Test + public void isAdmin_noAdminClaim() throws Exception { + String jwt = createJwt(false); + OpenIdAuthorizer authorizer = new OpenIdAuthorizer(publicJwk); + assertFalse(authorizer.isAdmin(jwt)); } @Test @@ -113,8 +124,8 @@ public void testExpiredTokenRejectedByLoginAndIsAdmin() throws Exception { "roles", Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME)))); - Assert.assertFalse(authorizer.login(expiredToken, "", false)); - Assert.assertFalse(authorizer.isAdmin(expiredToken)); + assertFalse(authorizer.login(expiredToken, "", false)); + assertFalse(authorizer.isAdmin(expiredToken)); } @Test @@ -140,8 +151,8 @@ public void testWrongIssuerRejected() throws Exception { "roles", Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME)))); - Assert.assertFalse(authorizer.login(token, "", false)); - Assert.assertFalse(authorizer.isAdmin(token)); + assertFalse(authorizer.login(token, "", false)); + assertFalse(authorizer.isAdmin(token)); } finally { server.stop(0); } @@ -170,8 +181,8 @@ public void testWrongAudienceRejected() throws Exception { "roles", Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME)))); - Assert.assertFalse(authorizer.login(token, "", false)); - Assert.assertFalse(authorizer.isAdmin(token)); + assertFalse(authorizer.login(token, "", false)); + assertFalse(authorizer.isAdmin(token)); } finally { server.stop(0); } @@ -231,11 +242,20 @@ private void writeJson(HttpExchange exchange, String json) throws IOException { } } - private void deleteIfExists(Path path) { - try { - Files.deleteIfExists(path); - } catch (IOException e) { - throw new RuntimeException("Failed to delete test path " + path, e); - } + private String createJwt(boolean hasAdminRole) throws JOSEException { + JWTClaimsSet.Builder claimsBuilder = + new JWTClaimsSet.Builder() + .subject("datanode-test-user") + .expirationTime(Date.from(Instant.now().plusSeconds(3600))) + .claim( + "realm_access", + Collections.singletonMap( + "roles", + hasAdminRole + ? Collections.singletonList(OpenIdAuthorizer.IOTDB_ADMIN_ROLE_NAME) + : Collections.singletonList("offline_access"))); + SignedJWT signedJwt = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsBuilder.build()); + signedJwt.sign(new RSASSASigner(privateKey)); + return signedJwt.serialize(); } } From 36db6281eb85b5cfad1e9b4905466fbfb21628dc Mon Sep 17 00:00:00 2001 From: HTHou Date: Tue, 17 Mar 2026 15:39:44 +0800 Subject: [PATCH 9/9] Remove unused JJWT test dependencies from datanode --- iotdb-core/datanode/pom.xml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml index 141b3f7e8ad69..e894851fc11ca 100644 --- a/iotdb-core/datanode/pom.xml +++ b/iotdb-core/datanode/pom.xml @@ -277,18 +277,6 @@ mockito-core test - - - io.jsonwebtoken - jjwt-impl - test - - - - io.jsonwebtoken - jjwt-jackson - test - org.apache.ratis ratis-thirdparty-misc @@ -478,11 +466,6 @@ org.apache.iotdb:isession - - - io.jsonwebtoken:jjwt-impl - io.jsonwebtoken:jjwt-jackson -