若依中脱敏和密码传输加密

15 篇文章 0 订阅
3 篇文章 0 订阅

一、SpringBoot使用自定义注解实现返回数据脱敏操作

在实际项目中,对于敏感数据的保护十分重要,数据脱敏又称数据去隐私化或数据变形,是在给定的规则、策略下对敏感数据进行变换、修改的技术机制,能够在很大程度上解决敏感数据在非可信环境中使用的问题。

本文使用自定义注解,在返回数据给前端的时候,根据给定的脱敏规则实现敏感数据脱敏操作,实现过程非常简单,一起来看看吧!

1、引入依赖
<!-- hutool工具类 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.6.7</version>
</dependency>
2、自定义注解 - Desensitize
package com.ltkj.common.annotation;

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.ltkj.common.enums.SensitiveStrategy;
import com.ltkj.common.jackson.SensitiveJsonSerializer;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 数据脱敏注解
 *
 * @author wangl 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {
    SensitiveStrategy strategy();
}

3、脱敏规则枚举类
package com.ltkj.common.enums;

import cn.hutool.core.util.DesensitizedUtil;
import lombok.AllArgsConstructor;

import java.util.function.Function;

/**
 * 脱敏策略
 *
 * @author wangl
 */
@AllArgsConstructor
public enum SensitiveStrategy {

    /**
     * 身份证脱敏
     */
    ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)),

    /**
     * 手机号脱敏
     */
    PHONE(DesensitizedUtil::mobilePhone),

    /**
     * 地址脱敏
     */
    ADDRESS(s -> DesensitizedUtil.address(s, 8)),

    /**
     * 邮箱脱敏
     */
    EMAIL(DesensitizedUtil::email),

    /**
     * 银行卡
     */
    BANK_CARD(DesensitizedUtil::bankCard);

    //可自行添加其他脱敏策略

    private final Function<String, String> desensitizer;

    public Function<String, String> desensitizer() {
        return desensitizer;
    }
}

这其中的脱敏规则全都依赖 hutool 的 DesensitizedUtil 工具类,有其它的脱敏规则可以自定义实现

4、数据脱敏 JSON 序列化工具

package com.ltkj.common.jackson;

import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.ltkj.common.annotation.Sensitive;
import com.ltkj.common.core.service.SensitiveService;
import com.ltkj.common.enums.SensitiveStrategy;
import com.ltkj.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;

import java.io.IOException;
import java.util.Objects;

/**
 * 数据脱敏json序列化工具
 *
 * @author wangl
 */
@Slf4j
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private SensitiveStrategy strategy;

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        try {
            SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class);
            if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive()) {
                gen.writeString(strategy.desensitizer().apply(value));
            } else {
                gen.writeString(value);
            }
        } catch (BeansException e) {
            log.error("脱敏实现不存在, 采用默认处理 => {}", e.getMessage());
            gen.writeString(value);
        }
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        Sensitive annotation = property.getAnnotation(Sensitive.class);
        if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
            this.strategy = annotation.strategy();
            return this;
        }
        return prov.findValueSerializer(property.getType(), property);
    }
}

SpringBoot 中默认使用 jackson 作为 JSON 序列化工具,我们获取到自定义的脱敏注解(@Sensitive ),就对该数据实现我们的脱敏操作,就完成了我们的敏感数据脱敏操作。

import com.ltkj.common.core.service.SensitiveService;
import com.ltkj.common.helper.LoginHelper;
import org.springframework.stereotype.Service;

/**
 * 脱敏服务
 * 默认管理员不过滤
 * 需自行根据业务重写实现
 *
 * @author wangl脱敏服务
 * @date 2023-10-10
 */
@Service
public class SysSensitiveServiceImpl implements SensitiveService {

    /**
     * 动态判断是否脱敏
     * 默认管理员不过滤
     */
    @Override
    public boolean isSensitive() {
        return !LoginHelper.isAdmin();
    }

}

5 测试结果

访问接口:
http://localhost:8090/test
得到数据:
在这里插入图片描述

可以看出,对应的敏感数据被进行脱敏了,证明我们的脱敏注解生效了

二、vue + SpringBoot密码加密传输(前后分离板)

前言:目前登录接口密码是明文传输 为了提高安全性 调整为加密方式传输( 这里选择Rsa加密算法)

修改前
在这里插入图片描述
修改后

在这里插入图片描述

大概加密流程:

  1. 后台生成随机公钥私钥 (原本是固定公私秘钥 我改为了随机生成)
  2. 前台拿到公钥 集成jsencrypt实现密码加密
  3. 传输加密后的密码给后台
  4. 后台通过私钥对加密后的密码进行解密

后台改动

  1. 首先新建RsaUtils类
/*
 * Copyright (c) 2023. ltkj.com 石家庄旅投集团科技有限公司  All rights reserved.
 */
package com.ltkj.common.utils;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * 加密工具类
 *
 * @author wangl
 * @date 2023-10-10
 */
