Cloud Scheduler使用错误服务账号认证问题求助
问题:Cloud Scheduler调用Cloud Run返回403,令牌邮箱与配置的服务账号不符
我在Cloud Run上部署了应用,并创建了Cloud Scheduler任务调用该应用的端点。我创建了一个服务账号用于Cloud Scheduler的令牌认证,且多次确认任务配置中已选中正确的服务账号。
应用中配置了用于拦截未授权请求的中间件(简化版本):
import ( "fmt" "github.com/gin-gonic/gin" "google.golang.org/api/oauth2/v2" "net/http" "strings" ) func ForceCloudScheduler(c *gin.Context) { if c.Request.UserAgent() != "Google-Cloud-Scheduler" { c.AbortWithStatus(http.StatusForbidden) return } // 验证Google Cloud Scheduler请求 token := c.GetHeader("Authorization") if token == "" { c.AbortWithStatus(http.StatusForbidden) return } idToken := strings.Split(token, "Bearer ")[0] authenticator, err := oauth2.NewService(c) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to acquire authenticator: %w", err)) return } info, err := authenticator.Tokeninfo().IdToken(idToken).Do() if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to retrieve token information: %w", err)) return } // 此处验证失败 if info.Email != "agora-job-scheduler-account@agoradesecrivains.iam.gserviceaccount.com" { c.AbortWithStatus(http.StatusForbidden) return } c.Next() }
运行任务时请求返回403错误,日志显示令牌负载中的邮箱为:
{ "audience": "107655128939031897672", "email": "970356934135-compute@developer.gserviceaccount.com", "expires_in": 1644, "issued_to": "107655128939031897672", "scope": "openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", "user_id": "107655128939031897672", "verified_email": true }
该邮箱与我在Cloud Scheduler中设置的服务账号邮箱不符,请问这是预期情况吗?如何修复?
解答
这不是预期情况,问题核心及修复方案如下:
1. 令牌提取逻辑错误
你的代码中分割Authorization头时使用strings.Split(token, "Bearer ")[0]会拿到无效值。Authorization头格式为Bearer <令牌>,分割后数组的索引0是空字符串,索引1才是真实令牌。错误的令牌会导致验证流程返回默认服务账号的信息,也就是日志里的compute@developer.gserviceaccount.com。
修复这一行代码:
// 替换原提取逻辑,增加长度校验避免数组越界 parts := strings.Split(token, "Bearer ") if len(parts) != 2 { c.AbortWithStatus(http.StatusForbidden) return } idToken := parts[1]
2. 补充完整的令牌验证逻辑
除了修复令牌提取,建议补充以下验证步骤,提升请求合法性校验的严谨性:
- 验证令牌签发者(
iss)为Google官方签发地址:https://accounts.google.com或accounts.google.com - 验证令牌受众(
aud)匹配你的Cloud Run服务对应的客户端ID(可从Cloud Run服务详情页获取,或直接使用日志中显示的audience值确认)
修改后的完整中间件示例:
func ForceCloudScheduler(c *gin.Context) { if c.Request.UserAgent() != "Google-Cloud-Scheduler" { c.AbortWithStatus(http.StatusForbidden) return } token := c.GetHeader("Authorization") if token == "" { c.AbortWithStatus(http.StatusForbidden) return } // 修复令牌提取逻辑 parts := strings.Split(token, "Bearer ") if len(parts) != 2 { c.AbortWithStatus(http.StatusForbidden) return } idToken := parts[1] authenticator, err := oauth2.NewService(c) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to acquire authenticator: %w", err)) return } info, err := authenticator.Tokeninfo().IdToken(idToken).Do() if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to retrieve token information: %w", err)) return } // 验证签发者合法性 if info.Issuer != "https://accounts.google.com" && info.Issuer != "accounts.google.com" { c.AbortWithStatus(http.StatusForbidden) return } // 验证受众匹配Cloud Run服务(替换为你的服务对应客户端ID) expectedAudience := "107655128939031897672" if info.Audience != expectedAudience { c.AbortWithStatus(http.StatusForbidden) return } // 验证服务账号邮箱 if info.Email != "agora-job-scheduler-account@agoradesecrivains.iam.gserviceaccount.com" { c.AbortWithStatus(http.StatusForbidden) return } c.Next() }
3. 确认Cloud Scheduler任务配置
最后再次检查任务的核心配置:
- 确认任务选择的服务账号为
agora-job-scheduler-account@agoradesecrivains.iam.gserviceaccount.com - 认证方法设置为OAuth令牌,且受众填写你的Cloud Run服务完整URL(如
https://your-service-abc.a.run.app) - 确保该服务账号拥有
roles/run.invoker权限,允许调用目标Cloud Run服务
内容的提问来源于stack exchange,提问作者KawaLo
相关产品推荐
相关产品推荐

