前几天帮一个合作方排查登录问题他们的应用从 Spring Security 6 升级到 7 之后用户在登录后看到的姓名、头像全部变成了默认值。查了半天问题出在 ID Token 和 userinfo 端点的 claims 合并上。说实话类似的问题我见过不止一次根因往往不是配置写错了而是对 OIDC 协议里“ID Token 管认证、userinfo 端点管用户资料”这个分工没理清。这篇文章就把 Spring Security 7 下的 /connect/userinfo 端点、ID Token 解析与用户信息获取这条链路从协议设计到代码实现完整拆一遍。先说结论标准 OIDC 授权码流程里Spring Security 7 会自动完成这些事——带 scopeopenid 跳到授权服务器用户同意后拿授权码换 token从 token 响应里解析出 ID Token 和 Access Token对 ID Token 做签名与声明校验再用 Access Token 请求 userinfo 端点最后把两边返回的 claims 合并成 OidcUser。框架自动化程度很高但一旦涉及自定义用户映射、多 provider 切换、业务字段提取你就必须知道每一步背后的机制。这篇文章适合三类人刚接触 OIDC 登录的 Spring 新手正在从 6 往 7 迁移的老用户以及需要对接多套身份提供商的后端工程师。1. 先从协议层面看懂 userinfo 端点和 ID Token 的关系1.1 一次完整 OIDC 授权登录中两个令牌各自的角色很多新手容易把 OIDC 和 OAuth2 混在一起看其实 OIDCOpenID Connect是在 OAuth2 授权码模式之上加了一层身份认证层。OAuth2 只负责发访问令牌而 OIDC 额外签发一个 ID Token专门用来向应用证明“当前登录的人是谁”。一次标准 Authorization Code Flow 大致是下面这条线用户点击登录浏览器被重定向到授权服务器附带scopeopenid profile email、client_id、redirect_uri等参数。用户在授权服务器上完成登录和授权确认。授权服务器把授权码通过浏览器回传到应用的回调地址。应用在服务端用授权码去换 token。这时 token 响应里既有access_token也有id_token。应用解析并校验 ID Token确认用户身份。应用拿着 Access Token 去请求 userinfo 端点获取用户的详细资料。业务系统把 userinfo 返回的用户属性填充到自己的用户模型或者页面里。注意第 4 步和第 6 步。第 4 步里拿到的 ID Token 是一个 JWT里面已经有一批用户声明了但同时第 6 步还有一个专门的 userinfo 请求。很多人会问既然 ID Token 里已经有 name、email为什么还要再调一次 userinfo 端点这正是整个协议里最容易忽视的设计意图。在实际对接中/connect/userinfo 这类路径并不是协议强制的统一路径。标准要求的是授权服务器在/.well-known/openid-configuration发现文档中公布userinfo_endpoint地址具体路径由服务提供方自定义。你只要配置了 issuer-uriSpring Security 会自动从发现文档里读出真正可用的 userinfo 端点。1.2 为什么拿到了 ID Token 还不够还要再调一次 userinfo 端点这个问题可以从四个维度来看。第一是体积控制。ID Token 会跟着认证流程在浏览器和服务端之间传递如果所有用户资料都塞进去token 体积会越来越大。JWT 的 header 和 payload 都只是 Base64URL 编码没有加密放太多敏感信息还容易泄露。协议设计者的本意是 ID Token 保持精简只放认证所需的核心声明。第二是数据时效性。ID Token 一旦签发Claims 就固定了。用户在身份平台改了头像、换了手机号旧的 ID Token 里还是旧数据。而 userinfo 端点是实时返回最新数据的应用可以拿到最新画像。第三是职责分离。ID Token 解决的是“你是谁”的认证问题userinfo 端点解决的是“你长什么样”的用户资料问题。认证逻辑和用户资料管理解耦后授权服务器可以独立维护用户信息应用侧也不用把 token 解析逻辑和用户数据同步逻辑搅在一起。第四是隐私最小化。ID Token 的 claims 集合受 scope 限制开发者在申请 scope 时可以控制返回字段userinfo 端点返回的字段同样受 scope 控制而且应用在调用时还可以根据实际业务需要选择只取其中几个字段。对比一下就很直观一个典型的 ID Token Payload 可能只有下面几个字段{ iss: https://iam.example.com, sub: 10086, aud: demo-client, exp: 1738380000, iat: 1738376400, nonce: abc123, at_hash: pL1uY8Zf6g }而 userinfo 端点返回的内容要丰富得多{ sub: 10086, name: 张三, given_name: 三, family_name: 张, email: zhangsanexample.com, picture: https://cdn.example.com/avatar/10086.png, department: 研发部, title: 高级工程师, updated_at: 1738300000 }这就是“认证”和“用户资料”两个层面的区别。Spring Security 7 里也是沿着这条线设计的。1.3 Spring Security 7 把协议翻译成了哪些类和流程对应上面 6 步流程Spring Security 7 的 OAuth2 Client 模块里主要组件是下面这些协议角色Spring Security 组件职责授权码流程总控OidcAuthorizationCodeAuthenticationProvider处理授权码兑换 token、校验 ID Token、触发用户信息加载token 与注册信息载体OidcUserRequest封装 Access Token、ID Token、ClientRegistration、userinfo 端点地址用户组装与合并DefaultOidcUserService合并 ID Token 与 userinfo 返回的 claims生成 OidcUserHTTP 调用端点DefaultOAuth2UserService真正发请求请求 userinfo 端点解析响应为 OAuth2User最终用户对象OidcUser / DefaultOidcUser代表登录用户暴露 getClaims、getUserInfo、getSubject 等方法大部分开发者的日常工作其实只接触到了DefaultOidcUserService和OidcUser这一层。Spring Security 7 跟前几个版本相比OAuth2/OIDC 模块做了一次整理旧版本里依赖WebSecurityConfigurerAdapter的那套写法已经彻底移除了OAuth2 登录配置基本都收敛到HttpSecurity的 lambda 配置里。不过 OidcUserService 的扩展机制和 DefaultOidcUser 的使用方式仍然延续了下来。2. 搭一个最小可运行的 OIDC 登录工程2.1 环境与依赖准备Spring Security 7 运行在 Spring Framework 7 之上对应 Spring Boot 4.x要求 Java 17 及以上。如果你项目还在用 Spring Boot 3.x对应的其实是 Spring Security 6.x部分 API 会有差异后面第五节我会专门列一下升级注意点。工程只需要一个基础 Spring Boot Web 项目和 oauth2-client 依赖。Maven 配置如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-oauth2-client/artifactId /dependency如果还要做简单页面展示再加 thymeleaf 或其它模板依赖。oauth2-client 这个 starter 会同时引入 Spring Security 的核心模块所以不需要单独引 spring-security-config。2.2 在身份提供商侧申请客户端注册信息在动手写代码之前先去你要对接的身份平台无论是自建的还是第三方的 IAM申请一个应用/客户端拿到三样东西client-id应用标识client-secret应用密钥授权回调地址比如http://localhost:8080/login/oauth2/code/demo这个回调地址非常关键。Spring Security 的 OAuth2 登录默认回调路径就是{baseUrl}/login/oauth2/code/{registrationId}registrationId 对应配置文件里的命名。你在身份平台注册的回调地址必须和这里保持一致差一个斜杠都会导致回调失败。Scope 这里要特别注意必须包含openid否则整个流程会退化为普通 OAuth2不会签发 ID TokenSpring Security 也就不会走 OIDC 的用户信息获取逻辑。通常再加profile和email分别对应姓名等基本资料和邮箱声明。2.3 核心配置application.ymlSpring Security 支持两种 Provider 配置方式。一种是直接指定 issuer-uri让框架通过发现文档自动获取所有端点另一种是手动指定 authorization-uri、token-uri、user-info-uri 等。强烈建议优先用 issuer-uri因为发现文档是 OIDC 标准的一部分框架能自动拿到准确端点少写配置也少出错。一个典型的配置长这样spring: security: oauth2: client: registration: demo: provider: iam client-id: demo-client client-secret: demo-secret authorization-grant-type: authorization_code redirect-uri: {baseUrl}/login/oauth2/code/{registrationId} scope: openid, profile, email provider: iam: issuer-uri: https://iam.example.com这里issuer-uri的值必须和身份平台签发的 ID Token 里的iss声明一致。很多平台在官网填的“Issuer”是不带尾部斜杠的但实际签发 token 时却带了/这种细节会导致校验失败我在第五节会讲排查方法。如果用不了 issuer-uri比如内部自研平台不提供标准发现文档可以手动补全端点spring: security: oauth2: client: provider: iam: authorization-uri: https://iam.example.com/oauth2/authorize token-uri: https://iam.example.com/oauth2/token user-info-uri: https://iam.example.com/connect/userinfo jwk-set-uri: https://iam.example.com/oauth2/jwks user-info-authentication-method: header手动指定端点时最容易被漏掉的就是 jwk-set-uri。没有它ID Token 的签名校验无法进行登录会直接失败。2.4 写一个 SecurityFilterChainSpring Security 7 要求显式声明 SecurityFilterChain。最简单的配置如下Configuration EnableWebSecurity public class SecurityConfig { Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth - auth .requestMatchers(/, /public/**).permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 - oauth2 .userInfoEndpoint(userInfo - userInfo .oidcUserService(oidcUserService()) ) ); return http.build(); } Bean public OidcUserService oidcUserService() { return new DefaultOidcUserService(); } }默认情况下访问受保护页面时会自动跳到授权服务器登录成功后回到原页面。oidcUserService这里先用默认实现后续需要自定义用户映射时再替换成自己的 Service。到这里一个能跑通的 OIDC 登录工程就搭好了。启动应用访问一个受保护路径走完授权流程你就能在代码里拿到 OidcUser 了。但此时很多同学对“ID Token 和 userinfo 到底怎么关联的”仍然不清楚下面两章分别拆开讲。3. 核心环节一ID Token 的解析与校验3.1 ID Token 的结构与常见 ClaimsID Token 本质是一个 JWS 格式的 JWT分三部分Header、Payload、Signature。Header 里主要放着签名算法和密钥 IDPayload 是 Claims 集合Signature 由授权服务器用私钥签名应用侧通过 JWKS 公钥验证。标准 Claims 大致分两类一类是必选核心声明一类是可选的资料声明Claim含义必选性iss签发者必须匹配配置的 issuer必选sub用户在授权服务器上的唯一标识必选aud受众必须包含本应用的 client-id必选exp过期时间戳必选iat签发时间戳可选nonce防重放随机数保持与登录发起时一致流程内必选at_hashAccess Token 的哈希值有条件必选azp授权方多受众场景出现有条件必选name / email / picture 等用户资料声明受 scope 限制可选ID Token 的 payload 是可以被任何人 Base64URL 解码的因为它不加密。也就是说你把它贴到 https://jwt.io 上就能看到明文内容。正因如此ID Token 里不应该塞敏感信息比如手机号、身份证号这类数据。这也引出一个重要的安全认知能解码 JWT 不等于能信任 JWT。任何客户端都能解码看到 claims但只有验签通过后才能确认这些 claims 确实由你信任的授权服务器签发。所以业务代码里可以主动读取 ID Token 的 claims但要校验的工作必须依赖框架或自己完整实现签名验证不能只解 payload 就完事。3.2 Spring Security 7 替我们完成了哪些校验在 OidcAuthorizationCodeAuthenticationProvider 的流程里拿到 token 响应后框架会对 ID Token 做一串校验这个清单非常关键签名校验通过发现文档里的jwks_uri获取公钥按照 JOSE Header 中的kid选择公钥验证签名。Issuer 校验iss必须等于配置的 issuer-uri。Audience 校验aud中必须包含当前客户端的 client-id。Expiry 校验exp必须晚于当前时间框架允许一定的时钟偏移。Nonce 校验nonce必须和认证请求发起时保存在 session 里的值一致防止跨站登录伪造。有条件校验如果aud有多个值且出现了azp那么azp必须是 client-id。这些校验的配置在框架内部不需要你手动逐条实现。但你得知道它们的逻辑否则出事时很难快速定位。Issuer 发现机制这里多说一句。当你配置了 issuer-uriSpring Security 会去拉取{issuer}/.well-known/openid-configuration这个文档里包含了jwks_uri。框架再根据 jwks_uri 获取公钥列表按 kid 动态选公钥验签。生产环境里这一步经常出现内网证书不信任导致拉取失败的问题具体解法我在第五节讲。3.3 业务代码中如何获取并解析 Claims在 Controller 里拿到 OidcUser 最直接的方式是AuthenticationPrincipalRestController public class UserController { GetMapping(/me) public MapString, Object me(AuthenticationPrincipal OidcUser oidcUser) { return Map.of( subject, oidcUser.getSubject(), name, oidcUser.getFullName(), email, oidcUser.getEmail(), claims, oidcUser.getClaims() ); } }注意getClaims()这个名字有一点误导。这里返回的 map 实际上已经是 ID Token 和 userinfo 合并后的结果并不是单纯 ID Token 里的 claims。如果你只想要 ID Token 里的原始 claims要从oidcUser.getIdToken().getClaims()取。另一个写法是从 SecurityContext 里拿 Authentication 再转型GetMapping(/profile) public String profile(Authentication authentication) { OidcUser oidcUser (OidcUser) authentication.getPrincipal(); return oidcUser.getUserInfo().getFullName(); }这两种方式在 oauth2Login 流程里都是有效的。OidcUser 既实现了OAuth2User又持有OidcIdToken和OidcUserInfo所以业务代码能一头拿认证信息一头拿用户资料。4. 核心环节二/connect/userinfo 端点的自动调用与自定义映射4.1 userinfo 端点自动调用的完整链路当框架走完 ID Token 校验后会构造一个OidcUserRequest里面装着四样东西OidcUserRequest 的总流程是把 Access Token、ID Token、ClientRegistration 以及从发现文档里解析出的userInfoEndpointUri打包起来交给DefaultOidcUserService.loadUser方法。看一下 DefaultOidcUserService 的调用链会更清楚DefaultOidcUserService.loadUser 先构造一个 DefaultOAuth2UserService。DefaultOAuth2UserService 使用RestOperations发送GET {userInfoUri}请求头带上Authorization: Bearer {access_token}。userinfo 端点根据 Content-Type 返回响应。如果是application/json直接解析为 Map如果返回application/jwt框架会用 JwtDecoder 解码后取 claims。返回的 userinfo claims 被包装成 OAuth2User。DefaultOidcUserService 把 ID Token 里的 claims 与 userinfo 返回的 attributes 合并构建出 OidcUser。这个调用时机是登录过程内一次的不是每个 HTTP 请求都触发。登录成功后认证信息会保存在 session 里后续请求直接复用不会反复请求 userinfo 端点。这个设计既保证了首次登录能拿到完整资料也不会因为频繁调用 userinfo 给身份平台造成压力。4.2 自定义 OidcUserService 接管用户信息处理默认的 DefaultOidcUserService 返回的 OidcUser 只包含 OIDC 协议中的 claims没有任何业务逻辑。真实系统里登录后通常要做三件事关联本地用户表、分配业务角色、把业务用户 ID 塞进认证对象。这些逻辑最好的落点就是自定义 OidcUserService。下面是一个完整的自定义实现Service public class LocalUserOidcUserService extends DefaultOidcUserService { private final LocalUserRepository userRepository; private final RoleMapper roleMapper; public LocalUserOidcUserService(LocalUserRepository userRepository, RoleMapper roleMapper) { this.userRepository userRepository; this.roleMapper roleMapper; } Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { // 1. 先走框架默认逻辑完成ID Token校验、userinfo获取、claims合并 OidcUser oidcUser super.loadUser(userRequest); // 2. 从合并后的claims里取唯一标识 String issuer oidcUser.getIssuer().toString(); String subject oidcUser.getSubject(); String email oidcUser.getEmail(); // 3. 关联或创建本地用户 LocalUser localUser userRepository.findByIssuerAndSubject(issuer, subject); if (localUser null) { localUser userRepository.create(issuer, subject, email, oidcUser.getFullName(), oidcUser.getUserInfo().getPicture()); } // 4. 把本地角色转换成Spring Security的权限 ListGrantedAuthority authorities new ArrayList(oidcUser.getAuthorities()); for (String role : roleMapper.findByLocalUser(localUser.getId())) { authorities.add(new SimpleGrantedAuthority(role)); } // 5. 把本地业务ID等字段混入claims业务代码直接可取 MapString, Object mergedClaims new HashMap(oidcUser.getClaims()); mergedClaims.put(localUserId, localUser.getId()); mergedClaims.put(localUserName, localUser.getDisplayName()); return new DefaultOidcUser( authorities, oidcUser.getIdToken(), oidcUser.getUserInfo(), mergedClaims ); } }配置到 HttpSecurity 时把默认的 OidcUserService 换成这个自定义实现Bean SecurityFilterChain securityFilterChain(HttpSecurity http, LocalUserOidcUserService localUserOidcUserService) throws Exception { http .authorizeHttpRequests(auth - auth .anyRequest().authenticated() ) .oauth2Login(oauth2 - oauth2 .userInfoEndpoint(userInfo - userInfo .oidcUserService(localUserOidcUserService) ) .successHandler((request, response, authentication) - { // 登录成功的业务埋点或跳转 response.sendRedirect(/me); }) ); return http.build(); }这里的扩展点就是 Spring Security 留给业务开发的“后门”。默认实现是一套纯协议逻辑自定义实现是在协议逻辑之上叠加业务逻辑。注意super.loadUser一定不能省否则 ID Token 校验和 userinfo 获取都不会执行loadUser 拿到的 OidcUserRequest 里只有原始 token没有用户对象。另外如果在自定义 OidcUserService 里抛OAuth2AuthenticationExceptionSpring Security 会把异常翻译成登录失败。业务上经常用它来做“该用户未在本地开通权限”的拦截。4.3 claims 合并规则哪个来源说了算这是文章开头那个合作方踩坑的核心点。DefaultOidcUserService 在构建 OidcUser 时合并规则是先以 ID Token 的 claims 为底再把 userinfo 返回的 attributes 放进去遇到同名 key 时userinfo 里的值会覆盖 ID Token 里的值。举个例子ID Token 里返回的name是“张三”而 userinfo 端点当天返回的name已经被用户改成“张叁”。合并后 OidcUser 的getClaims().get(name)得到的是“张叁”。这几条规则在实际项目中要记住用户名、头像、邮箱这类资料型字段以 userinfo 为准。sub、iss、aud、exp 这类协议安全字段以 ID Token 为准因为 userinfo 响应本身由 Access Token 保护但 sub 必须与 ID Token 的 sub 一致框架会在某些协议实现中校验这个一致性。如果某个字段只在 ID Token 里有userinfo 里没返回那合并结果仍然会保留它。如果某个自定义字段只在 userinfo 里返回ID Token 里没有合并结果也会有。所以从业务代码角度直接读oidcUser.getClaims()拿到的就是“最终数据”而不需要关心它最初来自哪个来源。但你要做数据同步或审计时就必须回到getIdToken().getClaims()和getUserInfo().getAttributes()分别取。自定义 OidcUserService 里如果自己手动改 claims 时也要遵循同样的逻辑先在默认合并结果的基础上做修改不要自己另起炉灶、完全丢开 super.loadUser 的合并结果否则容易丢失 ID Token 里的安全字段。4.4 手动调用 userinfo 端点的兜底方案大部分场景靠框架自动调用就够了但有两个场景需要手动调用 userinfo一是纯 OAuth2 流程scope 没有 openid没有 ID Token自然没有 OidcUser二是你要在非登录上下文中比如定时任务里用已保存的 Refresh Token 换新 Access Token 后重新同步用户资料。手动调用其实很简单用 RestClient 或 WebClient 带 Bearer Token 发 GET 请求即可Service public class UserInfoManualClient { private final RestClient restClient RestClient.create(); public MapString, Object fetch(String userInfoUri, String accessToken) { return restClient.get() .uri(userInfoUri) .header(HttpHeaders.AUTHORIZATION, Bearer accessToken) .retrieve() .body(new ParameterizedTypeReferenceMapString, Object() {}); } }调用时要注意身份平台返回的 Content-Type。多数平台返回application/json可以直接转 Map。如果遇到返回application/jwt的平台你需要先用 JwtDecoder 解码JwtDecoder decoder JwtDecoders.fromIssuerLocation(https://iam.example.com); Jwt jwt decoder.decode(jwtString); MapString, Object claims jwt.getClaims();为什么要关心这个因为 OIDC 核心规范允许 userinfo 端点用 JWT 格式响应而且这种格式比 JSON 更安全因为它自带签名。对接新平台时先看一下它的发现文档里有没有userinfo_signing_alg_values_supported如果有就要做好处理 JWT 响应的准备。5. 常见问题排查与避坑实录5.1 高频报错与定位思路速查表下面这个表是从实践中整理出来的高频问题出现频率最高的是前三个。现象根因排查思路[invalid_token] JWT expiredID Token 或 userinfo JWT 过期先看系统时钟是否漂移再检查 token 有效期是否太短最后看是否在自定义 OidcUserService 里缓存了过期数据[invalid_token] Invalid issuer配置的 issuer-uri 与 token 中 iss 不一致抓包或者用 jwt.io 解出实际 iss对比配置值注意斜杠差异[invalid_token] Invalid audienceaud 不包含客户端 client-id确认身份平台上 client-id 是否被改动检查配置是否拼写错误回调地址 404redirect-uri 没有完全匹配注册的回调逐字符比对包括协议、域名、端口、路径、尾斜杠登录后马上又跳回登录页session 中认证信息丢失或 authorities 为空检查 cookie 是否正常写入打印 authentication 查看 authoritiesuserinfo 请求返回 401access_token 非法或过期确认代码使用的 token 是本次登录的手动验证 token 有效性Claims 里没有自定义字段username_attribute 或自定义 claim 未配置直接 curl userinfo 端点看原始返回确认字段名大小写和层级内网环境报 PKIX path building failed拉取 jwks_uri 时证书不被信任配置自定义 SSLContext 或导入内网 CA 证书5.2 ID Token 明明能解开却被拒绝签名和时钟问题我在实际排障里遇到最多的一类问题是把 ID Token 贴到在线解码工具里payload 显示完全正常可 Spring Security 就是报 invalid_token。这类问题通常分为两个方向。第一个方向是验签失败。本地解码工具不验签只解 Base64URL当然怎样都能解出来。Spring Security 用授权服务器的公钥验证签名如果 jwks_uri 拉取失败或者平台在后台轮换了密钥而缓存里还是旧公钥签名校验就会失败。遇到这种情况先把 jwks_uri 返回的 keys 列表和 ID Token Header 里的 kid 对应起来看确认平台侧公钥是否已经更换。第二个方向是 exp 判断的时钟漂移。认证服务器和应用服务器时间不同步时ID Token 明明标注的过期时间还没到但应用本地时间已经超过了。生产环境里应用服务器一定配置好时间同步不然登录会在某个时间点突然批量失败。Spring Security 默认在验证时留有一定时钟偏移量但这不是你忽略时间同步的借口。真遇到紧急情况可以临时扩大偏移量或关闭时间校验但只能作为应急手段不能带到生产。5.3 自定义字段丢失和数据新鲜度问题有同行问过我userinfo 里明明有 department 字段为什么 OidcUser 里拿不到这类问题的常规原因是 scope 没包含对应权限。department 这类企业属性通常需要额外申请organization或者自定义 scope身份平台才会在 userinfo 响应里返回。排查时最有效的方法是直接带 Access Token 访问 userinfo 端点看原始 JSON 里到底有没有这个字段。如果原始响应有、OidcUser 里没有那就是 claims 合并或自定义 OidcUserService 里的逻辑出了问题。数据新鲜度是另一个经常被忽略的点。第一次登录后Spring Security 会把 OidcUser 放入 session后续请求不再请求 userinfo。所以用户在身份平台改了头像应用里看到的还是旧的这是预期行为不是 bug。如果你的业务对头像、昵称实时性要求高要么在关键操作时主动刷新 userinfo要么做定时任务同步。用缓存的时候尤其注意不要在一次登录后把整个身份平台的用户信息缓存 24 小时以上否则线上会出现大量“用户资料已更新系统里还是老数据”的工单。5.4 从 Spring Security 6 升级到 7 的几个明显变化升级到 Spring Security 7 后老代码最常见的三类问题第一凡是继承WebSecurityConfigurerAdapter的配置类全部失效启动直接报类不存在。7.0 移除了这类适配器必须改成组件式 SecurityFilterChain 写法也就是在 Configuration 类里暴露一个返回 SecurityFilterChain 的 Bean。第二OAuth2 登录的配置里部分旧方法被整体调整。比如oauth2Login()里的.permitAll()行为、OAuth2LoginConfigurer下的一些 helper 方法。建议直接按新版 API 重写而不是尝试微调旧代码。第三依赖版本要对齐。Spring Boot 4.x 默认使用 Spring Security 7如果你在 Boot 4 项目里手动指定了 Spring Security 6 的版本会导致 classpath 冲突和不明所以的配置失效。最简单的做法是删除所有 Spring Security 相关 version 声明直接用 Boot 的 BOM 管理。升级之后默认的 OIDC 登录链路并没有颠覆性变化OidcUserService、OidcUser、DefaultOidcUser 这些核心概念仍是原型只是配置 API 更强调 lambda 配置和安全过滤链的显式声明。你的业务代码如果之前是在自定义 OidcUserService 里做的用户映射迁移到 7 的改动量通常很小。最后说一点个人体会。很多人在对接 OIDC 时习惯直接看配置项照着官方文档填完就完事了。但 userinfo 端点、ID Token、claims 合并这些问题如果不把协议层面的分工搞清楚等遇到多 provider 切换、claims 冲突、签名密钥轮换这类情况时就会很被动。我自己的习惯是不管用哪个框架先把那家身份提供商的/.well-known/openid-configuration完整拉下来看一遍确认它支持哪些 scope、userinfo 返回什么格式、有没有自定义 claims 映射再动手写配置。这套流程在 Spring Security 7 上完全适用换到别的技术栈也同样成立。
阅读完成 · 觉得有帮助?