加密登录逻辑
实际的功能肯定是吃需求的。所以,这里假设两种需求:
情景一
用户登录要求能记住用户名密码,并且在某一段时间后过期。
情景二
同样要求记住密码,但不需要过期。
情景一是多数商业化的系统要求,情景二则可能是个人的小系统要求,满足自己可以(比如我?)
流程
输入信息 -> 勾选记住 -> 调用登录接口 -> 存储信息 -> 页面跳转(流程图软件出问题, 将就一下)
勾选记住及之前的都是用户操作步骤,这里不讨论怎么web实现,主要聊的是从调用接口校验登录信息到页面跳转之前的内容。
构思
主要考虑两个问题:存储信息和加密信息。
存储信息
情景一中记住用户名密码功能,想在一定时间后过期,又不要自己写额外的代码控制的话,最好的方式是将信息存在Cookie中;情景二中的话,localStorage就是不二之选了。
加密信息
要明白一个问题,加密完后的信息,是要能够解密的。比如,记住用户名密码后,下次登录读取出来密文,可以通过某种算法还原信息。
目前比较合适的一个用户名密码加解密算法是肯定就是RSA,靠谱!
为了保证接口传输信息在被劫持下来后没有作用,RSA依赖的公钥在每次请求时重新生成,逻辑即是调用登录接口前调用一个获取公钥的接口。
但是这只能用户接口信息传输上的加解密,存储信息没有很可靠的办法做到不被解密(因为存储的信息是在用户端上,而RSA需要的秘钥就没办法作为秘钥存放在用户端上,js代码又是公共资源),所以存储信息只能自我安慰的加密解密,这里就采用AES实现。
实现
加解密算法原理可以学习学习,代码咱就不自己写了,用用库就OK了,这里用到两个库:crypto-js和jsencrypt,前一个主要用来调用AES算法加解密存储信息(记住密码),后一个主要用来调用RSA算法加密(登录)接口传输信息。
shell
yarn add crypto-js jsencrypt
用户信息不过期
- 前端代码
js
import JSEncrypt from 'jsencrypt'
import aes from 'crypto-js/aes'
import encUtf8 from 'crypto-js/enc-utf8'
// AES依赖的key
const KEY = ':!vi_Vv&kijrUIxu*vV:EaK'
// 用户名
const NAME_KEY = 'olw5dgto8leiolz5'
// 密码
const PWD_KEY = 'kio7udc5utkcel0o'
// 是否记住
const CACKE_KEY = '2fgaobbcjkm0r59e'
const user = {
name: 'zbf',
password: '123456'
}
// 记住用户信息状态
const cache = true
// 读出存储的信息
const init = () => {
const _name = localStorage.getItem(NAME_KEY)
const _password = localStorage.getItem(PWD_KEY)
const _cache = localStorage.getItem(CACKE_KEY)
if (name && password) {
user.name = aes.decrypt(_name, KEY).toString(encUtf8)
user.password = aes.decrypt(_password, KEY).toString(encUtf8)
cache = !!_cache
}
}
// 本地存储用户信息
const store = () => {
// 判断是否记住用户信息
if(cache) {
localStorage.setItem(NAME_KEY, aes.encrypt(user.name, KEY).toString())
localStorage.setItem(PWD_KEY, aes.encrypt(user.password, KEY).toString())
// 记住用户信息状态
localStorage.setItem(CACKE_KEY, cache)
}
}
// validate已通过
// 获取公钥
axios.get('/pubKey').then(({ data }) => {
const encrypt = new JSEncrypt()
encrypt.setPublicKey(data.key)
// 尝试登陆
axios.post('/login', {
name: encrypt.encrypt(user.name),
password: encrypt.encrypt(user.password)
})
.then(({ data }) => {
// 登录成功,存储数据
data.code === 0 && store()
// 其他错误判断
})
})
- 服务端代码
服务端需要提供一个生成公钥的接口,一个根据私钥解密信息的接口,/pubKey接口生成公私钥,返回公钥,session存储私钥,/login接口获取session私钥,解密传输信息。
算法方法,这是我找到的网上的一个原文https://blog.csdn.net/qy20115549/article/details/83105736
java
package com.zhoubangfu.util;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class RSAEncrypt {
/**
* 随机生成密钥对
*/
public static Map<Integer, String> genKeyPair() throws NoSuchAlgorithmException {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到Map
Map<Integer, String> keyMap = new HashMap<>();
keyMap.put(0, publicKeyString); //0表示公钥
keyMap.put(1, privateKeyString); //1表示私钥
return keyMap;
}
/**
* RSA公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt(String str, String publicKey) throws Exception {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
}
/**
* RSA私钥解密
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 铭文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(String str, String privateKey) throws Exception {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
return new String(cipher.doFinal(inputByte));
}
}
获取公钥
java
private static final String PRI_KEY = "xxxxx";
@GetMapping("/pubKey")
public ResultUtil pubKey(HttpServletRequest request) throws NoSuchAlgorithmException {
HttpSession session = request.getSession();
Map<Integer, String> map = RSAEncrypt.genKeyPair();
session.setAttribute(PRI_KEY, map.get(1));
return ResultUtil.ok().put("data", map.get(0));
}
解密信息
java
@PostMapping("/login")
public ResultUtil login(@RequestBody UserModel user, HttpServletRequest request) throws Exception {
HttpSession session = request.getSession();
user.setUsername(RSAEncrypt.decrypt(user.getUsername(), (String) session.getAttribute(PRI_KEY)));
user.setPassword(Encrypt.SHA256(RSAEncrypt.decrypt(user.getPassword(), (String) session.getAttribute(PRI_KEY))));
// 验证逻辑
return ResultUtil.ok();
}
用户信息过期
改用Cookie存数据就可以了~~
结束
demo代码写的比较快,就没有优化了,勿喷
![[微笑]](/face/0.gif)
![[嘻嘻]](/face/1.gif)
![[哈哈]](/face/2.gif)
![[可爱]](/face/3.gif)
![[可怜]](/face/4.gif)
![[挖鼻]](/face/5.gif)
![[吃惊]](/face/6.gif)
![[害羞]](/face/7.gif)
![[挤眼]](/face/8.gif)
![[闭嘴]](/face/9.gif)
![[鄙视]](/face/10.gif)
![[爱你]](/face/11.gif)
![[泪]](/face/12.gif)
![[偷笑]](/face/13.gif)
![[亲亲]](/face/14.gif)
![[生病]](/face/15.gif)
![[太开心]](/face/16.gif)
![[白眼]](/face/17.gif)
![[右哼哼]](/face/18.gif)
![[左哼哼]](/face/19.gif)
![[嘘]](/face/20.gif)
![[衰]](/face/21.gif)
![[委屈]](/face/22.gif)
![[吐]](/face/23.gif)
![[哈欠]](/face/24.gif)
![[抱抱]](/face/25.gif)
![[怒]](/face/26.gif)
![[疑问]](/face/27.gif)
![[馋嘴]](/face/28.gif)
![[拜拜]](/face/29.gif)
![[思考]](/face/30.gif)
![[汗]](/face/31.gif)
![[困]](/face/32.gif)
![[睡]](/face/33.gif)
![[钱]](/face/34.gif)
![[失望]](/face/35.gif)
![[酷]](/face/36.gif)
![[色]](/face/37.gif)
![[哼]](/face/38.gif)
![[鼓掌]](/face/39.gif)
![[晕]](/face/40.gif)
![[悲伤]](/face/41.gif)
![[抓狂]](/face/42.gif)
![[黑线]](/face/43.gif)
![[阴险]](/face/44.gif)
![[怒骂]](/face/45.gif)
![[互粉]](/face/46.gif)
![[心]](/face/47.gif)
![[伤心]](/face/48.gif)
![[猪头]](/face/49.gif)
![[熊猫]](/face/50.gif)
![[兔子]](/face/51.gif)
![[ok]](/face/52.gif)
![[耶]](/face/53.gif)
![[good]](/face/54.gif)
![[NO]](/face/55.gif)
![[赞]](/face/56.gif)
![[来]](/face/57.gif)
![[弱]](/face/58.gif)
![[草泥马]](/face/59.gif)
![[神马]](/face/60.gif)
![[囧]](/face/61.gif)
![[浮云]](/face/62.gif)
![[给力]](/face/63.gif)
![[围观]](/face/64.gif)
![[威武]](/face/65.gif)
![[奥特曼]](/face/66.gif)
![[礼物]](/face/67.gif)
![[钟]](/face/68.gif)
![[话筒]](/face/69.gif)
![[蜡烛]](/face/70.gif)
![[蛋糕]](/face/71.gif)