自定义注解+Aop实现数据库指定字段脱敏(可以处理obj和list<obj>形式的入参和响应)

需要的依赖

        <!-- aop切面 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.6.13</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <version>1.16.4</version>
        </dependency>

        <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>       
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>28.1-jre</version>
        </dependency>
    

自定义注解

package com.czc.datamask.annotation;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import java.lang.annotation.*;

/**
 * 数据加密注解 ——与方法加密注解配合使用
 */
@Documented
@Target(ElementType.FIELD)  //注解生效在字段上
@Retention(RetentionPolicy.RUNTIME)//在运行时生效
@Order(Ordered.HIGHEST_PRECEDENCE)//设置优先级
public @interface Dataprocess {
}
package com.czc.datamask.annotation;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import java.lang.annotation.*;

/**
 * 方法加密注解 ——与数据加密注解配合使用
 */
@Documented
@Target(ElementType.METHOD)  //注解生效在方法上
@Retention(RetentionPolicy.RUNTIME)//在运行时生效
@Order(Ordered.HIGHEST_PRECEDENCE)//设置优先级
public @interface MethodProcess {
}

加密工具类(使用的ase对称加密)

package com.czc.datamask.util;

import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;


import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;



/**
 * ASE加密工具类
 */
public class AseUtil {

    private static final String KEY_ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法

    /**
     * AES 加密操作
     *
     * @param content  待加密内容
     * @param password 加密密码
     * @return 返回Base64转码后的加密数据
     */
    public static String encrypt(String content, String password) {
        try {
            if (!StringUtils.isEmpty(content)) {
                Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器
                byte[] byteContent = content.getBytes("utf-8");
                cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(password));// 初始化为加密模式的密码器
                byte[] result = cipher.doFinal(byteContent);// 加密
                return Base64Utils.encodeToString(result);
            }
            return null;
        } catch (Exception ex) {
            Logger.getLogger(AseUtil.class.getName()).log(Level.SEVERE, null, ex);
        }

        return null;
    }

    /**
     * AES 解密操作
     *
     * @param content
     * @param password
     * @return
     */


    public static String decrypt(String content, String password) {
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password));
            byte[] result = cipher.doFinal(Base64Utils.decodeFromString(content));
            return new String(result, "utf-8");
        } catch (InvalidKeyException e) {
            Logger.getLogger(AseUtil.class.getName()).log(Level.SEVERE, "解密失败,可能是由于加密的盐不同", e);
        } catch (Exception ex) {
            return content;
        }
        return content;
    }



    /**
     * 生成加密秘钥
     *
     * @return
     */
    private static SecretKeySpec getSecretKey(final String password) {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = null;
        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);
            //AES 要求密钥长度为 128
            kg.init(128, new SecureRandom(password.getBytes()));
            //生成一个密钥
            SecretKey secretKey = kg.generateKey();
            return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);// 转换为AES专用密钥
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(AseUtil.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
}

配置文件中增加配置

64944ade06974fccbcf1915eba00782a.png

aop切面类

package com.czc.datamask.aop;

import com.czc.datamask.annotation.Dataprocess;
import com.czc.datamask.util.AseUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;


@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
@Aspect
public class DataProcessAspect {
    Logger log = LoggerFactory.getLogger(this.getClass());

    @Value("${secretKey}")
    private String secretKey;

    @Pointcut("@annotation(com.czc.datamask.annotation.MethodProcess)")
    public void annotationPointCut() {
    }

    @Around("annotationPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object responseObj = null;
        try {
                if (joinPoint.getArgs().length != 0) {  //请求参数不为空的情况下
                    Object requestObj = joinPoint.getArgs()[0];
                    handleEncrypt(requestObj);
                }
                    responseObj = joinPoint.proceed();
                    handleDecrypt(responseObj);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            log.error("Aop自动加解密处理出现异常{}", e);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            log.error("Aop自动加解密处理出现异常{}", throwable);
        }
        return responseObj;
    }

