指南:从零开始为 GKE 应用启用 Google 账号登录 (IAP)
本文档将非常详细地描述我们如何从零开始,为部署在 GKE 上的 Web 应用(一个前端 chat-ui 和一个后端 chat-api-svc)启用通过 Google 账号登录的 IAP(Identity-Aware Proxy)保护。
1. 最终架构
我们的目标是实现以下请求流程:当用户访问前端并调用后端 API 时,Google 的负载均衡器会拦截请求,强制用户通过 Google 账号认证,然后将认证信息安全地传递给我们的后端服务。
User's Browser Google Cloud Load Balancer (GKE Gateway) Google IAP Frontend Pod (chat-ui) Backend Pod (chat-api-svc) 访问前端 UI 请求受 IAP 保护的前端服务 重定向到 Google 登录页面 登录成功后返回 alt [用户未登录] 注入认证 Header 并转发请求 返回前端静态文件 (HTML/JS/CSS) 返回前端静态文件 渲染前端页面 前端 App 加载并执行 JS JS 发起 API 请求 (fetch('/chat-api-svc/api/v1/me')) 拦截发往后端的请求 验证用户之前的 IAP session 验证通过,注入 X-Goog-* Header 并转发请求 后端 /me 接口读取 Header,返回 JSON 返回 JSON 响应 浏览器收到用户信息 前端 UI 在左下角显示用户 Email User's Browser Google Cloud Load Balancer (GKE Gateway) Google IAP Frontend Pod (chat-ui) Backend Pod (chat-api-svc)
2. 前提条件
- 一个正在运行的 GKE 集群。
- 集群中已安装并配置好 GKE Gateway Controller。
- 您的前端 (
chat-ui) 和后端 (chat-api-svc) 应用已通过 Deployment 部署到集群。 - 已有一个
Gateway和HTTPRoute资源,将公网流量路由到您的服务。
3. 操作步骤
第 1 步:在 Google Cloud Console 中创建 OAuth 凭证
IAP 需要一个 OAuth 2.0 客户端 ID 和密钥来识别您的应用并展示统一的登录界面。
-
配置 OAuth 同意屏幕
- 导航到 Google Cloud Console -> "API 和服务" -> "OAuth 同意屏幕"。
- 选择 "内部" 用户类型(如果您组织内的所有用户都可以访问)或"外部"(如果您想允许任何 Google 用户)。
- 填写应用名称(例如 "Chat App")、用户支持电子邮件等信息。
- 保存并继续。在范围页面,您不需要添加任何范围,直接点击"保存并继续"。
在这里插入图片描述
-
创建 OAuth 2.0 客户端 ID
-
导航到 "API 和服务" -> "凭据"。
-
点击 "+ 创建凭据" -> "OAuth 客户端 ID"。
-

-
在"应用类型"中选择 "Web 应用"。
-
为它命名,例如 "chat-ui-iap-client"。
-
关键步骤:添加已获授权的重定向 URI
- 点击 "+ 添加 URI"。
- 您需要添加一个特殊的 URI,IAP 会用它来处理登录回调。这个 URI 的格式是:
https://iap.googleapis.com/v1/oauth2/clientServiceAccounts/[PROJECT_NUMBER]:handleRedirect - 您可以在 Cloud Console -> "IAM 和管理" -> "设置" 中找到您的 项目编号 (Project Number)。
- 最终的 URI 类似于:
https://iap.googleapis.com/v1/oauth2/clientServiceAccounts/912156613264:handleRedirect
-

