过完年,因为业务,需要对采购单进行改价限制,通过密码去修改价格,做了一个加盐的方式,进行密码校验,现在刚好有时间进行整理,列举一下接口,和底层的代码逻辑。
1.先说一下整体逻辑:
设置密码:
(1)前端先调取后端,获取随机生成的8位key,拿到这个8位随机生成的key,后端将8位随机数存入redis。
(2)用户输入的6位数字与刚刚的8位key进行des加密然后传到后端,后端拿着加密后的密码,以及对应的用户唯一标识,去redis中拿去8位随机数,然后进行des解密,获取用户的6位密码,然后随机生成10位数(也就是盐),然后将6位数密码和10位数盐进行MD5加密,然后入库即可。
若要修改密码:
和上面类似,需要输入已有密码,再输入新密码,将前端输入的已有密码与已存在的盐进行MD5加密,再和数据库中的密码进行equals比较,若相同,则校验密码正确,再对新密码进行md5加密入库即可。
流程图:
● 加密:DES( 明文 + passwordKey[8位随机数])
● 解密:DES( 密文 + passwordKey[8位随机数])
● 数据库存储:MD5(明文 + salt[10位随机数])
2.密码的安全性如何保证:
<1>密码传给后端时如何防止被抓包,给后端传数据的时候就是哈希加盐的。盐存在哪里? 各个存储密码的业务方自己去生成盐、保存盐。
<2> 数据库存储密码,必须为密文存储。
3.sql新增字段
ALTER TABLE db_user
ADD COLUMN password VARCHAR(50) DEFAULT NULL COMMENT '开关密码,用户级别',
ADD COLUMN salt VARCHAR(20) DEFAULT NULL COMMENT '密码的盐';
4.下面是主要的代码逻辑:
(1)获取加密的key
(2)查询是否存在密码
(3)校验密码
(4)设置密码
(5)修改密码
Controller
/**
* 密码
*/
@RestController
@RequestMapping("/api/rest/password")
@Api(tags = "密码")
@Slf4j
public class PasswordController {
@DubboReference(version="0.1", timeout = 10000)
private PasswordService passwordService;
@ApiOperation(value = "获取加密的key")
@GetMapping("/encryptKey")
public WebResponse<String> encryptKey(User user) {
long startTime = System.currentTimeMillis();
String result = passwordService.getEncryptKey(user);
log.info("获取加密的key,costTime: {},用户id: {},结果:{}", System.currentTimeMillis() - startTime, user.getTenantId(), result);
return WebResponse.success(result);
}
@ApiOperation(value = "查询是否存在密码")
@GetMapping("/checkExist")
public WebResponse<Boolean> checkExist(User user) {
long startTime = System.currentTimeMillis();
Boolean result = passwordService.checkExist(user);
log.info("查询是否存在密码,costTime: {},用户id: {},结果:{}", System.currentTimeMillis() - startTime, user.getTenantId(), result);
return WebResponse.success(result);
}
@ApiOperation(value = "校验密码")
@PostMapping("/checkPassword")
public WebResponse<String> checkPassword(User user, @RequestBody @Validated CheckPasswordRequest checkPasswordRequest) {
long startTime = System.currentTimeMillis();
log.info("校验密码,用户id:{},参数:{}", user.getTenantId(), checkPasswordRequest.toString());
CheckPasswordRequestTo checkPasswordRequestTo = JsonUtils.convertValue(checkPasswordRequest, CheckPasswordRequestTo.class);
String result = passwordService.checkPassword(user, checkPasswordRequestTo);
log.info("设置密码,costTime:{},用户id:{},结果:{}", System.currentTimeMillis() - startTime, user.getTenantId(), result);
if (StringUtils.isEmpty(result)) {
return WebResponse.success("校验密码成功");
}
return WebResponse.fail(result);
}
@ApiOperation(value = "设置密码")
@PostMapping("/setPassword")
public WebResponse<String> setPassword(User user, @RequestBody @Validated SetPasswordRequest setPasswordRequest) {
long startTime = System.currentTimeMillis();
log.info("设置密码,用户id:{},参数:{}", user.getTenantId(), setPasswordRequest.toString());
SetPasswordRequestTo setPasswordRequestTo = JsonUtils.convertValue(setPasswordRequest, SetPasswordRequestTo.class);
String result = passwordService.setPassword(user, setPasswordRequestTo);
log.info("设置密码,costTime:{},用户id:{},结果:{}", System.currentTimeMillis() - startTime, user.getTenantId(), result);
if (StringUtils.isEmpty(result)) {
return WebResponse.success("设置成功,请保管好你的支付密码");
}
return WebResponse.fail(result);
}
@ApiOperation(value = "修改密码")
@PostMapping("/modifyPassword")
public WebResponse<String> modifyPassword(User user, @RequestBody @Validated PModifyPasswordRequest modifyPasswordRequest) {
long startTime = System.currentTimeMillis();
log.info("修改密码,用户id:{},参数:{}", user.getTenantId(), modifyPasswordRequest.toString());
ModifyPasswordRequestTo modifyPasswordRequestTo = JsonUtils.convertValue(modifyPasswordRequest, ModifyPasswordRequestTo.class);
String result = passwordService.modifyPassword(user, modifyPasswordRequestTo);
log.info("修改密码,costTime:{},用户id:{},结果:{}", System.currentTimeMillis() - startTime, user.getTenantId(), result);
if (StringUtils.isEmpty(result)) {
return WebResponse.success("修改密码成功");
}
return WebResponse.fail(result);
}
}
service
@DubboService(version="0.1")
@Slf4j
public class PasswordServiceImpl implements PasswordService {
private final static int ENCRYPT_KEY_LENGTH = 8;
private final static String CACHE_ENCRYPT_KEY_PREFIX = "password.";
private final static Pattern PASSWORD_PATTERN = Pattern.compile("\\d{6}");
@Autowired
private RedisService redisService;
@Autowired
private DBService dbService;
/**
* 使用des加密一个8位长的校验字符
*/
@Override
public String getEncryptKey(User user) {
// 缓存的key 为用户级别
String cacheKey = CACHE_ENCRYPT_KEY_PREFIX + user.getId();
String encryptKey = redisService.get(cacheKey);
String value;
if (!StringUtils.isEmpty(encryptKey)) {
value = encryptKey;
}
else {
value = DesUtil.randomString(ENCRYPT_KEY_LENGTH);
}
redisService.set(cacheKey, value, 1, TimeUnit.DAYS);
log.info("获取加密的随机数,用户id:{},value:{}", user.getId(), value);
return value;
}
@Override
public Boolean checkExist(User user) {
DBData existData = dbService.query(user));
log.info("是否存在密码,用户id:{},结果:{}", user.getId(), existData.getPassword());
return !StringUtils.isEmpty(existData.getPassword());
}
/**
* 校验密码
*/
@Override
public String checkPassword(User user, PasswordRequestTo param) {
log.info("校验密码,用户id:{},输入的密码:{}", user.getId(), param.toString());
// 1.获取缓存中的8位加密随机数
String encryptKey = redisService.get(CACHE_ENCRYPT_KEY_PREFIX + user.getId());
if (StringUtils.isEmpty(encryptKey)) {
log.info("校验密码,没有拿到缓存的des加密随机数,用户id:{}", user.getId());
return "调用超时,请刷新重试";
}
DBData existData = dbService.query(user);
// 2.若没有密码或者加盐字段,则表示没有输入过密码
if (StringUtils.isEmpty(existData.getPassword()) || StringUtils.isEmpty(existData.getSalt())) {
log.info("用户没有原始密码,数据异常,租户id:{}", user.getId());
return "用户没有原始密码";
}
String originalEncodePassword = existData.getPassword();
String originalSalt = existData.getSalt();
// 3.解密前端传来的密码,和已存在的盐进行hash加密后,进行比较
String inputPassword = DesUtil.decode(param.getEncodedPassword(), encryptKey);
String checkPassword = MD5Utils.hash(inputPassword + originalSalt);
if (!Objects.equals(checkPassword, originalEncodePassword)) {
log.info("校验密码错误,用户id:{},输入的密码:{}", user.getId(), inputPassword);
return "输入密码错误";
}
log.info("校验成功,用户id:{}", user.getId());
return null;
}
/**
* 此方法仅为第一次设置密码
*/
@Override
public String setPassword(User user, SetPasswordRequestTo param) {
log.info("设置密码,用户id:{}", user.getId());
// 1.校验两次输入密码一致
if (!Objects.equals(param.getEncodedPassword(), param.getSecondEncodedPassword())) {
log.info("设置密码,两次密码不一致,用户id:{},密码:{},确认密码:{}",
user.getTenantId(), param.getEncodedPassword(), param.getSecondEncodedPassword());
return "两次输入密码不一致";
}
// 2.获取缓存中的8位加密随机数
String encryptKey = redisService.get(CACHE_ENCRYPT_KEY_PREFIX + user.getId());
if (StringUtils.isEmpty(encryptKey)) {
log.info("设置密码,没有拿到缓存的des加密随机数,用户id:{}", user.getId());
return "调用超时,请刷新重试";
}
// 3.判断是否已存在密码
DBData existData = dbService.query(user);
if (!StringUtils.isEmpty(existData.getPassword()) || !StringUtils.isEmpty(existData.getSalt())) {
log.info("设置密码,用户已存在原始密码,用户id:{}", user.getId());
return "租户已存在原始密码";
}
// 4.校验密码格式 只能是6位数字
String inputPassword = DesUtil.decode(param.getEncodedPassword(), encryptKey);
boolean isPasswordNum = PASSWORD_PATTERN.matcher(inputPassword).matches();
if (!isPasswordNum) {
log.info("设置密码,输入的密码不是6位数字,用户id:{},解密后的密码为:{}", user.getId(), inputPassword);
return "密码必须为6位数字";
}
// 5.设置密码
String salt = RandomStringUtils.randomAlphanumeric(10);
existData.setPassword(MD5Utils.hash(inputPassword + salt));
existData.setSalt(salt);
try {
if (Objects.isNull(existData.getId()) || Objects.equals(0L, existData.getId())) {
dbService.insertOne(user, existData);
log.info("设置密码,新增配置数据,用户id:{}", user.getId());
}
else {
dbService.updateOne(user, existData);
log.info("设置密码,更新配置数据,用户id:{}", user.getId());
}
return null;
}
catch (Exception e) {
log.error("设置密码异常,用户id:{}", user.getId(), e);
return "设置密码异常";
}
}
/**
* 此方法为重新设置密码,先校验,再更新
*/
@Override
public String modifyPassword(User user, ModifyPasswordRequestTo param) {
log.info("修改密码,用户id:{}", user.getId());
// 1.校验修改的两次输入密码一致
if (!Objects.equals(param.getEncodedPassword(), param.getSecondEncodedPassword())) {
log.info("修改密码,两次密码不一致,用户id:{},密码:{},确认密码:{}",
user.getId(), param.getEncodedPassword(), param.getSecondEncodedPassword());
return "两次输入密码不一致";
}
// 2.获取缓存中的8位加密随机数
String encryptKey = redisService.get(CACHE_ENCRYPT_KEY_PREFIX + user.getId());
if (StringUtils.isEmpty(encryptKey)) {
log.info("修改密码,没有拿到缓存的des加密随机数,用户id:{}", user.getId());
return "调用超时,请刷新重试";
}
DBData existData = dbService.query(user);
// 3.若没有密码或者加盐字段,则表示没有输入过密码
if (StringUtils.isEmpty(existData.getPassword()) || StringUtils.isEmpty(existData.getSalt())) {
log.info("修改密码,租户没有原始密码,数据异常,用户id:{}", user.getId());
return "租户没有原始密码";
}
// 4.先校验原始密码是否正确
String originalEncodePassword = existData.getPassword();
String originalSalt = existData.getSalt();
String inputOriginalPassword = DesUtil.decode(param.getOldEncodedPassword(), encryptKey);
String checkOriginalPassword = MD5Utils.hash(inputOriginalPassword + originalSalt);
if (!Objects.equals(checkOriginalPassword, originalEncodePassword)) {
log.info("修改密码,校验密码错误,用户id:{},输入的密码:{}", user.getId(), inputOriginalPassword);
return "输入密码错误";
}
// 5.开始校验修改的密码 只能是6位数字
String inputModifyPassword = DesUtil.decode(param.getEncodedPassword(), encryptKey);
boolean isPasswordNum = PASSWORD_PATTERN.matcher(inputModifyPassword).matches();
if (!isPasswordNum) {
log.info("修改密码,输入的密码不是6位数字,用户id:{},解密后的密码为:{}", user.getId(), inputModifyPassword);
return "密码必须为6位数字";
}
// 6.开始修改密码
existData.setPayableOrderPassword(MD5Utils.hash(inputModifyPassword + originalSalt));
try {
dbService.updateOne(user, existData);
log.info("修改密码,更新数据,用户id:{}", user.getId());
return null;
}
catch (BusinessException e) {
log.error("修改密码异常,用户id:{}", user.getId(), e);
return "修改密码异常";
}
}
}