本文实验了以下内容:
- 生成 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 得知原因来排查。