教程1-金钥匙#
- 作者:
在……里面 教程0-使其运行 我们创建了一个只读API,只有一个端点可以列出待办事项。有很多方向可以让这个API更有趣,但一个好的起点是允许一些用户除了读取数据之外还可以更改数据。
步骤1.添加受信任用户#
上一教程创建了一个 web_anon
用于执行匿名Web请求的数据库中的角色。让我们制作一个角色,名为 todo_user
适用于使用API进行身份验证的用户。此角色将有权对待办事项列表执行任何操作。
-- run this in psql using the database created
-- in the previous tutorial
create role todo_user nologin;
grant todo_user to authenticator;
grant usage on schema api to todo_user;
grant all on api.todos to todo_user;
第二步:保密#
客户端使用JSON Web令牌通过API进行身份验证。这些是JSON对象,它们使用只有服务器知道的秘密进行加密签名。因为客户端不知道这个秘密,所以他们不能篡改其令牌的内容。PostgREST将检测到假币并拒绝它们。
让我们创建一个秘密并将其提供给PostgREST。想一个漂亮的长的,或者用一个工具来生成它。 Your secret must be at least 32 characters long.
备注
Unix工具可以为您生成一个很好的秘密:
# Allow "tr" to process non-utf8 byte sequences
export LC_CTYPE=C
# Read random bytes keeping only alphanumerics and add the secret to the configuration file
echo "jwt-secret = \"$(< /dev/urandom tr -dc A-Za-z0-9 | head -c32)\"" >> tutorial.conf
检查是否有 tutorial.conf
(在上一教程中创建)中设置了密码 jwt-secret
:
# THE SECRET MUST BE AT LEAST 32 CHARS LONG
cat tutorial.conf
如果上一教程中的PostgREST服务器仍在运行,请重新启动它以加载更新的配置文件。
步骤3.签署令牌#
通常,您自己在数据库或另一台服务器中的代码将创建并签署身份验证令牌,但在本教程中,我们将“手动”创建一个。去 jwt.io 并像这样填写字段:

如何在https://jwt.io上创建令牌#
Remember to fill in the secret you generated rather than the word "secret". 在您填写了密码和有效负载之后,左侧的编码数据将更新。复制编码的令牌。
备注
虽然令牌看起来很模糊,但很容易对有效载荷进行反向工程。令牌只是签名的,而不是加密的,所以不要将您不想让坚定的客户看到的东西放在里面。虽然可以读取令牌的有效负载,但不可能读取其签名时使用的秘密。
步骤4.提出请求#
回到终端,让我们使用 curl
添加待办事项。该请求将包括一个包含身份验证令牌的HTTP头。
export TOKEN="<paste token here>"
curl http://localhost:3000/todos -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "learn how to auth"}'
现在我们已经完成了待办事项列表中的所有三项,所以让我们设置 done
对他们所有人都是真的 PATCH
请求。
curl http://localhost:3000/todos -X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"done": true}'
一份关于待办事项的请求显示了其中的三个,并且都完成了。
curl http://localhost:3000/todos
[
{
"id": 1,
"done": true,
"task": "finish tutorial 0",
"due": null
},
{
"id": 2,
"done": true,
"task": "pat self on back",
"due": null
},
{
"id": 3,
"done": true,
"task": "learn how to auth",
"due": null
}
]
步骤5.添加过期时间#
目前,我们的身份验证令牌对所有永恒有效。只要服务器继续使用相同的JWT密码,它就会认可该令牌。
更好的策略是为使用 exp
认领。这是PostgREST特别处理的两个JWT声明之一。
索赔 |
释义 |
---|---|
|
执行API请求的SQL所使用的数据库角色 |
|
令牌的到期时间戳,以“Unix纪元时间”表示 |
备注
纪元时间的定义是自1970年1月1日协调世界时间(UTC)00:00:00起经过的秒数减去此后发生的闰秒数。
为了实际观察过期时间,我们将添加一个 exp
要求在未来五分钟内给我们以前的代币。首先找出五分钟后的纪元价值。在……里面 psql
运行以下命令:
select extract(epoch from now() + '5 minutes'::interval) :: integer;
返回到jwt.io并将有效负载更改为
{
"role": "todo_user",
"exp": 123456789
}
NOTE :不要忘记更改虚拟纪元的值 123456789
在上面的代码片段中设置为 psql
指挥部。
像以前一样复制更新后的令牌,并将其另存为新的环境变量。
export NEW_TOKEN="<paste new token>"
尝试在到期时间之前和之后以cURL发出此请求:
curl http://localhost:3000/todos \
-H "Authorization: Bearer $NEW_TOKEN"
过期后,接口返回未经授权的HTTP 401:
{
"code": "PGRST301",
"details": null,
"hint": null,
"message": "JWT expired"
}
奖励主题:立即撤销#
即使令牌过期,有时您也可能希望立即撤销对特定令牌的访问。例如,假设您了解到一名心怀不满的员工正在做坏事,而他的令牌仍然有效。
要撤销一个特定的令牌,我们需要一种方法来区分它和其他令牌。让我们添加一个定制 email
与颁发令牌的客户端的电子邮件匹配的声明。
继续使用有效载荷创建新的令牌
{
"role": "todo_user",
"email": "disgruntled@mycompany.com"
}
将其保存到环境变量:
export WAYWARD_TOKEN="<paste new token>"
PostgREST允许我们指定在尝试身份验证期间运行的函数。该函数可以做它喜欢做的任何事情,包括引发异常来终止请求。
首先创建一个新的模式并添加函数:
create schema auth;
grant usage on schema auth to web_anon, todo_user;
create or replace function auth.check_token() returns void
language plpgsql
as $$
begin
if current_setting('request.jwt.claims', true)::json->>'email' =
'disgruntled@mycompany.com' then
raise insufficient_privilege
using hint = 'Nope, we are on to you';
end if;
end
$$;
下一次更新 tutorial.conf
并指定新函数:
# add this line to tutorial.conf
db-pre-request = "auth.check_token"
重新启动PostgREST以使更改生效。接下来,尝试使用我们的原始令牌发出请求,然后使用被吊销的令牌发出请求。
# this request still works
curl http://localhost:3000/todos -X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"done": true}'
# this one is rejected
curl http://localhost:3000/todos -X PATCH \
-H "Authorization: Bearer $WAYWARD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "AAAHHHH!", "done": false}'
服务器以403禁止响应:
{
"code": "42501",
"details": null,
"hint": "Nope, we are on to you",
"message": "insufficient_privilege"
}