Spring Security Google 登录重定向 404?原因与解决方法
2025-03-29 21:11:16
搞定 Spring Security OAuth2 Google 登录重定向 404 报错
哥们儿,配 Spring Security 和 OAuth2 Google 登录的时候,是不是美滋滋地以为点一下“用 Google 继续”就能跳到 defaultSuccessUrl
了?结果呢,啪叽一下,浏览器地址栏显示 localhost:端口号//login/oauth2/code/google
,然后给你一个大大的 404 页面,是不是有点懵?别急,这问题挺常见的,咱们来捋一捋。
遇到这个问题,你用的 Spring Boot 版本大概是 5.3.x,Spring Security OAuth2 Client 版本可能是 5.7.x。你说 Google Cloud Console 里的 URI 和 OAuth2ClientConfig
都配对了,那问题可能出在哪儿呢?
啥情况会导致 404?
这个 /login/oauth2/code/google
URL 不是随便写的,它是 Spring Security OAuth2 Client 默认处理 Google 登录回调的端点 (Endpoint)。它的格式通常是 {baseUrl}/login/oauth2/code/{registrationId}
。
当你从 Google 登录页面成功授权,Google 会把你重定向回你的应用程序,带着一个授权码 (authorization code) ,目标就是这个回调 URL。Spring Security 有个专门的过滤器 (Filter) 应该在这里等着,拿到授权码,再去跟 Google 换访问令牌 (access token),然后完成登录流程,最后才跳转到你指定的 defaultSuccessUrl
。
现在它 404 了,意思就是:请求是发过来了,但是 Spring Security 没有成功拦截并处理这个 /login/oauth2/code/google
请求。 为啥没处理呢?原因可能五花八门,但主要就那么几个方向:
- Security 配置没对:
SecurityFilterChain
配置里,可能没正确启用 OAuth2 登录,或者授权规则不小心把这个回调地址给挡了。 application.properties
/yml
配错了: 虽然你说配置对了,但魔鬼在细节。Client ID、Client Secret 或者特别是重定向 URI (redirect-uri
) 没跟 Google Cloud Console 完全对上。registrationId
不匹配: URL 里的google
必须跟你配置文件里的 registration ID 一致。- 根路径 (Base URL) 或 上下文路径 (Context Path) 有坑: Spring Security 拼
redirect_uri
或者处理回调请求时,对应用的基础 URL 或 context path 理解错了,导致路径匹配不上。特别注意那个//
双斜杠,这往往是路径拼接出问题的信号。 - 依赖冲突或版本问题: 项目里依赖复杂,可能存在版本冲突。
- 其他过滤器捣乱: 自定义的 Filter 或者其他 Web 配置,可能抢在 Spring Security 前面处理了请求,或者干扰了路径匹配。
怎么一步步解决?
别慌,咱们一个个来排查。
1. 检查 SecurityFilterChain
配置
这是最直接的地方。看看你的 SecurityConfig.java
。
原理:
你需要确保:
.oauth2Login()
被调用了,表示启用 OAuth2 登录流程。authorizeRequests()
(或新版authorizeHttpRequests()
) 里的规则,必须允许对/login/oauth2/code/**
路径的访问。因为用户在 Google 授权后跳回来时,是处于未完全认证状态的,需要先能访问这个回调地址,才能完成认证。
怎么做:
检查你的 securityFilterChain
Bean 定义:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
public class SecurityConfig { // 注意:你的例子里 @Import(OAuth2ClientConfig.class) 可能不是必须的,
// 如果 ClientRegistrationRepository 已经能通过依赖注入获得
// ClientRegistrationRepository 可以通过 Spring Boot 自动配置注入
// 无需显式 @Import 和构造函数注入,除非你有非常特殊的配置需求
// public SecurityConfig(ClientRegistrationRepository clientRegistrationRepository) {
// this.clientRegistrationRepository = clientRegistrationRepository;
// }
// AuthenticationManager 通常由 Spring Boot 自动配置管理,一般不需要自己显式配置
// @Bean
// public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
// return http.getSharedObject(AuthenticationManagerBuilder.class).build();
// }
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 开发环境可以禁用,生产环境建议开启并配置
.authorizeHttpRequests(authorize -> authorize
// 核心:确保回调路径是公开访问的
// 注意你代码里的 .antMatchers("/login", "/login/oauth2/**", "/oauth2/** ", "/myapp/login/oauth2/code/**").permitAll()
// 一般只需要 "/login/oauth2/code/**" 和可能的登录页面路径
// 如果你的应用没有context path "/myapp",那么 "/myapp/login/oauth2/code/**" 就没用
// 建议精简为必须的路径
.mvcMatchers("/", "/login", "/login/**", "/oauth2/** ", "/error").permitAll() // 允许访问登录页, 错误页及OAuth2相关路径
// .antMatchers("/login/oauth2/code/*").permitAll() // 更精确一点也可以
.anyRequest().authenticated() // 其他所有请求都需要认证
)
// 启用 OAuth2 登录
.oauth2Login(oauth2 -> oauth2
// 这里可以指定登录页面,如果需要自定义的话
// .loginPage("/login")
// 认证成功后的默认跳转地址
.defaultSuccessUrl("/api/hi", true) // true 表示强制跳转,即使用户之前访问了受保护页面
// 根据需求看是否需要强制
// (可选) 配置用户信息端点等
// .userInfoEndpoint(userInfo -> ...)
// (可选) 配置授权请求处理
// .authorizationEndpoint(authz -> authz
// .baseUri("/oauth2/authorization") // 默认的授权请求触发路径
// // ... 其他配置
// )
// (可选) 配置重定向回调处理
.redirectionEndpoint(redir -> redir
.baseUri("/login/oauth2/code/*") // 指定处理回调的基础URI,确保和Google配置及路径匹配
)
);
// 如果有 form login 也需要配置
// .formLogin(formLogin -> formLogin
// .loginPage("/login")
// .permitAll()
// );
return http.build();
}
}
注意点:
authorizeRequests()
现在推荐用authorizeHttpRequests()
和 lambda 表达式配置,更简洁。- 确认你的
permitAll()
规则确实覆盖了/login/oauth2/code/google
。最保险的是用/login/oauth2/code/**
或者更具体的/login/oauth2/code/google
。 - 检查是不是有其他
antMatcher
规则意外地覆盖了或优先拦截了这个路径,并要求了认证。Spring Security 配置的顺序很重要。 - 那个
/myapp/login/oauth2/code/**
的permitAll
有点奇怪,除非你的应用真的部署在/myapp
context path 下,并且回调 URI 也配置成了这个。一般情况下,标准回调路径不带应用名。
2. 核对 application.properties
或 application.yml
这是 OAuth2 客户端信息的核心配置地。一点差错都可能导致流程失败。
原理:
Spring Boot 会根据这里的配置信息,自动帮你构建 ClientRegistration
对象。关键信息包括:
client-id
: Google Cloud Console 给你的客户端 ID。client-secret
: Google Cloud Console 给你的客户端密钥。scope
: 你向 Google 请求的用户信息范围,如openid
,profile
,email
。redirect-uri
: 重中之重! 这个 URI 必须和你 在 Google Cloud Console -> APIs & Services -> Credentials -> 你的 OAuth 2.0 Client ID -> Authorized redirect URIs 里添加的那个 URI 一模一样! 一点不多,一点不少,包括http
还是https
,端口号,路径等。
怎么做:
检查你的 application.yml
(或者 .properties
文件):
spring:
security:
oauth2:
client:
registration:
google: # 这个 'google' 就是 registrationId,要和回调URL路径一致
client-id: YOUR_GOOGLE_CLIENT_ID # 替换成你的 Client ID
client-secret: YOUR_GOOGLE_CLIENT_SECRET # 替换成你的 Client Secret
scope: openid, profile, email # 根据需要调整 scope
# redirect-uri: 不需要显式配置,除非你需要覆盖默认值
# 如果不配置,Spring Boot 会默认使用模板: {baseUrl}/{action}/oauth2/code/{registrationId}
# 假设你的应用跑在 http://localhost:8080 且没有 context path
# 那么默认解析出来就是 http://localhost:8080/login/oauth2/code/google
# ---
# 如果你的应用有 context path, 比如 /myapp (server.servlet.context-path=/myapp)
# Spring Boot 通常能正确处理,解析为 http://localhost:8080/myapp/login/oauth2/code/google
# ---
# 只有在默认值不对,或者你想强制指定时才配置:
# redirect-uri: http://localhost:8080/custom/callback/google # 极不推荐,除非完全理解影响
# 如果配置了,那么 Google Console 里也必须是这个 URI,
# 并且 SecurityConfig 里 .redirectionEndpoint().baseUri() 也需要对应调整。
# 如果还有其他 provider (如 GitHub),类似地配置
# provider:
# google: # 这部分通常由 Spring Boot 预设,除非需要自定义 issuer-uri 等
# issuer-uri: https://accounts.google.com
关键检查点:
client-id
和client-secret
:从 Google Cloud Console 复制过来,别带空格,别弄错。registrationId
(google
) : 确保和你看到的 404 URL 路径/login/oauth2/code/google
中的google
部分一致。如果你的配置里用的是my-google
,那 URL 就应该是/login/oauth2/code/my-google
。redirect-uri
的核对 :- 登录 Google Cloud Console。
- 找到你的项目 -> APIs & Services -> Credentials。
- 点击你的 "OAuth 2.0 Client ID"。
- 查看 "Authorized redirect URIs" 列表。
- 必须包含 你的应用回调地址。
- 如果你的应用跑在
http://localhost:8080
,没有 context path,这里就应该有http://localhost:8080/login/oauth2/code/google
。 - 如果你用了 80 端口或其他端口,相应修改。
- 如果你部署在服务器上,用的是域名和 HTTPS,那就应该是
https://yourdomain.com/login/oauth2/code/google
。 - 如果你配置了
server.servlet.context-path=/myapp
,这里就应该是http://localhost:8080/myapp/login/oauth2/code/google
。 这点非常非常重要!
- 如果你的应用跑在
- 确认 Spring Boot 最终使用的
redirect-uri
和 Google Console 里配置的完全一致。可以在 Spring Boot 启动日志里找找看,或者 Debug 时检查ClientRegistration
对象。
安全建议:
别把 client-secret
直接写在代码或配置文件里提交到 Git!用环境变量、配置文件服务器 (Config Server) 或云服务商的秘密管理服务来存。
3. 确认 registrationId
一致
这通常和上一步一起检查,但值得单独拎出来。
原理:
URL /login/oauth2/code/{registrationId}
中的 {registrationId}
部分,是由 Spring Security 动态获取的,它直接关联到你在配置文件中 spring.security.oauth2.client.registration.
下定义的那个 key。
怎么做:
- 看你的 404 URL 是
/login/oauth2/code/google
。 - 那么你的配置文件里必须是
spring.security.oauth2.client.registration.google
。 - 如果配置文件是
registration.google-sso
,那 URL 应该是/login/oauth2/code/google-sso
。确保两边大小写、拼写、连字符都一样。
4. 检查 Base URL 和 Context Path (处理 //
双斜杠)
那个 loalhost:myportnum//login/oauth2/code/google
中的 //
是个强烈的危险信号,通常意味着 URL 路径拼接出了问题。
原理:
Spring Security 在构建发往 Google 的认证请求中的 redirect_uri
参数时,需要知道你应用的基础 URL (schema, host, port)。它通常能自动探测,但有时会出错,尤其是在反向代理后面,或者配置了 server.servlet.context-path
时。同样地,当请求回调时,它也需要根据自身认为的基础 URL + context path 来匹配回调路径。如果这两个过程中的路径计算不一致,或者与 Google 那边配置的不符,就会出问题。双斜杠 //
往往是因为某个部分(比如 context path)被错误地包含了两次或者格式不对。
怎么做:
- 检查
server.servlet.context-path
:- 在
application.properties
或yml
中找找看有没有设置server.servlet.context-path
? - 如果有,比如
server.servlet.context-path=/myapp
。 - 那么: Google Cloud Console 里的 Authorized redirect URI 必须是
http://localhost:端口号/myapp/login/oauth2/code/google
(或其他对应的 schema/host) 。 - 同时,确认你的应用内链接、访问都是基于
/myapp
这个前缀的。 - 尝试暂时去掉
context-path
配置 ,把 Google Console 的 URI 也改成不带/myapp
的标准形式 (http://localhost:端口号/login/oauth2/code/google
),看看问题是否消失。如果消失了,说明就是 context path 处理有问题。
- 在
- 检查反向代理 (如果用了 Nginx, Apache 等):
- 如果你的 Spring Boot 应用跑在反向代理后面 (比如 Nginx 处理 HTTPS 和域名,然后转发到后端的 Spring Boot 应用),确保代理配置正确传递了原始请求的协议 (
X-Forwarded-Proto
)、主机 (X-Forwarded-Host
) 和端口 (X-Forwarded-Port
) 头信息。 - 在 Spring Boot 的
application.properties
/yml
里配置server.forward-headers-strategy=framework
(较新版本) 或server.use-forward-headers=true
(较旧版本),让 Spring Boot 能识别并使用这些头信息来判断自己的 Base URL。否则,它可能以为自己还在http://localhost:8080
上运行,导致redirect_uri
不匹配。
- 如果你的 Spring Boot 应用跑在反向代理后面 (比如 Nginx 处理 HTTPS 和域名,然后转发到后端的 Spring Boot 应用),确保代理配置正确传递了原始请求的协议 (
- 浏览器开发者工具:
- 打开浏览器开发者工具 (F12),切换到 Network (网络) 面板。
- 重新触发登录流程。
- 观察:
- 点击“用 Google 继续”后,跳转到 Google 登录页的那个请求,看 URL 参数里的
redirect_uri
是什么?它是否和你期望的一致?是否包含双斜杠? - 从 Google 回调到你的应用,导致 404 的那个请求,看它的确切 URL 是什么?是不是真的有双斜杠
//
?
- 点击“用 Google 继续”后,跳转到 Google 登录页的那个请求,看 URL 参数里的
- 这个双斜杠非常可疑。它可能是因为
context-path
配置最后带了个/
,或者 Spring 的路径拼接逻辑在某个环节出了 bug。确保你的context-path
(如果设置) 值是像/myapp
这样,前后都没有多余的斜杠。
5. 检查依赖版本
虽然你的版本看着还行 (Spring 5.3.x, Security 5.7.x),但保险起见可以检查下。
原理:
不同库之间可能存在不兼容。Spring Boot 的 starter
包通常能很好地管理传递性依赖,但如果你手动指定了某些 Spring Security 或 OAuth2 相关库的版本,可能会引入冲突。
怎么做:
-
依赖管理: 优先使用 Spring Boot 提供的
spring-boot-starter-parent
作为父 POM,并直接依赖spring-boot-starter-oauth2-client
。让 Spring Boot 统一管理版本。<!-- pom.xml --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <!-- 使用你项目对应的 Spring Boot 版本 --> <version>2.7.x</version> <!-- Spring Boot 2.7.x 对应 Spring Framework 5.3.x, Security 5.7.x --> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <!-- 其他依赖 --> </dependencies>
-
检查冲突: 用 Maven 的
mvn dependency:tree
或 Gradle 的gradle dependencies
命令查看项目依赖树,检查是否有多个不同版本的 Spring Security 或 OAuth2 相关库被引入。如果有,使用<dependencyManagement>
(Maven) 或resolutionStrategy
(Gradle) 解决冲突,尽量统一到 Spring Boot Parent 管理的版本。
6. 终极手段:开 Debug 日志
如果以上都检查了还没好,那就得看日志挖细节了。
原理:
打开 Spring Security 的 DEBUG 级别日志,可以看到它处理请求的详细过程,包括哪个 Filter 在工作,URL 匹配情况,发生了什么错误等。
怎么做:
在 application.properties
或 application.yml
中添加:
logging:
level:
org.springframework.security: DEBUG
org.springframework.web: DEBUG # 查看请求分发和处理的日志也可能有帮助
org.springframework.security.oauth2: DEBUG # 更细致的 OAuth2 日志
然后重启应用,再次尝试 Google 登录。观察控制台输出的日志。特别关注和 /login/oauth2/code/google
请求相关的日志条目。看看是哪个 Filter 链在处理它,匹配结果如何,有没有异常抛出。
进阶调试:
可以在关键的 Spring Security Filter 上打断点进行调试,比如:
OAuth2LoginAuthenticationFilter
: 处理 OAuth2 登录认证的核心 Filter。OAuth2AuthorizationCodeGrantFilter
: 处理授权码模式的回调,用 code 换 token。- 检查进入 Filter 时的
HttpServletRequest
对象的getRequestURI()
和getContextPath()
等方法,看看实际收到的路径和你预期的是否一致。
排查这个 404 问题,通常就是要细心、耐心。从配置到代码,再到依赖和环境,一步步来。多数情况下,问题就藏在 redirect-uri
的完全匹配、SecurityFilterChain
的 permitAll
规则,或者是由 context-path
、反向代理引起的 Base URL 解析错误上。那个 //
双斜杠尤其值得关注,很可能是 context-path
或路径拼接逻辑的问题。按着上面的步骤走一遍,应该能找到症结所在。