Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
249de69
[CVE] CVE-2025-53960 fixed
wolfboys Aug 1, 2025
beed2b8
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 1, 2025
c2c9fbc
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 1, 2025
09af5cd
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 1, 2025
268b511
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 1, 2025
4f9eaac
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 1, 2025
1cc4a1e
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 1, 2025
db983bc
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 1, 2025
a228ecd
[Improve] set file permission improvement
wolfboys Aug 1, 2025
bf8219b
[Improve] minor improvement
wolfboys Aug 2, 2025
1c8a2f2
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 2, 2025
fd0bb40
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 2, 2025
d447306
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 2, 2025
221ffb8
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 2, 2025
484fdb3
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 2, 2025
b87fa25
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 2, 2025
69ace27
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 2, 2025
7627146
Update streampark-console/streampark-console-service/src/main/java/or…
wolfboys Aug 2, 2025
419d3c0
[CI] dorny/paths-filter@v3.0.2 issue fixed
wolfboys Aug 2, 2025
e02fabd
[Improve] paths-filter improvement
wolfboys Aug 2, 2025
4e0949a
[Improve] paths-filter plugin improvement
wolfboys Aug 3, 2025
7e8fd81
[Improve] paths-filter plugin improvement
wolfboys Aug 3, 2025
74cd002
[Improve] import package improvement
wolfboys Aug 3, 2025
bfe59f1
[Improve] .github/workflows/frontend.yml improvement
wolfboys Aug 3, 2025
31dec56
[Improve] JWTUtil minor improvement
wolfboys Aug 3, 2025
5e0231e
[Improve] JWT verify token bug fixed
wolfboys Aug 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ start() {
fi

if [[ "${HADOOP_HOME}"x == ""x ]]; then
echo_y "WARN: HADOOP_HOME is undefined on your system env,please check it."
echo_y "WARN: HADOOP_HOME is undefined on your system env, please check it."
else
echo_w "Using HADOOP_HOME: ${HADOOP_HOME}"
fi
Expand Down Expand Up @@ -394,6 +394,8 @@ start() {

echo_g "JAVA_OPTS: ${JAVA_OPTS}"

jwt_secret

eval $NOHUP $JAVACMD $JAVA_OPTS \
-classpath "$APP_CLASSPATH" \
-Dapp.home="${APP_HOME}" \
Expand Down Expand Up @@ -426,7 +428,7 @@ start_docker() {
fi

if [[ "${HADOOP_HOME}"x == ""x ]]; then
echo_y "WARN: HADOOP_HOME is undefined on your system env,please check it."
echo_y "WARN: HADOOP_HOME is undefined on your system env, please check it."
else
echo_w "Using HADOOP_HOME: ${HADOOP_HOME}"
fi
Expand Down Expand Up @@ -517,6 +519,13 @@ stop() {
fi
}

jwt_secret() {
local secret=`$JAVACMD -cp "$APP_LIB/*" $BASH_UTIL --jwt_secret`
if [[ -n "$secret" ]]; then
echo "JWT_SECRET: $secret"
fi
}

status() {
# shellcheck disable=SC2155
# shellcheck disable=SC2006
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@
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.Arrays;
import java.util.Base64;
import java.util.Map;

public class BashJavaUtils {
Expand All @@ -46,6 +49,12 @@ public static void main(String[] args) throws IOException {
String[] actionArgs = Arrays.copyOfRange(args, 1, args.length);

switch (action) {
case "--jwt_secret":
String secret = getFirstOrCreateJWTSecret();
if (secret != null) {
System.out.println(secret);
}
break;
case "--get_yaml":
String key = actionArgs[0];
String conf = actionArgs[1];
Expand Down Expand Up @@ -129,4 +138,29 @@ public static void main(String[] args) throws IOException {
break;
}
}

private static String getFirstOrCreateJWTSecret() {
String userHome = System.getProperty("user.home");
File keyFile = new File(userHome, "streampark.jwt.key");
if (!keyFile.exists()) {
byte[] bytes = new byte[32];
new SecureRandom().nextBytes(bytes);
String secret = Base64.getEncoder().encodeToString(bytes);
try {
FileUtils.writeFile(secret, keyFile);
try {
Files.setPosixFilePermissions(
Paths.get(keyFile.getAbsolutePath()),
PosixFilePermissions.fromString("rw-------"));
} catch (UnsupportedOperationException e) {
System.err.println("Warning: setPosixFilePermissions for " + keyFile.getAbsolutePath());
}
} catch (Exception e) {
throw new SecurityException("Failed to generate JWT key", e);
}
return secret;
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package org.apache.streampark.console.system.authentication;

import org.apache.streampark.console.base.util.EncryptUtils;

import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;

Expand Down Expand Up @@ -58,7 +56,7 @@ protected boolean executeLogin(ServletRequest request, ServletResponse response)
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader(TOKEN);
try {
token = EncryptUtils.decrypt(token);
token = JWTUtil.decrypt(token);
JWTToken jwtToken = new JWTToken(token);
getSubject(request, response).login(jwtToken);
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,28 @@

package org.apache.streampark.console.system.authentication;

import org.apache.streampark.console.base.util.EncryptUtils;
import org.apache.streampark.common.util.FileUtils;
import org.apache.streampark.console.core.enums.AuthenticationType;
import org.apache.streampark.console.system.entity.User;

import org.apache.commons.lang3.StringUtils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import java.io.File;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Date;
import java.util.regex.Pattern;

Expand All @@ -36,72 +47,74 @@ public class JWTUtil {

private static Long ttlOfSecond;

private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_TAG_LENGTH = 128;
private static final int GCM_IV_LENGTH = 12;

private static final String JWT_USERID = "userId";
private static final String JWT_USERNAME = "userName";
private static final String JWT_TYPE = "type";
private static final String JWT_TIMESTAMP = "timestamp";

/**
* verify token
*
* @param token token
* @return is valid token
*/
public static boolean verify(String token, String username, String secret) {
private static byte[] JWT_KEY = loadSigningKey(); // Used for HMAC256

private static byte[] loadSigningKey() {
String userHome = System.getProperty("user.home");
File keyFile = new File(userHome, "streampark.jwt.key");
String secret = null;
if (keyFile.exists()) {
try {
secret = FileUtils.readFile(keyFile).trim();
} catch (Exception e) {
log.error("Failed to read JWT key file", e);
}
}

if (StringUtils.isEmpty(secret)) {
throw new ExceptionInInitializerError("JWT secret initialization failed.");
}
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).withClaim(JWT_USERNAME, username).build();
verifier.verify(token);
return true;
} catch (Exception ignored) {
return false;
byte[] key = Base64.getDecoder().decode(secret);
if (key.length != 32) {
throw new SecurityException("HMAC key must be 32 bytes");
}
return key;
} catch (Exception e) {
throw new SecurityException("Invalid JWT secret format", e);
}
}

/** get username from token */
public static String getUserName(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(JWT_USERNAME).asString();
} catch (Exception ignored) {
return null;
}
DecodedJWT jwt = decode(token);
return jwt != null ? jwt.getClaim(JWT_USERNAME).asString() : null;
}

public static Long getUserId(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(JWT_USERID).asLong();
} catch (Exception ignored) {
return null;
}
DecodedJWT jwt = decode(token);
return jwt != null ? jwt.getClaim(JWT_USERID).asLong() : null;
}

/**
* @param token
* @return
*/
public static Long getTimestamp(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(JWT_TIMESTAMP).asLong();
} catch (Exception ignored) {
return 0L;
}
DecodedJWT jwt = decode(token);
return jwt != null ? jwt.getClaim(JWT_TIMESTAMP).asLong() : 0L;
}

/**
* @param token
* @return
*/
public static AuthenticationType getAuthType(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
int type = jwt.getClaim(JWT_TYPE).asInt();
return AuthenticationType.of(type);
} catch (Exception ignored) {
DecodedJWT jwt = decode(token);
if (jwt == null) {
return null;
}
int type = jwt.getClaim(JWT_TYPE).asInt();
return AuthenticationType.of(type);
}

/**
Expand All @@ -125,7 +138,7 @@ public static String sign(User user, AuthenticationType authType) throws Excepti
*/
public static String sign(User user, AuthenticationType authType, Long expireTime) throws Exception {
Date date = new Date(expireTime);
Algorithm algorithm = Algorithm.HMAC256(user.getPassword());
Algorithm algorithm = Algorithm.HMAC256(JWT_KEY);

JWTCreator.Builder builder =
JWT.create()
Expand All @@ -139,7 +152,7 @@ public static String sign(User user, AuthenticationType authType, Long expireTim
}

String token = builder.sign(algorithm);
return EncryptUtils.encrypt(token);
return encrypt(token);
}

public static Long getTTLOfSecond() {
Expand Down Expand Up @@ -167,4 +180,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);

SecretKeySpec keySpec = new SecretKeySpec(JWT_KEY, "AES");

// Initialize the cipher
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new GCMParameterSpec(GCM_TAG_LENGTH, iv));

// Encrypt data
byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));

// Combine IV and ciphertext
ByteBuffer buffer = ByteBuffer.allocate(iv.length + encrypted.length);
buffer.put(iv);
buffer.put(encrypted);

return Base64.getEncoder().encodeToString(buffer.array());
}

public static String decrypt(String content) throws Exception {
byte[] data = Base64.getDecoder().decode(content);
ByteBuffer buffer = ByteBuffer.wrap(data);

byte[] iv = new byte[GCM_IV_LENGTH];
buffer.get(iv);
byte[] encrypted = new byte[buffer.remaining()];
buffer.get(encrypted);

SecretKeySpec keySpec = new SecretKeySpec(JWT_KEY, "AES");

Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, spec);

return new String(cipher.doFinal(encrypted), StandardCharsets.UTF_8);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package org.apache.streampark.console.system.authentication;

import org.apache.streampark.common.util.SystemPropertyUtils;
import org.apache.streampark.console.base.util.EncryptUtils;
import org.apache.streampark.console.core.enums.AuthenticationType;
import org.apache.streampark.console.system.entity.AccessToken;
import org.apache.streampark.console.system.entity.User;
Expand Down Expand Up @@ -90,6 +89,12 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authent
throw new AuthenticationException("the authorization token is invalid");
}

// Query user information by username
User user = userService.getByUsername(username);
if (user == null || !user.getUserId().equals(userId)) {
throw new AuthenticationException("the authorization token verification failed.");
}

switch (authType) {
case SIGN:
Long timestamp = JWTUtil.getTimestamp(credential);
Expand All @@ -102,7 +107,7 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authent
// Check whether the token belongs to the api and whether the permission is valid
AccessToken accessToken = accessTokenService.getByUserId(userId);
try {
String encryptToken = EncryptUtils.encrypt(credential);
String encryptToken = JWTUtil.encrypt(credential);
if (accessToken == null || !accessToken.getToken().equals(encryptToken)) {
throw new AuthenticationException("the openapi authorization token is invalid");
}
Expand All @@ -112,7 +117,7 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authent

if (AccessToken.STATUS_DISABLE.equals(accessToken.getStatus())) {
throw new AuthenticationException(
"the openapi authorization token is disabled, please contact the administrator");
"The OpenAPI authorization token is disabled. Please contact the administrator.");
}

if (User.STATUS_LOCK.equals(accessToken.getUserStatus())) {
Expand All @@ -125,12 +130,6 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authent
break;
}

// Query user information by username
User user = userService.getByUsername(username);
if (user == null || !JWTUtil.verify(credential, username, user.getPassword())) {
throw new AuthenticationException("the authorization token verification failed.");
}

return new SimpleAuthenticationInfo(credential, credential, "streampark_shiro_realm");
}
}
Loading