# 指南:从零开始为 GKE 应用启用 Google 账号登录 (IAP)

指南:从零开始为 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 部署到集群。
  • 已有一个 GatewayHTTPRoute 资源,将公网流量路由到您的服务。

3. 操作步骤

第 1 步:在 Google Cloud Console 中创建 OAuth 凭证

IAP 需要一个 OAuth 2.0 客户端 ID 和密钥来识别您的应用并展示统一的登录界面。

  1. 配置 OAuth 同意屏幕

    • 导航到 Google Cloud Console -> "API 和服务" -> "OAuth 同意屏幕"。
    • 选择 "内部" 用户类型(如果您组织内的所有用户都可以访问)或"外部"(如果您想允许任何 Google 用户)。
    • 填写应用名称(例如 "Chat App")、用户支持电子邮件等信息。
    • 保存并继续。在范围页面,您不需要添加任何范围,直接点击"保存并继续"。
      在这里插入图片描述
  2. 创建 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。

  1. 创建 iap-secret.yaml 文件

    k8s/ 目录下创建一个名为 iap-secret.yaml 的文件。

  2. 填充 Secret 内容

    将您上一步获取的客户端 ID 和密钥填入以下模板。注意: 值需要经过 Base64 编码。

    您可以使用命令 echo -n 'YOUR_CLIENT_ID' | base64echo -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"
  3. 应用 Secret

    执行 kubectl apply -f k8s/iap-secret.yaml 将这个 Secret 创建在您的集群中。

第 3 步:为后端 API 创建专用的 IAP Service 和策略

为了将 IAP 仅应用于后端 API,同时保持内部服务间的通信不受影响,最佳实践是创建一个专用的 Service 来接收 IAP 流量。

  1. 创建新的 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
  2. 创建 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) 的错误。

  1. 导航到 IAP 页面

    • 在 Google Cloud Console 中,前往 "安全" -> "Identity-Aware Proxy"。
  2. 找到您的后端服务

    • 在列表中,找到与您的 GKE 负载均衡器关联的后端服务。您应该能看到对应 chat-ui-iap-serviceclusterip-chat-api-svc-iap 的条目。
  3. 添加主账号

    • 选中您需要授权的后端服务(例如 chat-ui-iap-service)。
    • 在右侧打开的信息面板中,点击 "添加主账号"
  4. 分配角色

    • 新的主账号 : 输入您希望授予访问权限的 Google 账号(例如 your-email@gmail.com)、Google 群组 (my-group@example.com),或者 allAuthenticatedUsers (允许所有登录了 Google 的用户)。
    • 选择角色 : 在下拉菜单中,找到并选择 Cloud IAP -> IAP-secured Web App User
  1. 保存
    • 点击"保存"。对所有需要保护的服务(包括前端和后端)重复此操作。

现在,只有被授权的用户才能在通过 Google 登录后访问您的前端应用。

3.3 为后端 API 创建专用的 IAP Service 和策略

为了将 IAP 仅应用于后端 API,同时保持内部服务间的通信不受影响,最佳实践是创建一个专用的 Service 来接收 IAP 流量。

  1. 创建新的 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
  2. 创建 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 上。

  1. 修改 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
  2. 应用变更

    由于直接 apply 可能会遇到 resourceVersion 冲突,我们使用 replace 命令来确保更新成功。

    bash 复制代码
    kubectl replace -f k8s/httproute-iap-fix.yaml

第 5 步:后端代码修改 (/me 接口)

后端需要一个接口来读取 IAP 注入的 Header 并返回给前端。

  1. 修改 src/routers/user_router.py

    我们添加了一个新的 /me 接口。

    python 复制代码
    from 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 上展示出来。

  1. 更新 API 服务 (src/services/api.ts)

    添加一个新函数来调用 /me 接口,并为本地开发提供模拟数据。

    typescript 复制代码
    export 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 };
      }
    }
  2. 更新侧边栏组件 (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>
          <!-- ... -->
        `;
      }
    }
  3. 更新主应用组件 (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 展示,是一个完整的端到端实践。

相关推荐
nvd117 小时前
Auth0 /userinfo 速率限制问题解决方案
googlecloud·authing
jumu2021 天前
探索非线性电液伺服系统:从PID到反步控制的奇妙之旅
googlecloud
@YDWLCloud7 天前
谷歌云 Compute Engine 实操手册:虚拟机配置与负载均衡全流程
java·运维·服务器·云计算·负载均衡·googlecloud
TG:@yunlaoda360 云老大11 天前
谷歌云AI 时代的算力革命:CPU、GPU 到 TPU 的架构与定位解析
人工智能·架构·googlecloud
酷尔。12 天前
Gemini学生认证、订阅方法和常见问题的解决方法
ai·googlecloud·使用教程·gemini
攻城狮杰森14 天前
AI·重启思维:Gemini 3 带你走进智能的下一个维度
人工智能·语言模型·ai作画·aigc·googlecloud
这儿有一堆花17 天前
重磅推出!Google Antigravity:一次 “以 Agent 为中心 (agent-first)” 的 IDE 革命
vscode·ai·ai编程·googlecloud
TG:@yunlaoda360 云老大1 个月前
AI 电影制作迈入新阶段:谷歌云Veo 3.1模型发布,实现音频全覆盖与精细化创意剪辑
人工智能·云计算·音视频·googlecloud
TG:@yunlaoda360 云老大1 个月前
谷歌云Flink 核心组成及生态发展:实时数据处理的下一代引擎
大数据·flink·googlecloud