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

实现原理

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

定义一个 Converter

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

    @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 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 jcAnnotations = jcVariableDecl.mods.annotations;
                    try {
                        if (jcAnnotations != null && jcAnnotations.size() > 0) {
                            List 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

你可能感兴趣的:(Java,SpringBoot,java,JPA,敏感数据)