You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Authorization Server自定义UserDetails JSON反序列化问题求助

问题:Spring Authorization Server迁移中的JSON反序列化异常解决

背景

从Keycloak迁移到Spring Authorization Server,使用授权码模式获取access token时,遭遇JSON反序列化相关问题。

第一个异常

最初遇到自定义CustomUserDetails的反序列化限制:

Caused by: java.lang.IllegalArgumentException: The class with com.company.user.CustomUserDetails and name of com.company.user.CustomUserDetails 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

按照错误提示定义了CustomUserDetailsMixin、CustomUserDetailsSerializer并注册到AuthorizationServerConfiguration后,出现新的异常。

当前异常

java.lang.IllegalArgumentException: Could not resolve type id 'java.util.ImmutableCollections$ListN' as a subtype of `java.util.Set<org.springframework.security.core.authority.SimpleGrantedAuthority>`: Not a subtype
 at [Source: UNKNOWN; byte offset: #UNKNOWN]
    at org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService$OAuth2AuthorizationRowMapper.parseMap(JdbcOAuth2AuthorizationService.java:467)

数据库中存储的Spring Security授权JSON结构如下:

{
  "@class": "java.util.Collections$UnmodifiableMap",
  "java.security.Principal": {
    "@class": "org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
    "authorities": [
      "java.util.Collections$UnmodifiableRandomAccessList",
      [
        {
          "@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
          "authority": "ROLE_USER"
        }
      ]
    ]
  }
  ....
}

解决方案

这个错误的核心原因是:Jackson反序列化时,将数据库中存储的List类型的authorities,尝试映射到UsernamePasswordAuthenticationToken的Set类型属性,导致类型不兼容。以下是具体解决步骤:

  1. 自定义List转Set的Jackson转换器
    实现一个Jackson转换器,将List格式的权限列表转换为Set:

    import com.fasterxml.jackson.databind.util.StdConverter;
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    
    public class ListToSetConverter extends StdConverter<List<SimpleGrantedAuthority>, Set<SimpleGrantedAuthority>> {
        @Override
        public Set<SimpleGrantedAuthority> convert(List<SimpleGrantedAuthority> value) {
            return value.stream().collect(Collectors.toSet());
        }
    }
    
  2. 配置UsernamePasswordAuthenticationToken的Mixin
    通过Mixin指定authorities属性的反序列化规则,使用上面的转换器处理类型转换:

    import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import java.util.Set;
    
    public abstract class UsernamePasswordAuthenticationTokenMixin {
        @JsonDeserialize(contentAs = SimpleGrantedAuthority.class, converter = ListToSetConverter.class)
        private Set<SimpleGrantedAuthority> authorities;
    }
    
  3. 在授权服务配置中注册Mixin和转换器
    修改AuthorizationServerConfiguration中的OAuth2AuthorizationService配置,将自定义的Mixin和转换器注册到Jackson的ObjectMapper中:

    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
                                                            OAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper) {
        ObjectMapper objectMapper = new ObjectMapper();
        // 注册UsernamePasswordAuthenticationToken的Mixin和转换器
        objectMapper.registerModule(new SimpleModule()
                .addMixin(UsernamePasswordAuthenticationToken.class, UsernamePasswordAuthenticationTokenMixin.class)
                .addConverter(new ListToSetConverter()));
        // 保留之前CustomUserDetails的相关配置
        objectMapper.addMixIn(CustomUserDetails.class, CustomUserDetailsMixin.class);
        objectMapper.registerModule(new SimpleModule().addSerializer(CustomUserDetails.class, new CustomUserDetailsSerializer()));
        // 配置rowMapper使用自定义的ObjectMapper
        rowMapper.setObjectMapper(objectMapper);
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, rowMapper);
    }
    
  4. 清理旧授权记录(可选)
    如果数据库中存在迁移前生成的旧授权记录,建议删除这些记录,让系统重新生成符合新序列化规则的授权数据,避免残留的不兼容JSON引发后续问题。


内容的提问来源于stack exchange,提问作者akuma8

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 21:35:32