rsa加密登录解决方案

1.问题

账密登录方式中用户输入密码后,把账号、密码通过http传输到后端进行校验,然而密码属于敏感信息,不能以明文传输,否则容易被拦截窃取,因此需要考虑如何安全传输密码

2.解决方案

使用rsa加密方式,rsa属于非对称加密,特点就是公钥加密私钥解密

2.1后端生成公钥私钥

生成公私钥,把公钥返回给前端,私钥用redis缓存

ManagerController.java

java 复制代码
    @GetMapping("/key")
    public ResponseEntity<ApiResponse> key(@RequestParam("loginNo") String loginNo) {
        String key = managerService.generateKey(loginNo);
        return ApiResponse.success(key);
    }

ManagerServiceImpl.java

java 复制代码
    @Override
    public String generateKey(String loginNo) {
        QueryWrapper<Manager> wrapper = new QueryWrapper<>();
        wrapper.eq("loginNo", loginNo);
        Manager entity = this.getOne(wrapper);
        if (Objects.isNull(entity)) {
            throw new CodeException("用户不存在:loginNo=" + loginNo);
        }
        try {
            KeyPair keyPair = RSAUtil.generateKeyPair();
            String publicKey = RSAUtil.getPublicKey(keyPair);
            String privateKey = RSAUtil.getPrivateKey(keyPair);
            log.info("publicKey={}", publicKey);
            log.info("privateKey={}", privateKey);
            String redisKey = RedisKey.MANAGE_LOGIN_RSA_PRIVATEKEY + "$" + loginNo;
            // 清除缓存
            redisService.del(redisKey);
            // 私钥添加到缓存
            redisService.set(redisKey, privateKey, 5, TimeUnit.MINUTES);
            return publicKey;
        } catch (Exception e) {
            log.error("生成rsa密钥失败", e);
        }
        return null;
    }

RSAUtil.java

java 复制代码
public class RSAUtil {
    private static final Charset CHARSET = StandardCharsets.UTF_8;
    private static final String ALGORITHM = "RSA";

    /**
     * 生成密钥对
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
        keyPairGenerator.initialize(1024);
        return keyPairGenerator.generateKeyPair();
    }

    /**
     * 生成公钥
     * @param keyPair
     * @return
     */
    public static String getPublicKey(KeyPair keyPair) {
        PublicKey publicKey = keyPair.getPublic();
        byte[] bytes = base64Encode(publicKey.getEncoded());
        return new String(bytes, CHARSET);
    }

    /**
     * 生成私钥
     * @param keyPair
     * @return
     */
    public static String getPrivateKey(KeyPair keyPair) {
        PrivateKey privateKey = keyPair.getPrivate();
        byte[] bytes = base64Encode(privateKey.getEncoded());
        return new String(bytes, CHARSET);
    }

    /**
     * base64加密
     * @param bytes
     * @return
     */
    public static byte[] base64Encode(byte[] bytes) {
        return Base64.getEncoder().encode(bytes);
    }

    /**
     * base64解密
     * @param bytes
     * @return
     */
    public static byte[] base64Decode(byte[] bytes) {
        return Base64.getDecoder().decode(bytes);
    }

    /**
     * base64解密
     * @param src
     * @return
     */
    public static byte[] base64Decode(String src) {
        return Base64.getDecoder().decode(src);
    }

