当前位置:   article > 正文

系列一、RuoYi前后端分离(登录密码加密)_若依密码加密方式

若依密码加密方式

一、部署前后端服务

http://doc.ruoyi.vip/ruoyi-vue/

二、现象

        若依前后端环境分离版本,本地部署好前后端环境后,访问登录接口密码是明文的,这样显然是不安全的,如下图所示:

 三、解决方法

3.1、加密流程

①、后端生成随机公钥和私钥;

②、前端拿到公钥,集成jsencrypt实现密码加密;

③、前端传输加密后的秘密给后端;

④、后端通过私钥对加密后的秘钥进行解密,验证密码

3.2、若依加密方式

https://doc.ruoyi.vip/ruoyi-vue/document/cjjc.html#%E9%9B%86%E6%88%90jsencrypt%E5%AE%9E%E7%8E%B0%E5%AF%86%E7%A0%81%E5%8A%A0%E5%AF%86%E4%BC%A0%E8%BE%93%E6%96%B9%E5%BC%8F

3.3、后端代码修改

3.3.1、新建RSAUtil

  1. package com.ruoyi.business.utils;
  2. import org.apache.commons.codec.binary.Base64;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.stereotype.Component;
  5. import javax.crypto.Cipher;
  6. import java.security.*;
  7. import java.security.interfaces.RSAPrivateKey;
  8. import java.security.interfaces.RSAPublicKey;
  9. import java.security.spec.PKCS8EncodedKeySpec;
  10. import java.security.spec.X509EncodedKeySpec;
  11. @Component
  12. public class RSAUtil {
  13. // Rsa 私钥 也可固定秘钥对 若依原写法(不安全)
  14. public static String privateKeys = "";
  15. private static String publicKeyStr = "";
  16. private static String privateKeyStr = "";
  17. private static final RSAKeyPair rsaKeyPair = new RSAKeyPair();
  18. /**
  19. * 私钥解密
  20. *
  21. * @param text 待解密的文本
  22. * @return 解密后的文本
  23. */
  24. public static String decryptByPrivateKey(String text) throws Exception {
  25. return decryptByPrivateKey(rsaKeyPair.getPrivateKey(), text);
  26. }
  27. /**
  28. * 公钥解密
  29. *
  30. * @param publicKeyString 公钥
  31. * @param text 待解密的信息
  32. * @return 解密后的文本
  33. */
  34. public static String decryptByPublicKey(String publicKeyString, String text) throws Exception {
  35. X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));
  36. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  37. PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
  38. Cipher cipher = Cipher.getInstance("RSA");
  39. cipher.init(Cipher.DECRYPT_MODE, publicKey);
  40. byte[] result = cipher.doFinal(Base64.decodeBase64(text));
  41. return new String(result);
  42. }
  43. /**
  44. * 私钥加密
  45. *
  46. * @param privateKeyString 私钥
  47. * @param text 待加密的信息
  48. * @return 加密后的文本
  49. */
  50. public static String encryptByPrivateKey(String privateKeyString, String text) throws Exception {
  51. PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));
  52. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  53. PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
  54. Cipher cipher = Cipher.getInstance("RSA");
  55. cipher.init(Cipher.ENCRYPT_MODE, privateKey);
  56. byte[] result = cipher.doFinal(text.getBytes());
  57. return Base64.encodeBase64String(result);
  58. }
  59. /**
  60. * 私钥解密
  61. *
  62. * @param privateKeyString 私钥
  63. * @param text 待解密的文本
  64. * @return 解密后的文本
  65. */
  66. public static String decryptByPrivateKey(String privateKeyString, String text) throws Exception {
  67. PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));
  68. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  69. PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
  70. Cipher cipher = Cipher.getInstance("RSA");
  71. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  72. byte[] result = cipher.doFinal(Base64.decodeBase64(text));
  73. return new String(result);
  74. }
  75. /**
  76. * 公钥加密
  77. *
  78. * @param publicKeyString 公钥
  79. * @param text 待加密的文本
  80. * @return 加密后的文本
  81. */
  82. public static String encryptByPublicKey(String publicKeyString, String text) throws Exception {
  83. X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));
  84. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  85. PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
  86. Cipher cipher = Cipher.getInstance("RSA");
  87. cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  88. byte[] result = cipher.doFinal(text.getBytes());
  89. return Base64.encodeBase64String(result);
  90. }
  91. /**
  92. * 构建RSA密钥对
  93. *
  94. * @return 生成后的公私钥信息
  95. */
  96. @Bean
  97. public void generateKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException {
  98. KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
  99. keyPairGenerator.initialize(1024);
  100. KeyPair keyPair = keyPairGenerator.generateKeyPair();
  101. RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
  102. RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
  103. String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
  104. String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
  105. rsaKeyPair.setPrivateKey(privateKeyString);
  106. rsaKeyPair.setPublicKey(publicKeyString);
  107. publicKeyStr = publicKeyString;
  108. privateKeyStr = privateKeyString;
  109. }
  110. public static String getPublicKey() {
  111. return publicKeyStr;
  112. }
  113. public static String getPrivateKey() {
  114. return privateKeyStr;
  115. }
  116. public static RSAKeyPair rsaKeyPair() {
  117. return rsaKeyPair;
  118. }
  119. /**
  120. * RSA密钥对对象
  121. */
  122. public static class RSAKeyPair {
  123. private String publicKey;
  124. private String privateKey;
  125. public void setPublicKey(String publicKey) {
  126. this.publicKey = publicKey;
  127. }
  128. public void setPrivateKey(String privateKey) {
  129. this.privateKey = privateKey;
  130. }
  131. public RSAKeyPair() {
  132. }
  133. public RSAKeyPair(String publicKey, String privateKey) {
  134. this.publicKey = publicKey;
  135. this.privateKey = privateKey;
  136. }
  137. public String getPublicKey() {
  138. return publicKey;
  139. }
  140. public String getPrivateKey() {
  141. return privateKey;
  142. }
  143. }
  144. }

