(一)敏感信息混淆

最近网上出现的安全事故很多,最惨的莫过于那场酒店事件,因此项目整改也需要对用户的敏感信息进行脱敏,由于前期的项目并没有规划这一模块,为了满足安全规范达到上线标准,需要对项目进行改造。


改造方案

  1. 不能采用手动encode/decode的方式(避免在某个地方忘记encode/decode了)

  2. 非侵入性改造,后续的coding的时候无需在费心思考虑

方案1: 从数据层入手,在提交数据库前对敏感字段进行encode,在取得数据集后对结果集的敏感字段进行decode

  项目中选择mybatis为中间件,因此可以使用mybatis拦截器,方案实现起来简单,符合条件2,但却会带来关键问题:在单业务编写时基本没有问题,但是在多业务关联的时候却会对关联的字段有影响,需要在手动encode,不符合条件1,因此方案被pass。

方案2: 从控制层入手,在接收客户端提交的数据时对敏感字段进行encode,在response序列化对象时对敏感字段进行decode。

  该方案能保证敏感字段在业务逻辑实现过程中都是混淆的,后续也无需关注哪个对象的哪个字段是需要encode/decode,满足改造方案。


 实现方案

   1. 定义敏感字段类型,由于混淆后的字段类型均可以为字符串,因此我们可以仿造String类型来进行定义(由于String类是final,因此不考虑继承的方式)

/**
 * @author zhangqiuyang
 * Created on 2018/8/17.
 */
public class SecretString implements java.io.Serializable, Comparable<String>, CharSequence {
    @Setter
    @Getter
    private String value;

    public SecretString(String value) {
        this.value = value;
    }

    /**
     * Returns the length of this character sequence.  The length is the number
     * of 16-bit <code>char</code>s in the sequence.
     *
     * @return the number of <code>char</code>s in this sequence
     */
    @Override
    public int length() {
        return value.length();
    }

    /**
     * Returns a string representation of the object. In general, the
     * {@code toString} method returns a string that
     * "textually represents" this object. The result should
     * be a concise but informative representation that is easy for a
     * person to read.
     * It is recommended that all subclasses override this method.
     * <p>
     * The {@code toString} method for class {@code Object}
     * returns a string consisting of the name of the class of which the
     * object is an instance, the at-sign character `{@code @}', and
     * the unsigned hexadecimal representation of the hash code of the
     * object. In other words, this method returns a string equal to the
     * value of:
     * <blockquote>
     * <pre>
     * getClass().getName() + '@' + Integer.toHexString(hashCode())
     * </pre></blockquote>
     *
     * @return a string representation of the object.
     */
    @Override
    public String toString() {
        return value;
    }

    /**
     * Returns the <code>char</code> value at the specified index.  An index ranges from zero
     * to <tt>length() - 1</tt>.  The first <code>char</code> value of the sequence is at
     * index zero, the next at index one, and so on, as for array
     * indexing.
     *
     * <p>If the <code>char</code> value specified by the index is a
     * <a href="{@docRoot}/java/lang/Character.html#unicode">surrogate</a>, the surrogate
     * value is returned.
     *
     * @param index the index of the <code>char</code> value to be returned
     * @return the specified <code>char</code> value
     * @throws IndexOutOfBoundsException if the <tt>index</tt> argument is negative or not less than
     *                                   <tt>length()</tt>
     */
    @Override
    public char charAt(int index) {
        return value.charAt(index);
    }

    /**
     * Returns a <code>CharSequence</code> that is a subsequence of this sequence.
     * The subsequence starts with the <code>char</code> value at the specified index and
     * ends with the <code>char</code> value at index <tt>end - 1</tt>.  The length
     * (in <code>char</code>s) of the
     * returned sequence is <tt>end - start</tt>, so if <tt>start == end</tt>
     * then an empty sequence is returned.
     *
     * @param start the start index, inclusive
     * @param end   the end index, exclusive
     * @return the specified subsequence
     * @throws IndexOutOfBoundsException if <tt>start</tt> or <tt>end</tt> are negative,
     *                                   if <tt>end</tt> is greater than <tt>length()</tt>,
     *                                   or if <tt>start</tt> is greater than <tt>end</tt>
     */
    @Override
    public CharSequence subSequence(int start, int end) {
        return value.subSequence(start, end);
    }


    /**
     * Compares this object with the specified object for order.  Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     *
     * <p>The implementor must ensure <tt>sgn(x.compareTo(y)) ==
     * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>.  (This
     * implies that <tt>x.compareTo(y)</tt> must throw an exception iff
     * <tt>y.compareTo(x)</tt> throws an exception.)
     *
     * <p>The implementor must also ensure that the relation is transitive:
     * <tt>(x.compareTo(y)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
     * <tt>x.compareTo(z)&gt;0</tt>.
     *
     * <p>Finally, the implementor must ensure that <tt>x.compareTo(y)==0</tt>
     * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
     * all <tt>z</tt>.
     *
     * <p>It is strongly recommended, but <i>not</i> strictly required that
     * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>.  Generally speaking, any
     * class that implements the <tt>Comparable</tt> interface and violates
     * this condition should clearly indicate this fact.  The recommended
     * language is "Note: this class has a natural ordering that is
     * inconsistent with equals."
     *
     * <p>In the foregoing description, the notation
     * <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
     * <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
     * <tt>0</tt>, or <tt>1</tt> according to whether the value of
     * <i>expression</i> is negative, zero or positive.
     *
     * @param o the object to be compared.
     * @return a negative integer, zero, or a positive integer as this object
     * is less than, equal to, or greater than the specified object.
     * @throws NullPointerException if the specified object is null
     * @throws ClassCastException   if the specified object's type prevents it
     *                              from being compared to this object.
     */
    @Override
    public int compareTo(String o) {
        return value.compareTo(o);
    }

    /**
     * 解密后的值
     *
     * @return
     */
    public String decryptValue() {
        return SecurityUtil.decryptString(value);
    }
}

   2. 定义敏感字段类型的序列化器

/**
 * @author zhangqiuyang
 * Created on 2018/8/17.
 */
@JsonComponent
public class SecretStringJsonCombinedSerializer {


    /**
     * 密文序列化
     */
    public static class SecretStringJsonSerializer extends JsonSerializer<SecretString> {
        /**
         * 重写序列化SecretString类型字段
         *
         * @param secretString
         * @param jsonGenerator
         * @param serializerProvider
         * @throws IOException
         */
        @Override
        public void serialize(SecretString secretString, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
                throws IOException {

            jsonGenerator.writeString(secretString.decryptValue());
        }
    }

    /**
     * 密文反序列化
     */
    public static class SecretStringJsonDeserializer extends JsonDeserializer<SecretString> {

        @Override
        public SecretString deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
                throws IOException {
            TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser);
            return new SecretString(SecurityUtil.encryptString(((TextNode) treeNode).asText()));
        }
    }


}

综上,改造基本完成。

github:https://github.com/zqyx5201/SecretStringTest

作者: zhangQ
个人主页:https://www.yxzqy.com/
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文地址:https://www.cnblogs.com/zqyx/p/9635562.html