Sfoglia il codice sorgente

修改 密码加密规则,新增短信登录

Administrator 1 anno fa
parent
commit
499e5b3474
19 ha cambiato i file con 575 aggiunte e 87 eliminazioni
  1. 53 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/SendSmsController.java
  2. 1 1
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
  3. 20 25
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
  4. 3 4
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
  5. 1 1
      ruoyi-admin/src/main/resources/application-druid.yml
  6. 4 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/CommonConstants.java
  7. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
  8. 13 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java
  9. 28 0
      ruoyi-common/src/main/java/com/ruoyi/common/encoder/AesPasswordEncoder.java
  10. 206 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/AesUtil.java
  11. 11 4
      ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
  12. 132 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/SendSmsUtils.java
  13. 13 1
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
  14. 62 45
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
  15. 7 5
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java
  16. 2 0
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
  17. 2 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
  18. 5 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
  19. 11 0
      ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml

+ 53 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/SendSmsController.java

@@ -0,0 +1,53 @@
+package com.ruoyi.web.controller.common;
+
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginBody;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.SendSmsUtils;
+import com.ruoyi.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.concurrent.TimeUnit;
+
+import static com.ruoyi.common.constant.CommonConstants.LOGIN_USER_SMS;
+
+
+/**
+ * @Author: tjf
+ * @Date: 2024/03/05 17:15
+ * @Describe:
+ */
+@RestController
+@RequestMapping("/sendSms")
+public class SendSmsController {
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private ISysUserService sysUserService;
+
+    /**
+     * 发送登录短信接口
+     * @return
+     */
+    @PostMapping("/sendLoginSms")
+    @RepeatSubmit(interval = 1000, message = "请求过于频繁")
+    public AjaxResult sendLoginSms(@RequestBody LoginBody user) {
+        String code = SendSmsUtils.getCode(4);
+        String username = user.getUsername();
+        SysUser sysUser = sysUserService.selectUserByPhonenumber(username);
+        if (sysUser != null){
+            redisCache.setCacheObject(LOGIN_USER_SMS + username, code,5, TimeUnit.MINUTES);
+            String msg = SendSmsUtils.sendPassword(code, username);
+            System.out.println(username+"登录短信发送回复:"+msg);
+            return AjaxResult.success();
+        }
+        return AjaxResult.error("当前手机号不存在");
+
+    }
+}

+ 1 - 1
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@@ -46,7 +46,7 @@ public class SysLoginController
         AjaxResult ajax = AjaxResult.success();
         // 生成令牌
         String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
-                loginBody.getUuid());
+                loginBody.getUuid(),loginBody.getType());
         ajax.put(Constants.TOKEN, token);
         return ajax;
     }

+ 20 - 25
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java

@@ -1,5 +1,7 @@
 package com.ruoyi.web.controller.system;
 
+
+import com.ruoyi.common.utils.AesUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -23,17 +25,19 @@ import com.ruoyi.common.utils.file.MimeTypeUtils;
 import com.ruoyi.framework.web.service.TokenService;
 import com.ruoyi.system.service.ISysUserService;
 
+import java.util.Objects;
+
+import static com.ruoyi.common.constant.CommonConstants.LOGIN_PASSWORD_AES;
 import static com.ruoyi.common.constant.CommonConstants.ONE;
 
 /**
  * 个人信息 业务处理
- * 
+ *
  * @author ruoyi
  */
 @RestController
 @RequestMapping("/system/user/profile")
