身份验证#
PostgREST旨在将数据库保持在API安全的中心。全 authorization happens in the database 。PostgREST的工作是 authenticate 请求--即验证客户端是否如他们所说的那样--然后让数据库 authorize 客户端操作。
角色系统概述#
PostgREST使用三种类型的角色,即 authenticator , anonymous 和 user 角色。数据库管理员创建这些角色并配置PostgREST以使用它们。

验证者角色用于连接到数据库,应该配置为具有非常有限的访问权限。它是一条变色龙,它的工作是“成为”其他用户,为经过身份验证的HTTP请求提供服务。
CREATE ROLE authenticator LOGIN NOINHERIT NOCREATEDB NOCREATEROLE NOSUPERUSER;
CREATE ROLE anonymous NOLOGIN;
CREATE ROLE webuser NOLOGIN;
备注
名称“验证者”和“匿名”名称是可配置的,不是神圣的,我们只是为了清楚起见而选择它们。看见 Db-uri 和 DB-anon-角色 。
用户模拟#
下图显示了服务器如何处理身份验证。如果auth成功,它将切换到请求指定的用户角色,否则它将切换到匿名角色(如果在 DB-anon-角色 )。

这种角色切换机制称为 user impersonation 。在PostgreSQL中,它是用 SET ROLE
陈述。
备注
模拟的角色将应用其设置。看见 模拟角色设置 。
基于JWT的用户模拟#
我们用 JSON Web Tokens 为了对API请求进行身份验证,这允许我们是无状态的,并且不需要通过数据库查找进行验证。您可能还记得,JWT包含一个经过加密签名的声明列表。所有声明都是允许的,但PostgREST特别关心一个名为Role的声明。
{
"role": "user123"
}
当请求包含具有角色声明的有效JWT时,在HTTP请求期间,PostgREST将切换到具有该名称的数据库角色。
SET LOCAL ROLE user123;
请注意,数据库管理员必须通过先前执行以下操作来允许验证者角色切换到此用户
GRANT user123 TO authenticator;
-- similarly for the anonymous role
-- GRANT anonymous TO authenticator;
如果客户端不包含JWT(或没有角色声明的客户端),则PostgREST切换到匿名角色。数据库管理员必须正确设置匿名角色权限,以防止匿名用户查看或更改他们不应该看到的内容。
JWT一代#
客户端身份验证#
要发出经过身份验证的请求,客户端必须包括 Authorization
具有值的HTTP标头 Bearer <jwt>
。例如:
curl "http://localhost:3000/foo" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiamRvZSIsImV4cCI6MTQ3NTUxNjI1MH0.GYDZV3yM0gqvuEtJmfpplLBXSGYnke_Pvnl0tbKAjB4"
这个 Bearer
标题值可以使用大写,也可以不使用大写 (bearer
)。
JWT缓存#
PostgREST验证 JWTs
对于每一个请求。我们可以缓存 JWTs
来避免这种性能开销。
要启用JWT缓存,配置 jwt-cache-max-lifetime
是要被设定的。这是缓存存储JWT验证结果的最大秒数。缓存使用 exp
声明设置缓存条目生存期。如果JWT没有 exp
声明时,它使用配置值。看见 JWT-缓存-最长生存时间 了解更多详细信息。
备注
您可以使用 服务器计时头 查看JWT缓存的效果。
对称密钥#
每个令牌都使用密钥进行加密签名。在对称加密的情况下,签名者和验证者共享相同的秘密密码短语,该密码短语可以配置为 JWT-机密 。如果将其设置为简单的字符串值,如“reallyreallyreallyreallyvery”,则PostgREST将其解释为HMAC-SHA256密码短语。
非对称密钥#
在非对称密码学中,签名者使用私钥,验证者使用公钥。
如中所述 配置 节中,PostgREST接受一个 jwt-secret
配置文件参数。但是,您还可以指定文字JSON Web密钥(JWK)或集合。例如,您可以使用编码为JWK的RSA-256公钥:
{
"alg":"RS256",
"e":"AQAB",
"key_ops":["verify"],
"kty":"RSA",
"n":"9zKNYTaYGfGm1tBMpRT6FxOYrM720GhXdettc02uyakYSEHU2IJz90G_MLlEl4-WWWYoS_QKFupw3s7aPYlaAjamG22rAnvWu-rRkP5sSSkKvud_IgKL4iE6Y2WJx2Bkl1XUFkdZ8wlEUR6O1ft3TS4uA-qKifSZ43CahzAJyUezOH9shI--tirC028lNg767ldEki3WnVr3zokSujC9YJ_9XXjw2hFBfmJUrNb0-wldvxQbFU8RPXip-GQ_JPTrCTZhrzGFeWPvhA6Rqmc3b1PhM9jY7Dur1sjYWYVyXlFNCK3c-6feo5WlRfe1aCWmwZQh6O18eTmLeT4nWYkDzQ"
}
备注
也可以是JSON Web密钥集(JWKS),如果它包含在分配给 keys 成员,例如 { keys: [jwk1, jwk2] }
。
只需将其作为单行字符串传递,转义引号即可:
jwt-secret = "{ \"alg\":\"RS256\", … }"
要生成这样的公钥/私钥对,请使用如下实用程序 latchset/jose 。
jose jwk gen -i '{"alg": "RS256"}' -o rsa.jwk
jose jwk pub -i rsa.jwk -o rsa.jwk.pub
# now rsa.jwk.pub contains the desired JSON object
您可以像我们前面看到的那样指定文字值,或者引用文件名从文件加载JWK:
jwt-secret = "@rsa.jwk.pub"
JWT索赔验证#
PostgREST向 exp
令牌过期声明,拒绝过期令牌。
JWT安全#
反对使用JWT至少有三种常见的批评:1)反对标准本身,2)反对使用有已知安全漏洞的库,3)反对将JWT用于Web会话。我们将简要解释每个批评,以及PostgREST如何处理它,并为适当的用户操作提供建议。
对这一现象的批判 JWT standard 详细地说出了 elsewhere on the web 。对于PostgREST来说,最相关的部分是所谓的 alg=none
问题。一些实现JWT的服务器允许客户端选择用于签署JWT的算法。在这种情况下,攻击者可以将算法设置为 none
,根本不需要任何签名,并获得未经授权的访问权限。但是,当前的PostgREST实现不允许客户端在HTTP请求中设置签名算法,从而使此攻击无关紧要。对该标准的批评是,它要求实现 alg=none
完全没有。
另一种类型的批评集中在滥用JWT来维护Web会话。基本建议是 stop using JWT for sessions 因为当你这样做时,如果不是所有的,也是大多数的解决方案, do not work 。链接的文章深入讨论了这些问题,但问题的实质是,JWT不是被设计为用于客户端存储的安全和有状态单元,因此不适合会话管理。
PostgREST主要将JWT用于身份验证和授权目的,并鼓励用户也这样做。对于Web会话,使用HTTPS上的Cookie已经足够好了,并且标准Web框架很好地满足了这一要求。
自定义验证#
除JWT验证外,PostgREST不强制任何额外的约束。额外约束的一个例子是立即撤销特定用户的访问权限。vbl.使用 数据库-请求前 您可以指定要立即调用的函数 用户模拟 并且在主查询本身运行之前。
db-pre-request = "public.check_user"
在该函数中,您可以运行任意代码来检查请求并引发异常(请参见 引发带有HTTP状态代码的错误 )来阻止它(如果需要)。在这里,您可以利用 请求头、Cookie和JWT声明 用于根据Web用户信息进行定制逻辑。
CREATE OR REPLACE FUNCTION check_user() RETURNS void AS $$
DECLARE
email text := current_setting('request.jwt.claims', true)::json->>'email';
BEGIN
IF email = 'evil.user@malicious.com' THEN
RAISE EXCEPTION 'No, you are evil'
USING HINT = 'Stop being so evil and maybe you can log in';
END IF;
END
$$ LANGUAGE plpgsql;