说明:

        RSAUtil中添加了@Component注解,generateKeyPair()构建秘钥对添加了@Bean注解,在项目启动时通过@Bean的方式将普通类实例化到Spring容器中,所以当系统启动后,每次调用接口得到的公钥&私钥是一样的,服务重启后,公钥&私钥重新生成。

3.3.2、SysLoginController提供获取公钥接口

  1. /**
  2. * 获取公钥:前端用来密码加密
  3. * @return
  4. */
  5. @GetMapping("/getPublicKey")
  6. public RSAUtil.RSAKeyPair getPublicKey() {
  7. return RSAUtil.rsaKeyPair();
  8. }

3.3.3、SecurityConfig配置白名单

.antMatchers("/getPublicKey").permitAll()

3.3.4、Postman调用接口获取publicKey 

 3.3.5、SysLoginService.java#login

  1. /**
  2. * 登录验证
  3. *
  4. * @param username 用户名
  5. * @param password 密码
  6. * @param code 验证码
  7. * @param uuid 唯一标识
  8. * @return 结果
  9. */
  10. public String login(String username, String password, String code, String uuid)
  11. {
  12. // 验证码校验
  13. // validateCaptcha(username, code, uuid);
  14. // 登录前置校验
  15. loginPreCheck(username, password);
  16. // 用户验证
  17. Authentication authentication = null;
  18. try
  19. {
  20. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,RSAUtil.decryptByPrivateKey(password));
  21. AuthenticationContextHolder.setContext(authenticationToken);
  22. // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
  23. authentication = authenticationManager.authenticate(authenticationToken);
  24. }
  25. catch (Exception e)
  26. {
  27. if (e instanceof BadCredentialsException)
  28. {
  29. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
  30. throw new UserPasswordNotMatchException();
  31. }
  32. else
  33. {
  34. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
  35. throw new ServiceException(e.getMessage());
  36. }
  37. }
  38. finally
  39. {
  40. AuthenticationContextHolder.clearContext();
  41. }
  42. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
  43. LoginUser loginUser = (LoginUser) authentication.getPrincipal();
  44. recordLoginInfo(loginUser.getUserId());
  45. // 生成token
  46. return tokenService.createToken(loginUser);
  47. }

 3.3.6、SysProfileController重置密码接口修改

  1. @Log(title = "个人信息", businessType = BusinessType.UPDATE)
  2. @PutMapping("/updatePwd")
  3. public AjaxResult updatePwd(String oldPassword, String newPassword) throws Exception {
  4. LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
  5. String userName = loginUser.getUsername();
  6. //加密后的
  7. String password = loginUser.getPassword();
  8. //解密
  9. oldPassword = RSAUtil.decryptByPrivateKey(oldPassword);
  10. newPassword = RSAUtil.decryptByPrivateKey(newPassword);
  11. //拿原密码和加密后的解密
  12. if (!SecurityUtils.matchesPassword(oldPassword, password)) {
  13. return AjaxResult.error("修改密码失败,旧密码错误");
  14. }
  15. if (SecurityUtils.matchesPassword(newPassword, password)) {
  16. return AjaxResult.error("新密码不能与旧密码相同");
  17. }
  18. if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) {
  19. // 更新缓存用户密码
  20. loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword));
  21. tokenService.setLoginUser(loginUser);
  22. return AjaxResult.success();
  23. }
  24. return AjaxResult.error("修改密码异常,请联系管理员");
  25. }