* 点击"创建"。弹出的窗口会显示您的 **客户端 ID** 和 **客户端密钥**。请**务必**将这两个值复制下来,下一步会用到。
理解"已获授权的重定向 URI"
问:这个 URI 的作用是什么?为什么需要手动配置?
答:简单来说,配置这个 URI 就是一个授权 步骤。它明确地告诉 Google 的认证系统:"我允许我刚刚创建的这个客户端 ID (Client ID) 被我当前 GCP 项目中的 IAP 服务使用。"
这是一个核心的安全机制,确保只有您明确授权的应用和服务才能使用您的 OAuth 凭证。Google 要求手动配置此项,是为了让您作为项目所有者进行一次明确的安全确认。
第 2 步:将 OAuth 凭证存储为 Kubernetes Secret
为了让 GKE 中的 IAP 配置能安全地使用这些凭证,我们需要将它们存储为 Kubernetes Secret。
-
创建
iap-secret.yaml文件在
k8s/目录下创建一个名为iap-secret.yaml的文件。 -
填充 Secret 内容
将您上一步获取的客户端 ID 和密钥填入以下模板。注意: 值需要经过 Base64 编码。
您可以使用命令
echo -n 'YOUR_CLIENT_ID' | base64和echo -n 'YOUR_CLIENT_SECRET' | base64来生成编码后的字符串。yaml# k8s/iap-secret.yaml apiVersion: v1 kind: Secret metadata: name: iap-oauth-credentials type: Opaque data: client_id: "YOUR_CLIENT_ID_BASE64" client_secret: "YOUR_CLIENT_SECRET_BASE64" -
应用 Secret
执行
kubectl apply -f k8s/iap-secret.yaml将这个 Secret 创建在您的集群中。
第 3 步:为后端 API 创建专用的 IAP Service 和策略
为了将 IAP 仅应用于后端 API,同时保持内部服务间的通信不受影响,最佳实践是创建一个专用的 Service 来接收 IAP 流量。
-
创建新的 Service (
service-chat-api-iap.yaml)这个 Service 会和现有的
clusterip-chat-api-svc指向相同的 Pods(通过selector: app: chat-api-svc),但它有自己独立的名字。yaml# k8s/service-chat-api-iap.yaml # This service is a dedicated entrypoint for IAP traffic to the chat-api backend. apiVersion: v1 kind: Service metadata: name: clusterip-chat-api-svc-iap spec: type: ClusterIP selector: app: chat-api-svc # 确保这与后端 Pod 的标签匹配 ports: - protocol: TCP port: 8000 targetPort: 8000 -
创建 GCPBackendPolicy (
gcp-backend-policy-chat-api-svc.yaml)这个策略会将 IAP 规则附加到我们刚刚创建的新 Service 上。
yaml# k8s/gcp-backend-policy-chat-api-svc.yaml # This policy applies IAP to the dedicated backend IAP service. apiVersion: networking.gke.io/v1 kind: GCPBackendPolicy metadata: name: iap-policy-chat-api-svc spec: default: iap: enabled: true oauth2ClientSecret: name: iap-oauth-credentials # 引用我们创建的 K8s Secret clientID: "912156613264-eaphf3lhji9jbp402e2qac050oiq1cdm.apps.googleusercontent.com" # 填入您的客户端 ID targetRef: group: "" kind: Service name: clusterip-chat-api-svc-iap # 将 IAP 策略指向新的 Service
第 3 步:为前端和后端服务启用 IAP
为了实现完整的端到端认证,我们不仅需要保护后端 API,通常也需要保护前端应用本身。这意味着当用户第一次访问网站时,就需要登录。
3.1 为前端服务启用 IAP
项目中已经存在一个为前端启用 IAP 的策略。它的原理和我们即将为后端创建的策略完全相同:
- 目标服务 :
chat-ui-iap-service(一个专门用于 IAP 流量的前端 Service)。 - 策略文件 :
k8s/gcp-backend-policy.yaml
yaml
# k8s/gcp-backend-policy.yaml
apiVersion: networking.gke.io/v1
kind: GCPBackendPolicy
metadata:
name: iap-backend-policy
spec:
default:
iap:
enabled: true
oauth2ClientSecret:
name: iap-oauth-credentials
clientID: "912156613264-eaphf3lhji9jbp402e2qac050oiq1cdm.apps.googleusercontent.com"
targetRef:
group: ""
kind: Service
name: chat-ui-iap-service # 策略指向前端的 IAP Service
当您部署 k8s/ 目录下的所有文件时,这个策略会被应用,从而保护前端的访问。
当做完上面步骤, 我们再次打开前端后会直接到google的登录窗口

