WEB开发之敏感数据处理(一) - JPA敏感数据自动加解密

实现原理

JPA提供AttributeConverter接口用于实现数据库和实体之间数据的转换。利用这个特性可以在转换时进行加解密,从而实现自动加解密的功能

定义一个 Converter

  • 定义一个SensitiveConverter 实现JPA的 AttributeConverter<String, String>
  • convertToDatabaseColumn entity值转换为数据库值,可实现加密逻辑
  • convertToEntityAttribute 将数据库值转换为entity值,可实现解密
@Converter
public class SensitiveConverter implements AttributeConverter<String, String> {

    @Override
    public String convertToDatabaseColumn(String attribute) {
        // TODO: 2023/5/30 加密逻辑 
        return null;
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        // TODO: 2023/5/30 解密逻辑 
        return null;
    }
}

定义一个实体

定义一个User实体,通过@Convert(converter = SensitiveConverter.class)注解的需要加密的属性。

  • 在JPA入库时会自动调用SensitiveConverter#convertToDatabaseColumn 方法,在这个方法内实现加密逻辑
  • 在JPA查询时会自动调用SensitiveConverter#convertToEntityAttribute方法,可以实现解密逻辑
@Getter
@Setter
@Entity
public class User {
    @Id
    private Long id;
    
    private String username;

    @Convert(converter = SensitiveConverter.class)
    private String password;
}

优雅实现

上边的是实现方式,需要在字段上增加@Convert(converter = SensitiveConverter.class),由于@Convert可用用于任意类型的转换,converter的值可以是任意实现了AttributeConverter的类型。
这里我们可以借助JSR 269: Pluggable Annotation Processing API 通过插件式注解处理方式,通过定义一个Sensitive注解,在编译时自动替换成@Convert(converter = SensitiveConverter.class) (有点类似于C的宏)

定义敏感数据注解 - @Sensitive

  • @Retention(RetentionPolicy.SOURCE):注解仅在源码阶段生效,编译后会自动转换为@Convert(converter = SensitiveConverter.class)
  • @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}): 可使用注解的范围,这里和@Convert保持一致即可
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface Sensitive {
}

定义注解处理器 - SensitiveProcessor

继承AbstractProcessor 在编译时对注解进行处理

@SupportedSourceVersion(value = SourceVersion.RELEASE_8)
@SupportedAnnotationTypes(value = "net.reduck.sdp.annotation.Sensitive")
public class SensitiveProcessor extends AbstractProcessor {
    private Types types = null;
    private Elements elements = null;
    private Filer filer = null;
    private Messager messager = null;

    private JavacTrees trees;
    private TreeMaker treeMaker;
    private Names names;
    private Symtab symtab;
    private AstMojo astMojo;

    public SensitiveProcessor() {
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 判断方法应该添加什么注解
        for (Element element : roundEnvironment.getElementsAnnotatedWith(Sensitive.class)) {

            javax.lang.model.element.Name methodName = element.getSimpleName();

            TypeElement typeElem = (TypeElement) element.getEnclosingElement();

            String typeName = typeElem.getQualifiedName().toString();
            astMojo.importIfAbsent(element, Convert.class);
            astMojo.importIfAbsent(element, SensitiveConverter.class);
            JCTree jcTree = trees.getTree(typeElem);

            jcTree.accept(new TreeTranslator() {

                @Override
                public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
                    List<JCTree.JCAnnotation> jcAnnotations = jcVariableDecl.mods.annotations;
                    try {
                        if (jcAnnotations != null && jcAnnotations.size() > 0) {
                            List<JCTree.JCAnnotation> nil = List.nil();
                            System.err.println(nil.toString());
                            System.err.println("===============");
                            for (JCTree.JCAnnotation jcAnnotation : jcAnnotations) {
                                if (Sensitive.class.getName().equals(jcAnnotation.getAnnotationType().type.tsym.toString())) {
                                    JCTree.JCAnnotation converterAnnotation = treeMaker.Annotation(
                                            astMojo.select(Convert.class.getName())
                                            , List.of(treeMaker.Assign(treeMaker.Ident(names.fromString("converter"))
                                                    , treeMaker.Select(treeMaker.Ident(names.fromString(SensitiveConverter.class.getSimpleName()))
                                                            , names.fromString("class")))));

                                    nil = nil.append(converterAnnotation);
                                } else {
                                    nil = nil.append(jcAnnotation);
                                }
                            }

                            jcVariableDecl.mods.annotations = nil;

                            System.err.println(nil.toString());
                        }
                    } catch (Exception e) {
                        System.err.println(e);
                    }

                    try {
                        super.visitVarDef(jcVariableDecl);
                    } catch (Exception e) {
                        System.err.println(e);
                    }

                }
            });
        }

        return true;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        this.types = processingEnv.getTypeUtils();
        this.elements = processingEnv.getElementUtils();
        this.filer = processingEnv.getFiler();
        this.messager = processingEnv.getMessager();

        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
        this.symtab = Symtab.instance(context);

        this.astMojo = new AstMojo(trees, treeMaker, names, symtab);
    }

}
public class AstMojo {

    private final JavacTrees trees;

    private final TreeMaker treeMaker;

    private final Names names;

    private final Symtab symtab;

    public AstMojo(JavacTrees trees, TreeMaker treeMaker, Names names, Symtab symtab) {
        this.trees = trees;
        this.treeMaker = treeMaker;
        this.names = names;
        this.symtab = symtab;
    }

    public void importIfAbsent(Element element, Class<?> importType) {
        JCTree.JCCompilationUnit compilationUnit = ((JCTree.JCCompilationUnit) trees.getPath(element).getCompilationUnit());
        boolean contains = false;
        for (JCTree.JCImport jcImport : compilationUnit.getImports()) {
            if (importType.getName().equals(jcImport.getQualifiedIdentifier().toString())) {
                contains = true;
                break;
            }
        }

        if (!contains) {
            JCTree.JCIdent jcIdent = treeMaker.Ident(names.fromString(importType.getPackage().getName()));
            Name className = names.fromString(importType.getSimpleName());
            JCTree.JCFieldAccess jcFieldAccess = treeMaker.Select(jcIdent, className);
            JCTree.JCImport jcImport = treeMaker.Import(jcFieldAccess, false);
            compilationUnit.defs = compilationUnit.defs.prepend(jcImport);
        }
    }

    public JCTree.JCExpression select(String path) {
        JCTree.JCExpression expression = null;
        int i = 0;
        for (String split : path.split("\\.")) {
            if (i == 0)
                expression = treeMaker.Ident(names.fromString(split));
            else {
                expression = treeMaker.Select(expression, names.fromString(split));
            }
            i++;
        }

        return expression;
    }

    public JCTree.JCIdent identFromString(String name) {
        return treeMaker.Ident(names.fromString(name));
    }
}

注解处理器生效

在项目的resources 下新增文件META-INF/services/javax.annotation.processing.Processor
内容如下

net.reduck.sdp.processor.SensitiveProcessor
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值