这几天在弄一个统一的支持多租户的认证鉴权平台,就想到了使用oauth这种授权协议来进行,通过包含open-user信息的jwt来进行分发。使用这个遇到了一些坑,记录如下:
先说明一下情况,遇到的是fasterxml的json反序列化的白名单问题,由于处理json的框架容易受到攻击,经常出现一些反序列化漏洞,新出一个如果采用黑名单,则这个名单会不断庞大,需要不断更新,故此fasterxml采用了白名单的机制来实现json反序列化。
这个问题爆发在于使用了OAuth2AuthorizationService这个实现类,里面有new ObjectMapper();咱们不能控制它里面自己new的ObjectMapper,所以咱们必须重新定义一个OAuth2AuthorizationService,
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository,
ObjectMapper objectMapper) {
JdbcOAuth2AuthorizationService service = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper authorizationRowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
authorizationRowMapper.setLobHandler(new DefaultLobHandler());
authorizationRowMapper.setObjectMapper(objectMapper);
service.setAuthorizationRowMapper(authorizationRowMapper);
return service;
}
这里注入ObjectMapper,我们就能对其进行定制化了。
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
//jackson反序列化 安全白名单
builder.mixIn(OpenAppUser.class, OpenAppUserMixin.class);
List securityModules = SecurityJackson2Modules.getModules(LocalDateTimeSerializerConfig.class.getClassLoader());
securityModules.add(new OAuth2AuthorizationServerJackson2Module());
builder.modulesToInstall(securityModules.toArray(new Module[securityModules.size()]));
};
}
可以去看看这几个Module,里面都是security定义的一些加入安全白名单的类。这里我自己定义了一个用户类,因为登录查询的用户就是使用的这个
这样就可以进行自定义用户对象的json安全反序列化了。
//实际上使用security和objectmapper的时候提供了三种方式来实现加入反序列化的白名单,在上面的那个加载Module代码里,首先都会调用
SecurityJackson2Modules.enableDefaultTyping(mapper);
//方法,然后里面调用
mapper.setDefaultTyping(createAllowlistedDefaultTyping());
//返回的是一个TypeResolverBuilder,这里的实现类是AllowlistTypeResolverBuilder
private static TypeResolverBuilder extends TypeResolverBuilder> createAllowlistedDefaultTyping() {
TypeResolverBuilder extends TypeResolverBuilder> result = new AllowlistTypeResolverBuilder(
ObjectMapper.DefaultTyping.NON_FINAL);
result = result.init(JsonTypeInfo.Id.CLASS, null);
result = result.inclusion(JsonTypeInfo.As.PROPERTY);
return result;
}
//AllowlistTypeResolverBuilder类里面的idResolver()方法返回的是TypeIdResolver,实现是一个AllowlistTypeIdResolver()
@Override
protected TypeIdResolver idResolver(MapperConfig> config, JavaType baseType, PolymorphicTypeValidator subtypeValidator, Collection subtypes, boolean forSer, boolean forDeser) {
TypeIdResolver result = super.idResolver(config, baseType,subtypeValidator, subtypes, forSer, forDeser);
return new AllowlistTypeIdResolver(result);
}
//重点就是这个类了,它里面的typeFromId方法
@Override
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
DeserializationConfig config = (DeserializationConfig) context.getConfig();
JavaType result = this.delegate.typeFromId(context, id);
String className = result.getRawClass().getName();
if (isInAllowlist(className)) {
return result;
}
boolean isExplicitMixin = config.findMixInClassFor(result.getRawClass()) != null;
if (isExplicitMixin) {
return result;
}
JacksonAnnotation jacksonAnnotation = AnnotationUtils.findAnnotation(result.getRawClass(),
JacksonAnnotation.class);
if (jacksonAnnotation != null) {
eturn result;
}
throw new IllegalArgumentException("The class with " + id + " and name of " + className
+ " is not in the allowlist. "
+ "If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. "
+ "If the serialization is only done by a trusted source, you can also enable default typing. "
+ "See https://github.com/spring-projects/spring-security/issues/4370 for details");
}
可以看到security提供的这个方法里面的实现,三种方式分别是
1.isInAllowlist()是否在这个白名单集合里
2.是否有加入到MixIn,这个就是我们前面使用的方式
3.反序列化类上是否有@JacksonAnnotation注解
对于方式1,是个static集合不能修改;其他两种都可以
其实objectMapper提供是否安全是通过PolymorphicTypeValidator类来实现的,security上面默认使用的是
AllowlistTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) {
super(defaultTyping,
// we do explicit validation in the TypeIdResolver
BasicPolymorphicTypeValidator.builder().allowIfSubType(Object.class).build());
}
这个意思是只要是object的子类都通过,所以security是在获取JavaType的时候自定义了一下白名单而没有采用objectMapper的方式。