在项目安全扫描中,发现仅采用秘钥加密的形式存在安全隐患。
如果秘钥泄露,那每一次的会话及请求的报文,则无异于暴露在外。
这种现象虽不太会发生,但秘钥一旦泄露,或被恶意攻破,会导致客户信息泄露,后果是很严重的。
AES是对称加密,而RSA是非对称加密。
如果单采用AES对报文加密,虽然可行,但是存在风险,于是就想到了采用组合加密的形式。
具体思路:前后端互相交换RSA公钥、私钥。然后再进行AES秘钥交换。
目的:为每一次的会话,生成只有会话的前后端知道的AES秘钥,会话与会话之前秘钥不相同。
大致如下图:
参考链接:https://blog.csdn.net/qq_38254635/article/details/129622075
参考链接:https://blog.csdn.net/qq_38254635/article/details/129623413
参考链接:https://blog.csdn.net/qq_38254635/article/details/129735679
参考链接:https://blog.csdn.net/qq_38254635/article/details/129746320
关键代码:
public Result getVerifyCode() throws IOException {
String verifyCode = VerifyCodeUtils.generateVerifyCode(4);
log.info("The current verification code is:" + verifyCode + "\n");
String verifyKey = SessionUtil.getUUId();
SessionUtil.getSession().setAttribute(VerifyCodeUtils.VERIFY_CODE,verifyCode + "_" + System.currentTimeMillis());
redisUtil.delete(RedisBean.VERIFY_CODE + verifyKey);
redisUtil.setEx(RedisBean.VERIFY_CODE + verifyKey, verifyCode, 300);
ByteArrayOutputStream output = new ByteArrayOutputStream();
VerifyCodeUtils.outputImage(VerifyCodeUtils.VERIFY_CODE_WIDE, VerifyCodeUtils.VERIFY_CODE_HIGH, output, verifyKey);
String imageBase = Base64.getEncoder().encodeToString(output.toByteArray());
Result<Map<String,Object>> result = new Result<>();
result.setData(new HashMap<String,Object>(){{
put("verifyKey", verifyKey);
put("verifyIO", imageBase);
}});
return result;
}
关键代码:
public Result getRSAKey(){
try {
//first:generate RSA key pair
String sessionKey = SessionUtil.getUUId();
Map<String, Object> keyMap = RSAUtils.generateRSAKeyPair();
String publicKey = RSAUtils.getRSAPublicKey(keyMap);
String privateKey = RSAUtils.getRSAPrivateKey(keyMap);
log.info("sessionKey:" + sessionKey + "\n" + "publicKey:" + publicKey + "\n" + "privateKey:" + privateKey);
//second:save public key and private key to redis
redisUtil.setEx(RedisBean.CLIENT_PUBLIC + sessionKey, publicKey,30, TimeUnit.MINUTES);
redisUtil.setEx(RedisBean.CLIENT_PRIVATE + sessionKey, privateKey,30, TimeUnit.MINUTES);
//third:return session key and public key to web
Result<Map<String,Object>> result = new Result<>();
result.setData(new HashMap<String,Object>(){{
put("sessionKey", sessionKey);
put("publicKey", publicKey);
}});
return result;
} catch (Exception e){
throw new RuntimeException("get RSA public key error", e);
}
}
关键代码:
public Result getAESKey(String param) throws Exception{
Encrypt vo = JSON.parseObject(param, Encrypt.class);
//first:decrypt param got web RSA public key.
String sessionKey = vo.getSessionKey();
String clientPrivateKey = redisUtil.get(RedisBean.CLIENT_PRIVATE + sessionKey);
String webPublicKey = new String(RSAUtils.decryptByPrivateKey(Base64Utils.decode(vo.getEncryptedString()), clientPrivateKey));
//second:generate AES key
String aesKey = AesUtil.generateAESKey();
redisUtil.setEx(RedisBean.AES_KEY + sessionKey, aesKey,30, TimeUnit.MINUTES);
//third:use web RSA public key to encrypt AES key, return encrypted string
String encryptedString = Base64Utils.encode(RSAUtils.encryptByPublicKey(aesKey.getBytes(), webPublicKey));
return Result.success(encryptedString);
}
关键代码:
public Result login(Encrypt encrypt){
//1、使用sessionKey,从redis获取AES秘钥
String aesKey = redisUtil.get(RedisBean.AES_KEY + encrypt.getSessionKey());
//2、解密加密报文
String json = AesUtil.decryptAes(encrypt.getEncryptedString(), aesKey);
LoginVO vo = JSON.parseObject(json, LoginVO.class);
if(null == vo) return Result.error("数据有误,请刷新当前页面");
//3、校验验证码
Result codeResult = this.checkVerifyCode(vo);
if(codeResult.getCode() != 0) return codeResult;
//4、校验账号密码
SysUser user = sysUserService.queryOne(vo.getUserCode());
if(StringUtils.isEmpty(user)) return Result.error("账号或密码有误,请核实!");
if(!"1".equals(user.getStatus())) return Result.error("当前用户已失效,请核实!");
//5、账号锁定功能
Result lockResult = this.checkAccountLock(vo.getUserCode(), user.getPassword(), vo.getPassWord());
if(lockResult.getCode() != 0) return codeResult;
//6、初始化login信息
LoginSession login = new LoginSession(user.getId(), user.getUserCode(), user.getUserName(),
user.getOrgCode(), user.getStatus(), new Date(), aesKey);
//7、生成token
String token = SessionUtil.putLoginToSession(login, null);
//8、清理redis中RSA、AES秘钥
redisUtil.delete(RedisBean.CLIENT_PUBLIC + encrypt.getSessionKey());
redisUtil.delete(RedisBean.CLIENT_PRIVATE + encrypt.getSessionKey());
redisUtil.delete(RedisBean.AES_KEY + encrypt.getSessionKey());
//9、返回前端参数
Result<Map<String,Object>> result = new Result<>();
result.setData(new HashMap<String,Object>(){{
put("token", token);
put("login", login);
}});
return result;
}
private Result checkVerifyCode(LoginVO vo){
String code = redisUtil.get(vo.getVerifyCodeId());
if(StringUtils.isEmpty(code)) code = (String) SessionUtil.getSession().getAttribute(VerifyCodeUtils.VERIFY_CODE);
redisUtil.delete(vo.getVerifyCodeId());
SessionUtil.getSession().removeAttribute(VerifyCodeUtils.VERIFY_CODE);
if(!Optional.ofNullable(code).isPresent()) return Result.error(500, "验证码已失效");
if(code.contains("_")){
String[] splitCode = code.split("_");
boolean isTimeOut = isTimeOut(Long.valueOf(splitCode[1]), 300L);
if(isTimeOut) return Result.error(500, "验证码已失效");
if(!vo.getVerifyCode().equalsIgnoreCase(splitCode[0])) return Result.error(500, "验证码输入有误");
} else {
if(!vo.getVerifyCode().equalsIgnoreCase(code)) return Result.error(500, "验证码输入有误");
}
return Result.ok();
}
private static boolean isTimeOut(Long var, Long intervalTime) {
long nm = 1000;
Date d1 = new Date(System.currentTimeMillis());
Date d2 = new Date(var);
long diff = d1.getTime() - d2.getTime();
long minute = diff / nm;
return minute > intervalTime;
}
private Result checkAccountLock(String userCode, String userPassword, String password){
if(!"on".equals(LOCK_ACCOUNT_SWITCH)) {
if(!PassWordUtil.check(password, userPassword)) return Result.error("账号或密码有误,请核实!");
return Result.ok();
}
//校验已冻结帐号
if (!StringUtils.isEmpty(redisUtil.get(RedisBean.FREEZE_ACCOUNT + userCode))) {
Long time = redisUtil.getExpire(RedisBean.FREEZE_ACCOUNT + userCode);
Long m = time/60;
if(m == 0){
return Result.error(500, "账号已锁定,请在" + time + "秒后重新登陆!");
} else {
return Result.error(500, "账号已锁定,请在" + m + "分钟后重新登陆!");
}
}
if(!PassWordUtil.check(password, userPassword)){
Long count = redisUtil.incrBy(RedisBean.FREEZE_COUNT + userCode, 1);
//密码错误五次冻结
if (count == 1) {
redisUtil.expire(RedisBean.FREEZE_COUNT + userCode,1,TimeUnit.HOURS);
}
if (count >= WRONG_TIMES) {
//冻结一小时
redisUtil.setEx(RedisBean.FREEZE_ACCOUNT + userCode, "1", 3600);
//重置次数
redisUtil.delete(RedisBean.FREEZE_COUNT + userCode);
}
return Result.error("账号或密码有误,请核实!");
}
//重置错误验证次数
if("on".equals(LOCK_ACCOUNT_SWITCH)) redisUtil.delete(RedisBean.FREEZE_COUNT + userCode);
return Result.ok();
}
关键代码:
public Result loginOut(String token) throws Exception {
SessionUtil.removeLoginFromSession(token);
return Result.ok();
}
CSDN下载:https://download.csdn.net/download/qq_38254635/87620796
百度网盘下载:https://pan.baidu.com/s/1v7Wvev9w0xS-8-VAMEGOmw?pwd=wu8n
提取码:wu8n