    /**
     * 解密
     * @param key
     * @param data
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public static String decrypt(String key, String data) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        byte[] bytes = base64Decode(key);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(cipher.doFinal(base64Decode(data.getBytes(CHARSET))), CHARSET);
    }

2.2前端使用公钥加密

安装这个依赖

复制代码
npm install jsencrypt@3.2.0
js 复制代码
import JSEncrypt from 'jsencrypt';

// key为后端返回的公钥,this.form.password为明文密码
const jsEncrypt = new JSEncrypt();
jsEncrypt.setPublicKey(key);
// pwd为加密后的密码
var pwd = jsEncrypt.encrypt(this.form.password);

2.3后端使用私钥解密

从redis获取私钥,用私钥解密,得到明文后和数据库保存的密码比对,数据库的密码是以用户id为盐值对明文密码作md5加密,相同则放行,否则报错,注意登录成功的话需要把redis的密钥移除

ManagerController.java

java 复制代码
    @PostMapping("/login")
    public ResponseEntity<ApiResponse> login(@RequestParam("loginNo") String loginNo, @RequestParam("password") String password)
            throws Exception {
        UserDTOWithToken dto = managerService.login(loginNo, password);
        return ApiResponse.success(dto);
    }

ManagerServiceImpl.java

java 复制代码
    @Override
    public UserDTOWithToken login(String loginNo, String password) throws Exception {
        QueryWrapper<Manager> wrapper = new QueryWrapper<>();
        wrapper.eq("loginNo", loginNo);
        Manager entity = this.getOne(wrapper);
        if (Objects.isNull(entity)) {
            throw new CodeException("用户不存在:loginNo=" + loginNo);
        }
        String redisKey = RedisKey.MANAGE_LOGIN_RSA_PRIVATEKEY + "$" + loginNo;
        // 从缓存获取私钥
        String privateKey = (String) redisService.get(redisKey);
        if (StrUtil.isEmpty(privateKey)) {
            throw new CodeException("私钥不存在");
        }
        // 用私钥解密
        String realPassword = RSAUtil.decrypt(privateKey, password);
        log.info("realPassword={}", realPassword);
        // 校验密码
        Digester digester = new Digester(DigestAlgorithm.MD5);
        digester.setSalt(entity.getId().getBytes(StandardCharsets.UTF_8));
        String encodePassword = digester.digestHex(realPassword);
        log.info("encodePassword={}", encodePassword);
        if (!encodePassword.equals(entity.getPassword())) {
            throw new CodeException("密码错误");
        }
        //从认证服务获取token
        ResponseEntity<ApiResponse> responseEntity = authClient.token(AuthConst.PASSWORD_GRANT_TYPE, AuthConst.ADMIN_CLIENT_ID,
                AuthConst.ADMIN_CLIENT_SECRET, null, loginNo, password);
        ApiResponse response = responseEntity.getBody();
        TokenDTO tokenDTO = response.toObject(TokenDTO.class);
        if (Objects.isNull(tokenDTO)) {
            throw new CodeException("获取token失败:" + response.getMessage());
        }
        UserDTOWithToken dto = new UserDTOWithToken();
        dto.setUserId(entity.getId());
        dto.setToken(tokenDTO);
        entity.setLoginTime(LocalDateTime.now());
        Integer loginCount = entity.getLoginCount();
        entity.setLoginCount(Objects.isNull(loginCount) ? 1 : loginCount + 1);
        entity.setUpdateTime(LocalDateTime.now());
        this.updateById(entity);
        // 成功则清除缓存的私钥
        redisService.del(redisKey);
        return dto;
    }

3.总结

非对称加密还有其它算法,rsa是其中一种

后端存储私钥除了redis也可以用其它缓存工具如J2Cache

相关推荐
梵得儿SHI1 分钟前
SpringCloud 核心组件精讲:OpenFeign 实战指南-服务调用优雅实现方案(含自定义拦截器、超时重试、LoadBalance 整合避坑)
spring boot·spring·spring cloud·负载均衡·openfeign的核心应用·微服务调用·熔断组件
Dylan的码园2 分钟前
栈与stack
java·数据结构·链表
董世昌413 分钟前
break和continue的区别是什么?
java·jvm·算法
Chase_______5 分钟前
【JAVA基础指南(一)】快速掌握基础语法
java·开发语言
陈逸轩*^_^*9 分钟前
微服务常见八股(分布式seat, 网关,服务注册与发现、负载均衡、断路器、API 网关、分布式配置中心)
java·微服务
爱笑的眼睛119 分钟前
MLflow Tracking API:超越实验记录,构建可复现的机器学习工作流
java·人工智能·python·ai
好学且牛逼的马10 分钟前
Apache Commons DbUtils
java·设计模式·apache
榮十一19 分钟前
100道Java面试SQL题及答案
java·sql·面试
专注于大数据技术栈19 分钟前
java学习--String
java·开发语言·学习
胡玉洋22 分钟前
Spring Boot 项目配置文件密码加密解决方案 —— Jasypt 实战指南
java·spring boot·后端·安全·加密·配置文件·jasypt