==========================O(∩_∩)O 后端修改over O(∩_∩)O==========================

3.4、前端代码修改

3.4.1、login.js添加获取公钥的接口

  1. import request from '@/utils/request'
  2. // 获取公钥
  3. export function getPublicKey() {
  4. return request({
  5. url: '/getPublicKey',
  6. method: 'get',
  7. })
  8. }
  9. // 登录方法
  10. export function login(username, password, code, uuid) {
  11. const data = {
  12. username,
  13. password,
  14. code,
  15. uuid
  16. }
  17. return request({
  18. url: '/login',
  19. headers: {
  20. isToken: false
  21. },
  22. method: 'post',
  23. data: data
  24. })
  25. }
  26. // 注册方法
  27. export function register(data) {
  28. return request({
  29. url: '/register',
  30. headers: {
  31. isToken: false
  32. },
  33. method: 'post',
  34. data: data
  35. })
  36. }
  37. // 获取用户详细信息
  38. export function getInfo() {
  39. return request({
  40. url: '/getInfo',
  41. method: 'get'
  42. })
  43. }
  44. // 退出方法
  45. export function logout() {
  46. return request({
  47. url: '/logout',
  48. method: 'post'
  49. })
  50. }
  51. // 获取验证码
  52. export function getCodeImg() {
  53. return request({
  54. url: '/captchaImage',
  55. headers: {
  56. isToken: false
  57. },
  58. method: 'get',
  59. timeout: 20000
  60. })
  61. }

3.4.2、user.js修改登录方法

  1. import { getToken, setToken, removeToken } from '@/utils/auth'
  2. import { login, logout, getInfo, getPublicKey } from '@/api/login'
  3. import {encrypt} from "../../utils/jsencrypt";
  4. const user = {
  5. state: {
  6. token: getToken(),
  7. name: '',
  8. avatar: '',
  9. roles: [],
  10. permissions: []
  11. },
  12. mutations: {
  13. SET_TOKEN: (state, token) => {
  14. state.token = token
  15. },
  16. SET_NAME: (state, name) => {
  17. state.name = name
  18. },
  19. SET_AVATAR: (state, avatar) => {
  20. state.avatar = avatar
  21. },
  22. SET_ROLES: (state, roles) => {
  23. state.roles = roles
  24. },
  25. SET_PERMISSIONS: (state, permissions) => {
  26. state.permissions = permissions
  27. }
  28. },
  29. actions: {
  30. getPublicKey() {
  31. return new Promise((resolve, reject) => {
  32. getPublicKey()
  33. .then(res => {
  34. resolve(res)
  35. })
  36. .catch(error => {
  37. reject(error)
  38. })
  39. })
  40. },
  41. // 登录
  42. Login({ commit, dispatch }, userInfo) {
  43. return new Promise((resolve, reject) => {
  44. dispatch('getPublicKey').then(res => {
  45. let publicKey = res.publicKey
  46. const username = userInfo.username.trim()
  47. //调用加密方法(传密码和公钥)
  48. const password = encrypt(userInfo.password, publicKey)
  49. const code = userInfo.code
  50. const uuid = userInfo.uuid
  51. login(username, password, code, uuid)
  52. .then(res => {
  53. setToken(res.token)
  54. commit('SET_TOKEN', res.token)
  55. resolve()
  56. })
  57. .catch(error => {
  58. reject(error)
  59. })
  60. })
  61. })
  62. },
  63. // 获取用户信息
  64. GetInfo({ commit, state }) {
  65. return new Promise((resolve, reject) => {
  66. getInfo().then(res => {
  67. const user = res.user
  68. const avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;
  69. if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
  70. commit('SET_ROLES', res.roles)
  71. commit('SET_PERMISSIONS', res.permissions)
  72. } else {
  73. commit('SET_ROLES', ['ROLE_DEFAULT'])
  74. }
  75. commit('SET_NAME', user.userName)
  76. commit('SET_AVATAR', avatar)
  77. resolve(res)
  78. }).catch(error => {
  79. reject(error)
  80. })
  81. })
  82. },
  83. // 退出系统
  84. LogOut({ commit, state }) {
  85. return new Promise((resolve, reject) => {
  86. logout(state.token).then(() => {
  87. commit('SET_TOKEN', '')
  88. commit('SET_ROLES', [])
  89. commit('SET_PERMISSIONS', [])
  90. removeToken()
  91. resolve()
  92. }).catch(error => {
  93. reject(error)
  94. })
  95. })
  96. },
  97. // 前端 登出
  98. FedLogOut({ commit }) {
  99. return new Promise(resolve => {
  100. commit('SET_TOKEN', '')
  101. removeToken()
  102. resolve()
  103. })
  104. }
  105. }
  106. }
  107. export default user

 

