记录:AWS ApiGateway 配置 JWT + Fake OIDC

447 阅读3分钟

本文实验了以下内容:

  • 生成 RS256 算法的 JWK 公私钥对
  • 使用 AWS S3 托管一个只提供 JWK 接口的静态 OIDC 服务
  • 为 AWS ApiGateway 启用 JWT 接口验证

AWS ApiGateway 只支持 RSA JWK,下文也只讨论这个方式。其他方法要么类似要么更简单。

首先需要了解 JWT 概念和 JWT 机制的一个细节——JWK。

JWT 本身有三部分:头部、载荷、签名。JWT 的头部有签名算法,而签名部分则根据该算法实现,收到 JWT 的一方通过验签以确认 JWT 是完整的、未被篡改的。

签名算法既可以是 HSxxx,它通过一个 secret 字符串生成签名,也可以是 RSxxx ESxxx PSxxx 这样的通过公私钥对生成签名的。无论何种方法,验签时必须从 JWT 之外获取信息—— secret 或公钥。

可以使用 jwt.io/ 生成、解码和校验一个 JWT。

JWT 有这个需求,JWK 就是该需求的实现。OIDC 是一套使用 JWT 提供鉴权、授权服务的规范。

首先,从 mkjwk.org/ 生成一个可用的 JWK 格式的公私钥对。

这里随便生成一个:

// public and private keypair
// 没必要展示
// public key
{
    "kty": "RSA",
    "e": "AQAB",
    "use": "sig",
    "kid": "sig-1646744166",
    "alg": "RS256",
    "n": "oJ5Bu_txwsiVMz7uGpFa8J7sdj_7dDOAU5Vn9CW-c1X4-xnNvgm4IB90VpqL1AYr1weMfNNvwHGqhlsIQBRtpNYI0OleWisBMdcxz-z9m0WdozYtjgHRihVPEDn6y-HOW6wnIKlb00stz7kL2F5c9UGE9h-2cIDWOUgHMmoWVgbpnon3gNjDpeFXEO158DDLEYNQuKHslFld-Ff7ABOsN_RXpUdMEY_ys7QIZT3l6KJYfYhpk_4-p1YmcBiIVDyleAPg7wxAAmfvCH7Qp9OD6qXinRA30cQLGuVgIo54-R8RM1IUoVhyKV-mVlLryW-dO-EKL6DwsjK1gXa9TJQJFQ"
}

然后创建一个 S3 Bucket,给予如下(或类似的)安全策略:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::bucket-name/*"
        }
    ]
}

然后向里面放置两个 JSON 文件,分别位于:

  • /fake-oidc/.well-known/openid-configuration
  • /fake-oidc/public/jwks

openid-configuration 是 OIDC 的 discovery-endpoint,内容如下所示:

{
  "issuer": "https://bucket-name.s3.ap-northeast-1.amazonaws.com/fake-oidc",
  "authorization_endpoint": "https://bucket-name.s3.ap-northeast-1.amazonaws.com/fake-oidc/connect/authorize",
  "token_endpoint": "https://bucket-name.s3.ap-northeast-1.amazonaws.com/fake-oidc/connect/token",
  "jwks_uri": "https://bucket-name.s3.ap-northeast-1.amazonaws.com/fake-oidc/public/jwks",
  "registration_endpoint": "https://bucket-name.s3.ap-northeast-1.amazonaws.com/fake-oidc/connect/register",
  "scopes_supported": ["openid"],
  "response_types_supported": ["code", "code id_token", "id_token", "token id_token"],
  "subject_types_supported": ["public", "pairwise"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "request_object_signing_alg_values_supported": ["none", "RS256"],
  "token_endpoint_auth_methods_supported": ["private_key_jwt"],
  "token_endpoint_auth_signing_alg_values_supported": ["RS256"],
  "claim_types_supported": ["normal"],
  "claims_supported": ["sub", "iss", "kid", "aud", "iat", "exp", "nbf", "scp"],
}

别看这里提供了好些接口信息,AWS ApiGateway 只会拿自己想看的几个参数并访问 jwks_uri

我们再提供 jwks_uri 对应的内容。如下所示:

{
    "keys": [
        // 这里是 mkjwk.org 生成的 public key 对象
    ]
}

这样就准备好了一个 Fake OIDC,配合 S3 HTTPS 安全地提供了 JWK 公钥,从而允许 ApiGateway 验签。

接下来在 ApiGateway 里配置 Authorization - Authorizers,添加一个授权方:

Identity source
    $request.header.Authorization
Issuer URL
    https://bucket-name.s3.ap-northeast-1.amazonaws.com/fake-oidc
Audience
    my-audience

绑定到 ApiGateway 的路由上,不做赘述。

然后用 jwt.io 生成一个 JWT,实验效果:

// HEADER
{
  "alg": "RS256",
  "typ": "JWT"
}

// PAYLOAD
{
  "iss": "https://bucket-name.s3.ap-northeast-1.amazonaws.com/fake-oidc",
  "kid": "sig-1646744166", // 注意要与 JWK kid 相同
  "aud": "my-audience",
  "iat": 1516239022,
  "exp": 1800000000,
  "nbf": 1400000000
}

// VERIFY SIGNATURE
// 分别把公私两个 JWK 复制进去即可

JWT 作为 Bearer Token,访问 API,可以成功。

如果遇到 401,可以从响应头 www-authenticate 得知原因来排查。