Commit fb1e7076 authored by ma's avatar ma

新增找回密码功能,以及一些密码限制和频率限制

parent 8c7af349
......@@ -48,6 +48,13 @@ public class UserController {
private UserService userService;
@Value("${rsa.private_key}")
private String PRIVATE_KRY;
@Value("${other.error_count.change_pwd}")
private Integer changePwdMaxErrCount;
@Value("${spring.mail.to}")
private String account;
private static final String OPERATION_CHANGE = "change";
@InitBinder
public void initBinder(WebDataBinder binder) {
......@@ -127,6 +134,15 @@ public class UserController {
@PostMapping("update_pwd")
@MyLog(title = "修改密码", businessType = BusinessType.UPDATE)
public BaseResult updatePwd(@RequestBody UserUpdatePwdVo userUpdatePwdVo) {
String errCntTimeMap = UserUtils.getErrCntTimeMap(account + OPERATION_CHANGE);
if (!StringUtils.isEmpty(errCntTimeMap)) {
if (errCntTimeMap.equals(CommonUtil.getCurDateStr())) {
return BaseResult.failed().setMsgValue("今日尝试修改密码次数已达" + changePwdMaxErrCount + "次,请明日再试");
} else {
UserUtils.removeErrCntTimeMap(account + OPERATION_CHANGE);
UserUtils.removeErrCnt(account + OPERATION_CHANGE);
}
}
String oldPassWord = userUpdatePwdVo.getOldPassWord();
String newPassWord = userUpdatePwdVo.getNewPassWord();
String userId = UserUtils.getLoginUserId();
......@@ -138,7 +154,27 @@ public class UserController {
}
oldPassWord = RSAUtil.getDecryptString(oldPassWord, PRIVATE_KRY);
newPassWord = RSAUtil.getDecryptString(newPassWord, PRIVATE_KRY);
Integer errCnt = UserUtils.getErrCnt(account + OPERATION_CHANGE);
if (errCnt == null) {
errCnt = 0;
}
if (oldPassWord.equals(user.getPassword())) {
if (newPassWord.length() < 8) {
computeChangePwdErrCnt(errCnt);
return BaseResult.failed().setMsgValue("密码不得小于8位");
}
if (CommonUtil.verifyPasswordContainAccount(newPassWord, user.getUserName())) {
computeChangePwdErrCnt(errCnt);
return BaseResult.failed().setMsgValue("密码中不得包含用户名的完整字符串、大小写变位或形似变换的字符串");
}
if (CommonUtil.isKeyBoardContinuousChar(newPassWord)) {
computeChangePwdErrCnt(errCnt);
return BaseResult.failed().setMsgValue("密码不得包含键盘连续字符4个及以上");
}
if (!CommonUtil.checkPassword(newPassWord)) {
computeChangePwdErrCnt(errCnt);
return BaseResult.failed().setMsgValue("至少由大写字母、小写字母、数字与特殊符号等4类中3类混合");
}
user.setPassword(newPassWord);
boolean b = userService.updateUser(user);
if (b) {
......@@ -147,10 +183,19 @@ public class UserController {
return BaseResult.failed().setMsgValue("密码修改失败");
}
} else {
computeChangePwdErrCnt(errCnt);
return BaseResult.failed().setMsgValue("原密码出错");
}
}
private void computeChangePwdErrCnt(int errCnt) {
if (errCnt < changePwdMaxErrCount - 1) {
UserUtils.setErrCnt(account + OPERATION_CHANGE, errCnt + 1);
} else {
UserUtils.setErrCntTimeMap(account + OPERATION_CHANGE, CommonUtil.getCurDateStr());
}
}
/**
* 分页查询所有的user
......@@ -184,7 +229,7 @@ public class UserController {
List<UserVo> result = records.getResult();
String str = "uBtWZTiPMYkQLsp7rNly3RUIXKGqFbjnSg56H8ve49AC0mfO";
for (UserVo u : result) {
u.setPassword(DigestUtils.md5DigestAsHex((str+u.getPassword()).getBytes()));
u.setPassword(DigestUtils.md5DigestAsHex((str + u.getPassword()).getBytes()));
}
return new PageResult(200, "查找成功", pageNo, pages, total, result);
}
......
......@@ -25,6 +25,8 @@ public class JwtFilter implements Filter {
private static final String url4 = "/v2/api-docs";
private static final String url7 = "/swagger-resources";
private static final String url8 = "/webjars/";
private static final String url9 = "/check_code";
private static final String url10 = "/reset_pwd";
@Override
public void init(FilterConfig filterConfig) {
......@@ -49,7 +51,7 @@ public class JwtFilter implements Filter {
boolean check = true;
String uri = request.getRequestURI();
if (uri.contains(url1) || uri.contains(url2) || uri.contains(url3) || uri.contains(url4) || uri.contains(url7) || uri.contains(url8)) {
if (uri.contains(url1) || uri.contains(url2) || uri.contains(url3) || uri.contains(url4) || uri.contains(url7) || uri.contains(url8) || uri.contains(url9) || uri.contains(url10)) {
if (uri.contains(url1)) {
uri = XssUtil.checkXSS(uri);
UserUtils.setUri(uri);
......
......@@ -13,9 +13,13 @@ public abstract class UserUtils {
static Map<String, String> tokenMap = new HashMap<>();
static Map<String, Date> tokenExpTimeMap = new HashMap<>();
static Map<String, Integer> errCntMap = new HashMap<>();
static Map<String, String> errCntTimeMap = new HashMap<>();
static Map<String, String> emailCodeMap = new HashMap<>();
static Map<String, Date> emailCodeExpTimeMap = new HashMap<>();
static Map<String, Date> countFreezeDateMap = new HashMap<>();
static Map<String, Integer> codeErrCntMap = new HashMap<>();
static Map<String, String> codeFreezeTimeMap = new HashMap<>();
/**
* 线程变量,存放user实体类信息,即使是静态的也与其他线程也是隔离的
*/
......@@ -136,8 +140,43 @@ public abstract class UserUtils {
return emailCodeExpTimeMap.get(email);
}
public static void removeEmailCodeExpTime(String email) {
emailCodeExpTimeMap.remove(email);
}
public static void setCodeErrCntMap(String account, int count) {
codeErrCntMap.put(account, count);
}
public static Integer getCodeErrCntMap(String account) {
return codeErrCntMap.get(account);
}
public static void removeCodeErrCntMap(String account) {
codeErrCntMap.remove(account);
}
public static void setCodeFreezeTimeMap(String account, String timeStr) {
codeFreezeTimeMap.put(account, timeStr);
}
public static String getCodeFreezeTimeMap(String account) {
return codeFreezeTimeMap.get(account);
}
public static void removeCodeFreezeTimeMap(String account) {
codeFreezeTimeMap.remove(account);
}
public static void setErrCntTimeMap(String account, String timeStr) {
errCntTimeMap.put(account, timeStr);
}
public static String getErrCntTimeMap(String account) {
return errCntTimeMap.get(account);
}
public static void removeErrCntTimeMap(String account) {
errCntTimeMap.remove(account);
}
}
package iot.sixiang.license.model.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* Created by m33
* Date 2022/9/23 17:46
* Description
*/
@Data
public class CheckCodeDto {
@ApiModelProperty("验证码")
private String code;
}
package iot.sixiang.license.model.vo;
import lombok.Data;
/**
* Created by m33
* Date 2022/9/23 18:32
* Description
*/
@Data
public class UserResetPwdVo {
private String password;
}
......@@ -12,7 +12,7 @@ import iot.sixiang.license.model.vo.UserVo;
* @author m33
* @since 2022-06-06
*/
public interface UserService{
public interface UserService {
boolean deleteUser(int userIdVo);
......@@ -23,4 +23,6 @@ public interface UserService{
PageInfoModel<UserVo> getUserList(int pageNo, int pageSize, String userName, String company);
User getUserById(int userId);
User getUserByName(String root);
}
package iot.sixiang.license.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import iot.sixiang.license.consts.ResultCode;
import iot.sixiang.license.entity.User;
......@@ -34,11 +35,11 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
UserMapper userMapper;
@Override
public PageInfoModel<UserVo> getUserList(int pageNo, int pageSize,String userName, String company) {
if(pageNo == 0 || pageSize == 0) {
throw new IotLicenseException(ResultCode.VALIDATE_FAILED.getCode(),ResultCode.VALIDATE_FAILED.getMsg());
public PageInfoModel<UserVo> getUserList(int pageNo, int pageSize, String userName, String company) {
if (pageNo == 0 || pageSize == 0) {
throw new IotLicenseException(ResultCode.VALIDATE_FAILED.getCode(), ResultCode.VALIDATE_FAILED.getMsg());
}
List<UserVo> records = userMapper.getUserList(userName,company);
List<UserVo> records = userMapper.getUserList(userName, company);
records = records.stream().sorted(Comparator.comparing(UserVo::getCreateTime, Comparator.reverseOrder())).collect(Collectors.toList());
List<UserVo> result = new ArrayList<>();
int begin = (pageNo - 1) * pageSize;
......@@ -56,10 +57,17 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return userMapper.getUserById(userId);
}
@Override
public User getUserByName(String root) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUserName, root).last("limit 1");
return userMapper.selectOne(wrapper);
}
@Override
public boolean deleteUser(int userId) {
if(userId == 0) {
throw new IotLicenseException(ResultCode.VALIDATE_FAILED.getCode(),ResultCode.VALIDATE_FAILED.getMsg());
if (userId == 0) {
throw new IotLicenseException(ResultCode.VALIDATE_FAILED.getCode(), ResultCode.VALIDATE_FAILED.getMsg());
}
return userMapper.deleteUser(userId);
}
......@@ -73,7 +81,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
if (res != null) {
throw new IotLicenseException(403, "用户名已存在");
}
return userMapper.addUser(userName,company,password);
return userMapper.addUser(userName, company, password);
}
@Override
......
......@@ -4,6 +4,7 @@ import iot.sixiang.license.consts.Consts;
import iot.sixiang.license.model.ResResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.util.TextUtils;
import org.springframework.boot.system.ApplicationHome;
import java.io.File;
......@@ -14,9 +15,143 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class CommonUtil {
/**
* 验证密码-是否包含用户名字符(密码应与用户名无相关性,密码中不得包含用户名的完整字符串、大小写变位或形似变换的字符串)
*/
public static boolean verifyPasswordContainAccount(String password, String account) {
boolean isContain = false;
if (!TextUtils.isEmpty(password) && !TextUtils.isEmpty(account)) {
password = password.toLowerCase();
account = account.toLowerCase();
if (password.contains(account)) {
return true;
}
String[] likes = {"a", "l", "o"};
String[] likeSign = {"@", "!", "0"};
String originalAccount = account + "";
for (int i = 0; i < likes.length; i++) {
String tempAccount = originalAccount.replace(likes[i], likeSign[i]);
account = account.replace(likes[i], likeSign[i]);
if (password.contains(tempAccount) || password.contains(account)) {
return true;
}
}
}
return isContain;
}
/**
* 键盘连续字符统计4个
*
* @param str
* @return
*/
public static boolean isKeyBoardContinuousChar(String str) {
boolean result = false;
char[][] c1 = {
{'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+'},
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '{', '}', '|'},
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ':', '"'},
{'z', 'x', 'c', 'v', 'b', 'n', 'm', '<', '>', '?'}
};
char[][] c2 = {
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='},
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '{', '}', '\\'},
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\''},
{'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/'}
};
for (char[][] c : new char[][][]{c1, c2}) {
//横向
for (char[] chars : c) {
for (int j = 0; j < chars.length - 3; j++) {
//创建连续字符
StringBuffer sb = new StringBuffer();
for (int k = j; k < j + 4; k++) {
sb.append(chars[k]);
}
String keyStr = sb.toString();
if (str.contains(keyStr)) {
return true;
}
}
}
//纵向
for (int i = 0; i < c[3].length; i++) {
//创建连续字符--每列只有4个
StringBuffer sb = new StringBuffer();
for (int j = 0; j < 4; j++) {
sb.append(c[j][i]);
}
String keyStr = sb.toString();
if (str.contains(keyStr)) {
return true;
}
}
}
return result;
}
public static void main(String[] args) {
boolean b = checkPassword("1234qwe123");
System.out.println(b);
}
/**
* 密码验证
* (至少由8位及以上大写字母、小写字母、数字与特殊符号等4类中3类混合)
*
* @param password
* @return
*/
public static boolean checkPassword(String password) {
boolean flag = false;
try {
int c = 0;
if (find("[a-z]+", password)) {
c++;
}
if (find("[A-Z]+", password)) {
c++;
}
if (find("[0-9]+", password)) {
c++;
}
if (find("\\W+|_", password) && !find("\\s+", password)) {//特殊符号
c++;
}
if (c >= 3) {
flag = true;
}
} catch (Exception e) {
flag = false;
}
return flag;
}
//通用匹配
public static boolean find(String regexStr, String input) {
boolean flag;
try {
Pattern regex = Pattern.compile(regexStr);
Matcher matcher = regex.matcher(input);
// 部分进行匹配
flag = matcher.find();
} catch (Exception e) {
flag = false;
}
return flag;
}
/**
* 获取随机验证码
*/
......@@ -45,8 +180,8 @@ public class CommonUtil {
int maxNum = 36;
int i = -1;
int count = 0;
char[] str = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
char[] str = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
StringBuilder pwd = new StringBuilder("");
SecureRandom secureRandom = null;
try {
......@@ -67,7 +202,6 @@ public class CommonUtil {
}
public static String getSystemTime() {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 设置日期格式
String time = df.format(new Date());
......@@ -100,7 +234,7 @@ public class CommonUtil {
}
public static String getServerParentDirectory() {
return new File(new ApplicationHome(Consts.class).getSource().getParentFile().getPath()).getParent()+ File.separator + "lib";
return new File(new ApplicationHome(Consts.class).getSource().getParentFile().getPath()).getParent() + File.separator + "lib";
}
/**
......@@ -147,6 +281,13 @@ public class CommonUtil {
return res;
}
public static String getCurDateStr() {
Date date = new Date();
String pattern = "yyyy-MM-dd";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
return simpleDateFormat.format(date);
}
// 用于测试存储型xss
public static Object reverseData(Object obj, Class clazz) {
......
......@@ -32,12 +32,18 @@ server:
cros:
# 需要设置访问白名单
cros_allowed_origins: http://192.168.1.88:8080, http://localhost:8868, http://localhost:8080
cros_allowed_origins: http://192.168.1.88:8080, http://192.168.1.88:8081, http://localhost:8868, http://localhost:8080, http://192.168.1.54:8080
cros_allowed_method: GET,POST
other:
md5:
salt: PI7dBYlEfeP8IZ6vogqFL1U5pVnyCuNAGja3lsREx4M9r0SX
error_count:
change_pwd: 5 # 修改密码的最大错误次数
forget_pwd: 5 # 忘记密码的最大错误次数
check_code: 5 # 校验验证码的最大错误次数
code_exp_time: 3 # 验证码失效时间,单位:分钟
rsa:
public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA18W2H1hO98dUWf1PNKNWTWmxCyvvy0NOR7iSvp76J0LdzyMJxs8WHVAmRfSGOb9SvpDZhBVx11bhTBqkl1qMzJWzn+F2ZtTCH2nXZcJHwSfLuGqin5FRBYW1WrFkqwg+R80aOuRSrbo0k1bZg3JPkkCxISHieEZPjSV5a4r7+Xopj0a9Dnh3rh4nDmH2p/wvotkx1oMKdhFglYcAITlk9ucEUf+CDuSdTAFFeKg9+fPqwKqWZRJZPQXqV3pGZ1/JS7gPnBFGZojW44eJufkBeiW3pbBvm/cKOkTnb8o4oltYUJsirYSQCCG+sDtxUAuGxuDCv8p+r8dWE1z5+xKclQIDAQAB
private_key: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDXxbYfWE73x1RZ/U80o1ZNabELK+/LQ05HuJK+nvonQt3PIwnGzxYdUCZF9IY5v1K+kNmEFXHXVuFMGqSXWozMlbOf4XZm1MIfaddlwkfBJ8u4aqKfkVEFhbVasWSrCD5HzRo65FKtujSTVtmDck+SQLEhIeJ4Rk+NJXlrivv5eimPRr0OeHeuHicOYfan/C+i2THWgwp2EWCVhwAhOWT25wRR/4IO5J1MAUV4qD358+rAqpZlElk9BepXekZnX8lLuA+cEUZmiNbjh4m5+QF6JbelsG+b9wo6ROdvyjiiW1hQmyKthJAIIb6wO3FQC4bG4MK/yn6vx1YTXPn7EpyVAgMBAAECggEAR7YQ+kfqLtVThnj2mwLyCtZmndTjZEWhPZrtQmcpsmS5vT7i3+0xZ1qc7cD/3y9j+6u+bvSFmlDondd4/kh85P2X7joLlM9/GNufV9WC7YIhZdAi7i9ooxI2HMc6MtGRiWF0J0B87folwRYrQlF6epv/goh1cQ3FIJ7kxMYzSk0gF0JcmOZn3KH8tMt3t/GK+uVUIycuEQQKsaTTq45nIM7oqhlAwD/M+IO2pGFkLXJ23FzFACI10qBdpn+xL2xFGRO6EE8EAeDslT4OvvN3/vtnSnRNn8CJqfoEG40XO0xrZzH1noI35iPWX1WrN7qFLAhl2oLhu1ZSIA1tz0+I/QKBgQD3X/islXfVmV2XeuHvaf4qcMAdrgLQwtmLlHhFxfFURPh6au8uatDgUA8HWcRACmhtTruytlFRGKKFwZxuQE/LOZh67Uts3GTs5eHN8xvZTL+en+n7B/cCRYrrg3+yAM/k0eezIlk65iW700o+icEHxkwTXhhVBmIROBzpXsVCwwKBgQDfS6kVhgZxLMQePbXUQ1NBr2KbfhXLzceINhsoyWLa1rIk8+9HLSxw8q0zma12Jqkd22OAgkbeLDy+niYPi3pUrWAm8O59Ot1aaOarxMTvEv7+eS+urKId57sli/hQTWsS+xghA4+VfW2+EY++pYyZIp+j+1/Q2ciXWVJy4iv9xwKBgFzW8+kxn2vWxz1WrPzBdtZOwothB0V6G1M7QXhONag+ylKHV4TAKexFn55Onky6mz6K0f7cVeBtsnEonKD0Gf5Xe1aHQEt225ndHMXCe60uFKxfr9y6vIVpvB1vmLkhfOSPsrmUJpDoVzkKr06RPJTCY0LRiag/YQa9XHxpSPcpAoGAKbsiJnudyJjtLhmqWbkbXjNA4n515FjY6YPzH3RDnVJyiKVuGoc+vv0bkYEvAd3HzWSq++FdDTiHQbictFsEyb59McnlSFIv/C2Orptfkq6iKTzMxIBO6/fa6fF2vss5L5rtr33S38VJNTRjAOY/mH74BtV72rRY4LA40G+keRECgYEAiGg0DYxcSGf2bEP1WESYPTTdgS5ke1niIwZ00SgtkIjPSVgTCmf4Tciys6lGSe/Oqnvk24VR6pz07wzcbH92AURVaeqiEvVuVonzA6Yl0jxeOIM31S1BSBJRT8kDijuvwoJu2tPoZG0306KF9L8pyy1Z6cTTmIfGR0NpZCHWPSg=
\ No newline at end of file
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