注意事项:注意上方的导包

 3.4.3、修改resetPwd.vue

  1. <template>
  2. <el-form ref="form" :model="user" :rules="rules" label-width="80px">
  3. <el-form-item label="旧密码" prop="oldPassword">
  4. <el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password/>
  5. </el-form-item>
  6. <el-form-item label="新密码" prop="newPassword">
  7. <el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password/>
  8. </el-form-item>
  9. <el-form-item label="确认密码" prop="confirmPassword">
  10. <el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
  11. </el-form-item>
  12. <el-form-item>
  13. <el-button type="primary" size="mini" @click="submit">保存</el-button>
  14. <el-button type="danger" size="mini" @click="close">关闭</el-button>
  15. </el-form-item>
  16. </el-form>
  17. </template>
  18. <script>
  19. import { updateUserPwd } from "@/api/system/user";
  20. import { getPublicKey } from '@/api/login';
  21. import { encrypt } from '@/utils/jsencrypt';
  22. export default {
  23. data() {
  24. const equalToPassword = (rule, value, callback) => {
  25. if (this.user.newPassword !== value) {
  26. callback(new Error("两次输入的密码不一致"));
  27. } else {
  28. callback();
  29. }
  30. };
  31. return {
  32. user: {
  33. oldPassword: undefined,
  34. newPassword: undefined,
  35. confirmPassword: undefined
  36. },
  37. // 表单校验
  38. rules: {
  39. oldPassword: [
  40. { required: true, message: "旧密码不能为空", trigger: "blur" }
  41. ],
  42. newPassword: [
  43. { required: true, message: "新密码不能为空", trigger: "blur" },
  44. { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }
  45. ],
  46. confirmPassword: [
  47. { required: true, message: "确认密码不能为空", trigger: "blur" },
  48. { required: true, validator: equalToPassword, trigger: "blur" }
  49. ]
  50. }
  51. };
  52. },
  53. methods: {
  54. getPublicKey() {
  55. return new Promise((resolve, reject) => {
  56. getPublicKey()
  57. .then(res => {
  58. resolve(res)
  59. })
  60. .catch(error => {
  61. reject(error)
  62. })
  63. })
  64. },
  65. submit() {
  66. this.$refs["form"].validate(valid => {
  67. if (valid) {
  68. this.getPublicKey().then(res=>{
  69. let publicKey = res.publicKey
  70. console.log("res.publicKey",res.publicKey)
  71. const oldPassword = encrypt(this.user.oldPassword, publicKey)
  72. const newPassword = encrypt(this.user.newPassword, publicKey)
  73. updateUserPwd(oldPassword, newPassword).then(
  74. response => {
  75. this.msgSuccess("修改成功");
  76. }
  77. );
  78. })
  79. }
  80. });
  81. },
  82. close() {
  83. this.$tab.closePage();
  84. }
  85. }
  86. };
  87. </script>

3.4.4、jsencrypt.js更改加密方法

  1. import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
  2. // 密钥对生成 http://web.chacuo.net/netrsakeypair
  3. //这里注掉了原来固定的公私钥
  4. //const publicKey = ''
  5. //const privateKey = ''
  6. // 加密
  7. export function encrypt(txt, publicKey) {
  8. const encryptor = new JSEncrypt()
  9. encryptor.setPublicKey(publicKey) // 设置公钥
  10. return encryptor.encrypt(txt) // 对数据
  11. }
  12. // 解密(暂无使用)
  13. export function decrypt(txt) {
  14. const encryptor = new JSEncrypt()
  15. encryptor.setPrivateKey(privateKey) // 设置私钥
  16. return encryptor.decrypt(txt) // 对数据进行解密
  17. }

四、启动后端&前端服务,验证登录密码是否加密

五、参考

https://blog.csdn.net/weixin_56567361/article/details/124961493

 六、完整代码

  1. 链接:https://pan.baidu.com/s/1YKnAcixZGn5zeUkSti1DCA?pwd=yyds
  2. 提取码:yyds

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/581654
推荐阅读
相关标签
  

闽ICP备14008679号