Fastjson源码分析—ParserConfig的作用及原理(1)

2021SC@SDUSC
使用Fastjson进行反序列化时,我们总希望能够对反序列化的过程进行一个定制,有时候这种定制我们希望是临时的,有时希望是全局的。之前提到过我们可以通过feature参数对反序列化的属性进行定制,包括是否允许使用大括号、是否允许多重逗号等。今天谈一下ParserConfig类,它拥有比feature更强大的功能,从更高的角度对反序列化过程进行控制,支持全局定制,也可以进对某一次反序列化改变其默认的行为。下面开始分析。

ParserConfig的使用

上一次测试ASM的代码中,已经用到了ParserConfig的全局对象,进行ASM enable的设置。这次我们尝试使用局部对象来定制反序列化。

 @Test
    public void test03(){
        String str="{\"age\":13,\"name\":\"james\"}";
        ParserConfig config=new ParserConfig();
        config.setAsmEnable(false);
        Student student= JSON.parseObject(str,Student.class,config);
        System.out.println("age:"+student.getAge());
        System.out.println("name:"+student.getName());

    }

这里我们使用config对象,设置了ASM为关闭状态(默认打开),然后对json字符串进行了反序列化。这就是该类的简单用法。

ParserConfig的变量及含义

public static final String DENY_PROPERTY = "fastjson.parser.deny";
    public static final String AUTOTYPE_ACCEPT = "fastjson.parser.autoTypeAccept";
    public static final String AUTOTYPE_SUPPORT_PROPERTY = "fastjson.parser.autoTypeSupport";
    public static final String[] DENYS;
    private static final String[] AUTO_TYPE_ACCEPT_LIST;
    public static final boolean AUTO_SUPPORT;
    public static ParserConfig global;
    private final IdentityHashMap<Type, ObjectDeserializer> deserializers;
    private boolean asmEnable;
    public final SymbolTable symbolTable;
    public PropertyNamingStrategy propertyNamingStrategy;
    protected ClassLoader defaultClassLoader;
    protected ASMDeserializerFactory asmFactory;
    private static boolean awtError;
    private static boolean jdk8Error;
    private boolean autoTypeSupport;
    private long[] denyHashCodes;
    private long[] acceptHashCodes;
    public final boolean fieldBased;
    public boolean compatibleWithJavaBean;

解释一下这里面最关键的变量:
AUTO_SUPPORT:表示自动类型反序列化是否打开。打开后,允许用户在反序列化数据中通过“@type”指定反序列化的Class类型。

global:全局ParserConfig对象,对整个项目进行控制,包括是否使用asm、是否打开autotype等

asmEnable:设置asm是否可用,默认在安卓环境下为false(处于性能考虑),其他环境下为true

defaultClassLoader:默认的类加载器

ParserConfig的关键方法解析

checkAutoType(String typeName, Class<?> expectClass, int features)

fastjson需要将json字符串反序列化成对象,就需要调用对象的getter和setter,如果这些方法里面存在危险操作,就会导致漏洞。意识到这一问题,开发者使用了checkAutoType方法来检测类是否允许被反序列化。简单来说,这个方法就是一个黑名单检测方法,下面看一下源代码。

 public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        if (typeName == null) {
            return null;
        } else if (typeName.length() < 128 && typeName.length() >= 3) {
            String className = typeName.replace('$', '.');
            Class<?> clazz = null;
            long BASIC = -3750763034362895579L;
            long PRIME = 1099511628211L;
            long h1 = (-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L;
            if (h1 == -5808493101479473382L) {
                throw new JSONException("autoType is not support. " + typeName);
            } else if ((h1 ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
                throw new JSONException("autoType is not support. " + typeName);
            } else {
                long h3 = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
                long hash;
                int i;
                if (this.autoTypeSupport || expectClass != null) {
                    hash = h3;

                    for(i = 3; i < className.length(); ++i) {
                        hash ^= (long)className.charAt(i);
                        hash *= 1099511628211L;
                        if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
                            clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                            if (clazz != null) {
                                return clazz;
                            }
                        }

                        if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
                            throw new JSONException("autoType is not support. " + typeName);
                        }
                    }
                }

                if (clazz == null) {
                    clazz = TypeUtils.getClassFromMapping(typeName);
                }

                if (clazz == null) {
                    clazz = this.deserializers.findClass(typeName);
                }

                if (clazz != null) {
                    if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {
                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    } else {
                        return clazz;
                    }
                } else {
                    if (!this.autoTypeSupport) {
                        hash = h3;

                        for(i = 3; i < className.length(); ++i) {
                            char c = className.charAt(i);
                            hash ^= (long)c;
                            hash *= 1099511628211L;
                            if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0) {
                                throw new JSONException("autoType is not support. " + typeName);
                            }

                            if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
                                if (clazz == null) {
                                    clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                                }

                                if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                                }

                                return clazz;
                            }
                        }
                    }

                    if (clazz == null) {
                        clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                    }

                    if (clazz != null) {
                        if (TypeUtils.getAnnotation(clazz, JSONType.class) != null) {
                            return clazz;
                        }

                        if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
                            throw new JSONException("autoType is not support. " + typeName);
                        }

                        if (expectClass != null) {
                            if (expectClass.isAssignableFrom(clazz)) {
                                return clazz;
                            }

                            throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                        }

                        JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, this.propertyNamingStrategy);
                        if (beanInfo.creatorConstructor != null && this.autoTypeSupport) {
                            throw new JSONException("autoType is not support. " + typeName);
                        }
                    }

                    int mask = Feature.SupportAutoType.mask;
                    boolean autoTypeSupport = this.autoTypeSupport || (features & mask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;
                    if (!autoTypeSupport) {
                        throw new JSONException("autoType is not support. " + typeName);
                    } else {
                        return clazz;
                    }
                }
            }
        } else {
            throw new JSONException("autoType is not support. " + typeName);
        }
    }