public class RsaUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(RsaUtil.class);

    private static final String RSA = "RSA";

    // Rsa 私钥 也可固定秘钥对 若依原写法(不安全)
    public static String privateKeys = "";

    private static String publicKeyStr = "";

    private static String privateKeyStr = "";

    private static final RsaKeyPair rsaKeyPair = new RsaKeyPair();


    /**
     * 构建RSA密钥对
     *
     * @return 生成后的公私钥信息
     */
    static {
        KeyPairGenerator keyPairGenerator = null;
        try {
            keyPairGenerator = KeyPairGenerator.getInstance(RSA);
            keyPairGenerator.initialize(1024);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
            String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
            String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
            rsaKeyPair.setPrivateKey(privateKeyString);
            rsaKeyPair.setPublicKey(publicKeyString);
            publicKeyStr = publicKeyString;
            privateKeyStr = privateKeyString;
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 私钥解密
     *
     * @param text 待解密的文本
     * @return 解密后的文本
     */
    public static String decryptByPrivateKey(String text) {
        LOGGER.info("rsaKeyPair.privateKey[{}]", rsaKeyPair.getPrivateKey());
        return decryptByPrivateKey(rsaKeyPair.getPrivateKey(), text);
    }

    /**
     * 公钥解密
     *
     * @param publicKeyString 公钥
     * @param text            待解密的信息
     * @return 解密后的文本
     */
    public static String decryptByPublicKey(String publicKeyString, String text) throws Exception {
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        byte[] result = cipher.doFinal(Base64.decodeBase64(text));
        return new String(result);
    }

    /**
     * 私钥加密
     *
     * @param privateKeyString 私钥
     * @param text             待加密的信息
     * @return 加密后的文本
     */
    public static String encryptByPrivateKey(String privateKeyString, String text) throws Exception {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        byte[] result = cipher.doFinal(text.getBytes());
        return Base64.encodeBase64String(result);
    }

    /**
     * 私钥解密
     *
     * @param privateKeyString 私钥
     * @param text             待解密的文本
     * @return 解密后的文本
     */
    public static String decryptByPrivateKey(String privateKeyString, String text) {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));
        KeyFactory keyFactory = null;
        try {
            keyFactory = KeyFactory.getInstance(RSA);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] result = cipher.doFinal(Base64.decodeBase64(text));
            return new String(result);
        } catch (Exception e) {
            LOGGER.error("decryptByPrivateKey error, privateKeyString[{}], text[{}]", privateKeyString, text);
            LOGGER.error("decryptByPrivateKey error: ", e);
            throw new RuntimeException("decryptByPrivateKey error");
        }
    }

    /**
     * 公钥加密
     *
     * @param publicKeyString 公钥
     * @param text            待加密的文本
     * @return 加密后的文本
     */
    public static String encryptByPublicKey(String publicKeyString, String text) throws Exception {
        X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] result = cipher.doFinal(text.getBytes());
        return Base64.encodeBase64String(result);
    }

    public static String getPublicKey() {
        return publicKeyStr;
    }

    public static String getPrivateKey() {
        return privateKeyStr;
    }

    public static RsaKeyPair rsaKeyPair() {
        return rsaKeyPair;
    }

    /**
     * RSA密钥对对象
     */
    public static class RsaKeyPair {
        private String publicKey;

        private String privateKey;

        public void setPublicKey(String publicKey) {
            this.publicKey = publicKey;
        }

        public void setPrivateKey(String privateKey) {
            this.privateKey = privateKey;
        }

        public RsaKeyPair() {

        }

        public RsaKeyPair(String publicKey, String privateKey) {
            this.publicKey = publicKey;
            this.privateKey = privateKey;
        }

        public String getPublicKey() {
            return publicKey;
        }

        public String getPrivateKey() {
            return privateKey;
        }
    }

}


RsaUtils添加了 generateKeyPair()构建密钥对 在第一次类加载时,通过static代码块 生成, 每次调用接口会是一样的公私秘钥 每次重启后公私钥不同

  1. 在controller层添加获取公钥接口

    /**
     * 获取公钥前端用来密码加密
     */
    @SaIgnore
    @GetMapping("/inner/publicKey")
    @Operation(summary = "获取公钥", description = "获取公钥接口")
    public R<RsaUtil.RsaKeyPair> publicKey() {
        // 测试 公私秘钥都传了 后期改为只传公钥
        // RsaUtils.RsaKeyPair rsaKeyPair = new RsaUtils.RsaKeyPair();
        // rsaKeyPair.setPublicKey(RsaUtils.getPublicKey());
        // return rsaKeyPair;
        return R.ok(RsaUtil.rsaKeyPair());
    }

  1. 登录接口类 修改(重点)
    @SaIgnore
    @PostMapping("/login")
    @Operation(summary = "账号密码", description = "账号密码登录")
    public R<Map<String, Object>> login(@Validated @RequestBody LoginBody loginBody) {
        Map<String, Object> ajax = new HashMap<>();
        // 新增加解密密码 20240105
        String pwd = RsaUtil.decryptByPrivateKey(loginBody.getPassword());
        // 生成令牌
        String token = loginService.login(loginBody.getDeptId(), loginBody.getUsername(), pwd, loginBody.getCode(), loginBody.getUuid());
        ajax.put(Constants.TOKEN, token);
        return R.ok(ajax);
    }