3.2 在 IAP 中授权用户访问权限 (关键)
仅仅启用 IAP 是不够的,您还必须明确指定哪些用户或用户组有权访问您的应用。否则,所有用户(包括您自己)都会看到 "You don't have access" (403 Forbidden) 的错误。
-
导航到 IAP 页面
- 在 Google Cloud Console 中,前往 "安全" -> "Identity-Aware Proxy"。

- 在 Google Cloud Console 中,前往 "安全" -> "Identity-Aware Proxy"。
-
找到您的后端服务
- 在列表中,找到与您的 GKE 负载均衡器关联的后端服务。您应该能看到对应
chat-ui-iap-service和clusterip-chat-api-svc-iap的条目。
- 在列表中,找到与您的 GKE 负载均衡器关联的后端服务。您应该能看到对应
-
添加主账号
- 选中您需要授权的后端服务(例如
chat-ui-iap-service)。 - 在右侧打开的信息面板中,点击 "添加主账号"。
- 选中您需要授权的后端服务(例如
-
分配角色
- 新的主账号 : 输入您希望授予访问权限的 Google 账号(例如
your-email@gmail.com)、Google 群组 (my-group@example.com),或者allAuthenticatedUsers(允许所有登录了 Google 的用户)。 - 选择角色 : 在下拉菜单中,找到并选择
Cloud IAP->IAP-secured Web App User。
- 新的主账号 : 输入您希望授予访问权限的 Google 账号(例如

- 保存
- 点击"保存"。对所有需要保护的服务(包括前端和后端)重复此操作。
现在,只有被授权的用户才能在通过 Google 登录后访问您的前端应用。
3.3 为后端 API 创建专用的 IAP Service 和策略
为了将 IAP 仅应用于后端 API,同时保持内部服务间的通信不受影响,最佳实践是创建一个专用的 Service 来接收 IAP 流量。
-
创建新的 Service (
service-chat-api-iap.yaml)这个 Service 会和现有的
clusterip-chat-api-svc指向相同的 Pods(通过selector: app: chat-api-svc),但它有自己独立的名字。yaml# k8s/service-chat-api-iap.yaml # This service is a dedicated entrypoint for IAP traffic to the chat-api backend. apiVersion: v1 kind: Service metadata: name: clusterip-chat-api-svc-iap spec: type: ClusterIP selector: app: chat-api-svc # 确保这与后端 Pod 的标签匹配 ports: - protocol: TCP port: 8000 targetPort: 8000 -
创建 GCPBackendPolicy (
gcp-backend-policy-chat-api-svc.yaml)这个策略会将 IAP 规则附加到我们刚刚创建的新 Service 上。
yaml# k8s/gcp-backend-policy-chat-api-svc.yaml # This policy applies IAP to the dedicated backend IAP service. apiVersion: networking.gke.io/v1 kind: GCPBackendPolicy metadata: name: iap-policy-chat-api-svc spec: default: iap: enabled: true oauth2ClientSecret: name: iap-oauth-credentials # 引用我们创建的 K8s Secret clientID: "912156613264-eaphf3lhji9jbp402e2qac050oiq1cdm.apps.googleusercontent.com" # 填入您的客户端 ID targetRef: group: "" kind: Service name: clusterip-chat-api-svc-iap # 将 IAP 策略指向新的 Service
3.4 在 IAP 中授权用户访问权限 for 后端, 同3.2
略
现在,只有被授权的用户才能在通过 Google 登录后访问您的后端应用。
第 4 步:更新网关路由 (HTTPRoute)
现在,我们需要告诉 GKE Gateway,将发往 /chat-api-svc 的外部流量路由到受 IAP 保护的新 Service 上。
-
修改
HTTPRoute配置我们基于线上的
py-api-route配置,只修改/chat-api-svc规则的backendRefs部分。变更前:
yaml- backendRefs: - name: clusterip-chat-api-svc port: 8000变更后:
yaml- backendRefs: - name: clusterip-chat-api-svc-iap # 指向新的 IAP Service port: 8000 -
应用变更
由于直接
apply可能会遇到resourceVersion冲突,我们使用replace命令来确保更新成功。bashkubectl replace -f k8s/httproute-iap-fix.yaml
第 5 步:后端代码修改 (/me 接口)
后端需要一个接口来读取 IAP 注入的 Header 并返回给前端。
-
修改
src/routers/user_router.py我们添加了一个新的
/me接口。pythonfrom fastapi import APIRouter, Depends, HTTPException, Request from src.schemas.user import IAPUser # 需要先定义 IAPUser schema # ... (router 定义) ... @router.get("/me", response_model=IAPUser) async def get_current_user_from_iap(request: Request): """ 从 IAP 头中获取当前认证用户的信息。 """ email = request.headers.get("X-Goog-Authenticated-User-Email") user_id = request.headers.get("X-Goog-Authenticated-User-Id") # IAP email header 的格式通常是 "accounts.google.com:user@example.com" if email and ":" in email: email = email.split(":")[-1] return {"email": email, "user_id": user_id}
第 6 步:前端代码修改 (获取并显示用户信息)
最后,我们需要让前端应用调用 /me 接口,并在 UI 上展示出来。
-
更新 API 服务 (
src/services/api.ts)添加一个新函数来调用
/me接口,并为本地开发提供模拟数据。typescriptexport interface UserInfo { email: string | null; user_id: string | null; } export async function fetchUserInfo(): Promise<UserInfo> { const ME_API_URL = '/chat-api-svc/api/v1/me'; try { const response = await fetch(ME_API_URL); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const userInfo: UserInfo = await response.json(); // 为本地开发提供模拟数据 if (!userInfo.email && import.meta.env.DEV) { return { email: 'local-dev@example.com', user_id: 'dev-user-123' }; } return userInfo; } catch (error) { console.error('Error fetching user info:', error); if (import.meta.env.DEV) { return { email: 'local-dev-error@example.com', user_id: 'dev-user-error-123' }; } return { email: null, user_id: null }; } } -
更新侧边栏组件 (
src/components/conversation-list.ts)我们修改这个组件,让它自己获取并显示用户信息。
typescript// ... imports ... import { fetchUserInfo, type UserInfo } from '../services/api'; @customElement('conversation-list') export class ConversationList extends LitElement { // ... @state() private _userInfo: UserInfo | null = null; async connectedCallback() { super.connectedCallback(); // 组件加载时调用 API this._userInfo = await fetchUserInfo(); // 派发事件,通知父组件用户已加载 if (this._userInfo?.user_id) { this.dispatchEvent(new CustomEvent('user-loaded', { detail: { user: this._userInfo }, bubbles: true, composed: true })); } } // ... render() { return html` <!-- ... 其他模板 ... --> <sl-divider></sl-divider> <div class="profile-section"> ${this._userInfo?.email ? html` <div class="user-avatar">${this._userInfo.email.charAt(0)}</div> <div class="profile-info"> <span class="profile-email">${this._userInfo.email}</span> </div> ` : html`<sl-spinner></sl-spinner><span>Loading user...</span>`} </div> <!-- ... --> `; } } -
更新主应用组件 (
src/chat-app.ts)最后,修改主应用,移除硬编码的用户,并监听
user-loaded事件来动态加载对话。typescript@customElement('chat-app') export class ChatApp extends LitElement { // ... // 移除硬编码的 _currentUser @state() private _currentUser: UserInfo | null = null; // 移除 firstUpdated // 添加事件处理器 private _handleUserLoaded(e: CustomEvent) { console.log('User loaded:', e.detail.user); this._currentUser = e.detail.user; this._loadConversations(); // 在获取到用户信息后再加载对话 } // ... render() { const conversationList = html` <conversation-list .conversations=${this.conversations...} .selectedConversationId=${this._selectedConversationId} @conversation-selected=${this._handleConversationSelected} @conversation-created=${this._handleConversationCreated} @user-loaded=${this._handleUserLoaded} ></conversation-list> `; // ... } }
效果

4. 结论
通过以上步骤,我们成功地为 GKE 上的应用配置了 IAP,实现了无需修改应用核心代码的安全登录。整个流程涉及了云平台配置、Kubernetes 资源编排、后端接口实现和前端 UI 展示,是一个完整的端到端实践。