概述
思路
//思路
每个会话都有服务器送过来的RSA私钥,然后服务器在用会话的RSA公钥加密一把AES密钥给浏览器用户(会话),浏览器拿到加密的AES密钥,在用前面后台给的RSA私钥进行解密出AES密钥,登录时账号密码则用前面的AES密钥进行加密 即可
工具包
AES前端加解密那个包官方没有提供直接浏览器可引入使用的js包,我自行打包的,大家可直接使用
链接: https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQ?pwd=gnwq 提取码: gnwq 复制这段内容后打开百度网盘手机App,操作更方便哦
--来自百度网盘超级会员v5的分享
代码
后端
UserController.java
package work.linruchang.lrcutilsweb.controller;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import work.linruchang.lrcutilsweb.bean.User;
import work.linruchang.lrcutilsweb.config.handler.ResponseResult;
import work.linruchang.lrcutilsweb.config.handler.SystemBaseCustomException;
import work.linruchang.lrcutilsweb.consts.*;
import work.linruchang.lrcutilsweb.controller.base.BaseModelController;
import work.linruchang.lrcutilsweb.controller.vo.LoginQO;
import work.linruchang.lrcutilsweb.controller.vo.OauthCallback;
import work.linruchang.lrcutilsweb.controller.vo.OauthPlatformInfoVO;
import work.linruchang.lrcutilsweb.controller.vo.RegisterQO;
import work.linruchang.lrcutilsweb.service.CacheService;
import work.linruchang.lrcutilsweb.service.SysDictService;
import work.linruchang.lrcutilsweb.service.UserService;
import work.linruchang.lrcutilsweb.util.EnhanceJWTUtil;
import work.linruchang.lrcutilsweb.util.EnhanceSecureUtil;
import work.linruchang.lrcutilsweb.util.EnhanceSpringUtil;
import work.linruchang.lrcutilsweb.util.oauth.OauthPlatform;
import work.linruchang.lrcutilsweb.util.oauth.OauthPlatformFactory;
import work.linruchang.lrcutilsweb.util.oauth.OauthUser;
import javax.crypto.SecretKey;
import java.io.Serializable;
import java.security.KeyPair;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 用户接口层
*
* @author lrc
* @version 1.0
* @date 2022-10-28
* @since 1.8
**/
@Api(tags = "用户模块")
@Controller
@RequestMapping("user")
@Slf4j
public class UserController implements BaseModelController<User> {
@Autowired
UserService userService;
@Autowired
SysDictService sysDictService;
@Autowired
CacheService cacheService;
@ApiOperation(value = "生成获取会话用户的RSA私钥")
@GetMapping("rsa-private-key")
@ResponseBody
public ResponseResult<String> getRSAPrivateKey() {
String sessionId = EnhanceSpringUtil.getCurrrentRequest().getSession().getId();
KeyPair rsa = EnhanceSecureUtil.generateKeyPair("RSA");
String publicKey = Base64.encode(rsa.getPublic().getEncoded());
String privateKey = Base64.encode(rsa.getPrivate().getEncoded());
cacheService.set(CacheKeyConstant.SESSION_RSA_PRIVATE_KEY + sessionId, privateKey);
cacheService.set(CacheKeyConstant.SESSION_RSA_PUBLIC_KEY + sessionId, publicKey);
log.info("会话【{}】RSA密钥对 \n公钥:\n{}\n私钥:\n{}", sessionId, publicKey, privateKey);
return ResponseResult.success(privateKey);
}
@ApiOperation(value = "生成获取会话用户的AES密钥(经过会话用户的RSA私钥进行加密)")
@GetMapping("aes-key")
@ResponseBody
public ResponseResult<String> getAESKey() {
String sessionId = EnhanceSpringUtil.getCurrrentRequest().getSession().getId();
String sessionRsaPrivateKey = cacheService.get(CacheKeyConstant.SESSION_RSA_PRIVATE_KEY + sessionId);
String sessionRsaPublicKey = cacheService.get(CacheKeyConstant.SESSION_RSA_PUBLIC_KEY + sessionId);
Assert.notBlank(sessionRsaPrivateKey, () -> new SystemBaseCustomException(RequestEnum.ResponseCodeEnum.TOKEN_SESSION_RSA_PRIVATE_KEY_ERROR, sessionId));
RSA rsa = new RSA(sessionRsaPrivateKey, sessionRsaPublicKey);
SecretKey aesSecretKey = EnhanceSecureUtil.generateAESKey();
// String aesKey = Base64.encode(aesSecretKey.getEncoded());
String aesKey = new String(aesSecretKey.getEncoded());
cacheService.set(CacheKeyConstant.SESSION_AES_KEY + sessionId, aesKey);
String encryAesKey = rsa.encryptBase64(aesKey, KeyType.PublicKey);
log.info("会话【{}】AES密钥 \n未加密\n{}\n加密(RSA公钥)\n{}", sessionId, aesKey, encryAesKey);
return ResponseResult.success(encryAesKey);
}
@ApiOperation(value = "登录")
@PostMapping("login")
@ResponseBody
public ResponseResult<User> login(@RequestBody LoginQO loginQO) {
String sessionId = EnhanceSpringUtil.getCurrrentRequest().getSession().getId();
String aesKey = cacheService.get(CacheKeyConstant.SESSION_AES_KEY + sessionId);
Assert.notBlank(aesKey, () -> new SystemBaseCustomException(RequestEnum.ResponseCodeEnum.TOKEN_SESSION_AES_KEY_ERROR, sessionId));
// 解密密码
String originalPassword = EnhanceSecureUtil.aesDecryptStr(aesKey, loginQO.getPassword());
loginQO.setPassword(originalPassword);
User existUser = userService.getOne(Wrappers.<User>lambdaQuery()
.eq(User::getLoginName, loginQO.getLoginName())
.eq(User::getPassword, loginQO.getPassword()));
if (existUser != null) {
String token = userService.createToken(existUser);
existUser.setToken(token);
existUser.setPassword(null);
return ResponseResult.success(existUser);
} else {
return ResponseResult.fail(null, "登录失败:账号或密码错误,请检查");
}
}
@ApiOperation(value = "用户注册")
@PostMapping("register")
@ResponseBody
public ResponseResult<Boolean> register(@RequestBody RegisterQO registerQO) {
userService.registerVerify(registerQO);
return ResponseResult.success(true);
}
}
EnhanceSecureUtil.java
package work.linruchang.lrcutilsweb.util;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import javax.crypto.SecretKey;
import java.util.stream.IntStream;
/**
* 增强的安全相关工具类
* @author LinRuChang
* @version 1.0
* @date 2022/11/13
* @since 1.8
**/
public class EnhanceSecureUtil extends SecureUtil {
public static byte[] recognizableByteArray = new String("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM123456789[]{};':,./<>?!@#$%^&*()_+").getBytes();
public static Integer AES_BYTE_SIZE_16 = 16;
public static Integer AES_BYTE_SIZE_24 = 24;
public static Integer AES_BYTE_SIZE_32 = 32;
/**
* 生成AES密钥
*/
public static String generateAESKeyStr() {
return new String(generateAESKey(AES_BYTE_SIZE_32).getEncoded());
}
/**
* 生成AES密钥
* @return 获取密钥 {@link java.security.Key#getEncoded()}
*/
public static SecretKey generateAESKey() {
return generateAESKey(AES_BYTE_SIZE_32);
}
/**
* 生成AES密钥
* @param byteSize 密钥字节长度{@link EnhanceSecureUtil#AES_BYTE_SIZE_16}
* @return 获取密钥 {@link java.security.Key#getEncoded()}
*/
public static SecretKey generateAESKey(int byteSize) {
Assert.isTrue(ArrayUtil.contains(new Integer[]{16,24,32},byteSize), "密钥字节数组的长度必须是16、24或32字节长度, 请检查");
byte[] key = new byte[byteSize];
IntStream.range(0,byteSize)
.forEach(keyIndex -> {
int recognizableByteArrayIndex = RandomUtil.randomInt(0, recognizableByteArray.length);
key[keyIndex] = recognizableByteArray[recognizableByteArrayIndex];
});
return generateAESKey(key);
}
/**
*
* AES密钥长度必须是:16、24、32字节数组
* @param key 密钥
* @return 获取密钥 {@link java.security.Key#getEncoded()}
*/
public static SecretKey generateAESKey(byte[] key) {
Assert.isTrue(ArrayUtil.contains(new Integer[]{16,24,32},ArrayUtil.length(key)), "密钥字节数组的长度必须是16、24或32字节长度, 请检查");
return KeyUtil.generateKey(SymmetricAlgorithm.AES.getValue(), key);
}
/**
* 构造,使用默认的AES/ECB/PKCS5Padding
* @param key 密钥
* @return
*/
public static AES aes(String key) {
byte[] keyBytes = key.getBytes();
Assert.isTrue(ArrayUtil.contains(new Integer[]{16,24,32},ArrayUtil.length(keyBytes)), "密钥字节数组的长度必须是16、24或32字节长度, 请检查");
return new AES(keyBytes);
}
/**
* AES解密
* @param key AES密钥
* @param rowContent 密文
* @return
*/
public static String aesDecryptStr(String key, String rowContent) {
if(StrUtil.isAllNotBlank(key,rowContent)) {
return EnhanceSecureUtil.aes(key).decryptStr(rowContent);
}
return rowContent;
}
}
前端
smallUtil.js
/**
* 字符串是否不为空
* @param content 字符串内容
*/
function strIsNotEmpty(content) {
let result = content && content.length > 0;
if( (typeof result) === 'string' ) {
return result.length > 0;
}else {
return result ? true : false ;
}
}
/**
* 字符串是否为空
* @param content 字符串内容
*/
function strIsEmpty(content) {
return !strIsNotEmpty(content);
}
/**
* 字符串是空则返回默认字符串内容
* @param content 字符串内容
* @param defaultContent 默认字符串内容
*/
function strEmptyToDefault(content, defaultContent) {
return strIsNotEmpty(content) ? content : defaultContent;
}
/**
* AES加密
* <a href="https://www.npmjs.com/package/cryptojs" />
* @param plaintext 明文
* @param aesKey 密钥
* @returns 经过Base64编码的密文
*/
function aesEncrypt(plaintext, aesKey) {
if(strIsNotEmpty(plaintext) && strIsNotEmpty(aesKey) ) {
const __key = CryptoJS.enc.Utf8.parse(aesKey) //将秘钥转换成Utf8字节数组
const encrypt = CryptoJS.AES.encrypt(String(plaintext), __key, {
// iv: iv,
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
return encrypt.toString()
}
log.warn("AES加密:明文以及密钥不可为空,请检查");
return null;
}
/**
* AES加密(密钥取自本地LocalStorage)
* <a href="https://www.npmjs.com/package/cryptojs" />
* @param plaintext 明文
* @param aesKey 密钥
* @returns 经过Base64编码的密文
*/
function aesEncryptBySessionKey(plaintext) {
return aesEncrypt(plaintext, localStorage.getItem(systemConst.session.AESKey))
}
/**
* AES加密(密钥取自本地LocalStorage)
* <a href="https://www.npmjs.com/package/cryptojs" />
* @param plaintext 明文
* @param aesKey 密钥
* @returns 经过Base64编码的密文
*/
function aesEncryptJsonBySessionKey(jsonObj) {
let jsonObjCopy = _.cloneDeep(jsonObj)
if(jsonObjCopy) {
let keys = _.keys(jsonObjCopy);
if(keys && keys.length > 0) {
keys.forEach((keyName,keyIndex) => {
if(jsonObjCopy[keyName] && (typeof jsonObjCopy[keyName]) === 'string' && strIsNotEmpty(jsonObjCopy[keyName])) {
jsonObjCopy[keyName] = aesEncryptBySessionKey(jsonObjCopy[keyName])
}
})
}
}
return jsonObjCopy;
}
/**
* AES解密
* <a href="https://www.npmjs.com/package/cryptojs" />
* @param plaintext 经过Base64编码的密文
* @param aesKey 密钥
* @returns 明文
*/
function aesDecrypt(ciphertext, aesKey) {
if(strIsNotEmpty(ciphertext) && strIsNotEmpty(aesKey) ) {
const __key = CryptoJS.enc.Utf8.parse(aesKey) //将秘钥转换成Utf8字节数组
const decrypt = CryptoJS.AES.decrypt(ciphertext, __key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
return decrypt.toString(CryptoJS.enc.Utf8) //解密后的数据
}
log.warn("AES解密:密文以及密钥不可为空,请检查");
return null;
}
/**
* RSA解密
* (jsencrypt工具仅支持公钥加密、私钥解密,私钥加密、但不支持公钥解密(反正就是很迷惑))
* <a href="https://www.npmjs.com/package/jsencrypt" />
* @param plaintext 密文
* @param aesKey RSA公钥(Base64编码)
* @returns 明文
*/
function RSADecryptByPrivateKey(ciphertext, privateKey) {
if(strIsNotEmpty(ciphertext) && strIsNotEmpty(privateKey)) {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(privateKey) // 设置公钥
return encryptor.decrypt(ciphertext);
}
log.warn("RSA解密:密文以及密钥不可为空,请检查");
return null;
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>个人在线工具</title>
<link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/index.css"/>
<link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/element-plus.css"/>
<link rel="stylesheet"
href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/generateShortLinkPage.css"/>
<link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/figureBedPage.css"/>
<link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/generateCodePage.css"/>
<link rel="stylesheet"
href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/systemSettingPage.css"/>
<link rel="stylesheet"
href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/updateTimelinePage.css"/>
<link rel="stylesheet"
href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/articleDetailPage.css"/>
<link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/articleListPage.css"/>
<!-- remarkable -->
<!-- <link rel="stylesheet" href="/js/highlight-11.6.0_all-language/styles/vs.min.css" /> -->
<!-- markdown-it -->
<link rel="stylesheet" href="/js/highlight-11.6.0_all-language/styles/atom-one-dark.min.css"/>
</head>
<body>
<div id="vueApp">
<el-container>
<el-aside width="13rem">
<el-menu
default-active="2"
unique-opened
router
class="leftElMenu el-menu-vertical-demo"
:collapse="isCollapse"
@click.self="switchCollapse"
>
<template v-for="menuData in menuDatas">
<!-- 有二级菜单 -->
<el-sub-menu v-if="menuData.childrens && menuData.childrens.length > 0"
:index="getMenuIndex(menuData)">
<template #title>
<el-icon v-if="menuData.menuIcon == 'icon-menu'">
<icon-menu/>
</el-icon>
<el-icon v-if="menuData.menuIcon == 'location'">
<location/>
</el-icon>
<el-icon v-if="menuData.menuIcon == 'setting'">
<setting/>
</el-icon>
<el-icon v-if="menuData.menuIcon == 'notebook'">
<notebook/>
</el-icon>
<span>{{menuData.name}}</span>
</template>
<template v-for="childrenMenuData in menuData.childrens">
<el-menu-item-group v-if="childrenMenuData.groupTitle">
<template #title><span>{{childrenMenuData.groupTitle}}</span></template>
<el-menu-item :index="getMenuIndex(childrenMenuData)">{{childrenMenuData.name}}
</el-menu-item>
</el-menu-item-group>
<el-menu-item
v-else-if="!childrenMenuData.childrens || childrenMenuData.childrens.length == 0"
:index="getMenuIndex(childrenMenuData)">{{childrenMenuData.name}}
</el-menu-item>
<el-sub-menu v-else :index="childrenMenuData.routerPath || childrenMenuData.id">
<template #title><span>{{childrenMenuData.name}}</span></template>
<el-menu-item v-for="childrenMenuDataChildren in childrenMenuData.childrens"
:index="getMenuIndex(childrenMenuDataChildren)">
{{childrenMenuDataChildren.name}}
</el-menu-item>
</el-sub-menu>
</template>
</el-sub-menu>
<!-- 一级菜单 -->
<el-menu-item v-else :index="getMenuIndex(menuData)">
<el-icon v-if="menuData.menuIcon == 'icon-menu'">
<icon-menu/>
</el-icon>
<el-icon v-if="menuData.menuIcon == 'location'">
<location/>
</el-icon>
<el-icon v-if="menuData.menuIcon == 'setting'">
<setting/>
</el-icon>
<el-icon v-if="menuData.menuIcon == 'notebook'">
<notebook/>
</el-icon>
<template #title>{{menuData.name}}</template>
</el-menu-item>
</template>
</el-menu>
</el-aside>
<el-container>
<el-header class="elHeader">
<div class="web-logo-box">
<el-avatar style="vertical-align: middle" shape="square" size="default" fit="fit"
src="/img/webLogo.jpg"></el-avatar>
<span class="web-name">个人在线工具</span>
</div>
<div style="margin-right: 13rem">
<div class="loginUserInfoBox" v-if="loginUserInfo">
<el-popover placement="bottom-end" :width="300" trigger="click">
<template #reference>
<el-avatar shape="circle" size="default" fit="fit" :src="loginUserInfo.headPortrait?loginUserInfo.headPortrait:'/img/defaultHeadPortrait.webp'"/>
</template>
<div class="el-popover-loginUserInfoBox-content">
<div class="el-popover-loginUserInfoBox-content-row userInfo">
<el-avatar shape="circle" size="default" fit="fit"
:src="loginUserInfo.headPortrait?loginUserInfo.headPortrait:'/img/defaultHeadPortrait.webp'"></el-avatar>
<div class="detailInfo">
<span style="font-size: 1.1rem;font-weight: bolder">{{loginUserInfo.nickname}}</span>
<span>加入时间:{{loginUserInfo.createTime}}</span>
</div>
</div>
<div class="el-popover-loginUserInfoBox-content-row btnBox">
<el-button type="danger" @click="logout">退出登录</el-button>
</div>
</div>
</el-popover>
</div>
<el-button-group v-else class="ml-4" class="headerButtonGroup">
<el-button type="primary" @click="openLoginDialog">登录</el-button>
<el-button type="primary" @click="openRegisterDialog">注册</el-button>
</el-button-group>
</div>
<!-- 模仿掘金的登录样式 -->
<el-dialog align-center class="loginRegisterDialog" v-model="loginRegisterDialog"
:title="loginFlag ? '登录' : '注册'" width="18%" destroy-on-close>
<div class="loginRegisterIconBox">
<img data-v-7b44b39a="" src="/img/loginIcon.svg" class="normal" style="">
</div>
<el-row>
<el-form class="loginRegisterForm" ref="loginRegisterFormRef" :model="loginInfo"
:rules="loginInfoRules">
<el-form-item prop="loginName">
<el-input v-model="loginInfo.loginName" placeholder="账号"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginInfo.password" placeholder="密码"></el-input>
</el-form-item>
<template v-if="!loginFlag">
<el-form-item prop="email" class="emailFormItem">
<el-input v-model="loginInfo.email" placeholder="邮箱"></el-input>
<el-button @click="getRegisterEmailVerifyCode" :disabled="registerEmailSended">{{registerEmailSended ? reSendRegisterEmailSecond + '秒后重新发送' : '获取验证码'}}</el-button>
</el-form-item>
<el-form-item prop="emailVerificationCode" >
<el-input v-model="loginInfo.emailVerificationCode"
placeholder="邮箱验证码"></el-input>
</el-form-item>
</template>
<el-button type="primary" class="loginRegisterBtn loginRegisterDialogRow"
@click="submitLoginRegister" v-text="loginFlag?'登录':'注册'">
<!-- @click="submitLoginRegister">{{loginFlag?'登录':'注册'}} -->
</el-button>
<el-row class="otherLoginWayBtn loginRegisterDialogRow " v-if="loginFlag">其他登录方式
</el-row>
<el-row class="avatar-boxs-row" v-if="loginFlag">
<div class="avatar-box"
v-for="(oauthPlatformInfoVO,oauthPlatformInfoVOIndex) in oauthPlatformInfoVOs"
:key="oauthPlatformInfoVOIndex"
@click="jumpOauthPlatformAuthUrl(oauthPlatformInfoVO)">
<div v-if="!oauthPlatformInfoVO.supportFlag" class="noSupportMask"></div>
<el-avatar shape="circle" size="small" fit="fit" :name="oauthPlatformInfoVO.name"
:code="oauthPlatformInfoVO.code" :src="oauthPlatformInfoVO.icon"/>
</div>
</el-row>
</el-form>
</el-row>
</el-dialog>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
<el-footer class="footerBox" v-if="webSiteFooterInfoVO.recordNumber">
<div clas="websiteRecordsBox">
<el-link :href="webSiteFooterInfoVO.recordLink" target="_blank">
{{webSiteFooterInfoVO.recordNumber}}
</el-link>
</div>
<div class="youPaiYunBox"
v-if="webSiteFooterInfoVO.youPaiYunLinkFlag && webSiteFooterInfoVO.youPaiYunLinkFlag == '1'">
<el-link :href="webSiteFooterInfoVO.youPaiYunLink" target="_blank">
本网站由 <img class="youPaiYunLogo" src="/img/又拍云_logo2.png"/> 提供CDN加速/云存储服务
</el-link>
</div>
</el-footer>
</el-container>
</el-container>
</div>
</body>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/vue3.2.37.global.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/vue-router.4.1.5.global.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/axios.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/qs.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/element-plus.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/elementui-plus-icons-vue.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/element-plus-zh-cn.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/jquery.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/layer.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/copy-to-clipboardPublic.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/dayjs.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/lodash.js"></script>
<!-- md转html、以及代码高亮 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/remarkable.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/highlight-11.6.0_all-language/highlight.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdown-it.13.0.1.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdownItAnchor.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdownItTocDoneRight.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdown-it-textual-uml.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdown-it-code-copy.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdown-it-emoji.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/mermaid.js"></script>
<!-- json转yml -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/json2yaml.js"></script>
<!-- html转markdown -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/turndown.js"></script>
<!-- 仅支持xml美化==可直接抛弃使用==smallUtil.xmlFormat -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/xml-formatter.js"></script>
<!-- 仅支持json美化==可直接抛弃使用==smallUtil.jsonFormat -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/json-beautify.js"></script>
<!-- CSS/JS/JSON/HTML/XML的美化==smallUtil.formatAny -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/beautify.js"></script>
<!-- sql美化 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/sqlformatter.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/md5.min.js"></script>
<!-- cookie操作 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/js.cookie.min.js"></script>
<!-- AES加解密 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/crypto-js.js"></script>
<!-- RSA加解密 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/jsencrypt.min.js"></script>
<!-- 假数据 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/mockData/mockData.js"></script>
<!-- 工具类部分 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/utils/systemConst.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/utils/smallUtil.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/utils/simpleAxios.js"></script>
<!-- 自定义组件部分 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/component/myTimeline/myTimeline.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/component/myTimeline/myTimelineItem.js"></script>
<!-- 页面部分 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/errorPage.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/resourceNotExistPage.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/notPermissionsPage.js"></script>
<!-- 短链生成器 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/generateShortLinkPage.js"></script>
<!-- 图床 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/figureBedPage.js"></script>
<!-- 代码生成器 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/generateCodePage.js"></script>
<!-- 系统更新时间线 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/updateTimelinePage.js"></script>
<!-- 系统设置 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/systemSettingPage.js"></script>
<!-- 文章列表页面 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/articleListPage.js"></script>
<!-- 文章详情页面 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/articleDetailPage.js"></script>
<!-- 系统接口 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/utils/systemApi.js"></script>
<!-- 页面路由器 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/router/my-router.js"></script>
<script>
window.onload = async function () {
//用户菜单 == 当前仅支持渲染二级,后台已经支持渲染多级的数据
let {data} = await getUserMenu()
let menuDatas = data.result;
log('用户菜单:', menuDatas)
const {createApp} = Vue
const app = createApp({
data() {
return {
//侧边栏是否不展开
isCollapse: true,
menuDatas: menuDatas,
webSiteFooterInfoVO: {},
recordOldValue: {},
observer: null,
//登录注册对话框
loginRegisterDialog: false,
//是否登录状态
loginFlag: true,
loginInfo: {
loginName: null,
password: null,
email: null,
emailVerificationCode: null,
},
loginInfoRules: {
loginName: [{
required: true,
message: '请输入账号',
trigger: 'change',
}],
password: [{
required: true,
message: '请输入密码',
trigger: 'change',
}],
email: [{
required: true,
message: '请输入邮箱',
trigger: 'change',
}],
emailVerificationCode: [{
required: true,
message: '请输入邮箱验证码',
trigger: 'change',
}]
},
oauthPlatformInfoVOs: [],
loginUserInfo: null,
registerEmailSended: false,
reSendRegisterEmailSecond: null,
}
},
methods: {
/**
* 获取注册验证码
*/
getRegisterEmailVerifyCode: async function () {
if(strIsEmpty(this.loginInfo.email)) {
warningMessage("请先输入您的邮箱")
return;
}
let response = await sendEmail({
emailType: systemConst.emailType.register,
targetEmail: this.loginInfo.email
})
if (axiosResponseSuccess(response, "邮件发送成功", "邮件发送失败")) {
log("注册邮件发送:", response)
this.reSendRegisterEmailSecond = response.data.result ? (response.data.result)/1000 : 60;
this.registerEmailSended = true;
let reSendRegisterEmailSecondInterval = setInterval(() => {
this.reSendRegisterEmailSecond = this.reSendRegisterEmailSecond - 1;
if(this.reSendRegisterEmailSecond<=0) {
log("可重新发送邮件状态")
clearInterval(reSendRegisterEmailSecondInterval);
this.registerEmailSended = false;
this.reSendRegisterEmailSecond = null;
}
},1000)
}
},
logout: function () {
messageConfirm("确定退出登录?", () => {
//删除本地以及系统中的Token
logout();
this.loginUserInfo = null;
localStorage.removeItem("token");
//重新加载页面
window.location = '/'
})
},
submitLoginRegister: async function () {
// warningMessage("抱歉,当前系统暂不支持【账号密码】方式进行登录,敬请期待后续支持")
// return;
//登录
await this.$refs.loginRegisterFormRef.validate(async (valid, fields) => {
log("登录表单校验状态:", valid, fields)
if (valid) {
let loginInfoCopy = _.cloneDeep(this.loginInfo);
if (this.loginFlag) { //登录
loginInfoCopy.password = aesEncryptBySessionKey(loginInfoCopy.password)
let loginResponse = await login(loginInfoCopy)
if (axiosResponseSuccess(loginResponse, "登录成功", "登录失败")) {
let loginUserInfo = loginResponse.data.result;
log("登录成功信息:", loginUserInfo);
localStorage.setItem(systemConst.cookie.token, loginUserInfo.token);
//基于Token获取用户信息
this.getUserInfo()
//关闭登录弹框
this.loginRegisterDialog = false;
}
} else { //注册
let registerResponse = await register(loginInfoCopy)
if (axiosResponseSuccess(registerResponse, "注册成功", "注册失败")) {
this.loginInfo = {
loginName: null,
password: null,
email: null,
emailVerificationCode: null,
}
this.loginFlag = true
this.$refs.loginRegisterFormRef.resetFields();
}
}
} else {
warningMessage("请根据提示填入对应的登录信息")
}
})
},
/**
* 获取并跳转Oauth平台的授权链接
* @param oauthPlatformInfoVO 平台类型信息
* @returns {Promise<void>}
*/
jumpOauthPlatformAuthUrl: async function (oauthPlatformInfoVO) {
if (!oauthPlatformInfoVO.supportFlag) {
warningMessage("抱歉,当前系统暂不支持【Oauth-" + oauthPlatformInfoVO.name + "】方式进行登录,敬请期待后续支持")
return;
}
let response = await getOauthPlatformAuthUrl(oauthPlatformInfoVO.code)
log('获取Oauth平台的授权链接', oauthPlatformInfoVO, response)
if (axiosResponseSuccess(response, null, "获取Oauth平台授权链接失败")) {
window.location = response.data.result;
}
},
/**
* 打开登录弹框
*/
openLoginDialog: function () {
this.loginRegisterDialog = true;
this.loginFlag = true;
},
/**
* 打开注册弹框
*/
openRegisterDialog: function () {
this.loginRegisterDialog = true;
this.loginFlag = false;
},
switchCollapse: function () {
this.isCollapse = !this.isCollapse;
},
getIcon(iconName) {
return '<' + iconName + '/>'
},
getMenuIndex(menuData) {
// return menuData.routerPath || menuData.id;
return _.toString(menuData.routerPath || menuData.id);
},
changeStyle: function () {
listeningElemStyleChange("html", (element) => {
let width = str2Num(getComputedStyle(element).getPropertyValue('width'));
let height = str2Num(getComputedStyle(element).getPropertyValue('height'));
if (width === this.recordOldValue.width && height === this.recordOldValue.height) {
return
}
log("html元素尺寸发生变化:", this.recordOldValue, {width, height})
this.recordOldValue = {
width,
height
}
this.$nextTick(() => {
if (window.innerHeight > $("html").height()) {
$(".footerBox").addClass("outerWindowHeight");
} else {
$(".footerBox").removeClass("outerWindowHeight");
}
})
})
},
/**
* 获取Oauth授权平台信息
* @returns {Promise<void>}
*/
getOauthPlatformInfos: async function () {
let response = await getOauthPlatformInfos()
log("获取Oauth平台信息", response)
if (axiosResponseSuccess(response, null, "获取Oauth平台信息失败")) {
this.oauthPlatformInfoVOs = response.data.result;
}
},
/**
* 获取用户信息
* @returns {Promise<void>}
*/
getUserInfo: async function () {
let token = getUrlParam(systemConst.cookie.token)
let firstLoginFlag = false;
if (strIsNotEmpty(token)) {
localStorage.setItem(systemConst.cookie.token, token)
firstLoginFlag = true;
}
token = token || localStorage.getItem(systemConst.cookie.token)
//如果从请求以及本地都无发现token则直接退出即可
if (strIsEmpty(token)) {
return;
}
//校验用户的有效性
let response = await tokenVerification()
log("校验Token有效性", token, response)
if (axiosResponseSuccess(response)) {
//校验通过获取用户信息
if (firstLoginFlag) {
window.location = '/'
} else {
response = await getUserInfo()
log("获取当前请求的用户信息", token, response)
if (axiosResponseSuccess(response), "登录成功") {
this.loginUserInfo = response.data.result;
}
}
} else {
localStorage.removeItem("token");
}
},
/**
* 获取会话用户的密钥(RSA公钥以及AES密钥)
* @returns {Promise<void>}
*/
getSessionKey: async function () {
let getRSAPrivateKeyResponse = await getRSAPrivateKey()
if (axiosResponseSuccess(getRSAPrivateKeyResponse), null, "当前会话的RSA公钥获取失败,请检查") {
let RSAPrivateKey = getRSAPrivateKeyResponse.data.result;
localStorage.setItem(systemConst.session.RSAPrivateKey, RSAPrivateKey)
log("会话RSA私钥:", RSAPrivateKey)
let getAESKeyResponse = await getAESKey()
if (axiosResponseSuccess(getAESKeyResponse), null, "当前会话的AES密钥获取失败,请检查") {
let encryAESKey = getAESKeyResponse.data.result;
log("会话AES密钥(加密):", encryAESKey)
let AESKey = RSADecryptByPrivateKey(encryAESKey, RSAPrivateKey)
log("会话AES密钥(解密):", AESKey)
localStorage.setItem(systemConst.session.AESKey, AESKey)
}
}
}
},
mounted: async function () {
//路由注册 == 不要放在这里否则使用链接进行访问页面时访问不了
// registerRoute(menuDatas, myRouter)
//网站页脚信息
let response = await getWebSiteFooterInfoVO()
if (axiosResponseSuccess(response)) {
this.webSiteFooterInfoVO = response.data.result;
this.changeStyle()
}
//获取用户信息
this.getUserInfo();
//Oauth平台信息
this.getOauthPlatformInfos();
//获取会话用户的密钥
this.getSessionKey();
}
});
// 是否屏蔽警告日志
if (!getGlobalLogSwitch()) {
app.config.warnHandler = (msg, instance, trace) => {
}
}
//将全部图标设置为全局组件
Object.keys(ElementPlusIconsVue).forEach((componentName) => {
app.component(componentName, ElementPlusIconsVue[componentName]);
})
//路由注册
// myRouter.beforeEach((to, from) => {
// log('当前打算去的路由:', to)
// if(!existRoutePath(to.fullPath, myRouter.getRoutes())) {
// registerRoute(menuDatas, myRouter)
// log("所有路由信息:", myRouter.getRoutes())
// //前面注册完,再次检测是否存有,没有,说明没权限访问的路由页面
// if(existRoutePath(to.fullPath,myRouter.getRoutes())) {
// return to.fullPath
// }else {
// return 'not-permissions-page'
// }
// }
// })
myRouter.beforeEach((to, from) => {
log('当前打算去的路由:', to)
if (!existRoutePath2(myRouter, to)) {
registerRoute(menuDatas, myRouter)
log("所有路由信息:", myRouter.getRoutes())
//前面注册完,再次检测是否存有,没有,说明没权限访问的路由页面
if (existRoutePath2(myRouter, to)) {
return to.fullPath
} else {
return 'not-permissions-page'
}
}
})
app.use(ElementPlus, {locale: ElementPlusLocaleZhCn})
.use(myRouter)
.component("IconMenu", ElementPlusIconsVue.Menu)
.component("MyTimeline", MyTimeline)
.component("MyTimelineItem", MyTimelineItem)
.mixin({ //图标变量全局混入注册
data() {
let result = {
IconMenu: Vue.shallowRef(ElementPlusIconsVue.Menu)
}
_.keys(ElementPlusIconsVue).forEach(propertyName => {
result[propertyName] = Vue.shallowRef(ElementPlusIconsVue[propertyName])
})
return result;
// return {
// ...ElementPlusIconsVue,
// IconMenu: ElementPlusIconsVue.Menu
// }
},
})
.mount('#vueApp');
};
</script>
</html>
版权声明:本文为weixin_39651356原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。