代码很长,总结一下该类的处理顺序:

  1. 之前黑名单绕过的修复
  2. 开启 autoTypeSupport 或者 expectClass 不为空时的黑白名单(白名单直接返回)
  3. 尝试从缓存 Map 中加载类,若成功且无 expectClass 时可以直接返回
  4. 尝试从 deserializers 中加载类,若成功且无 expectClass 时可以直接返回
  5. 未开启 autoTypeSupport 时的黑白名单
  6. 加载类
  7. expectClass 校验和注解校验,如果通过则返回
  8. 未开启 autoTypeSupport 则抛出异常

这种黑名单检测机制,保证了黑客无法通过写恶意类来绕过autotyoe的反序列化过程对程序造成坏的影响。

public void configFromPropety(Properties properties)

这个方法如其名字一样,从配置类导入配置,完成对ParserConfig的初始化。

 public void configFromPropety(Properties properties) {
        String property = properties.getProperty("fastjson.parser.deny");
        String[] items = splitItemsFormProperty(property);
        this.addItemsToDeny(items);
        property = properties.getProperty("fastjson.parser.autoTypeAccept");
        items = splitItemsFormProperty(property);
        this.addItemsToAccept(items);
        property = properties.getProperty("fastjson.parser.autoTypeSupport");
        if ("true".equals(property)) {
            this.autoTypeSupport = true;
        } else if ("false".equals(property)) {
            this.autoTypeSupport = false;
        }

    }

可以看到,该方法首先从配置类中获取fastjson.parser.deny值,保存为字符串,然后分隔该字符串得到每个deny值,再保存在字符串数组里面,最终增添到ParserConfig对象中。然后获取fastjson.parser.autoTypeAccept值,以同样的方法增添到ParserConfig对象中。最后,获取fastjson.parser.autoTypeSupport,设置对应的值。这里解释一下这三个变量:deny为拒绝反序列化的类,即黑名单,禁止这些类反序列化。accept反之。其中accept优先级高于deny。autotypeSupport表示是否支持自动类型转换。

总结

本篇提到了ParserConfig类既可以对项目进行全局的控制,也可以用来生成临时变量,将某一次或几次的反序列化改变行为。ParserConfig中涉及许多方法,还包括了黑名单、白名单的存在,用来防治恶意攻击。
下一篇我们继续深入ParserConfig类,探究它更深层次的用法。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
fastjson是一款Java语言编写的高性能JSON处理库,其主要特点是速度快、内存占用低、功能强大、易于使用。下面就简单介绍一下fastjson源码分析。 1. 核心架构 fastjson的核心架构包括JSONReader、JSONWriter、JSONLexer、JSONParser、JSONScanner、JSONSerializer、JSONDeserializer等模块。其中,JSONLexer和JSONScanner是fastjson的词法分析器,JSONParser是解析器,JSONSerializer和JSONDeserializer是序列化和反序列化实现。 2. 序列化和反序列化 fastjson的序列化和反序列化实现主要基于Java的反射机制和ASM字节码生成技术。在序列化时,fastjson会根据Java对象的类型信息来生成相应的序列化代码,并将序列化后的数据输出到JSONWriter中。在反序列化时,fastjson根据JSON数据的类型信息来生成相应的反序列化代码,并将反序列化后的Java对象输出到JSONDeserializer中。 3. 性能优化 fastjson在性能上有很多优化,其中最重要的是使用了预编译技术,将JSON字符串解析为Java对象时,fastjson会对JSON字符串进行预编译,生成一个解析器,以提高解析效率。此外,fastjson还使用了缓存技术,将解析器和序列化器缓存起来,以便重复使用,从而减少了对象的创建和销毁所带来的开销。 4. 使用场景 fastjson广泛应用于互联网领域,如阿里巴巴、淘宝、天猫等都在使用fastjson来处理JSON数据。fastjson还支持多种数据格式的解析和序列化,如XML、CSV等。 以上是fastjson源码分析的简单介绍,相信大家对fastjson有了更深入的了解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值