rsa加密登录解决方案

1.问题

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

2.解决方案

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

2.1后端生成公钥私钥

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

ManagerController.java

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

ManagerServiceImpl.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

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 [email protected]
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

    @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

    @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

你可能感兴趣的:(项目相关,java,spring,boot)