-public class SysProfileController extends BaseController
-{
+public class SysProfileController extends BaseController {
     @Autowired
     private ISysUserService userService;
 
@@ -44,8 +48,7 @@ public class SysProfileController extends BaseController
      * 个人信息
      */
     @GetMapping
-    public AjaxResult profile()
-    {
+    public AjaxResult profile() {
         LoginUser loginUser = getLoginUser();
         SysUser user = loginUser.getUser();
         AjaxResult ajax = AjaxResult.success(user);
@@ -59,24 +62,20 @@ public class SysProfileController extends BaseController
      */
     @Log(title = "个人信息", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult updateProfile(@RequestBody SysUser user)
-    {
+    public AjaxResult updateProfile(@RequestBody SysUser user) {
         LoginUser loginUser = getLoginUser();
         SysUser currentUser = loginUser.getUser();
         currentUser.setNickName(user.getNickName());
         currentUser.setEmail(user.getEmail());
         currentUser.setPhonenumber(user.getPhonenumber());
         currentUser.setSex(user.getSex());
-        if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser))
-        {
+        if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser)) {
             return error("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在");
         }
-        if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser))
-        {
+        if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser)) {
             return error("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在");
         }
-        if (userService.updateUserProfile(currentUser) > 0)
-        {
+        if (userService.updateUserProfile(currentUser) > 0) {
             // 更新缓存用户信息
             tokenService.setLoginUser(loginUser);
             return success();
@@ -88,17 +87,16 @@ public class SysProfileController extends BaseController
      * 重置密码
      */
     @Log(title = "个人信息", businessType = BusinessType.UPDATE)
-    @PutMapping("/updatePwd")
-    public AjaxResult updatePwd(String oldPassword, String newPassword)
-    {
+    @PostMapping("/updatePwd")
+    public AjaxResult updatePwd(String oldPassword, String newPassword) {
+        LoginUser loginUser = getLoginUser();
+        String userName = loginUser.getUsername();
+        String password = loginUser.getPassword();
         SysUser sysUser = new SysUser();
         sysUser.setPassword(newPassword);
         if (ONE.equals(userService.checkStrongPwd(sysUser))) {
             return AjaxResult.error("密码必须包含数字、大小写字母、特殊符号且大于8位");
         }
-        LoginUser loginUser = getLoginUser();
-        String userName = loginUser.getUsername();
-        String password = loginUser.getPassword();
         if (!SecurityUtils.matchesPassword(oldPassword, password))
         {
             return error("修改密码失败,旧密码错误");
@@ -123,14 +121,11 @@ public class SysProfileController extends BaseController
      */
     @Log(title = "用户头像", businessType = BusinessType.UPDATE)
     @PostMapping("/avatar")
-    public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception
-    {
-        if (!file.isEmpty())
-        {
+    public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception {
+        if (!file.isEmpty()) {
             LoginUser loginUser = getLoginUser();
             String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION);
-            if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
-            {
+            if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) {
                 AjaxResult ajax = AjaxResult.success();
                 ajax.put("imgUrl", avatar);
                 // 更新缓存用户头像

+ 3 - 4
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java

@@ -3,6 +3,8 @@ package com.ruoyi.web.controller.system;
 import java.util.List;
 import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.common.utils.AesUtil;
 import org.apache.commons.lang3.ArrayUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -32,6 +34,7 @@ import com.ruoyi.system.service.ISysPostService;
 import com.ruoyi.system.service.ISysRoleService;
 import com.ruoyi.system.service.ISysUserService;
 
+import static com.ruoyi.common.constant.CommonConstants.LOGIN_PASSWORD_AES;
 import static com.ruoyi.common.constant.CommonConstants.ONE;
 
 /**
@@ -134,10 +137,6 @@ public class SysUserController extends BaseController
         {
             return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
         }
-        else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
-        {
-            return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
-        }
         else if (ONE.equals(userService.checkStrongPwd(user))) {
             return AjaxResult.error("密码必须包含数字、大小写字母、特殊符号且大于8位");
         }

+ 1 - 1
ruoyi-admin/src/main/resources/application-druid.yml

@@ -7,7 +7,7 @@ ruoyi:
     # 版权年份
     copyrightYear: 2023
     # 实例演示开关
-    demoEnabled: true
+    demoEnabled: false
     # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
     profile: D:/ruoyi/uploadPath
     # 获取ip地址开关

+ 4 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/CommonConstants.java

@@ -32,5 +32,9 @@ public class CommonConstants {
      * 短信登录验证码前缀
      */
     public static final String LOGIN_USER_SMS = "login_user_sms:";
+    /**
+     * 密码加密16位秘钥
+     */
+    public static final String LOGIN_PASSWORD_AES = "qwertyuiopasdfgh";
 
 }

+ 1 - 1
ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java

@@ -73,6 +73,6 @@ public class UserConstants
     /**
      * 密码长度限制
      */
-    public static final int PASSWORD_MIN_LENGTH = 5;
+    public static final int PASSWORD_MIN_LENGTH = 8;
     public static final int PASSWORD_MAX_LENGTH = 20;
 }

+ 13 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java

@@ -27,6 +27,19 @@ public class LoginBody
      */
     private String uuid;
 
+    /**
+     * 登录模式:1:账号密码 2:短信验证码
+     */
+    private String type;
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
     public String getUsername()
     {
         return username;

+ 28 - 0
ruoyi-common/src/main/java/com/ruoyi/common/encoder/AesPasswordEncoder.java

@@ -0,0 +1,28 @@
+package com.ruoyi.common.encoder;
+
+import com.ruoyi.common.utils.AesUtil;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.util.Objects;
+
+import static com.ruoyi.common.constant.CommonConstants.LOGIN_PASSWORD_AES;
+
+/**
+ * @Author: tjf
+ * @Date: 2024/4/16 11:05
+ * @Describe:
+ */
+public class AesPasswordEncoder implements PasswordEncoder {
+    @Override
+    public String encode(CharSequence rawPassword) {
+        if (rawPassword == null) {
+            throw new IllegalArgumentException("rawPassword cannot be null");
+        }
+        return AesUtil.encryptCBC(rawPassword.toString(),LOGIN_PASSWORD_AES);
+    }
+
+    @Override
+    public boolean matches(CharSequence rawPassword, String encodedPassword) {
+        return Objects.equals(AesUtil.encryptCBC(rawPassword.toString(), LOGIN_PASSWORD_AES), encodedPassword);
+    }
+}

+ 206 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/AesUtil.java

@@ -0,0 +1,206 @@
+package com.ruoyi.common.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.util.Base64Utils;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import static com.ruoyi.common.constant.CommonConstants.LOGIN_PASSWORD_AES;
+
+/**
+ * @Author: tjf AES加密工具类
+ * @Date: 2024/4/16 10:09
+ * @Describe:
+ */
+public class AesUtil {
+    /**
+     * 日志相关
+     */
+    private static final Logger LOGGER = LoggerFactory.getLogger(AesUtil.class);
+    /**
+     * 编码
+     */
+    private static final String ENCODING = "UTF-8";
+    /**
+     * 算法定义
+     */
+    private static final String AES_ALGORITHM = "AES";
+    /**
+     * 指定填充方式
+     */
+    private static final String CIPHER_PADDING = "AES/ECB/PKCS5Padding";
+    private static final String CIPHER_CBC_PADDING = "AES/CBC/PKCS5Padding";
+    /**
+     * 偏移量(CBC中使用,增强加密算法强度)
+     */
+    private static final String IV_SEED = "1234567812345678";
+
+    /**
+     * AES加密
+     *
+     * @param content 待加密内容
+     * @param aesKey  密码
+     * @return
+     */
+    public static String encrypt(String content, String aesKey) {
+        if (StringUtils.isBlank(content)) {
+            LOGGER.info("AES encrypt: the content is null!");
+            return null;
+        }
+        //判断秘钥是否为16位
+        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
+            try {
+                //对密码进行编码
+                byte[] bytes = aesKey.getBytes(ENCODING);
+                //设置加密算法,生成秘钥
+                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
+                // "算法/模式/补码方式"
+                Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
+                //选择加密
+                cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
+                //根据待加密内容生成字节数组
+                byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
+                //返回base64字符串
+                return Base64Utils.encodeToString(encrypted);
+            } catch (Exception e) {
+                LOGGER.info("AES encrypt exception:" + e.getMessage());
+                throw new RuntimeException(e);
+            }
+
+        } else {
+            LOGGER.info("AES encrypt: the aesKey is null or error!");
+            return null;
+        }
+    }
+
+    /**
+     * 解密
+     *
+     * @param content 待解密内容
+     * @param aesKey  密码
+     * @return
+     */
+    public static String decrypt(String content, String aesKey) {
+        if (StringUtils.isBlank(content)) {
+            LOGGER.info("AES decrypt: the content is null!");
+            return null;
+        }
+        //判断秘钥是否为16位
+        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
+            try {
+                //对密码进行编码
+                byte[] bytes = aesKey.getBytes(ENCODING);
+                //设置解密算法,生成秘钥
+                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
+                // "算法/模式/补码方式"
+                Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
+                //选择解密
+                cipher.init(Cipher.DECRYPT_MODE, skeySpec);
+
+                //先进行Base64解码
+                byte[] decodeBase64 = Base64Utils.decodeFromString(content);
+
+                //根据待解密内容进行解密
+                byte[] decrypted = cipher.doFinal(decodeBase64);
+                //将字节数组转成字符串
+                return new String(decrypted, ENCODING);
+            } catch (Exception e) {
+                LOGGER.info("AES decrypt exception:" + e.getMessage());
+                throw new RuntimeException(e);
+            }
+
+        } else {
+            LOGGER.info("AES decrypt: the aesKey is null or error!");
+            return null;
+        }
+    }
+
+    /**
+     * AES_CBC加密
+     *
+     * @param content 待加密内容
+     * @param aesKey  密码
+     * @return
+     */
+    public static String encryptCBC(String content, String aesKey) {
+        if (StringUtils.isBlank(content)) {
+            LOGGER.info("AES_CBC encrypt: the content is null!");
+            return null;
+        }
+        //判断秘钥是否为16位
+        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
+            try {
+                //对密码进行编码
+                byte[] bytes = aesKey.getBytes(ENCODING);
+                //设置加密算法,生成秘钥
+                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
+                // "算法/模式/补码方式"
+                Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
+                //偏移
+                IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));
+                //选择加密
+                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
+                //根据待加密内容生成字节数组
+                byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
+                //返回base64字符串
+                return Base64Utils.encodeToString(encrypted);
+            } catch (Exception e) {
+                LOGGER.info("AES_CBC encrypt exception:" + e.getMessage());
+                throw new RuntimeException(e);
+            }
+
+        } else {
+            LOGGER.info("AES_CBC encrypt: the aesKey is null or error!");
+            return null;
+        }
+    }
+
+    /**
+     * AES_CBC解密
+     *
+     * @param content 待解密内容
+     * @param aesKey  密码
+     * @return
+     */
+    public static String decryptCBC(String content, String aesKey) {
+        if (StringUtils.isBlank(content)) {
+            LOGGER.info("AES_CBC decrypt: the content is null!");
+            return null;
+        }
+        //判断秘钥是否为16位
+        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
+            try {
+                //对密码进行编码
+                byte[] bytes = aesKey.getBytes(ENCODING);
+                //设置解密算法,生成秘钥
+                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
+                //偏移
+                IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));
+                // "算法/模式/补码方式"
+                Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
+                //选择解密
+                cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
+
+                //先进行Base64解码
+                byte[] decodeBase64 = Base64Utils.decodeFromString(content);
+
+                //根据待解密内容进行解密
+                byte[] decrypted = cipher.doFinal(decodeBase64);
+                //将字节数组转成字符串
+                return new String(decrypted, ENCODING);
+            } catch (Exception e) {
+                LOGGER.info("AES_CBC decrypt exception:" + e.getMessage());
+                throw new RuntimeException(e);
+            }
+        } else {
+            LOGGER.info("AES_CBC decrypt: the aesKey is null or error!");
+            return null;
+        }
+    }
+}
+

+ 11 - 4
ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java

@@ -3,6 +3,8 @@ package com.ruoyi.common.utils;
 import java.util.Collection;
 import java.util.List;
 import java.util.stream.Collectors;
+
+import com.ruoyi.common.encoder.AesPasswordEncoder;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@@ -13,6 +15,8 @@ import com.ruoyi.common.core.domain.entity.SysRole;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.exception.ServiceException;
 
+import static com.ruoyi.common.constant.CommonConstants.LOGIN_PASSWORD_AES;
+
 /**
  * 安全服务工具类
  * 
@@ -97,8 +101,9 @@ public class SecurityUtils
      */
     public static String encryptPassword(String password)
     {
-        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
-        return passwordEncoder.encode(password);
+        //BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        //return passwordEncoder.encode(password);
+        return AesUtil.encryptCBC(password,LOGIN_PASSWORD_AES);
     }
 
     /**
@@ -110,8 +115,10 @@ public class SecurityUtils
      */
     public static boolean matchesPassword(String rawPassword, String encodedPassword)
     {
-        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
-        return passwordEncoder.matches(rawPassword, encodedPassword);
+/*        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.matches(rawPassword, encodedPassword);*/
+        AesPasswordEncoder aesPasswordEncoder = new AesPasswordEncoder();
+        return aesPasswordEncoder.matches(rawPassword,encodedPassword);
     }
 
     /**

+ 132 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/SendSmsUtils.java

@@ -0,0 +1,132 @@
+package com.ruoyi.common.utils;
+
+
+import com.aliyun.dysmsapi20170525.models.SendBatchSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendBatchSmsResponse;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teautil.models.RuntimeOptions;
+
+/**
+ * @author tjf
+ * @Date: 2021/07/15/10:21
+ */
+public class SendSmsUtils {
+    //短信参数
+    static final String ACCESS_KEY_ID = "LTAI5tNA2fcBJH6EWRH6Pxr6";
+    static final String ACCESS_KEY_SECRET = "5WdaPEOvC3u9LC7pwy2DQ9pgmJvgUr";
+
+
+    //生成X位验证码
+    public static String getCode(Integer num) {
+        String[] codes = {"1", "2", "3", "4", "5", "6", "7", "8", "9"};
+        StringBuilder code = new StringBuilder();
+        for (int i = 0; i < num; i++) {
+            int j = (int) (Math.random() * 10);
+            if (j <= 0) {
+                j = 1;
+            }
+            code.append(codes[j - 1]);
+
+        }
+        return code.toString();
+    }
+
+    /**
+     * 使用AK&SK初始化账号Client
+     *
+     * @return Client
+     * @throws Exception
+     */
+    public static com.aliyun.dysmsapi20170525.Client createClient() throws Exception {
+        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
+            // 必填,您的 AccessKey ID
+            .setAccessKeyId(ACCESS_KEY_ID)
+            // 必填,您的 AccessKey Secret
+            .setAccessKeySecret(ACCESS_KEY_SECRET);
+        // 访问的域名
+        config.endpoint = "dysmsapi.aliyuncs.com";
+        return new com.aliyun.dysmsapi20170525.Client(config);
+    }
+
+
+    /**
+     * 发送短信消息
+     *
+     * @return
+     */
+    public static String sendSms(String phone, String templateCode, String smsCode) {
+        String code = "";
+        try {
+            // 工程代码泄露可能会导致AccessKey泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html
+            com.aliyun.dysmsapi20170525.Client client = SendSmsUtils.createClient();
+
+            com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
+                //手机号码
+                .setPhoneNumbers(phone)
+                //短信签名名称。中新云
+                .setSignName("中新云")
+                //短信模板变量对应的实际值{"name": code}
+                .setTemplateParam(smsCode)
+                //短信模板CODE
+                .setTemplateCode(templateCode);
+            // 复制代码运行请自行打印 API 的返回值
+            SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, new RuntimeOptions());
+            code = sendSmsResponse.getBody().code;
+        } catch (Exception _error) {
+        }
+        return code;
+    }
+
+    /**
+     * 阿里云批量发送 短信接口,一次最多100个手机号码
+     *
+     * @return
+     * @throws
+     */
+    public static SendBatchSmsResponse sendBatchSms(SendBatchSmsRequest sendBatchSmsRequest){
+        try {
+            com.aliyun.dysmsapi20170525.Client client = SendSmsUtils.createClient();
+            RuntimeOptions runtime = new RuntimeOptions();
+            SendBatchSmsResponse sendBatchSmsResponse = client.sendBatchSmsWithOptions(sendBatchSmsRequest, runtime);
+            return sendBatchSmsResponse;
+            // 复制代码运行请自行打印 API 的返回值
+        } catch (TeaException error) {
+            // 如有需要,请打印 error
+            com.aliyun.teautil.Common.assertAsString(error.message);
+        } catch (Exception _error) {
+            TeaException error = new TeaException(_error.getMessage(), _error);
+            // 如有需要,请打印 error
+            com.aliyun.teautil.Common.assertAsString(error.message);
+        }
+        return null;
+    }
+
+    /**
+     * 发送注册的随机密码
+     *
+     * @return
+     */
+    public static String sendPassword(String code, String phone) {
+        try {
+            // 工程代码泄露可能会导致AccessKey泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html
+            com.aliyun.dysmsapi20170525.Client client = SendSmsUtils.createClient();
+            String smsCode = "{\"code\":\"" + code + "\"}";
+            com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
+                //手机号码
+                .setPhoneNumbers(phone)
+                //短信签名名称。潜山市数据资源局
+                .setSignName("潜山市政协办公室")
+                //短信模板CODE
+                .setTemplateCode("SMS_219525380")
+                //短信模板变量对应的实际值{"name": code}
+                .setTemplateParam(smsCode);
+            // 复制代码运行请自行打印 API 的返回值
+            SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, new RuntimeOptions());
+            code = sendSmsResponse.getBody().code;
+        } catch (Exception _error) {
+        }
+        return code;
+    }
+}
+

