Spring Security授权服务器自定义Principal引发Jackson反序列化失败
问题根源
当你用自定义OidcUserService返回MyPrincipal(同时实现UserDetails和OidcUser)时,Spring Security会将OIDC提供商返回的所有ID Token属性(如audience、expiresAt、authenticationContextClass等)存入OAuth2Authorization的attributes集合中。JdbcOAuth2AuthorizationService在持久化时会把整个attributes序列化为JSON存入数据库,后续反序列化时Jackson会尝试将这些额外属性映射到MyPrincipal,但你的类中没有对应的字段和setter,因此抛出反序列化错误。
解决方案
方案1:自定义OAuth2AuthorizationService过滤冗余属性
继承JdbcOAuth2AuthorizationService,重写save方法,在持久化前清理掉不需要的OIDC相关属性,只保留核心用户信息:
@Component public class CustomJdbcOAuth2AuthorizationService extends JdbcOAuth2AuthorizationService { public CustomJdbcOAuth2AuthorizationService(DataSource dataSource, OAuth2AuthorizationRowMapper authorizationRowMapper, OAuth2AuthorizationServiceConfigurer configurer) { super(dataSource, authorizationRowMapper, configurer); } @Override public void save(OAuth2Authorization authorization) { // 过滤attributes,只保留用户核心信息 Map<String, Object> filteredAttributes = authorization.getAttributes().entrySet().stream() .filter(entry -> entry.getKey().equals("username") || entry.getKey().equals("authorities") || entry.getKey().startsWith("user_")) // 自定义用户属性前缀 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); // 创建新的授权实例,替换过滤后的属性集合 OAuth2Authorization filteredAuthorization = OAuth2Authorization.from(authorization) .attributes(attrs -> attrs.putAll(filteredAttributes)) .build(); super.save(filteredAuthorization); } }
方案2:给MyPrincipal添加Jackson忽略未知属性注解
快速临时解决方式:在MyPrincipal类上添加注解,让Jackson反序列化时跳过不存在的属性:
@JsonIgnoreProperties(ignoreUnknown = true) public class MyPrincipal implements UserDetails, OidcUser { // 你的类实现代码 }
注意:这种方式只是绕过错误,数据库仍会存储冗余属性,长期建议结合方案1清理数据。
方案3:调整自定义OidcUserService,控制返回的用户属性
在自定义OidcUserService中,构建MyPrincipal时只传入核心用户字段,避免带入OIDC的全量属性:
@Component public class CustomOidcUserService extends OidcUserService { @Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { OidcUser oidcUser = super.loadUser(userRequest); // 只提取必要的用户信息 String userId = oidcUser.getSubject(); String username = oidcUser.getFullName(); String email = oidcUser.getEmail(); Collection<? extends GrantedAuthority> authorities = oidcUser.getAuthorities(); // 仅传递核心信息构建MyPrincipal,不传入OIDC全量属性 return new MyPrincipal(userId, username, email, authorities); } }
推荐组合
优先使用方案1+方案3:通过CustomOidcUserService控制进入attributes的内容,再通过CustomJdbcOAuth2AuthorizationService做最终过滤,确保数据库只存储必要的用户信息,从根源避免反序列化问题。
内容的提问来源于stack exchange,提问作者NotSure

