Skip to content
Merged
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
7 changes: 7 additions & 0 deletions bigtop-manager-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<janino.version>3.0.16</janino.version>
<jaxb-runtime.version>4.0.4</jaxb-runtime.version>
<commons-text.version>1.11.0</commons-text.version>
<password4j.version>1.8.3</password4j.version>
<prometheus-java-client.version>1.0.0</prometheus-java-client.version>
<oshi-core.version>6.4.11</oshi-core.version>
<micrometer.version>1.12.4</micrometer.version>
Expand Down Expand Up @@ -161,6 +162,12 @@
<version>${commons-text.version}</version>
</dependency>

<dependency>
<groupId>com.password4j</groupId>
<artifactId>password4j</artifactId>
<version>${password4j.version}</version>
</dependency>

<!-- prometheus java client -->
<dependency>
<groupId>io.prometheus</groupId>
Expand Down
5 changes: 5 additions & 0 deletions bigtop-manager-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@
<artifactId>commons-text</artifactId>
</dependency>

<dependency>
<groupId>com.password4j</groupId>
<artifactId>password4j</artifactId>
</dependency>

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**")
// Server APIs
.excludePathPatterns("/api/login")
.excludePathPatterns("/api/salt", "/api/nonce", "/api/login")
// Frontend pages
.excludePathPatterns("/", "/ui/**", "/favicon.ico", "/error")
// Swagger pages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
import org.apache.bigtop.manager.server.model.req.LoginReq;
import org.apache.bigtop.manager.server.model.vo.LoginVO;
import org.apache.bigtop.manager.server.service.LoginService;
import org.apache.bigtop.manager.server.utils.CacheUtils;
import org.apache.bigtop.manager.server.utils.PasswordUtils;
import org.apache.bigtop.manager.server.utils.Pbkdf2Utils;
import org.apache.bigtop.manager.server.utils.ResponseEntity;

