升级Spring Boot至2.0.0后,password授权模式OAuth2令牌异常
我之前在升级Spring Boot到2.0版本时,也碰到过类似的OAuth2+MongoDB兼容性问题,结合你的场景,来帮你梳理下问题根源和解决办法:
问题根源分析
Spring Boot 2.0对Spring Data MongoDB的默认配置做了不少调整,尤其是MappingMongoConverter的行为。当你用password模式首次获取Token时,OAuth2的MongoTokenStore能把Token对象存入MongoDB,但后续读取(用Token访问接口)或重复获取同一用户的Token时,Converter无法正确解析之前存储的文档结构——因为1.5版本的默认序列化逻辑和2.0版本不兼容,导致类型转换异常。
另外,你添加的oauth2-autoconfigure修复了基础的OAuth2功能,但并没有处理MongoDB和OAuth2对象的序列化适配问题,这就是为什么client_credentials模式(不需要关联用户信息)没问题,但password模式(关联用户认证信息)后续操作会报错。
分步解决方案
1. 自定义MongoDB Converter处理OAuth2对象
你需要创建一个配置类,给MappingMongoConverter添加针对OAuth2核心对象的自定义序列化/反序列化规则,确保MongoDB能正确读写OAuth2AccessToken、OAuth2Authentication、OAuth2RefreshToken这些对象:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.store.mongodb.MongoTokenStore; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.*; @Configuration public class MongoOAuth2TokenConfig { @Bean public MongoTokenStore mongoTokenStore(MongoTemplate mongoTemplate) { MongoTokenStore tokenStore = new MongoTokenStore(mongoTemplate); // 配置自定义Converter MappingMongoConverter converter = (MappingMongoConverter) mongoTemplate.getConverter(); converter.setCustomConversions(new MongoCustomConversions( Arrays.asList( new OAuth2AccessTokenReadConverter(), new OAuth2AccessTokenWriteConverter(), new OAuth2AuthenticationReadConverter(), new OAuth2AuthenticationWriteConverter(), new OAuth2RefreshTokenReadConverter(), new OAuth2RefreshTokenWriteConverter() ) )); converter.afterPropertiesSet(); return tokenStore; } // 读取MongoDB文档转OAuth2AccessToken static class OAuth2AccessTokenReadConverter implements Converter<Map<String, Object>, OAuth2AccessToken> { @Override public OAuth2AccessToken convert(Map<String, Object> source) { DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken((String) source.get("value")); token.setExpiration((Date) source.get("expiration")); token.setRefreshToken((OAuth2RefreshToken) source.get("refreshToken")); token.setScope((Collection<String>) source.get("scope")); token.setAdditionalInformation((Map<String, Object>) source.get("additionalInformation")); return token; } } // 写入OAuth2AccessToken转MongoDB文档 static class OAuth2AccessTokenWriteConverter implements Converter<OAuth2AccessToken, Map<String, Object>> { @Override public Map<String, Object> convert(OAuth2AccessToken source) { Map<String, Object> doc = new HashMap<>(); doc.put("value", source.getValue()); doc.put("expiration", source.getExpiration()); doc.put("refreshToken", source.getRefreshToken()); doc.put("scope", source.getScope()); doc.put("additionalInformation", source.getAdditionalInformation()); return doc; } } // 处理OAuth2Authentication的反序列化 static class OAuth2AuthenticationReadConverter implements Converter<Map<String, Object>, OAuth2Authentication> { @Override public OAuth2Authentication convert(Map<String, Object> source) { ObjectMapper mapper = new ObjectMapper(); try { return mapper.readValue(mapper.writeValueAsString(source), OAuth2Authentication.class); } catch (JsonProcessingException e) { throw new RuntimeException("Failed to deserialize OAuth2Authentication", e); } } } // 处理OAuth2Authentication的序列化 static class OAuth2AuthenticationWriteConverter implements Converter<OAuth2Authentication, Map<String, Object>> { @Override public Map<String, Object> convert(OAuth2Authentication source) { ObjectMapper mapper = new ObjectMapper(); try { return mapper.readValue(mapper.writeValueAsString(source), Map.class); } catch (JsonProcessingException e) { throw new RuntimeException("Failed to serialize OAuth2Authentication", e); } } } // 处理OAuth2RefreshToken的反序列化 static class OAuth2RefreshTokenReadConverter implements Converter<Map<String, Object>, OAuth2RefreshToken> { @Override public OAuth2RefreshToken convert(Map<String, Object> source) { return new DefaultOAuth2RefreshToken((String) source.get("value")); } } // 处理OAuth2RefreshToken的序列化 static class OAuth2RefreshTokenWriteConverter implements Converter<OAuth2RefreshToken, Map<String, Object>> { @Override public Map<String, Object> convert(OAuth2RefreshToken source) { Map<String, Object> doc = new HashMap<>(); doc.put("value", source.getValue()); return doc; } } }
2. 清理MongoDB中的旧Token数据
因为你之前已经用旧逻辑存储了一些Token文档,这些文档的结构和新Converter期望的不匹配,所以需要先清空MongoDB中的oauth_access_token和oauth_refresh_token集合,避免后续读取旧数据时触发异常。
3. 更新AuthorizationServer配置
确保你的授权服务器配置类中,使用自定义的MongoTokenStore,而不是默认实现:
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { private final AuthenticationManager authenticationManager; private final MongoTemplate mongoTemplate; // 构造注入依赖 public AuthorizationServerConfig(AuthenticationManager authenticationManager, MongoTemplate mongoTemplate) { this.authenticationManager = authenticationManager; this.mongoTemplate = mongoTemplate; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .tokenStore(mongoTokenStore()); // 绑定自定义的TokenStore } @Bean public MongoTokenStore mongoTokenStore() { return new MongoOAuth2TokenConfig().mongoTokenStore(mongoTemplate); } // 其他客户端详情、权限配置按需保留 }
4. 校验依赖版本兼容性
确保你的spring-security-oauth2和oauth2-autoconfigure版本和Spring Boot 2.0.0.RELEASE匹配,推荐的版本组合:
<!-- Maven依赖示例 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.0.0.RELEASE</version> </dependency>
验证效果
完成上述配置后,重启服务:
- 用password模式首次获取Token,确认正常
- 用该Token访问受保护接口,确认能正常解析
- 再次用同一用户的password模式获取Token,确认不会触发MappingMongoConverter异常
内容的提问来源于stack exchange,提问作者Bilal Nasir