    /**
     * 处理加密
     *
     * @param requestObj
     */
    private void handleEncrypt(Object requestObj) throws IllegalAccessException {
        if (Objects.isNull(requestObj)) {
            return;
        }
        if (requestObj instanceof List) {
            List<Object> requestList = (List<Object>) requestObj;
            for (Object obj : requestList) {
                encryptFields(obj);
            }
        } else {
            encryptFields(requestObj);
        }
    }

    private void encryptFields(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            boolean hasSecureField = field.isAnnotationPresent(Dataprocess.class);
            if (hasSecureField) {
                field.setAccessible(true);
                String plaintextValue = (String) field.get(obj);
                String encryptValue = AseUtil.encrypt(plaintextValue, secretKey);
                field.set(obj, encryptValue);
            }
        }
    }


    private Object handleDecrypt(Object responseObj) throws IllegalAccessException {
        if (Objects.isNull(responseObj)) {
            return null;
        }
        if (responseObj instanceof List) {
            List<Object> responseList = (List<Object>) responseObj;
            for (Object obj : responseList) {
                decryptFields(obj);
            }
        } else {
            decryptFields(responseObj);
        }
        return responseObj;
    }

    private void decryptFields(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            boolean hasSecureField = field.isAnnotationPresent(Dataprocess.class);
            if (hasSecureField) {
                field.setAccessible(true);
                String encryptValue = (String) field.get(obj);
                String plaintextValue = AseUtil.decrypt(encryptValue, secretKey);
                field.set(obj, plaintextValue);
            }
        }
    }
}

如何使用

把@DataProcess加在需要自动加解密的字段上

234802d479e642e789b5c1dbe31f8873.png

@MethodProcess加在需要加解密的方法上

9aead27fd2164f71a5946cb78caddc22.png

效果展示

加密

b6c5f07a6c04440ca52b9bcd12d0648c.png

f91c90d5c7ec430080f9daab1825130f.png

解密

2db3a9fc47ae4e58b6a646a4a0ad1bdc.png

beea2f393d804137ac6eb6dd186befb0.png

2236b54a27024464a57c9b87c725151e.png

 

demo地址(最新代码在dev分支)

自定义注解+aop实现数据库脱敏: 使用自定义注解+aop实现数据库字段脱敏,拉取之后install到本地仓库,在项目中引入,在启动类增加 @SpringBootApplication(scanBasePackages = {"com.czc"})即可生效 (gitee.com)

 

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要通过自定义注解AOP实现Spring Security配置指定接口不需要Token才能访问,可以按照以下步骤进行操作: 1. 创建一个自定义注解,例如`@NoTokenRequired`,用于标识不需要Token的接口。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoTokenRequired { } ``` 2. 创建一个切面类,用于拦截带有`@NoTokenRequired`注解的方法,并跳过Spring Security的Token验证。 ```java @Aspect @Component public class TokenValidationAspect { @Before("@annotation(com.example.NoTokenRequired)") public void skipTokenValidation(JoinPoint joinPoint) { // 跳过Spring Security的Token验证逻辑 SecurityContextHolder.getContext().setAuthentication(null); } } ``` 3. 配置Spring Security,将AOP切面类添加到Spring Security的配置中。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private TokenValidationAspect tokenValidationAspect; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 配置需要Token验证的接口 .anyRequest().authenticated() .and() .csrf().disable(); // 将AOP切面类添加到Spring Security的配置中 http.addFilterBefore(tokenValidationAspect, UsernamePasswordAuthenticationFilter.class); } } ``` 4. 在需要不需要Token验证的接口上,添加`@NoTokenRequired`注解。 ```java @RestController public class ExampleController { @NoTokenRequired @GetMapping("/example") public String example() { return "This API does not require Token"; } } ``` 这样配置之后,带有`@NoTokenRequired`注解的接口将不会进行Spring Security的Token验证,即可在没有Token的情况下访问该接口。其他接口仍然需要进行Token验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值