import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -45,6 +49,21 @@ public class LoginController {
@Resource
private LoginService loginService;

@Operation(summary = "salt", description = "Generate salt")
@GetMapping(value = "/salt")
public ResponseEntity<String> salt(String username) {
String salt = Pbkdf2Utils.generateSalt(username);
return ResponseEntity.success(salt);
}

@Operation(summary = "nonce", description = "Generate nonce")
@GetMapping(value = "/nonce")
public ResponseEntity<String> nonce(String username) {
String nonce = PasswordUtils.randomString(16);
CacheUtils.setCache(username, nonce);
return ResponseEntity.success(nonce);
}

@Audit
@Operation(summary = "login", description = "User Login")
@PostMapping(value = "/login")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ public class LoginDTO {
private String username;

private String password;

private String nonce;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ public class LoginReq {

@Schema(example = "admin")
private String password;

@Schema(example = "nonce")
private String nonce;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
import org.apache.bigtop.manager.server.model.dto.LoginDTO;
import org.apache.bigtop.manager.server.model.vo.LoginVO;
import org.apache.bigtop.manager.server.service.LoginService;
import org.apache.bigtop.manager.server.utils.CacheUtils;
import org.apache.bigtop.manager.server.utils.JWTUtils;
import org.apache.bigtop.manager.server.utils.PasswordUtils;

import org.springframework.stereotype.Service;

Expand All @@ -39,17 +41,31 @@ public class LoginServiceImpl implements LoginService {

@Override
public LoginVO login(LoginDTO loginDTO) {
UserPO userPO = userDao.findByUsername(loginDTO.getUsername());
if (userPO == null || !loginDTO.getPassword().equalsIgnoreCase(userPO.getPassword())) {
String username = loginDTO.getUsername();
String password = loginDTO.getPassword();
String nonce = loginDTO.getNonce();

UserPO user = userDao.findByUsername(username);
if (user == null) {
throw new ApiException(ApiExceptionEnum.INCORRECT_USERNAME_OR_PASSWORD);
}

if (!userPO.getStatus()) {
if (!user.getStatus()) {
throw new ApiException(ApiExceptionEnum.USER_IS_DISABLED);
}

String token = JWTUtils.generateToken(userPO.getId(), userPO.getUsername());
String cache = CacheUtils.getCache(username);
if (cache == null || !cache.equals(nonce)) {
throw new ApiException(ApiExceptionEnum.INCORRECT_USERNAME_OR_PASSWORD);
}

if (!PasswordUtils.checkBcryptPassword(password, user.getPassword())) {
throw new ApiException(ApiExceptionEnum.INCORRECT_USERNAME_OR_PASSWORD);
}

CacheUtils.removeCache(username);

String token = JWTUtils.generateToken(user.getId(), user.getUsername());
LoginVO loginVO = new LoginVO();
loginVO.setToken(token);
return loginVO;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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
*
* https://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.bigtop.manager.server.utils;

import org.apache.commons.lang3.StringUtils;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import java.util.concurrent.TimeUnit;

/**
* Cache utility class
*/
public class CacheUtils {

/**
* Create cache, expires in 5 minutes, maximum capacity 10000
*/
private static final Cache<String, String> CACHE = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.maximumSize(10000)
.build();

/**
* Store cache
*
* @param key cache key
* @param value cache value
*/
public static void setCache(String key, String value) {
if (StringUtils.isBlank(key) || value == null) {
return;
}
CACHE.put(key, value);
}

/**
* Get cache
*
* @param key cache key
* @return cache value (returns null if not found or expired)
*/
public static String getCache(String key) {
if (StringUtils.isBlank(key)) {
return null;
}
return CACHE.getIfPresent(key);
}

/**
* Remove specified cache
*
* @param key cache key
*/
public static void removeCache(String key) {
if (StringUtils.isBlank(key)) {
return;
}
CACHE.invalidate(key);
}

/**
* Clear all caches
*/
public static void clearAll() {
CACHE.invalidateAll();
CACHE.cleanUp();
}

/**
* Get cache size
*/
public static long size() {
return CACHE.size();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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
*
* https://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.bigtop.manager.server.utils;

import java.util.Objects;

/**
* Hexadecimal utility class
*/
public class HexUtils {

private HexUtils() {
throw new UnsupportedOperationException("Utility class cannot be instantiated");
}

/**
* Converts a byte array to a hexadecimal string (lowercase)
*
* @param bytes the input byte array
* @return the hexadecimal string representation
*/
public static String bytesToHex(byte[] bytes) {
Objects.requireNonNull(bytes, "bytes cannot be null");

StringBuilder hex = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
hex.append(String.format("%02x", b & 0xff));
}
return hex.toString();
}

/**
* Converts a hexadecimal string to a byte array
*
* @param hex the input hexadecimal string
* @return the corresponding byte array
* @throws IllegalArgumentException if the length of the hex string is not even
*/
public static byte[] hexToBytes(String hex) {
Objects.requireNonNull(hex, "hex cannot be null");

if (hex.length() % 2 != 0) {
throw new IllegalArgumentException("Hex string length must be even");
}

int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16));
}
return data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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
*
* https://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.bigtop.manager.server.utils;

import com.password4j.Password;

import java.util.concurrent.ThreadLocalRandom;

/**
* Password utility class
*/
public class PasswordUtils {

private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
private static final int BASE = ALPHABET.length();

private PasswordUtils() {
throw new UnsupportedOperationException("Utility class cannot be instantiated");
}

/**
* Generate random string
*
* @param length the length of the random string to generate
* @return a random string
*/
public static String randomString(int length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
int index = ThreadLocalRandom.current().nextInt(BASE);
sb.append(ALPHABET.charAt(index));
}
return sb.toString();
}

/**
* Get BCrypt password
*
* @param rawPassword raw password
* @return hashed password
*/
public static String getBcryptPassword(String rawPassword) {
return Password.hash(rawPassword).withBcrypt().getResult();
}

/**
* Verify BCrypt password
*
* @param rawPassword raw password
* @param hashedPassword hashed password
* @return verification result
*/
public static boolean checkBcryptPassword(String rawPassword, String hashedPassword) {
return Password.check(rawPassword, hashedPassword).withBcrypt();
}
}
Loading
Loading