M
SAAS权限管理系统| 密码RSA前端加密,后端解密
一、简介
用户进行登录时,需要输入明文密码,在传输过程中,有可能被拦截获取,因此使用前端rsa加密,后端rsa解密,使得密码以密文的形式进行传输,增强安全性。
策略:
- 登录前调用后端获取公钥接口,获取公钥,使用公钥加密密码后,在进行传输
- RSA秘钥对一天生成一对,并生成对应RSA标识,RSA标识与秘钥对一一对应。
- RSA秘钥对有效期比RSA标识有效期多一个小时,目的是为了防止前端刚获取公钥,结果后端密钥对整个就失效了,每次通过获取RSA来进行判断秘钥是否已经失效,即使RSA标识已经失效,至少RSA秘钥对还能用一个小时。
二、后端
- 使用的hutool-crypto工具包,
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.5.0</version>
</dependency>
- 使用redis缓存rsa秘钥对
@Service
public class CommonServiceImpl implements CommonService {
@Autowired
private RedisUtils redisUtils;
// 获取公钥
@Override
public Map<String, Object> getPublicKey() {
// 从redis中获取 rsa标识
String rsaTag = redisUtils.get(RedisConstants.RSA_TAG, String.class);
String publicKeyBase64 = null;
Long expire = 60 * 60 * 24L;
if (rsaTag == null) {
// rsa标识为空,生成新的rsa,有效期25个小时,生成rsa标识,有效期24个小时
// 差一个小时的原因是为了防止 刚获取到公钥,停了一会,然后公钥就失效了
rsaTag = NCUtils.getUUID().substring(0,8);
RSA rsa = new RSA();
publicKeyBase64 = rsa.getPublicKeyBase64();
String privateKeyBase64 = rsa.getPrivateKeyBase64();
redisUtils.set(RedisConstants.RSA_TAG, rsaTag, 60 * 60 * 24);
redisUtils.set(RedisConstants.RSA_PUBLIC_KEY + rsaTag, publicKeyBase64, 60 * 60 * 25);
redisUtils.set(RedisConstants.RSA_PRIVATE_KEY + rsaTag, privateKeyBase64, 60 * 60 * 25);
} else {
// 根据rsa标识从redis中获取公钥
publicKeyBase64 = redisUtils.get(RedisConstants.RSA_PUBLIC_KEY + rsaTag, String.class);
expire = redisUtils.getExpire(RedisConstants.RSA_TAG);
}
Map<String, Object> map = new HashMap<>();
map.put("rsaTag", rsaTag);
map.put("publicKeyBase64", publicKeyBase64);
map.put("expire", expire);
return map;
}
// 根据rsaTag获取私钥
@Override
public String getPrivateKey(String rsaTag) {
NCUtils.nullOrEmptyThrow(rsaTag);
String privateKeyBase64 = redisUtils.get(RedisConstants.RSA_PRIVATE_KEY + rsaTag, String.class);
if (privateKeyBase64 == null) {
throw new NCException("-1", "RSA已失效,请重新生成获取!");
}
return privateKeyBase64;
}
// 根据rsaTag和密文解密,返回明文
@Override
public String rsaDecrypt(String rsaTag, String mw) {
NCUtils.nullOrEmptyThrow(rsaTag);
NCUtils.nullOrEmptyThrow(mw);
String privateKeyBase64 = getPrivateKey(rsaTag);
RSA rsa = new RSA(privateKeyBase64, null);
// 密码的密文先进行base64解码,之后再进行解密
byte[] decrypt = rsa.decrypt(Base64.decode(mw), KeyType.PrivateKey);
return StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8);
}
}
- 获取公钥,将rsa标识存在cookie中,并设置过期时间
@RestController
@RequestMapping("/common")
public class CommonController {
@Autowired
private CommonService commonService;
@PostMapping("/getPublicKey")
public NCResult<String> getKeyOfRSA(HttpServletResponse response) {
Map<String, Object> map = commonService.getPublicKey();
Cookie rsaTag = new Cookie(HeaderConstants.RSA_TAG, map.get("rsaTag").toString());
rsaTag.setMaxAge(Integer.valueOf(map.get("expire").toString()));
rsaTag.setPath("/");
response.addCookie(rsaTag);
return NCResult.ok(map.get("publicKeyBase64"));
}
}
- 登录进行密码解密
@Autowired
private LoginService loginService;
@Autowired
private CommonService commonService;
@ApiAnnotation(modularName = "用户登陆", description = "用户登陆")
@PostMapping("/login")
public NCResult<NCLoginUserVO> login(@RequestBody LoginVO loginVO, HttpServletResponse response) {
// 登陆验证
loginVO.setPassword(commonService.rsaDecrypt(loginVO.getRsaTag(), loginVO.getPassword()));
NCLoginUserVO token = loginService.login(loginVO);
return NCResult.ok(token);
}
三、前端
- 使用node-forge进行加密
npm install node-forge
- 自己封装的从cookie中获取数据的方法
// 菜单工具类
const MenuUtils = {
// 设置cookie
setCookie (key, value, expire) {
var date = new Date()
date.setSeconds(date.getSeconds() + expire)
document.cookie = key + '=' + escape(value) + '; expires=' + date.toGMTString()
},
// 获取cookie
getCookie (name) {
if (document.cookie.length > 0) {
let start = document.cookie.indexOf(name + '=')
if (start !== -1) {
start = start + name.length + 1
let end = document.cookie.indexOf(';', start)
if (end === -1) end = document.cookie.length
return unescape(document.cookie.substring(start, end))
}
}
return ''
},
// 删除cookie
delCookie (name) {
this.setCookie(name, '', -1)
}
}
export {
MenuUtils
}
- 登录时密码加密
import * as Forge from 'node-forge'
operateApi.getPublicKey({}).then((pubRes) => {
const pki = Forge.pki
// 规定格式:publicKey之前需要加'-----BEGIN PUBLIC KEY-----\n',之后需要加'\n-----END PUBLIC KEY-----'
const publicK = pki.publicKeyFromPem('-----BEGIN PUBLIC KEY-----\n' + pubRes.rows[0] + '\n-----END PUBLIC KEY-----');
// forge通过公钥加密后一般会是乱码格式,可进行base64编码操作再进行传输,相应的,后台获取到密文的密码后需要先进行base64解码操作再进行解密
const password = Forge.util.encode64(publicK.encrypt(that.loginParm.password))
operateApi.userLogin({
account: that.loginParm.username,
password: password,
rsaTag: MenuUtils.getCookie('RSA-TAG')
}).then((res) => {
if (res.success) {
} else {
}
})
})