认证

JWT令牌

JSON Web Tokens


JSON Web Token(JWT)是一种数据结构,以字符串形式表示,通常包含用户的身份和授权信息。它编码了令牌的生命周期信息,并使用加密密钥进行签名,使其具备防篡改特性。

Supabase的访问令牌就是JWT。每次向Supabase服务发起请求时都会携带该JWT。通过验证令牌并检查其中包含的声明(claims),您可以允许或拒绝对资源的访问。行级安全策略正是基于JWT中的信息来实现的。

JWT 的编码与签名

JWT(JSON Web Token)的编码和签名过程如下:

JSON 对象最初看起来是这样的:

1
2
3
4
5
6
{ "sub": "0001", "name": "Sam Vimes", "iat": 1516239022, "exp": 1518239022}

其中:

  • sub 表示"subject"(主体),通常是用户的 UUID
  • name 是用户姓名
  • iat 是令牌创建时的 Unix 时间戳
  • exp 是令牌的过期时间(可选)

以下是一个包含自定义字段的 JWT 示例:

1
2
3
4
5
6
7
8
9
10
{ "sub": "0002", "name": "Věra Hrabánková", "iat": 1516239022, "exp": 1518239022, "theme": { "primary" : "#D80C14", "secondary" : "#FFFFFF" }}

注意:令牌中存储的数据越多,编码后的字符串就越长。

编码过程

使用 HS256 等算法对数据进行编码。可以使用 jsonwebtoken 等库进行编码/解码:

1
2
// 示例代码来自 https://replit.com/@awalias/jsonwebtokens#index.jslet token = jwt.sign({ name: 'Sam Vimes' }, 'some-secret')

生成的 JWT 字符串格式如下:

1
2
3
eyJhbGciOiJIUzI1NiJ9 .eyJzdWIiOiIwMDAxIiwibmFtZSI6IlNhbSBWaW1lcyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE4MjM5MDIyfQ .zMcHjKlkGhuVsiPIkyAkB2rjXzyzJsMMgpvEGvGtjvA

JWT 结构解析

JWT 由三部分组成:

  1. 头部 eyJhbGciOiJIUzI1NiJ9
1
2
3
{ "alg": "HS256"}
  1. 载荷 eyJzdWIiOiIwMDAxIiwibmFtZSI6IlNhbSBWaW1lcyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE4MjM5MDIyfQ
1
2
3
4
5
6
{ "sub": "0001", "name": "Sam Vimes", "iat": 1516239022, "exp": 1518239022}
  1. 签名 zMcHjKlkGhuVsiPIkyAkB2rjXzyzJsMMgpvEGvGtjvA: 签名是通过以下方式生成的:
1
2
3
4
5
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload) <jwt_secret>)

您可以在 https://jwt.io 上测试生成自己的令牌。

重要说明

  • 任何拥有 jwt_secret 的人都可以创建和验证令牌
  • 更高级的 JWT 算法使用两个密钥:一个用于创建令牌,另一个用于验证

JWT 的优势

JWT 之所以流行,是因为在微服务架构中:

  • 多个独立的微服务需要验证用户身份
  • 传统的会话令牌需要中心化验证,效率低下
  • JWT 实现了去中心化验证,任何拥有 jwt_secret 的服务都可以独立验证令牌

注意事项

JWT 的一个缺点是难以作废。如果令牌被泄露,恶意攻击者可以在到期前一直使用它,除非系统所有者更新 jwt_secret(但这会使所有现有令牌失效)。

Supabase 中的 JWT 令牌

在 Supabase 中,我们颁发三种不同用途的 JWT 令牌:

  1. 匿名密钥(anon key):该密钥用于绕过 Supabase API 网关,可在客户端代码中使用。
  2. 服务角色密钥(service role key):该密钥拥有超级管理员权限,可绕过行级安全策略。切勿将其放入客户端代码中,需保持私密。
  3. 用户专属 JWT:这些是我们颁发给登录您项目/服务/网站用户的令牌,相当于现代版的会话令牌,用户可用它来访问专属内容或权限。

第一种 anon key 令牌是开发者在需要与 Supabase 数据库交互时随 API 请求一起发送的。

假设您想读取表 colors 中所有行的名称,可以发起如下请求:

1
2
curl 'https://xscduanzzfseqszwzhcy.supabase.co/rest/v1/colors?select=name' \-H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxNDIwNTE3NCwiZXhwIjoxOTI5NzgxMTc0fQ.-NBR1WnZyQGpRLdXJfgfpszoZ0EeE6KHatJsDPLIX8c"

将该令牌放入 https://jwt.io 解码后可见:

1
2
3
4
5
6
{ "role": "anon", "iss": "supabase", "iat": 1614205174, "exp": 1929781174}

此 JWT 由开发者专属的 jwt_secret 签名(您可以在仪表板的 设置 > API 页面找到该密钥,与编码后的 "anon key" 并列),用于通过 Supabase API 网关访问开发者项目。

该密钥的设计理念是它可以安全地放入客户端,即终端用户看到此密钥也无妨——但前提是您必须首先启用行级安全(Row Level Security)。

第二个密钥 service role key 应仅用于您自己的服务器或环境,切勿与终端用户共享。您可能会使用此令牌执行批量数据插入等操作。

用户访问令牌(user access token) 是在调用以下方法时颁发的 JWT:

1
2
3
4
supabase.auth.signIn({ email: 'valid.email@supabase.io', password: 'They_Live_1988!',})

此令牌需要作为 Authorization Bearer 标头与 apikey 标头一起传递:

1
2
3
curl 'https://xscduanzzfseqszwzhcy.supabase.co/rest/v1/colors?select=name' \-H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxNDIwNTE3NCwiZXhwIjoxOTI5NzgxMTc0fQ.-NBR1WnZyQGpRLdXJfgfpszoZ0EeE6KHatJsDPLIX8c" \-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjE1ODI0Mzg4LCJzdWIiOiIwMzM0NzQ0YS1mMmEyLTRhYmEtOGM4YS02ZTc0OGY2MmExNzIiLCJlbWFpbCI6InNvbWVvbmVAZW1haWwuY29tIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwifSwidXNlcl9tZXRhZGF0YSI6bnVsbCwicm9sZSI6ImF1dGhlbnRpY2F0ZWQifQ.I-_oSsJamtinGxniPETBf-ezAUwDW2sY9bJIThvdX9s"

您会注意到此令牌明显更长,因为它包含用户专属信息:

1
2
3
4
5
6
7
8
9
10
11
{ "aud": "authenticated", "exp": 1615824388, "sub": "0334744a-f2a2-4aba-8c8a-6e748f62a172", "email": "valid.email@supabase.io", "app_metadata": { "provider": "email" }, "user_metadata": null, "role": "authenticated"}

若使用服务角色密钥,需将其同时传入 apikeyauthorization 标头(再次强调,仅限在您自己的服务器等安全环境中使用):

1
2
3
curl "$YOUR_PROJECT_URL/rest/v1/colors?select=name" \ -H "apikey: $YOUR_SERVICE_ROLE_KEY" \ -H "authorization: Bearer $YOUR_SERVICE_ROLE_KEY"

现在您已了解 JWT 是什么以及它们在 Supabase 中的用途,接下来可以探索如何结合行级安全策略来限制对 Postgres 数据库中特定表、行和列的访问。

资源