赞
踩
需求:用户在登录时密码经常使用明文传输,在不安全的网络环境下,很容易造成密码泄露,而密码直接存储在数据库中,如果数据泄露,也会造成安全问题。
解决方法:前端给后端传输密码关键信息时,进行加密后再传输,后端解密验证,然后将密码加密后再存储到数据库中。
实现思路:
采用RSA非对称加密加密和解密密码传输,采用哈希加盐算法加密密码并存储
1.前端需要传输密码时,先向服务器获取一个加密公钥(加密密钥对由后端生成,以公钥为key,私钥为value存储在redis中)。
2.获取到公钥后,将密码进行加密,并将加密后的密码以及公钥一起传给后端。
3.后端根据公钥从redis中得到配对的密钥,然后对密码解密。
4.将密码加盐后进行哈希加密,然后把加密的盐和加密后的密码一起存入数据库。
5.下次比较时,只需要比较密码+盐进行hash计算后的值是不是和数据库的加密密码相同即可。
代码:
RSA加密与解密:
- package com.zong.zongmail.config;
-
- import lombok.extern.slf4j.Slf4j;
- import org.apache.tomcat.util.codec.binary.Base64;
- import org.springframework.stereotype.Repository;
-
- import javax.crypto.Cipher;
- import java.nio.charset.StandardCharsets;
- import java.security.*;
- import java.security.spec.PKCS8EncodedKeySpec;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * @return
- * @Author: Zongmao on 2021/1/8 15:44
- * @description:非对称加密方法
- */
- @Slf4j
- @Repository
- public class RSAConfig {
-
- /**
- * @Author ZongMao
- * @Description //使用Java标准库提供的算法生成密钥对
- * @Date 2021/1/8 15:49
- **/
- public Map createKeyPair() throws GeneralSecurityException {
- // 生成公钥/私钥对:
- KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
- //它的密钥有256/512/1024/2048/4096等不同的长度。长度越长,密码强度越大,当然计算速度也越慢。
- //此处我设置256会报错:RSA keys must be at least 512 bits long
- kpGen.initialize(512);
- KeyPair kp = kpGen.generateKeyPair();
- PublicKey publicKey = kp.getPublic();
- PrivateKey privateKey = kp.getPrivate();
- //将公钥和私钥转换为base64编码便于存储和返回至前端
- byte[] publicKeyByte = publicKey.getEncoded();
- byte[] privateKeyByte = privateKey.getEncoded();
- String publicKeyStr = Base64.encodeBase64String(publicKeyByte);
- String privateKeyStr = Base64.encodeBase64String(privateKeyByte);
- Map map = new HashMap();
- map.put("publicKeyStr", publicKeyStr);
- map.put("privateKeyStr", privateKeyStr);
- return map;
- }
-
- /**
- * @Author ZongMao
- * @Description
- * 使用私钥解密(前端加密后传入的base64编码的信息,base64编码的私钥),
- * return 解密后的密码
- * @Date 2021/1/8 16:29
- **/
- public String decrypt(String passwordBase64, String privateKeyStr) throws GeneralSecurityException{
-
- Cipher cipher = Cipher.getInstance("RSA");
-
- //将Base64编码的私钥转为Private类型
- byte[] privateKeyByte = Base64.decodeBase64(privateKeyStr);
- KeyFactory kf = KeyFactory.getInstance("RSA"); // or "EC" or whatever
- PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(privateKeyByte));
-
- cipher.init(Cipher.DECRYPT_MODE, privateKey);
- //将base64编码的加密信息转为byte[]数据
- byte[] passwordByte = Base64.decodeBase64(passwordBase64);
- //获取解密后的byte[]数据
- byte[] resByte = cipher.doFinal(passwordByte);
- //将byte[]转为字符串,即为解密后的信息
- String resStr = new String(resByte, StandardCharsets.UTF_8);
- return resStr;
- }
-
- }

哈希加盐加密以及验证方法:
- package com.zongmao.schoolsystem.config;
-
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Repository;
-
- import java.io.UnsupportedEncodingException;
- import java.math.BigInteger;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
-
- /**
- * @return
- * @Author: Zongmao on 2021/1/8 16:56
- * @description:使用Md5对明文口令进行加密(加盐)
- */
- @Slf4j
- @Repository
- public class HashConfigWithSalt {
-
- /**
- * @Author ZongMao
- * @Description //加盐进行hash加密
- * @Date 2021/1/8 16:56
- **/
- public String HashWithSalt(String message, String salt) throws NoSuchAlgorithmException, UnsupportedEncodingException {
- // 创建一个MessageDigest实例:
- MessageDigest md = MessageDigest.getInstance("MD5");
- // 反复调用update输入数据:
- String endMessage = message + salt;
- md.update(endMessage.getBytes("UTF-8"));
- byte[] result = md.digest();
- return new BigInteger(1, result).toString(16);
- }
-
- /**
- * @Author ZongMao
- * @Description //比较是否相等(加密后的字符串,盐,需要验证的信息)
- * @Date 2021/1/8 16:59
- **/
- public boolean isCommon(String mdStr, String salt, String message) throws UnsupportedEncodingException, NoSuchAlgorithmException {
- String nowStr = HashWithSalt(message, salt);
- if (nowStr.equals(mdStr)){
- return true;
- }
- return false;
- }
- }

请求获取公钥:
- @PostMapping("/common/getPublicKey")
- public FormatResultData getPublicKey(){
- log.info("请求获取加密公钥");
- String publicKey = "";
- try {
- Map map = rsaConfig.createKeyPair();
- publicKey = (String) map.get("publicKeyStr");
- String privateKey = (String) map.get("privateKeyStr");
- //将publicKey作为key,privateKey作为value存入redis,并且设置过期时间
- stringRedisTemplate.opsForValue().set(publicKey, privateKey, 5 * 60 * 1000, TimeUnit.MILLISECONDS);
- }catch (GeneralSecurityException e){
- log.error(e.toString());
- return FormatResultData.error("获取公钥失败", 0);
- }catch (Exception e){
- log.info(e.toString());
- return FormatResultData.systemError();
- }
- return FormatResultData.success("获取公钥成功", publicKey);
- }

