Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -257,6 +257,6 @@ insert into `t_member` values (100000, 100000, 100000, 100001, now(), now());
-- ----------------------------
-- Records of t_user
-- ----------------------------
insert into `t_user` values (100000, 'admin', '', 'rh8b1ojwog777yrg0daesf04gk', '2513f3748847298ea324dffbf67fe68681dd92315bda830065facd8efe08f54f', null, 1, 0, 100000, '1', now(), now(),null,0,null,null);
insert into `t_user` values (100000, 'admin', '', '', '$sp$pbkdf2-sha256$600000$beyNAvVb7zMzZIBSG3/Y8Q==$ry679/ES4zIzmv7Cb3DPkhDlJfNTD4B17H9s2cf2afQ=', null, 1, 0, 100000, '1', now(), now(),null,0,null,null);

set foreign_key_checks = 1;
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,4 @@ insert into "public"."t_member" values (100000, 100000, 100000, 100001, now(), n
-- ----------------------------
-- Records of t_user
-- ----------------------------
insert into "public"."t_user" values (100000, 'admin', '', 'rh8b1ojwog777yrg0daesf04gk', '2513f3748847298ea324dffbf67fe68681dd92315bda830065facd8efe08f54f', null, 1, 0, 100000, '1', now(), now(), null, 0, null, null);
insert into "public"."t_user" values (100000, 'admin', '', '', '$sp$pbkdf2-sha256$600000$beyNAvVb7zMzZIBSG3/Y8Q==$ry679/ES4zIzmv7Cb3DPkhDlJfNTD4B17H9s2cf2afQ=', null, 1, 0, 100000, '1', now(), now(), null, 0, null, null);
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ create table `t_user` (
`username` varchar(64) collate utf8mb4_general_ci not null comment 'user name',
`nick_name` varchar(64) collate utf8mb4_general_ci not null comment 'nick name',
`salt` varchar(26) collate utf8mb4_general_ci default null comment 'salt',
`password` varchar(64) collate utf8mb4_general_ci not null comment 'password',
`password` varchar(255) collate utf8mb4_general_ci not null comment 'password',
`email` varchar(64) collate utf8mb4_general_ci default null comment 'email',
`user_type` int not null comment 'user type 1:admin 2:user',
`login_type` tinyint default 0 comment 'login type 0:password 1:ldap',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ create table "public"."t_user" (
"username" varchar(64) collate "pg_catalog"."default" not null,
"nick_name" varchar(64) collate "pg_catalog"."default" not null,
"salt" varchar(26) collate "pg_catalog"."default",
"password" varchar(64) collate "pg_catalog"."default" not null,
"password" varchar(255) collate "pg_catalog"."default" not null,
"email" varchar(64) collate "pg_catalog"."default",
"user_type" int4,
"login_type" int2 default 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,7 @@
* limitations under the License.
*/

package org.apache.streampark.console.base.util;
use streampark;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/** Test for {@link ShaHashUtils} */
class ShaHashUtilsTest {

@Test
void testEncrypt() {
String randomSalt = "rh8b1ojwog777yrg0daesf04gk";
String encryptPassword = ShaHashUtils.encrypt(randomSalt, "streampark");
Assertions.assertEquals(
"2513f3748847298ea324dffbf67fe68681dd92315bda830065facd8efe08f54f", encryptPassword);
}
}
ALTER TABLE `t_user`
MODIFY COLUMN `password` varchar(255) collate utf8mb4_general_ci not null comment 'password';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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.
*/

ALTER TABLE "public"."t_user"
ALTER COLUMN "password" TYPE varchar(255) collate "pg_catalog"."default";
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* 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.base.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.util.ByteSource;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.regex.Pattern;

public final class PasswordHashUtils {

public static final String PASSWORD_SALT_NOT_REQUIRED = "";

private static final String HASH_PREFIX = "$sp$pbkdf2-sha256$";
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
private static final int PBKDF2_ITERATIONS = 600000;
private static final int PBKDF2_SALT_BYTES = 16;
private static final int PBKDF2_HASH_BITS = 256;
private static final String RANDOM_CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private static final Pattern PBKDF2_PATTERN =
Pattern.compile(
"\\A\\$sp\\$pbkdf2-sha256\\$\\d+\\$[A-Za-z0-9+/]+={0,2}\\$[A-Za-z0-9+/]+={0,2}\\z");
private static final Pattern LEGACY_SHA256_PATTERN = Pattern.compile("\\A[0-9a-f]{64}\\z");
private static final SecureRandom RANDOM = new SecureRandom();

private PasswordHashUtils() {}

public static String encrypt(String password) {
byte[] salt = new byte[PBKDF2_SALT_BYTES];
RANDOM.nextBytes(salt);
byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS);
return HASH_PREFIX
+ PBKDF2_ITERATIONS
+ "$"
+ Base64.getEncoder().encodeToString(salt)
+ "$"
+ Base64.getEncoder().encodeToString(hash);
}

public static boolean matches(String rawPassword, String salt, String encodedPassword) {
if (rawPassword == null || StringUtils.isBlank(encodedPassword)) {
return false;
}

if (isPbkdf2Hash(encodedPassword)) {
return matchesPbkdf2(rawPassword, encodedPassword);
}

return isLegacySha256Hash(encodedPassword)
&& StringUtils.isNotBlank(salt)
&& StringUtils.equals(encrypt(salt, rawPassword), encodedPassword);
}

public static boolean needsRehash(String encodedPassword) {
return !isPbkdf2Hash(encodedPassword)
|| getPbkdf2Iterations(encodedPassword) < PBKDF2_ITERATIONS;
}

public static String getRandomPassword(int length) {
if (length <= 0) {
throw new IllegalArgumentException("password length must be greater than 0");
}
StringBuilder builder = new StringBuilder(length);
for (int i = 0; i < length; i++) {
builder.append(RANDOM_CHARS.charAt(RANDOM.nextInt(RANDOM_CHARS.length())));
}
return builder.toString();
}

static boolean isPbkdf2Hash(String encodedPassword) {
return encodedPassword != null && PBKDF2_PATTERN.matcher(encodedPassword).matches();
}

private static boolean isLegacySha256Hash(String encodedPassword) {
return encodedPassword != null && LEGACY_SHA256_PATTERN.matcher(encodedPassword).matches();
}

@Deprecated
private static String encrypt(String salt, String password) {

Check warning on line 103 in streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/PasswordHashUtils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=apache_incubator-streampark&issues=AZ5vAkRbYbSswgIMLYbU&open=AZ5vAkRbYbSswgIMLYbU&pullRequest=4352

Check warning on line 103 in streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/PasswordHashUtils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add the missing @deprecated Javadoc tag.

See more on https://sonarcloud.io/project/issues?id=apache_incubator-streampark&issues=AZ5vAkRbYbSswgIMLYbV&open=AZ5vAkRbYbSswgIMLYbV&pullRequest=4352
return new Sha256Hash(password, ByteSource.Util.bytes(salt), 1024).toHex();
}

private static boolean matchesPbkdf2(String rawPassword, String encodedPassword) {
String[] parts = encodedPassword.split("\\$");
if (parts.length != 6) {
return false;
}
int iterations = parseIterations(parts[3]);
if (iterations <= 0) {
return false;
}
try {
byte[] salt = Base64.getDecoder().decode(parts[4]);
byte[] expectedHash = Base64.getDecoder().decode(parts[5]);
byte[] actualHash = pbkdf2(rawPassword, salt, iterations);
return MessageDigest.isEqual(expectedHash, actualHash);
} catch (IllegalArgumentException e) {
return false;
}
}

private static int getPbkdf2Iterations(String encodedPassword) {
if (!isPbkdf2Hash(encodedPassword)) {
return -1;
}
return parseIterations(encodedPassword.split("\\$")[3]);
}

private static int parseIterations(String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return -1;
}
}

private static byte[] pbkdf2(String password, byte[] salt, int iterations) {
PBEKeySpec keySpec = null;
try {
keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, PBKDF2_HASH_BITS);
return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM).generateSecret(keySpec).getEncoded();
} catch (GeneralSecurityException e) {
throw new IllegalStateException("Failed to hash password.", e);
} finally {
if (keySpec != null) {
keySpec.clearPassword();
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@
package org.apache.streampark.console.system.security.impl;

import org.apache.streampark.console.base.exception.ApiAlertException;
import org.apache.streampark.console.base.util.ShaHashUtils;
import org.apache.streampark.console.base.util.PasswordHashUtils;
import org.apache.streampark.console.core.enums.LoginType;
import org.apache.streampark.console.core.enums.UserType;
import org.apache.streampark.console.system.entity.User;
import org.apache.streampark.console.system.security.Authenticator;
import org.apache.streampark.console.system.service.UserService;

import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

Expand All @@ -50,17 +48,20 @@ public User authenticate(String username, String password, LoginType loginType)
}
}

private User passwordAuthenticate(String username, String password) {
private User passwordAuthenticate(String username, String password) throws Exception {
User user = usersService.findByName(username);
if (user == null || user.getLoginType() != LoginType.PASSWORD) {
throw new ApiAlertException(
String.format("user [%s] does not exist or can not login with PASSWORD", username));
}
String salt = user.getSalt();
password = ShaHashUtils.encrypt(salt, password);
if (!StringUtils.equals(user.getPassword(), password)) {
if (!PasswordHashUtils.matches(password, user.getSalt(), user.getPassword())) {
return null;
}
if (PasswordHashUtils.needsRehash(user.getPassword())) {
user.setSalt(PasswordHashUtils.PASSWORD_SALT_NOT_REQUIRED);
user.setPassword(PasswordHashUtils.encrypt(password));
usersService.updateSaltPassword(user);
}
return user;
}

Expand All @@ -77,16 +78,12 @@ private User ldapAuthenticate(String username, String password) throws Exception
throw new ApiAlertException(
String.format("user [%s] can only sign in with %s", username, user.getLoginType()));
}
String saltPassword = ShaHashUtils.encrypt(user.getSalt(), password);

// ldap password changed, we should update user password
if (!StringUtils.equals(saltPassword, user.getPassword())) {

// encrypt password again
String salt = ShaHashUtils.getRandomSalt();
saltPassword = ShaHashUtils.encrypt(salt, password);
user.setSalt(salt);
user.setPassword(saltPassword);
boolean passwordMatched =
PasswordHashUtils.matches(password, user.getSalt(), user.getPassword());
// ldap password changed, or stored hash uses a legacy format, update user password hash
if (!passwordMatched || PasswordHashUtils.needsRehash(user.getPassword())) {
user.setSalt(PasswordHashUtils.PASSWORD_SALT_NOT_REQUIRED);
user.setPassword(PasswordHashUtils.encrypt(password));
usersService.updateSaltPassword(user);
}
return user;
Expand Down
Loading
Loading