Copilot commented on code in PR #4273: URL: https://github.com/apache/streampark/pull/4273#discussion_r2249318761
########## streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/authentication/JWTSecret.java: ########## @@ -0,0 +1,123 @@ +/* + * 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.streampark.console.system.authentication; + +import org.apache.streampark.common.util.FileUtils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermissions; +import java.security.SecureRandom; +import java.util.Base64; + +@Slf4j +public class JWTSecret { + + private static final int KEY_LENGTH = 32; + + public static byte[] getJWTSecret() { + Path keyPath = Paths.get(System.getProperty("user.home"), "streampark.jwt.key"); + File keyFile = keyPath.toFile(); + + // 尝试加载现有密钥 + byte[] keyBytes = loadExistingKey(keyFile); + if (keyBytes != null) { + return keyBytes; + } + + // 生成新密钥 + keyBytes = generateNewKey(); + saveNewKey(keyBytes, keyPath); + return keyBytes; + } + + private static byte[] loadExistingKey(File keyFile) { + if (!keyFile.exists()) { + return null; + } + + try { + String secret = FileUtils.readFile(keyFile).trim(); + byte[] keyBytes = Base64.getDecoder().decode(secret); + + if (keyBytes.length != KEY_LENGTH) { + log.error("Invalid HMAC key length: {} bytes (expected {} bytes)", keyBytes.length, KEY_LENGTH); + return null; + } + return keyBytes; + } catch (Exception e) { + log.error("Failed to read JWT key file", e); + } + // 清理无效文件 Review Comment: The comment contains Chinese characters. Comments should be in English for consistency with the codebase. ```suggestion // Clean up invalid file ``` ########## streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/authentication/JWTSecret.java: ########## @@ -0,0 +1,123 @@ +/* + * 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.streampark.console.system.authentication; + +import org.apache.streampark.common.util.FileUtils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermissions; +import java.security.SecureRandom; +import java.util.Base64; + +@Slf4j +public class JWTSecret { + + private static final int KEY_LENGTH = 32; + + public static byte[] getJWTSecret() { + Path keyPath = Paths.get(System.getProperty("user.home"), "streampark.jwt.key"); + File keyFile = keyPath.toFile(); + + // 尝试加载现有密钥 Review Comment: The comment contains Chinese characters. Comments should be in English for consistency with the codebase. ```suggestion // Try to load existing key ``` ########## streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/authentication/JWTSecret.java: ########## @@ -0,0 +1,123 @@ +/* + * 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.streampark.console.system.authentication; + +import org.apache.streampark.common.util.FileUtils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermissions; +import java.security.SecureRandom; +import java.util.Base64; + +@Slf4j +public class JWTSecret { + + private static final int KEY_LENGTH = 32; + + public static byte[] getJWTSecret() { + Path keyPath = Paths.get(System.getProperty("user.home"), "streampark.jwt.key"); + File keyFile = keyPath.toFile(); + + // 尝试加载现有密钥 + byte[] keyBytes = loadExistingKey(keyFile); + if (keyBytes != null) { + return keyBytes; + } + + // 生成新密钥 + keyBytes = generateNewKey(); + saveNewKey(keyBytes, keyPath); + return keyBytes; + } + + private static byte[] loadExistingKey(File keyFile) { + if (!keyFile.exists()) { + return null; + } + + try { + String secret = FileUtils.readFile(keyFile).trim(); + byte[] keyBytes = Base64.getDecoder().decode(secret); + + if (keyBytes.length != KEY_LENGTH) { + log.error("Invalid HMAC key length: {} bytes (expected {} bytes)", keyBytes.length, KEY_LENGTH); + return null; + } + return keyBytes; + } catch (Exception e) { + log.error("Failed to read JWT key file", e); + } + // 清理无效文件 + safelyDeleteFile(keyFile); + return null; + } + + private static byte[] generateNewKey() { + byte[] key = new byte[KEY_LENGTH]; + new SecureRandom().nextBytes(key); + return key; + } + + private static void saveNewKey(byte[] keyBytes, Path keyPath) { + String encodedKey = Base64.getEncoder().encodeToString(keyBytes); + try { + // 确保目录存在 Review Comment: The comment contains Chinese characters. Comments should be in English for consistency with the codebase. ```suggestion // Ensure the directory exists ``` ########## streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/authentication/JWTSecret.java: ########## @@ -0,0 +1,123 @@ +/* + * 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.streampark.console.system.authentication; + +import org.apache.streampark.common.util.FileUtils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermissions; +import java.security.SecureRandom; +import java.util.Base64; + +@Slf4j +public class JWTSecret { + + private static final int KEY_LENGTH = 32; + + public static byte[] getJWTSecret() { + Path keyPath = Paths.get(System.getProperty("user.home"), "streampark.jwt.key"); + File keyFile = keyPath.toFile(); + + // 尝试加载现有密钥 + byte[] keyBytes = loadExistingKey(keyFile); + if (keyBytes != null) { + return keyBytes; + } + + // 生成新密钥 + keyBytes = generateNewKey(); + saveNewKey(keyBytes, keyPath); + return keyBytes; + } + + private static byte[] loadExistingKey(File keyFile) { + if (!keyFile.exists()) { + return null; + } + + try { + String secret = FileUtils.readFile(keyFile).trim(); + byte[] keyBytes = Base64.getDecoder().decode(secret); + + if (keyBytes.length != KEY_LENGTH) { + log.error("Invalid HMAC key length: {} bytes (expected {} bytes)", keyBytes.length, KEY_LENGTH); + return null; + } + return keyBytes; + } catch (Exception e) { + log.error("Failed to read JWT key file", e); + } + // 清理无效文件 + safelyDeleteFile(keyFile); + return null; + } + + private static byte[] generateNewKey() { + byte[] key = new byte[KEY_LENGTH]; + new SecureRandom().nextBytes(key); + return key; + } + + private static void saveNewKey(byte[] keyBytes, Path keyPath) { + String encodedKey = Base64.getEncoder().encodeToString(keyBytes); + try { + // 确保目录存在 + Files.createDirectories(keyPath.getParent()); + // 安全写入临时文件后重命名 Review Comment: The comment contains Chinese characters. Comments should be in English for consistency with the codebase. ```suggestion // Safely write to a temporary file before renaming ``` ########## streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/authentication/JWTUtil.java: ########## @@ -167,4 +149,69 @@ public static Long getTTLOfSecond() { } return ttlOfSecond; } + + private static DecodedJWT decode(String token) { + try { + Algorithm algorithm = Algorithm.HMAC256(JWT_KEY); + JWTVerifier verifier = JWT.require(algorithm).build(); + return verifier.verify(token); + } catch (Exception e) { + return null; + } + } + + public static boolean verify(String token) { + try { + // Decode the signing key using Base64 + Algorithm algorithm = Algorithm.HMAC256(JWT_KEY); + JWTVerifier verifier = JWT.require(algorithm).build(); + verifier.verify(decrypt(token)); + return true; + } catch (Exception e) { + log.warn("Invalid JWT: {}", e.getMessage()); + return false; + } + } + + // Fix encryption method Review Comment: The comment 'Fix encryption method' is unclear. It should explain what the encryption method does and why it's being 'fixed'. ```suggestion /** * Encrypts the given content using AES-GCM with a randomly generated IV. * The IV is prepended to the ciphertext and the result is Base64-encoded. * This allows the decrypt method to extract the IV and correctly decrypt the content. * * @param content the plaintext string to encrypt * @return the Base64-encoded string containing the IV and ciphertext * @throws Exception if encryption fails */ ``` ########## streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/authentication/JWTSecret.java: ########## @@ -0,0 +1,123 @@ +/* + * 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.streampark.console.system.authentication; + +import org.apache.streampark.common.util.FileUtils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermissions; +import java.security.SecureRandom; +import java.util.Base64; + +@Slf4j +public class JWTSecret { + + private static final int KEY_LENGTH = 32; + + public static byte[] getJWTSecret() { + Path keyPath = Paths.get(System.getProperty("user.home"), "streampark.jwt.key"); + File keyFile = keyPath.toFile(); + + // 尝试加载现有密钥 + byte[] keyBytes = loadExistingKey(keyFile); + if (keyBytes != null) { + return keyBytes; + } + + // 生成新密钥 Review Comment: The comment contains Chinese characters. Comments should be in English for consistency with the codebase. ```suggestion // Generate new key ``` ########## streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/authentication/JWTSecret.java: ########## @@ -0,0 +1,123 @@ +/* + * 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.streampark.console.system.authentication; + +import org.apache.streampark.common.util.FileUtils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermissions; +import java.security.SecureRandom; +import java.util.Base64; + +@Slf4j +public class JWTSecret { + + private static final int KEY_LENGTH = 32; + + public static byte[] getJWTSecret() { + Path keyPath = Paths.get(System.getProperty("user.home"), "streampark.jwt.key"); + File keyFile = keyPath.toFile(); + + // 尝试加载现有密钥 + byte[] keyBytes = loadExistingKey(keyFile); + if (keyBytes != null) { + return keyBytes; + } + + // 生成新密钥 + keyBytes = generateNewKey(); + saveNewKey(keyBytes, keyPath); + return keyBytes; + } + + private static byte[] loadExistingKey(File keyFile) { + if (!keyFile.exists()) { + return null; + } + + try { + String secret = FileUtils.readFile(keyFile).trim(); + byte[] keyBytes = Base64.getDecoder().decode(secret); + + if (keyBytes.length != KEY_LENGTH) { + log.error("Invalid HMAC key length: {} bytes (expected {} bytes)", keyBytes.length, KEY_LENGTH); + return null; + } + return keyBytes; + } catch (Exception e) { + log.error("Failed to read JWT key file", e); + } + // 清理无效文件 + safelyDeleteFile(keyFile); + return null; + } + + private static byte[] generateNewKey() { + byte[] key = new byte[KEY_LENGTH]; + new SecureRandom().nextBytes(key); + return key; + } + + private static void saveNewKey(byte[] keyBytes, Path keyPath) { + String encodedKey = Base64.getEncoder().encodeToString(keyBytes); + try { + // 确保目录存在 + Files.createDirectories(keyPath.getParent()); + // 安全写入临时文件后重命名 + Path tempFile = Files.createTempFile(keyPath.getParent(), "streampark", ".tmp"); + Files.write(tempFile, encodedKey.getBytes(StandardCharsets.UTF_8)); + + // 设置权限后原子移动 Review Comment: The comment contains Chinese characters. Comments should be in English for consistency with the codebase. ```suggestion // Atomically move after setting permissions ``` ########## streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/authentication/JWTUtil.java: ########## @@ -167,4 +149,69 @@ public static Long getTTLOfSecond() { } return ttlOfSecond; } + + private static DecodedJWT decode(String token) { + try { + Algorithm algorithm = Algorithm.HMAC256(JWT_KEY); + JWTVerifier verifier = JWT.require(algorithm).build(); + return verifier.verify(token); + } catch (Exception e) { + return null; + } + } + + public static boolean verify(String token) { + try { + // Decode the signing key using Base64 + Algorithm algorithm = Algorithm.HMAC256(JWT_KEY); + JWTVerifier verifier = JWT.require(algorithm).build(); + verifier.verify(decrypt(token)); + return true; + } catch (Exception e) { + log.warn("Invalid JWT: {}", e.getMessage()); + return false; + } + } + + // Fix encryption method + public static String encrypt(String content) throws Exception { + // Generate a random IV + byte[] iv = new byte[GCM_IV_LENGTH]; + SecureRandom.getInstanceStrong().nextBytes(iv); Review Comment: Using SecureRandom.getInstanceStrong() can block on systems with low entropy. Consider using SecureRandom.getInstance("SHA1PRNG") or caching a SecureRandom instance for better performance. ```suggestion SECURE_RANDOM.nextBytes(iv); ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: issues-unsubscr...@streampark.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org