前端加密密码:
(使用jsencrypt)
- import {JSEncrypt} from 'jsencrypt'
- //加密
- export function encryptedData(publicKey, data) {
- let encryptor = new JSEncrypt();
- /*此处可以指定公钥的编码,也可以不指定*/
- encryptor.setPublicKey(publicKey, "base64");
- return encryptor.encrypt(data);
- }
-
- //解密
- export function decryptData(privateKey, data) {
- let decrypt = new JSEncrypt();
- decrypt.setPrivateKey(privateKey);
- return decrypt.decrypt(data);
- }
-
-
- //获取publicKey
- getPublicKey(){
- this.$http("/common/getPublicKey").then(res =>{
- if (res.code === 1){
- this.loginForm.publicKey = res.data;
- this.password = this.loginForm.password;
- this.loginForm.password = encryptedData(this.loginForm.publicKey, this.password);
- this.$http("/commonLogin/allUserLogin",this.loginForm).then(res =>{
- if (res.code === 1){
- this.Cookie.set("token", res.data, { expires: 1});
- this.$store.commit("changeUserType", this.loginForm.userType);
- localStorage.setItem("userName", res.extend);
- localStorage.setItem("account", this.loginForm.account);
- localStorage.setItem("userType", this.loginForm.userType);
- this.$router.push(this.fromPath);
- }else {
- this.loginForm.password = this.password;
- Message({
- message: res.msg,
- showClose: true,
- type:"error"
- });
- this.getCode();
- }
- }).catch(err =>{
- this.getCode();
- })
- }else {
- this.loginForm.publicKey = "";
- Message({
- message: "获取公钥失败,请稍后再试!",
- showClose: true,
- type:"error"
- });
- }
- }).catch(err =>{
-
- })
- },

注册新增账号时加密存储:
- /**
- * @Author ZongMao
- * @Description //新增管理员账号
- * @Date 2021/1/12 16:05
- **/
- @Override
- public String addAccount(AdminUser adminUser) throws Exception{
- String account = adminUser.getAccount();
- if (!account.equals("admin")){
- log.info("非法用户名!");
- throw new IllegalArgumentException("非法用户名");
- }
- QueryWrapper wrapper = new QueryWrapper();
- wrapper.eq("account", account);
- AdminUser hasUser = adminUserMapper.selectOne(wrapper);
- if (null != hasUser) {
- throw new IllegalArgumentException("管理员账号已存在");
- }
- String password = adminUser.getPassword(); //前端传的加密后的代码
- String publicKey = adminUser.getPublicKey(); //加密使用的公钥
- String privateKey = stringRedisTemplate.opsForValue().get(publicKey); //从redis中取出对应的密钥
- if (null == privateKey){
- throw new IllegalArgumentException("安全校验失败!");
- }
- String resPassword = rsaConfig.decrypt(password, privateKey); //使用密钥解密得到真实的密码
- String salt = randomConfig.randomData("", 6, false); //随机生成一个盐
- String hashPassword = hashConfigWithSalt.HashWithSalt(resPassword, salt); //把密码加盐后hash加密
- adminUser.setPassword(hashPassword); //把加密后的密码以及盐存入数据库
- adminUser.setSalt(salt);
- String id = UUID.randomUUID().toString();
- adminUser.setId(id);
- int addUser = adminUserMapper.insert(adminUser);
- if (addUser == 0){
- log.info("新增失败!");
- throw new IllegalArgumentException("新增失败!");
- }
- log.info("新增成功!");
- stringRedisTemplate.delete(publicKey);//新增成功后删除密钥对
- return "新增成功";
- }

在登录情况下进行验证:
- /**
- * @Author ZongMao
- * @Description //登录(adminUser)
- * @Date 2021/1/12 11:57
- **/
- @Override
- public String login(String account, String password) throws Exception{
- QueryWrapper wrapper = new QueryWrapper();
- wrapper.eq("account", account);
- AdminUser adminUser = adminUserMapper.selectOne(wrapper); //从数据库中查出对应的用户
- if (null == adminUser){
- throw new IllegalArgumentException("账号不存在!");
- }
- Date lockTime = adminUser.getLockTime();
- if (null != lockTime){
- lockAccount.checkLock(lockTime);
- }
- String salt = adminUser.getSalt(); //从数据库中得到盐
- String okPassword = adminUser.getPassword(); //数据库中存储的加密的密码
- boolean okLogin = hashConfigWithSalt.isCommon(okPassword, salt, password); //比较是否相等,password已经是解密了的
- if (!okLogin){
- boolean needLock = lockAccount.lockTime(account);
- if (needLock){
- Date date = new Date();
- date.setTime(date.getTime() + needLockTime * 60 * 1000);
- adminUser.setLockTime(date);
- UpdateWrapper<AdminUser> updateWrapper = new UpdateWrapper<>();
- updateWrapper.eq("account", account)
- .set("lock_time", date);
- int i = adminUserMapper.update(adminUser, wrapper);
- if (i == 1){
- throw new IllegalArgumentException("你的账号已被锁定,请在" + needLockTime + "分钟后重试");
- }
- throw new IllegalArgumentException("你已多次尝试登录失败,账号可能会被锁定");
- }
- throw new IllegalArgumentException("账号或密码错误!");
- }
- String userId = adminUser.getId();
- String token = tokenConfig.createToken(userId);
- return token;
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。