前端改动

  1. login.js添加获取公钥接口路径
export function getPublicKey() {
  return request({
    url: '/inner/publicKey',
    method: 'get',
  })
}
  1. user.js更改登陆方法
	getPublicKey() {
      return new Promise((resolve, reject) => {
        getPublicKey()
            .then(res => {
              resolve(res)
            })
            .catch(error => {
              reject(error)
            })
      })
    },

    // 登录
    Login({commit, dispatch}, userInfo) {
      const username = userInfo.username.trim()
      // const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      const deptId = userInfo.deptId
      return new Promise((resolve, reject) => {
        dispatch('getPublicKey').then(res => {
          let publicKey = res.data.publicKey
          const password = encrypt(userInfo.password, publicKey)
          login(username, password, deptId, code, uuid)
              .then(res => {
                setToken(res.data.token)
                commit('SET_TOKEN', res.data.token)
                resolve()
              }).catch(error => {
            reject(error)
          })
        })
      })
    },
  1. JSEncrypt.js更改加解密方法
// 加密
export function encrypt(txt, pubKey) {
    const encryptor = new JSEncrypt()
    encryptor.setPublicKey(pubKey) // 设置公钥
    return encryptor.encrypt(txt) // 对数据进行加密
}

// 解密
export function decrypt(txt, priKey) {
    const encryptor = new JSEncrypt()
    encryptor.setPrivateKey(privateKey) // 设置私钥
    return encryptor.decrypt(txt) // 对数据进行解密
}



好啦,登陆验证下吧~

或者把pubKey 写在encrypt 里获取 :

这样外面调用 encrypt时,就不用在每次都得在外面 先获取 pub-key了


import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
import request from '@/utils/request'

// 密钥对生成 http://web.chacuo.net/netrsakeypair

const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
  'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='

const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
  '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
  'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
  'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
  'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
  'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
  'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
  'UP8iWi1Qw0Y='

const key = {
  pubKey: 'ltkj123',
  priKey: 'ltkj321'
}

export function getPublicKey() {
  return request({
    url: '/inner/publicKey',
    method: 'get',
  })
}
// 加密
export async function encrypt(txt) {
  console.log("encrypt init==" + (key.pubKey == 'ltkj123'));
  if (key.pubKey == 'ltkj123') {
    await getPublicKey().then(res => {
      key.pubKey = res.data.publicKey;
      key.priKey = res.data.privateKey
    });
  }
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(key.pubKey) // 设置公钥
  return encryptor.encrypt(txt) // 对数据进行加密
}

// 解密
export async function decrypt(txt) {
    const encryptor = new JSEncrypt()
      console.log("decrypt key.priKey==" + (key.priKey == 'ltkj321'));
    if (key.priKey == 'ltkj321') {
        await getPublicKey().then(res => {
        key.pubKey = res.data.publicKey;
        key.priKey = res.data.privateKey
      });
    }
    encryptor.setPrivateKey(key.priKey) // 设置私钥
    return encryptor.decrypt(txt) // 对数据进行解密
}



user.js

 // 登录
    Login({commit, dispatch}, userInfo) {
      const username = userInfo.username.trim()
      // const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      const deptId = userInfo.deptId
      return new Promise((resolve, reject) => {
          encrypt(userInfo.password).then(res => {
            console.log("Login res==" + res);
            login(username, res, deptId, code, uuid)
                .then(res => {
                  setToken(res.data.token)
                  commit('SET_TOKEN', res.data.token)
                  resolve()
                }).catch(error => {
              reject(error)
            })
          })
        })
    },

login.vue

import { getCodeImg, getCompanyInfo } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
 

     
    handleLogin () {
      //表单验证-Element-ui的$refs.loginForm.validate()接口
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          // 切换为显示登陆中
          this.loading = true;
          //是否记住密码
          if (this.loginForm.rememberMe) {
            // 记住密码-设置到Cookie中-[过期时间为30天]
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            // 密码加密
            encrypt(this.loginForm.password).then(res => {

                console.log("handleLogin res==" + res);
                Cookies.set("password", res, { expires: 30 });
                Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
                Cookies.set('deptId', this.loginForm.deptId, { expires: 30 });
            });
          } else {
            // 不记住密码-将上一次设置到Cookie中的值移除
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove('rememberMe');
          }
          // 提交用户信息到 Vuex中
          // Vuex全局状态管理相关的内容
          this.$store.dispatch("Login", this.loginForm).then(() => {
            // 路由跳转相关的内容
            this.$router.push({ path: this.redirect || "/" }).catch(() => { });
          }).catch(() => {
            // 登陆失败时-取消loading显示,并尝试重新获取验证码图片资源
            this.loading = false;
            if (this.captchaEnabled) {
              this.getCode();
            }
          });
        }
      });
    }

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值