+ 13 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java

@@ -1,5 +1,6 @@
 package com.ruoyi.framework.config;
 
+import com.ruoyi.common.encoder.AesPasswordEncoder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.http.HttpMethod;
@@ -137,12 +138,23 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
         return new BCryptPasswordEncoder();
     }
 
+
+    /**
+     * AES加密实现
+     */
+    @Bean
+    public AesPasswordEncoder aesPasswordEncoder()
+    {
+        return new AesPasswordEncoder();
+    }
+
     /**
      * 身份认证接口
      */
     @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception
     {
-        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
+        //auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
+        auth.userDetailsService(userDetailsService).passwordEncoder(aesPasswordEncoder());
     }
 }

+ 62 - 45
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java

@@ -1,6 +1,7 @@
 package com.ruoyi.framework.web.service;
 
 import javax.annotation.Resource;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
@@ -29,14 +30,16 @@ import com.ruoyi.framework.security.context.AuthenticationContextHolder;
 import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.system.service.ISysUserService;
 
+import static com.ruoyi.common.constant.CommonConstants.LOGIN_USER_SMS;
+import static com.ruoyi.common.constant.CommonConstants.TWO;
+
 /**
  * 登录校验方法
- * 
+ *
  * @author ruoyi
  */
 @Component
-public class SysLoginService
-{
+public class SysLoginService {
     @Autowired
     private TokenService tokenService;
 
@@ -45,7 +48,7 @@ public class SysLoginService
 
     @Autowired
     private RedisCache redisCache;
-    
+
     @Autowired
     private ISysUserService userService;
 
@@ -54,43 +57,39 @@ public class SysLoginService
 
     /**
      * 登录验证
-     * 
+     *
      * @param username 用户名
      * @param password 密码
-     * @param code 验证码
-     * @param uuid 唯一标识
+     * @param code     验证码
+     * @param uuid     唯一标识
      * @return 结果
      */
-    public String login(String username, String password, String code, String uuid)
-    {
+    public String login(String username, String password, String code, String uuid, String type) {
         // 验证码校验
         validateCaptcha(username, code, uuid);
+        //登录类型校验 如果是短信验证码登录,校验短信验证码是否正确
+        String passwordResult = validateType(username, type, code);
+        if (StringUtils.isNotEmpty(passwordResult)) {
+            password = passwordResult;
+        }
         // 登录前置校验
         loginPreCheck(username, password);
         // 用户验证
         Authentication authentication = null;
-        try
-        {
+        try {
             UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
             AuthenticationContextHolder.setContext(authenticationToken);
             // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
             authentication = authenticationManager.authenticate(authenticationToken);
-        }
-        catch (Exception e)
-        {
-            if (e instanceof BadCredentialsException)
-            {
+        } catch (Exception e) {
+            if (e instanceof BadCredentialsException) {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                 throw new UserPasswordNotMatchException();
-            }
-            else
-            {
+            } else {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                 throw new ServiceException(e.getMessage());
             }
-        }
-        finally
-        {
+        } finally {
             AuthenticationContextHolder.clearContext();
         }
         AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
@@ -102,27 +101,23 @@ public class SysLoginService
 
     /**
      * 校验验证码
-     * 
+     *
      * @param username 用户名
-     * @param code 验证码
-     * @param uuid 唯一标识
+     * @param code     验证码
+     * @param uuid     唯一标识
      * @return 结果
      */
-    public void validateCaptcha(String username, String code, String uuid)
-    {
+    public void validateCaptcha(String username, String code, String uuid) {
         boolean captchaEnabled = configService.selectCaptchaEnabled();
-        if (captchaEnabled)
-        {
+        if (captchaEnabled) {
             String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
             String captcha = redisCache.getCacheObject(verifyKey);
             redisCache.deleteObject(verifyKey);
-            if (captcha == null)
-            {
+            if (captcha == null) {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                 throw new CaptchaExpireException();
             }
-            if (!code.equalsIgnoreCase(captcha))
-            {
+            if (!code.equalsIgnoreCase(captcha)) {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                 throw new CaptchaException();
             }
@@ -131,35 +126,31 @@ public class SysLoginService
 
     /**
      * 登录前置校验
+     *
      * @param username 用户名
      * @param password 用户密码
      */
-    public void loginPreCheck(String username, String password)
-    {
+    public void loginPreCheck(String username, String password) {
         // 用户名或密码为空 错误
-        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
-        {
+        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
             throw new UserNotExistsException();
         }
         // 密码如果不在指定范围内 错误
         if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
-                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
-        {
+                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
             throw new UserPasswordNotMatchException();
         }
         // 用户名不在指定范围内 错误
         if (username.length() < UserConstants.USERNAME_MIN_LENGTH
-                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
-        {
+                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
             throw new UserPasswordNotMatchException();
         }
         // IP黑名单校验
         String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
-        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
-        {
+        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
             throw new BlackListException();
         }
@@ -170,12 +161,38 @@ public class SysLoginService
      *
      * @param userId 用户ID
      */
-    public void recordLoginInfo(Long userId)
-    {
+    public void recordLoginInfo(Long userId) {
         SysUser sysUser = new SysUser();
         sysUser.setUserId(userId);
         sysUser.setLoginIp(IpUtils.getIpAddr());
         sysUser.setLoginDate(DateUtils.getNowDate());
         userService.updateUserProfile(sysUser);
     }
+
+    /**
+     * 登录类型校验 如果是短信验证码登录,校验短信验证码是否正确,给密码 查询用户信息后赋值
+     *
+     * @param username 用户名
+     * @param type     登录类型
+     * @param code     短信验证码
+     */
+    public String validateType(String username, String type, String code) {
+        String password = null;
+        if (TWO.equals(type)) {
+            //校验短信验证码
+            Object cacheObject = redisCache.getCacheObject(LOGIN_USER_SMS + username);
+            if (code.equals(cacheObject)) {
+                //根据手机号查询用户信息
+                SysUser sysUser = userService.selectUserByPhonenumber(username);
+                if (sysUser == null) {
+                    throw new UserPasswordNotMatchException();
+                }
+                password = sysUser.getPassword();
+                redisCache.deleteObject(LOGIN_USER_SMS + username);
+            } else {
+                throw new CaptchaExpireException();
+            }
+        }
+        return password;
+    }
 }

+ 7 - 5
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java

@@ -1,5 +1,6 @@
 package com.ruoyi.framework.web.service;
 
+import com.ruoyi.common.core.domain.AjaxResult;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import com.ruoyi.common.constant.CacheConstants;
@@ -18,6 +19,8 @@ import com.ruoyi.framework.manager.factory.AsyncFactory;
 import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.system.service.ISysUserService;
 
+import static com.ruoyi.common.constant.CommonConstants.ONE;
+
 /**
  * 注册校验方法
  * 
@@ -59,16 +62,15 @@ public class SysRegisterService
         {
             msg = "用户密码不能为空";
         }
+        sysUser.setPassword(password);
+        if (ONE.equals(userService.checkStrongPwd(sysUser))) {
+            msg = "密码必须包含数字、大小写字母、特殊符号且大于8位";
+        }
         else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                 || username.length() > UserConstants.USERNAME_MAX_LENGTH)
         {
             msg = "账户长度必须在2到20个字符之间";
         }
-        else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
-                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
-        {
-            msg = "密码长度必须在5到20个字符之间";
-        }
         else if (!userService.checkUserNameUnique(sysUser))
         {
             msg = "保存用户'" + username + "'失败,注册账号已存在";

+ 2 - 0
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java

@@ -124,4 +124,6 @@ public interface SysUserMapper
      * @return 结果
      */
     public SysUser checkEmailUnique(String email);
+
+    public SysUser selectUserByPhonenumber(String userName);
 }

+ 2 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java

@@ -210,4 +210,6 @@ public interface ISysUserService
      * @return 结果
      */
     public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName);
+
+    public SysUser selectUserByPhonenumber(String phonenumber);
 }

+ 5 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

@@ -565,4 +565,9 @@ public class SysUserServiceImpl implements ISysUserService
         }
         return successMsg.toString();
     }
+
+    @Override
+    public SysUser selectUserByPhonenumber(String phonenumber) {
+        return userMapper.selectUserByPhonenumber(phonenumber);
+    }
 }

+ 11 - 0
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml

@@ -137,6 +137,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 	<select id="checkPhoneUnique" parameterType="String" resultMap="SysUserResult">
 		select user_id, phonenumber from sys_user where phonenumber = #{phonenumber} and del_flag = '0' limit 1
 	</select>
+
+	<select id="selectUserByPhonenumber" parameterType="String" resultMap="SysUserResult">
+		select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password,u.plaintext, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
+			   d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
+			   r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
+		from sys_user u
+				 left join sys_dept d on u.dept_id = d.dept_id
+				 left join sys_user_role ur on u.user_id = ur.user_id
+				 left join sys_role r on r.role_id = ur.role_id
+		where u.phonenumber = #{phonenumber} and u.del_flag = '0'
+	</select>
 	
 	<select id="checkEmailUnique" parameterType="String" resultMap="SysUserResult">
 		select user_id, email from sys_user where email = #{email} and del_flag = '0' limit 1