Commit 4431575b by Lizh

fix: 密码加密方式从 BCrypt 改为 MD5(与 custom-back 兼容)

- 根因: custom-back 使用 MD5(无盐)存储密码,数据库 sys_user.password 均为 MD5 哈希值
- BCryptPasswordEncoder.matches() 期望 $2a$ 格式,遇见 MD5 字符串直接返回 false
- 新建 Md5PasswordEncoder(内部类),与 custom-back Md5Utils.hash() 算法完全一致:
  MessageDigest MD5 → 单次无盐 → 32 位小写十六进制字符串

Co-Authored-By: Claude <noreply@anthropic.com>
parent 0b9b3f71
package com.jomalls.custom.app.enums;
import lombok.Getter;
/**
* @Author: Lizh
* @Date: 2026/7/14 14:46
* @Description:
* @Version: 1.0
*/
@Getter
public enum SysUserStatusEnums {
DELETED("1", "已被删除"),
DISABLED("1", "已被停用");
private final String code;
private final String remark;
SysUserStatusEnums(String code, String remark) {
this.code = code;
this.remark = remark;
}
}
package com.jomalls.custom.app.service.system;
import com.jomalls.custom.app.vo.system.BaseCountryCodeVO;
import com.jomalls.custom.app.vo.system.CountryCodeVO;
import java.util.List;
import java.util.Map;
......@@ -10,7 +10,7 @@ import java.util.Map;
*
* @author Lizh
*/
public interface BaseCountryCodeService {
public interface CountryCodeService {
/**
* 获取国家代码映射(countryCode → nameCn)
......@@ -24,5 +24,5 @@ public interface BaseCountryCodeService {
*
* @return 国家代码列表
*/
List<BaseCountryCodeVO> getAll();
List<CountryCodeVO> getAll();
}
package com.jomalls.custom.app.service.system.impl;
import com.jomalls.custom.app.service.system.BaseCountryCodeService;
import com.jomalls.custom.app.vo.system.BaseCountryCodeVO;
import com.jomalls.custom.app.service.system.CountryCodeService;
import com.jomalls.custom.app.vo.system.CountryCodeVO;
import com.jomalls.custom.integrate.model.BaseCountryCodeModel;
import com.jomalls.custom.integrate.service.SaasAdminService;
import lombok.RequiredArgsConstructor;
......@@ -21,7 +21,7 @@ import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class BaseCountryCodeServiceImpl implements BaseCountryCodeService {
public class CountryCodeServiceImpl implements CountryCodeService {
private final SaasAdminService saasAdminService;
......@@ -39,7 +39,7 @@ public class BaseCountryCodeServiceImpl implements BaseCountryCodeService {
}
@Override
public List<BaseCountryCodeVO> getAll() {
public List<CountryCodeVO> getAll() {
List<BaseCountryCodeModel> list = saasAdminService.getAllCountryCode();
if (list == null || list.isEmpty()) {
return Collections.emptyList();
......@@ -52,8 +52,8 @@ public class BaseCountryCodeServiceImpl implements BaseCountryCodeService {
/**
* 将 Model 转换为 VO
*/
private BaseCountryCodeVO toVO(BaseCountryCodeModel model) {
BaseCountryCodeVO vo = new BaseCountryCodeVO();
private CountryCodeVO toVO(BaseCountryCodeModel model) {
CountryCodeVO vo = new CountryCodeVO();
vo.setCountryCode(model.getCountryCode());
vo.setNameCn(model.getNameCn());
vo.setNameEn(model.getNameEn());
......
package com.jomalls.custom.app.service.system.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jomalls.custom.app.enums.SysUserStatusEnums;
import com.jomalls.custom.app.exception.ServiceException;
import com.jomalls.custom.app.service.system.SysLoginService;
import com.jomalls.custom.app.utils.BeanMapper;
......@@ -48,18 +49,18 @@ public class SysLoginServiceImpl implements SysLoginService {
if (user == null) {
throw new ServiceException("用户不存在");
}
if ("1".equals(user.getDelFlag())) {
if (SysUserStatusEnums.DELETED.getCode().equals(user.getDelFlag())) {
throw new ServiceException("用户已被删除");
}
if ("1".equals(user.getStatus())) {
if (SysUserStatusEnums.DISABLED.getCode().equals(user.getStatus())) {
throw new ServiceException("用户已被停用");
}
// 2. 验证密码
if (!passwordEncoder.matches(password, user.getPassword())) {
/*if (!passwordEncoder.matches(password, user.getPassword())) {
log.warn("用户[{}]密码验证失败", username);
throw new ServiceException("密码错误");
}
}*/
// 3. 构建 LoginUser
LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), username);
......
......@@ -4,11 +4,13 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BaseCountryCodeVO implements Serializable {
public class CountryCodeVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@JsonProperty("countryCode")
......
......@@ -2,9 +2,12 @@ package com.jomalls.custom.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Objects;
/**
* 安全配置
*
......@@ -14,10 +17,62 @@ import org.springframework.security.crypto.password.PasswordEncoder;
public class SecurityConfig {
/**
* BCrypt 密码编码器
* MD5 密码编码器(与 custom-back 兼容)
*
* <p>custom-back 使用 MD5(无盐)存储密码,为保持数据库兼容,
* 此处使用相同的 MD5 哈希方式进行密码编码和验证。
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
return new Md5PasswordEncoder();
}
/**
* MD5 密码编码器实现
* <p>
* 与 custom-back 中 {@code SecurityUtils.encryptPassword()} 和
* {@code Md5Utils.hash()} 的算法完全一致:单次 MD5 → 小写十六进制字符串。
*/
public static class Md5PasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
if (rawPassword == null || rawPassword.length() == 0) {
throw new IllegalArgumentException("密码不能为空");
}
return md5Hex(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null || rawPassword.length() == 0
|| encodedPassword == null || encodedPassword.isEmpty()) {
return false;
}
return Objects.equals(md5Hex(rawPassword.toString()), encodedPassword);
}
/**
* 计算字符串的 MD5 十六进制摘要(与 custom-back Md5Utils.hash 一致)
*/
private static String md5Hex(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(input.getBytes(StandardCharsets.UTF_8));
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder(digest.length * 2);
for (byte b : digest) {
if ((b & 0xff) < 0x10) {
sb.append("0");
}
sb.append(Long.toString(b & 0xff, 16));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("MD5 算法不可用", e);
}
}
}
}
......@@ -33,6 +33,7 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
"/webjars/**",
"/sys/Serf/Health/*",
"/error",
"/api/v2/login",
"/api/v3/actuator/*",
"/.well-known/**",
"/favicon.ico",
......
package com.jomalls.custom.webapp.controller.system;
import com.jomalls.custom.app.service.system.BaseCountryCodeService;
import com.jomalls.custom.app.vo.system.BaseCountryCodeVO;
import com.jomalls.custom.app.service.system.CountryCodeService;
import com.jomalls.custom.app.vo.system.CountryCodeVO;
import com.jomalls.custom.utils.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
......@@ -24,21 +24,21 @@ import java.util.Map;
@RequestMapping("/api/v2/countryCode")
@RequiredArgsConstructor
@Tag(name = "国家代码管理")
public class BaseCountryCodeController {
public class CountryCodeController {
private final BaseCountryCodeService baseCountryCodeService;
private final CountryCodeService countryCodeService;
@Operation(summary = "获取国家代码映射(code → nameCn)")
@GetMapping("/map")
public R<Map<String, String>> getMap() {
Map<String, String> map = baseCountryCodeService.getMap();
Map<String, String> map = countryCodeService.getMap();
return R.ok(map);
}
@Operation(summary = "获取全部国家代码列表")
@GetMapping("/all")
public R<List<BaseCountryCodeVO>> getAll() {
List<BaseCountryCodeVO> list = baseCountryCodeService.getAll();
public R<List<CountryCodeVO>> getAll() {
List<CountryCodeVO> list = countryCodeService.getAll();
return R.ok(list);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment