亚马逊AWS官方博客

AI 时代如何快速搭建可视化数仓:使用 Amazon Quick App 快速搭建 Amazon Redshift 应用

摘要:在 AI 时代,业务对数据洞察的需求正从”天/周”级交付加速到”分钟”级。传统基于数据仓库的应用开发链路冗长、协作成本高,已成为交付瓶颈。本文介绍如何借助 Amazon Quick App 的 AI 原生能力,通过 MCP(Model Context Protocol)开放标准接入 Amazon Redshift,并结合 Amazon Cognito + API Gateway Lambda Authorizer 构建企业级 OAuth 认证,实现安全、合规地用自然语言查询数仓数据、快速构建并在组织内共享数据应用。


一、概述

随着生成式 AI 的快速发展,越来越多的业务人员和开发者希望以更低的门槛、更快的速度从数据中获取洞察并构建应用。然而在传统模式下,一个数据应用从需求到上线往往要经历数据建模ETL 开发、BI 报表/应用开发、联调部署等多个环节,周期动辄数周甚至数月,难以匹配 AI 时代对交付速度的要求。

与此同时,企业对数据访问的安全与合规要求不降反升。如何在”快”与”安全”之间取得平衡,是构建现代数据应用的核心挑战。

本文将介绍一套完整的方案:基于 Amazon Quick App,通过 MCP(Model Context Protocol)开放标准接入 Amazon Redshift 数据仓库,并结合 Amazon Cognito OAuth 2.0 认证保障访问安全。最终让用户以自然语言的方式安全查询数据、快速构建可视化应用,并在组织内一键共享。

二、传统数仓应用方案的困境

传统基于数据仓库构建数据应用的方案,通常需要一条冗长的开发链路:

需求 → 数据建模 → ETL 开发 → 数据服务/API 开发 → BI 报表或应用开发 → 前后端联调 → 部署上线

这套模式在实践中存在以下典型痛点:

  • 开发周期长:从需求到上线常以周甚至月计,难以快速响应业务变化。
  • 角色多、协作成本高:数据工程师、BI 工程师、应用开发、运维等多方协作,沟通与交接损耗大。
  • 技术门槛高:业务人员无法自助获取洞察,强依赖技术团队,需求排队等待。
  • 迭代僵化:业务口径或需求一旦变化,往往要重新走一遍开发流程。
  • 安全管控碎片化:数据访问权限散落在各环节,难以统一管控和审计。

在追求敏捷与即时洞察的 AI 时代,这套”重”模式正成为数据价值释放的瓶颈,亟需一种更轻量、更快速、同时安全可控的交付范式。

三、Amazon Quick App:AI 时代的敏捷数据应用平台

Amazon Quick 是亚马逊云科技推出的 AI 原生数据分析与应用平台,其中的 Quick App 能力让用户以低代码、自然语言驱动的方式快速构建数据应用。相较传统模式,它针对性地破解了上述痛点:

3.1 快速交付

通过自然语言和低代码方式构建应用,无需完整的后端开发链路,可在分钟级完成从数据到应用的搭建,显著缩短交付周期。

3.2 组织内快速共享

构建好的应用与洞察可在团队和组织内一键共享,配合统一的权限管理,让协作无缝、分发高效。

3.3 AI 原生能力

内置自然语言问答、生成式 BI、智能洞察等能力,业务人员无需编写 SQL 或代码即可获取数据洞察。

3.4 强大的内置及第三方集成方案

Amazon Quick 提供丰富的内置连接器,可对接多种数据源与企业应用;同时支持灵活的第三方接入方案,其中重点是对开放标准 MCP(Model Context Protocol)的支持。

MCP 是一种开放协议,为 AI 系统与外部工具、数据源之间提供了标准化的连接方式。借助 MCP,Amazon Quick 作为托管的 MCP 客户端,只需一次接入即可发现并调用远程 MCP Server 暴露的工具,并将其注册为可在对话中调用的 actions,避免为每个数据源做定制化集成。

3.5 安全接入:支持 OAuth 2.0 认证

Amazon Quick 的 MCP 集成原生支持多种认证方式,包括 OAuth 2.0 用户授权(3LO)和服务间认证(2LO / Client Credentials)。企业可结合 Amazon Cognito 等授权服务器,在 MCP 层面实现统一的访问控制,确保只有经过认证的调用方才能访问数据。这一能力使 MCP 方案满足企业级安全与合规要求。

四、方案概览:安全接入 Amazon Redshift 的完整架构

本方案的目标是让 Amazon Quick App 通过 OAuth 认证安全地查询 Amazon Redshift 数据,并快速构建数据应用。整体架构分为四层:认证层、网关层、桥接层和数据层。

4.1 整体架构

[图 1]

4.2 组件职责

  • Amazon Cognito 作为 OAuth 2.0 授权服务器,签发 JWT access token。
  • API Gateway +Lambda Authorizer 校验每个请求的 Bearer token(RS256 签名、issuer、过期时间、scope),校验通过才放行。
  • EC2 上安装 aws 官方提供的 redshift mcp server 和 mcp stdio 与 Streamable HTTP 协议的开源转换器组件:mcp-proxy

网关层(API Gateway)

  • 提供托管的 HTTPS 端点,无需自备域名与证书。
  • STREAM 模式支持流式响应与长连接(最长 15 分钟),满足 MCP 的传输要求。
  • 可叠加资源策略(IP 白名单)做二次防护。

桥接层(mcp-proxy + NLB)

  • 官方 MCP Server 仅支持 stdio 传输,mcp-proxy 将其桥接为 Streamable HTTP(/mcp)和 SSE(/sse)。
  • 通过 VPC Link + 内部 NLB 私有集成,后端端口不暴露公网,流量全程走 AWS 内部网络。

数据层(awslabs.redshift-mcp-server + Redshift)

  • 官方 server 通过 IAM 认证 + Redshift Data API 查询数据,无需硬编码数据库密码。
  • 一个实例自动发现账号内所有 Redshift 集群与 Serverless workgroup,新增 namespace 无需重新部署。
  • 提供 7 个标准工具:list_clusters、list_databases、list_schemas、list_tables、list_columns、execute_query、review_cluster。

4.3 安全设计亮点

  • 端到端认证:Quick → Cognito 拿 token → API Gateway 校验 → 才能到达后端。无认证请求一律被拒。
  • 网络隔离:EC2 后端端口不暴露公网,流量经 VPC Link 走 AWS 内部网络。
  • 只读保护:execute_query 强制只读,写操作被拒绝。
  • IAM 认证:数据库访问通过 IAM 临时凭证,无长期密码。
  • 最小权限:数据库层按需授权 schema/表的 SELECT 权限。

五、认证时序:OAuth 2.0 Client Credentials 流程

本方案采用 OAuth 2.0 Client Credentials(2LO,服务间认证)。Quick 作为”服务”身份获取 token,不涉及最终用户登录跳转。时序如下:

[图 2]

说明:

  • token 在有效期(默认 1 小时)内可复用,不必每次请求都走 ①②。
  • 无 token → 401(附 WWW-Authenticate 头,供 Quick 自动发现授权服务器)。
  • 无效/过期 token → 403。校验通过 → 放行到后端。
  • 2LO 下 Redshift 看到的始终是 MCP Server 的 IAM 身份,不区分 Quick 最终用户。若需按用户区分,可改用 3LO(Authorization Code Flow)。

六、部署及效果展示

本节给出完整的部署步骤与关键命令,读者可照此在自己的 AWS 账户中复现。命令中的占位符(如 <REGION>、<ApiId>、<USER_POOL_ID> 等)请替换为实际值。环境要求:一台 Amazon Linux EC2(具备 Redshift、EC2、ELB、API Gateway、Lambda、IAM 相关权限的 IAM 角色),已安装 uv/uvx。

6.1 部署 Redshift MCP Server 并桥接为 HTTP

这一步在 EC2 上部署官方 Redshift MCP Server,并用 mcp-proxy 把它的 stdio 传输桥接为 HTTP,供后续网关接入。

前置条件:

  • 一台 Amazon Linux EC2 实例,绑定的 IAM 角色具备 Redshift 访问权限(redshift-serverless:ListWorkgroups/GetWorkgroup/GetCredentials、redshift-data:ExecuteStatement/DescribeStatement/GetStatementResult、redshift:DescribeClusters/GetClusterCredentialsWithIAM)。
  • 已安装 uv / uvx(Astral 出品的 Python 包与环境管理工具)。

第一步:安装并运行官方 Redshift MCP Server。官方 server 以 PyPI 包 awslabs.redshift-mcp-server 发布,通过 uvx 直接运行,首次运行会自动下载依赖并管理 Python 版本,无需手动 clone 源码。它通过 IAM 认证 + Redshift Data API 访问数据库,无需硬编码密码;启动时通过 AWS_REGION 指定 Redshift 所在区域。

先验证 server 能正常启动(stdio 模式下用一个 initialize 请求握手)。注意 JSON 需写在一行,不要插入换行或反斜杠,否则会被当作非法 JSON 而收不到响应:

export AWS_REGION=<REGION>
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | uvx awslabs.redshift-mcp-server@latest

预期输出为一行 JSON,包含 “serverInfo”:{“name”:”awslabs.redshift-mcp-server”,…}(以及各工具的说明),说明 server 正常。它对外暴露 7 个工具:list_clusters、list_databases、list_schemas、list_tables、list_columns、execute_query、review_cluster。若只看到 “Installed N packages” 而无 JSON 输出,通常是 initialize 请求的 JSON 被换行/反斜杠破坏了,请确保 JSON 在同一行。

[图 3]

第二步:用 mcp-proxy 把 stdio 桥接为 HTTP。官方 server 仅支持 stdio,而 Amazon Quick 要求远程 HTTP/SSE 端点,因此需要 mcp-proxy 做桥接(同时提供 /mcp Streamable HTTP 与 /sse 两个端点)。注意 mcp-proxy 需搭配 mcp 1.x(2.x 移除了 request_ctx 会导致启动报错):

# 独立 venv 安装 mcp-proxy,并固定 mcp 到 1.x 兼容版本
uv venv /home/ec2-user/mcp-proxy-env --python 3.12
source /home/ec2-user/mcp-proxy-env/bin/activate
uv pip install mcp-proxy
uv pip install "mcp>=1.17.0,<2"

# 启动桥接:监听 8000,把请求转给 uvx 拉起的 MCP Server
export AWS_REGION=<REGION>
mcp-proxy --host 0.0.0.0 --port 8000 --pass-environment \
  -- uvx awslabs.redshift-mcp-server@latest

第三步:本地验证桥接后的 HTTP 端点(同样注意 JSON 写在一行):

curl -s -X POST http://127.0.0.1:8000/mcp \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1.0"}}}'

返回 HTTP 200 + serverInfo 即表示桥接成功。(生产环境建议将 mcp-proxy 做成常驻服务以保证开机自启与崩溃自愈。)

[图 4]

6.2 搭建网关出口:内部 NLB + Target Group

创建内部 NLB 与 Target Group(TCP 8000)指向 EC2,供 API Gateway 私有集成使用:

# Target Group(TCP/instance)
aws elbv2 create-target-group --name mcp-proxy-tg --protocol TCP --port 8000 \
  --vpc-id <VPC_ID> --target-type instance \
  --health-check-protocol TCP --health-check-port 8000 --region <REGION>
# 注册 EC2
aws elbv2 register-targets --target-group-arn <TG_ARN> \
  --targets Id=<EC2_INSTANCE_ID>,Port=8000 --region <REGION>
# 内部 NLB
aws elbv2 create-load-balancer --name mcp-proxy-nlb --type network \
  --scheme internal --subnets <SUBNET_ID> --region <REGION>
# 监听器 TCP 8000 -> Target Group
aws elbv2 create-listener --load-balancer-arn <NLB_ARN> --protocol TCP --port 8000 \
  --default-actions Type=forward,TargetGroupArn=<TG_ARN> --region <REGION>

同时收紧安全组,8000 端口仅允许 VPC 内网访问(NLB 转发),不对公网暴露:

aws ec2 authorize-security-group-ingress --group-id <SECURITY_GROUP_ID> \
  --ip-permissions 'IpProtocol=tcp,FromPort=8000,ToPort=8000,IpRanges=[{CidrIp=<VPC_CIDR>}]' \
  --region <REGION>

6.3 创建 API Gateway 私有集成(VPC Link + STREAM)

用 OpenAPI 导入 REST API({proxy+} 代理所有路径,STREAM 模式支持长连接),创建 VPC Link 指向 NLB,并将集成改为 VPC_LINK:

# 导入 REST API(OpenAPI 中 responseTransferMode=STREAM, timeoutInMillis=900000)
aws apigateway import-rest-api --body 'fileb://mcp-apigw.yaml' \
  --parameters endpointConfigurationTypes=REGIONAL --region <REGION>
# 创建 VPC Link 指向 NLB(等待 status=AVAILABLE,约数分钟)
aws apigateway create-vpc-link --name mcp-proxy-vpclink \
  --target-arns <NLB_ARN> --region <REGION>
# 将 {proxy+} ANY 方法的集成改为 VPC_LINK,并指向 NLB DNS
aws apigateway update-integration --rest-api-id <ApiId> \
  --resource-id <ResourceId> --http-method ANY --patch-operations \
    op=replace,path=/connectionType,value=VPC_LINK \
    op=replace,path=/connectionId,value=<VpcLinkId> \
    op=replace,path=/uri,value=http://<NLB_DNS>:8000/{proxy} \
  --region <REGION>
# 部署
aws apigateway create-deployment --rest-api-id <ApiId> --stage-name prod --region <REGION>

部署后得到托管 HTTPS 端点:https://<ApiId>.execute-api.<REGION>.amazonaws.com/prod/mcp

[图 5]

[图 6]

6.4 配置 Amazon Cognito(OAuth 授权服务器)

创建 user pool:

在控制台上进入 Cognito 然后依次点击:user pools->create user pool。Application Type 请选择“Machine-to-machine application”

[图 7]

创建资源服务器并且自定义 scope:

在控制台上进入 Cognito->点击进入上一步中创建的 user pool->Appplications->Resource servers->选择默认的 resource servers,点击“Edit”

在 Scope name 中输入您的自定义 scope 名称,在本案例中暂时将 scope 设置为“invoke”

[图 8]

创建带 secret 的 app client(为 Amazon Quick 准备):

在控制台上进入 Cognito->点击进入上一步中创建的 user pool->Appplications->App clients->点击“Create app client”,选择“Machine-to-machine application”

[图 9]

创建完成之后,client secret 会出现在下方的红色框中。

[图 10]

记录 issuer、token URL、client id/secret、scope 备用。

最后,在 EC2 服务器上调用以下命令测试是否会返回 token

# 用 client_credentials 换取 access_token
curl -s -X POST "https://<domain>.auth.<REGION>.amazoncognito.com/oauth2/token" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u '<CLIENT_ID>:<CLIENT_SECRET>' \
  -d 'grant_type=client_credentials&scope=<RESOURCE_SERVER>/invoke'
# 预期返回 access_token(JWT, token_use=access, alg=RS256)

[图 11]

6.5 部署 Lambda Authorizer(校验 JWT)

Lambda Authorizer 用 Cognito JWKS 校验每个请求携带的 JWT:验证 RS256 签名、issuer、过期时间、token_use、scope 与 client_id,通过返回 Allow 策略,失败返回 Deny,无 Bearer 抛 Unauthorized(触发 401)。以下为完整实现(纯标准库,无第三方依赖,可直接内联 zip 部署):

"""
Lambda Authorizer for Amazon Quick MCP -> Redshift.
校验 Cognito 2LO (client_credentials) 签发的 access token。
纯标准库实现 RS256 验签,无第三方依赖。
"""
import json
import os
import time
import base64
import hashlib
import urllib.request

# ---- 环境变量(部署时注入)----
ISSUER = os.environ["COGNITO_ISSUER"]           # https://cognito-idp.<region>.amazonaws.com/<pool>
JWKS_URL = os.environ["JWKS_URL"]
EXPECTED_SCOPE = os.environ.get("EXPECTED_SCOPE", "")   # 可空;非空则要求 token scope 含此值
EXPECTED_CLIENT_ID = os.environ.get("EXPECTED_CLIENT_ID", "")  # 可空;非空则要求匹配

_JWKS_CACHE = {"keys": None, "ts": 0}
_JWKS_TTL = 3600


def _b64url_decode(data: str) -> bytes:
    data += "=" * (-len(data) % 4)
    return base64.urlsafe_b64decode(data)


def _get_jwks():
    now = time.time()
    if _JWKS_CACHE["keys"] and now - _JWKS_CACHE["ts"] < _JWKS_TTL:
        return _JWKS_CACHE["keys"]
    with urllib.request.urlopen(JWKS_URL, timeout=5) as r:
        keys = json.loads(r.read())["keys"]
    _JWKS_CACHE["keys"] = keys
    _JWKS_CACHE["ts"] = now
    return keys


def _int_from_bytes(b: bytes) -> int:
    return int.from_bytes(b, "big")


def _rsa_verify_rs256(message: bytes, signature: bytes, n: int, e: int) -> bool:
    """纯 Python RS256 (RSASSA-PKCS1-v1_5, SHA-256) 验签。"""
    k = (n.bit_length() + 7) // 8
    if len(signature) != k:
        return False
    s = _int_from_bytes(signature)
    if s >= n:
        return False
    # RSA 公钥运算: m = s^e mod n
    m = pow(s, e, n)
    em = m.to_bytes(k, "big")
    # 构造期望的 EMSA-PKCS1-v1_5 编码
    digest = hashlib.sha256(message).digest()
    # SHA-256 的 DER 前缀
    der_prefix = bytes([
        0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
        0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
        0x00, 0x04, 0x20,
    ])
    t = der_prefix + digest
    ps_len = k - len(t) - 3
    if ps_len < 8:
        return False
    expected = b"\x00\x01" + b"\xff" * ps_len + b"\x00" + t
    # 常量时间比较
    return _consteq(em, expected)


def _consteq(a: bytes, b: bytes) -> bool:
    if len(a) != len(b):
        return False
    r = 0
    for x, y in zip(a, b):
        r |= x ^ y
    return r == 0


def _verify_token(token: str) -> dict:
    parts = token.split(".")
    if len(parts) != 3:
        raise ValueError("malformed token")
    header = json.loads(_b64url_decode(parts[0]))
    payload = json.loads(_b64url_decode(parts[1]))
    signature = _b64url_decode(parts[2])

    if header.get("alg") != "RS256":
        raise ValueError("unexpected alg")
    kid = header.get("kid")

    # 找到对应公钥
    jwk = None
    for k in _get_jwks():
        if k.get("kid") == kid:
            jwk = k
            break
    if not jwk:
        # 刷新缓存重试一次
        _JWKS_CACHE["ts"] = 0
        for k in _get_jwks():
            if k.get("kid") == kid:
                jwk = k
                break
    if not jwk:
        raise ValueError("signing key not found")

    n = _int_from_bytes(_b64url_decode(jwk["n"]))
    e = _int_from_bytes(_b64url_decode(jwk["e"]))
    signing_input = (parts[0] + "." + parts[1]).encode("ascii")
    if not _rsa_verify_rs256(signing_input, signature, n, e):
        raise ValueError("signature verification failed")

    # 校验 claims
    now = int(time.time())
    if payload.get("iss") != ISSUER:
        raise ValueError("issuer mismatch")
    if payload.get("exp", 0) < now:
        raise ValueError("token expired")
    if payload.get("token_use") != "access":
        raise ValueError("not an access token")
    if EXPECTED_CLIENT_ID and payload.get("client_id") != EXPECTED_CLIENT_ID:
        raise ValueError("client_id mismatch")
    if EXPECTED_SCOPE:
        scopes = (payload.get("scope") or "").split()
        if EXPECTED_SCOPE not in scopes:
            raise ValueError("insufficient scope")
    return payload


def _policy(principal: str, effect: str, resource: str, context=None):
    doc = {
        "principalId": principal,
        "policyDocument": {
            "Version": "2012-10-17",
            "Statement": [{
                "Action": "execute-api:Invoke",
                "Effect": effect,
                # 用 * 覆盖整个 API,避免缓存导致的资源不匹配
                "Resource": resource,
            }],
        },
    }
    if context:
        doc["context"] = context
    return doc


def handler(event, context):
    # TOKEN 类型 authorizer: event['authorizationToken'] = "Bearer xxx"
    token_header = event.get("authorizationToken", "") or ""
    method_arn = event.get("methodArn", "*")
    # 允许资源范围:同一 API/stage 下所有方法
    # methodArn 形如 arn:aws:execute-api:region:acct:apiId/stage/METHOD/path
    try:
        arn_parts = method_arn.split(":")
        api_part = arn_parts[5].split("/")
        resource_glob = ":".join(arn_parts[:5]) + ":" + api_part[0] + "/" + api_part[1] + "/*"
    except Exception:
        resource_glob = method_arn

    if not token_header.lower().startswith("bearer "):
        # 无 bearer -> 触发 401(Unauthorized)
        raise Exception("Unauthorized")

    token = token_header[7:].strip()
    try:
        claims = _verify_token(token)
    except Exception as ex:
        print("token verify failed:", str(ex))
        # 校验失败 -> Deny (返回 403) ;无效/过期 token
        return _policy("client", "Deny", resource_glob)

    ctx = {
        "client_id": str(claims.get("client_id", "")),
        "scope": str(claims.get("scope", "")),
    }
    return _policy(str(claims.get("client_id", "client")), "Allow", resource_glob, ctx)

部署函数并注入 Cognito 参数为环境变量:

# 创建执行角色
aws iam create-role --role-name mcp-authorizer-role \
  --assume-role-policy-document file://trust.json --region <REGION>
aws iam attach-role-policy --role-name mcp-authorizer-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# 打包并创建函数
zip -q authorizer.zip lambda_function.py
aws lambda create-function --function-name mcp-oauth-authorizer \
  --runtime python3.12 --handler lambda_function.handler --timeout 10 \
  --role arn:aws:iam::<ACCOUNT_ID>:role/mcp-authorizer-role \
  --zip-file fileb://authorizer.zip --region <REGION> \
  --environment 'Variables={COGNITO_ISSUER=https://cognito-idp.<REGION>.amazonaws.com/<USER_POOL_ID>,\
    JWKS_URL=https://cognito-idp.<REGION>.amazonaws.com/<USER_POOL_ID>/.well-known/jwks.json,\
    EXPECTED_SCOPE=<RESOURCE_SERVER>/invoke,EXPECTED_CLIENT_ID=<CLIENT_ID>}'

6.6 在 API Gateway 挂载 Authorizer 与元数据路由

创建 TOKEN Authorizer 并挂到 MCP 路由;添加 RFC 9728 元数据路由供 Quick 自动发现授权服务器;配置 401 响应携带 WWW-Authenticate 头。最后重新部署(变更需重新部署且传播约 20–60 秒才生效):

# 创建 TOKEN Authorizer
aws apigateway create-authorizer --rest-api-id <ApiId> \
  --name cognito-jwt-authorizer --type TOKEN \
  --authorizer-uri arn:aws:apigateway:<REGION>:lambda:path/2015-03-31/functions/\
arn:aws:lambda:<REGION>:<ACCOUNT_ID>:function:mcp-oauth-authorizer/invocations \
  --identity-source method.request.header.Authorization \
  --authorizer-result-ttl-in-seconds 0 --region <REGION>
# 授予 API Gateway 调用该 Lambda 的权限
aws lambda add-permission --function-name mcp-oauth-authorizer \
  --statement-id apigw --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn arn:aws:execute-api:<REGION>:<ACCOUNT_ID>:<ApiId>/authorizers/<AuthorizerId> \
  --region <REGION>
# 挂到 {proxy+} ANY 方法
aws apigateway update-method --rest-api-id <ApiId> --resource-id <ResourceId> \
  --http-method ANY --patch-operations \
    op=replace,path=/authorizationType,value=CUSTOM \
    op=replace,path=/authorizerId,value=<AuthorizerId> --region <REGION>
# 401 响应携带 WWW-Authenticate(指向元数据 URL)
aws apigateway put-gateway-response --rest-api-id <ApiId> \
  --response-type UNAUTHORIZED --status-code 401 \
  --response-parameters '{"gatewayresponse.header.WWW-Authenticate":\
    "'\''Bearer resource_metadata=\"https://<ApiId>.execute-api.<REGION>.amazonaws.com/prod/.well-known/oauth-protected-resource\"'\''"}' \
  --region <REGION>
# 重新部署
aws apigateway create-deployment --rest-api-id <ApiId> --stage-name prod --region <REGION>

元数据路由 /.well-known/oauth-protected-resource 用 MOCK 集成返回 RFC 9728 JSON(resource + authorization_servers + scopes_supported),且该路由 authorizationType 设为 NONE(无需认证)。

[图 12]

6.7 验证认证流程

先按 6.4 拿到 TOKEN,然后验证四个场景:

BASE="https://<ApiId>.execute-api.<REGION>.amazonaws.com/prod"; MCP="$BASE/mcp"
# 1) 元数据路由(无需认证)-> 200 + JSON
curl -s "$BASE/.well-known/oauth-protected-resource"
# 2) 无 token -> 401 + WWW-Authenticate
curl -s -i -X POST "$MCP" -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | grep -iE '^HTTP|www-authenticate'
# 3) 无效 token -> 403
curl -s -o /dev/null -w '%{http_code}\n' -X POST "$MCP" \
  -H 'Authorization: Bearer bad.token' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
# 4) 有效 token -> 200 + MCP 握手
curl -s -X POST "$MCP" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1.0"}}}'

6.8 Redshift 数据库授权

MCP Server 使用 IAM 身份连接 Redshift,对应到数据库中是独立的数据库用户。需由管理员用户为其授予 schema 使用权限与表的读权限,之后即可查询数据。具体是哪个用户,取决于您的 MCP Server 所在的 EC2 上的 IAM role 或者 user。具体用户名您可以通过在 EC2 上执行以下命令进行查询:

aws sts get-caller-identity

返回结果类似:

{
    "UserId": "userId",
    "Account": "1234567890",
    "Arn": "arn:aws:iam::1234567890:user/<用户名>"
}

这里得到的用户,就是您需要在 redshift 中赋予权限的用户。

接下来,您需要登陆 Redshift query editor v2 编辑器,在编辑器中执行如下命令:

-- 由 redshift admin 在 redshift 编辑器中执行(IAM 用户名含冒号,需加双引号)
GRANT USAGE ON SCHEMA <schema 名称>  TO "IAM:<用户名>";
GRANT SELECT ON ALL TABLES IN SCHEMA <schema 名称> TO "IAM:<用户名>";
ALTER DEFAULT PRIVILEGES IN SCHEMA <schema 名称> GRANT SELECT ON TABLES TO "IAM:<用户名>";

至此,Connector 就有权限读取 Redshift 中的数据了。

[图 13]

6.9 在 Amazon Quick 中接入 MCP Connector

Amazon Quick 控制台完成接入:

  • 进入 Connectors → Create for your team → 选择 Model Context Protocol (MCP)。
  • MCP server endpoint 填 API Gateway 的 HTTPS URL:https://<ApiId>.execute-api.<REGION>.amazonaws.com/prod/mcp
  • Connection type 选择 Public network。
  • 认证方式选择 Service authentication (Service-to-Service),填入 Cognito 的 Client ID、Client Secret、Token URL、Scope。
  • 创建后 Quick 自动发现 MCP Server 暴露的 7 个工具(list_clusters、list_databases、list_schemas、list_tables、list_columns、execute_query、review_cluster),注册为 actions。

[图 14]

点击进入 connector,可以看见 connector 已经处于 Ready 状态,api 列表也已经被自动发现,至此 Connector 配置完成。

[图 15]

6.10 效果展示:自然语言查询与快速构建应用

完成接入后,用户即可在 Amazon Quick App 中用自然语言描述需求,由平台调用 MCP 工具查询 Redshift 数据、自动生成可视化,并快速构建成应用在组织内共享。例如查询某张业务表的数据、生成趋势图表等,全程无需编写代码。

在本案例中,你可以在 Quick App 交互界面中输入:

帮我创建一个 app

在进入 app 编辑页面后,无需做任何配置,只要继续在对话框中

帮我创建一个查询页面。要根据列名列出用户数据,数据源要用 redshift-mcp-3 中 public-workgroup 的 dev.public.users 表

[图 16]

最后,自动生成查询页面:

[图 17]

七、总结

AI 正在重塑数据应用的构建方式。通过 Amazon Quick App 结合 MCP 开放标准接入 Amazon Redshift,并以 Cognito OAuth 2.0 认证作为安全基座,我们把数据应用的交付从传统的”重”模式转变为”快而安全”的”轻”模式——业务人员用自然语言即可安全查询数仓数据、快速构建并共享应用,交付周期从数周缩短到分钟级。

该方案具备以下核心价值:

  • 快速交付:低代码/自然语言驱动,分钟级从数据到应用。
  • 安全可控:OAuth 2.0 端到端认证 + VPC 私有集成 + IAM 数据库认证 + 只读查询保护,多层纵深防御。
  • 组织共享:应用与洞察在组织内一键共享,协作高效。
  • AI 原生:内置自然语言问答与生成式 BI 能力。
  • 开放集成:基于 MCP 开放标准,一次接入、自动发现,新增数仓 namespace 无需重新部署。

方案中使用的核心组件及其角色:

组件 角色
Amazon Quick App AI 原生前端,自然语言驱动的数据应用构建与共享平台
MCP (Model Context Protocol) 开放标准协议,标准化 AI 系统与外部工具的连接
awslabs.redshift-mcp-server 官方 Redshift MCP Server,IAM 认证,自动发现集群
Amazon Cognito OAuth 2.0 授权服务器,签发/管理 access token
API Gateway + Lambda Authorizer 托管 HTTPS 网关 + JWT 校验,端到端认证入口
VPC Link + NLB 私有集成网络通道,后端不暴露公网

对于希望进一步加固的场景,可在此基础上:叠加 IP 白名单做二次防护、切换到 3LO(Authorization Code Flow)实现按用户级别的授权与审计、或使用 AWS Bedrock AgentCore 等托管方案降低运维负担。

➡️ 下一步行动:

相关产品:

相关文章:

*前述特定亚马逊云科技生成式人工智能相关的服务目前在亚马逊云科技海外区域可用。亚马逊云科技中国区域相关云服务由西云数据和光环新网运营,具体信息以中国区域官网为准。

本篇作者

邢悦

亚马逊云科技迁移解决方案架构师,主要负责企业上云跨云迁移相关的技术支持工作。在制造、保险、物流等行业拥有10多年的研发和架构设计经验。

张振华

亚马逊云科技解决方案架构师,曾在携程、爱乐奇等互联网公司担任核心技术岗位,积累了丰富的系统架构设计经验。在AWS,主要负责云计算方案的架构设计与咨询,并协助企业在生成式 AI 方向的探索与落地实践。在 Edge、Serverless、容器化、微服务架构、云原生 DevOps、AI Agent 及 GenAI 企业级应用等领域拥有丰富的实战经验,致力于帮助企业构建云原生架构下的 AI 创新方案,加速 AI 业务落地,实现合规、安全的智能体开发与部署。


AWS 架构师中心:云端创新的引领者

探索 AWS 架构师中心,获取经实战验证的最佳实践与架构指南,助您高效构建安全、可靠的云上应用