システムログ内の機密フィールドマスク処理

33579 ワード

金融業務の開発として、多くのインタフェースがユーザー情報を使用する必要がありますが、ユーザー情報にはユーザー名、銀行カード番号などの敏感なフィールドがあります.したがって,ユーザ機密情報の保存やログ印刷の際には,これらの機密情報を明文的に保存することはできない.データベースに対してユーザ機密情報を保存する場合、一般的なシステムには復号化されたシステムがあります.ユーザ機密情報を保存する必要がある場合は,ユーザ情報を暗号化してデータベースに保存する.これは本明細書で説明する範疇ではなく、データベースに保存する際に推奨されるフォーマットは以下の通りです.
511911202005281234
      :P1234567
   DB   :P1234567:511******1234

ユーザー情報を比較する際にマスク情報を使用して、会員情報の本当の身分証明書番号が合っているかどうかを確認する可能性があります.
1、ログ印刷に考慮すべき問題
次に,システムにおける機密ログ印刷の問題について議論する.
ログを印刷する必要がある場合、通常は2つの方法でログの印刷を行います.
  • ObjectのtoStringを書き換え、注目フィールドの情報を印刷
  • fastjson(または他のJSON処理フレームワーク)のJSONを呼び出す.toStringメソッド
  • また、ログ処理では、次の2つの処理が一般的に使用されます.
  • ログは無視され、このフィールドがログクエリに影響を与えない場合、フィールドログは無視されます.オブジェクトフィールドが空の場合は印刷しません(またはnull値を印刷します)、値がある場合は***を印刷します.
  • 正規印刷ログは、このフィールドがログクエリに影響を与える場合に正規印刷ログを使用することができる.例えば、ユーザの身分証明書番号は、ログに身分証明書の上位3桁、および下位4桁の間に6個の*番号を印刷することができる.例:511******1234.

  • 以上の考慮に基づいて、私たちはそれを実現します.
    2、対象toString印刷
    2.1 Ignore
    注釈は、オブジェクト内のこのフィールドのログが無視できることを示します.
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Ignore {
    }
    

    2.2 Mask
    注釈は、オブジェクト内のこのフィールドのログがログのクエリーに影響することを示し、正規の方法で印刷できます.
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Mask {
    
        String pattern() default "";
    
    }
    

    2.3 AnnotationToStringBuilder
    common-lang 3のReflectionToStringBuilderを実現し、反射によって印刷対象のログを呼び出す.ReflectionToStringBuilder#appendFieldsInメソッドを書き換えて、私たちの印刷効果を実現しました.
    public class AnnotationToStringBuilder extends ReflectionToStringBuilder {
    
        public AnnotationToStringBuilder(Object object) {
            super(object);
        }
    
        public AnnotationToStringBuilder(Object object, ToStringStyle style) {
            super(object, style);
        }
    
        public AnnotationToStringBuilder(Object object, ToStringStyle style, StringBuffer buffer) {
            super(object, style, buffer);
        }
    
        public <T> AnnotationToStringBuilder(T object, ToStringStyle style, StringBuffer buffer, Class<? super T> reflectUpToClass, boolean outputTransients, boolean outputStatics) {
            super(object, style, buffer, reflectUpToClass, outputTransients, outputStatics);
        }
    
        public <T> AnnotationToStringBuilder(T object, ToStringStyle style, StringBuffer buffer, Class<? super T> reflectUpToClass, boolean outputTransients, boolean outputStatics, boolean excludeNullValues) {
            super(object, style, buffer, reflectUpToClass, outputTransients, outputStatics, excludeNullValues);
        }
    
        @Override
        protected void appendFieldsIn(Class clazz) {
            if (clazz.isArray()) {
                this.reflectionAppendArray(this.getObject());
                return;
            }
            Field[] fields = clazz.getDeclaredFields();
            AccessibleObject.setAccessible(fields, true);
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                String fieldName = field.getName();
                if (this.accept(field)) {
                    try {
                        Object fieldValue = this.getValue(field);
                        Mask mask = AnnotationUtils.getAnnotation(field, Mask.class);
                        if( (fieldValue instanceof String) && mask != null && StringUtils.isNotBlank(mask.pattern())) {
                            String value = String.class.cast(fieldValue);
                            String pattern = mask.pattern();
                            this.append(fieldName, OutMaskUtil.replaceWithMask(pattern, value));
                            continue;
                        }
                        Ignore ignore = AnnotationUtils.getAnnotation(field, Ignore.class);
                        if(ignore != null) {
                            if(fieldValue != null) {
                                this.append(fieldName, "***");
                            } else {
                                this.append(fieldName, "null");
                            }
                            continue;
                        }
                        this.append(fieldName, fieldValue);
                    } catch (IllegalAccessException ex) {
                        throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
                    }
                }
            }
        }
    }
    
    

    この方式はObjectのtoString法を実現する必要がある.例:
    @Data
    public class UserInfo {
    
    	/**      */
    	private String username;
    
    	/**      */
    	@Mask(pattern = "[\\w]{5}([\\w]*)[\\w]{3}")
    	private String idNo;
    
    	/**      */
    	@Ignore
    	private String address;
    	
    	@Override
    	public String toString(){
    		return new AnnotationToStringBuilder(this).toString();
    	}
    
    }
    

    3、JSONのtoJSOnString方法
    この場合、2つのケースがあります.1つは、このオブジェクトがjava beanオブジェクトであり、もう1つはJSONObjectです.これらは、fastjsonが提供するcom.alibaba.fastjson.serializer.ValueFilterを実装することによって処理することができる.Java beanでは反射処理が可能であり,JSONObjectの処理には特殊な処理が必要であり,汎用化できない.
    public class LoggerJSON {
    
    	static final SerializeConfig                    SERIALIZE_CONFIG;
    	static final MaskFilter                         MASK_FILTER;
    	static final Map<String, MaskStrategy>          MASK_FIELDS;
    
    	static {
    		SERIALIZE_CONFIG = new SerializeConfig();
    		MASK_FILTER = new MaskFilter();
    		MASK_FIELDS = buildMaskConfig();
    	}
    
    	/**
    	 *     
    	 */
    	public static Map<String, MaskStrategy> buildMaskConfig() {
    		Map<String, MaskStrategy> result = new HashMap<>();
    		result.put("idNo", new NullMaskStrategy());
    		return result;
    	}
    
    	public static <T> String toMaskJsonString(T content) {
    		return JSON.toJSONString(content, SERIALIZE_CONFIG, MASK_FILTER);
    	}
    
    	static class MaskFilter implements ValueFilter {
    
    		@Override
    		public Object process(Object object, String name, Object value) {
    			if(!isString(value)){
    				return value;
    			}
    			String stringValue = String.class.cast(value);
    			Class<?> clazz = object.getClass();
    			try {
    				// JSONObject       
    				if(MASK_FIELDS.containsKey(name)){
    					MaskStrategy maskStrategy = MASK_FIELDS.get(name);
    					return maskStrategy.process(stringValue);
    				}
    				Field field = clazz.getDeclaredField(name);
    				field.setAccessible(true);
    				// Java Bean    @Ignore   
    				Ignore ignore = AnnotationUtils.getAnnotation(field, Ignore.class);
    				if(ignore != null && value != null) {
    					return "***";
    				}
    				// Java Bean    @Mask   
    				Mask mask = AnnotationUtils.getAnnotation(field, Mask.class);
    				if(mask != null && StringUtils.isNotBlank(mask.pattern())) {
    					return OutMaskUtil.replaceWithMask(mask.pattern(), stringValue);
    
    				}
    			} catch (Exception e) {
    				// ignore
    			}
    			return value;
    		}
    
    		private boolean isString(Object value){
    			if(value == null) {
    				return false;
    			}
    			return value instanceof String;
    		}
    	}
    
    }
    

    Java beanに対しては元の@Ignoreと@Mask注記を用いて処理する.JSOnObjectの場合は、処理するフィールドを手動で追加し、ログ印刷ポリシーを指定できます.ポリシーインタフェースは次のとおりです.
    public interface MaskStrategy {
    
    	String process(String value);
    
    }
    

    ここでidNoは、空の印刷ポリシーを使用します.つまり、印刷しません.
    public class NullMaskStrategy implements MaskStrategy {
    
    	@Override
    	public String process(String value) {
    		return null;
    	}
    
    }
    

    JSON方式でログの脱敏を行うには、ObjectオブジェクトのtoStringメソッドを書き換える必要はありません.