PostgREST 权限篇

PostgREST 本身并没有权限管理的功能,而是将权限验证下放到了数据库层,通过数据库的角色来控制用户访问数据的权限,而 PostgREST 唯一要做的事就是获取每个请求发起者的角色,然后切换到这个角色再去执行 SQL,成败就看这个角色所具备的权限了。

为了安全的获取用户的角色,PostgREST 使用了 JWT 来传递角色信息。但是 PostREST 只能解析 JWT token,用户登录以及如何生成 jwt token 需要我们自己实现。

PostgREST 入门篇中,我们创建了 todos 表并完成了一次查询,下面让我们试试创建一条数据,打开 Postman,用 POST 发起请求。

在这里插入图片描述

不出意外的话会得到一个未授权的响应,表示我们没有权限修改 todos 表。这是正常的,因为我们不希望未授权的匿名用户执行任何有风险的操作。接下来我们来解决这一问题。

解析JWT

要让 PostgREST 解析 JWT 非常简单,只需要在配置文件中加一行配置即可:

jwt-secret = "01234567890123456789012345678901"

jwt-secret 是用来给 jwt 签名的,PostgREST 会拿着它去验证 jwt 的合法性。注意这个字符串的长度不能小于32,否则 PostgREST 会将它先进行 SHA256 加密,然后用加密后的字符串做为 jwt 密钥,这也是出于安全性的考虑。

还是延续上一篇的例子,上次我们创建了一个匿名角色 web_anon ,它对 todos 表只有查询权限。现在我们需要再创建一个角色 todo_user 并让它可以修改数据库。

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;

首先我们将 todo_user 的权限授予 authenticator ,这样 PostgREST 就能切换到 todo_user 角色了。然后分别授予 todo_user 角色使用 api schema 的权限以及对 todos 表的所有权限。

万事俱备,现在离发起请求只差一个 jwt token 了。如果是用 curl 发起请求,可以去 jwt 官网生成一个 token。

在这里插入图片描述

① 处填写我们的角色,② 处输入前面的 jwt 密钥,复制 ③ 处的 token,它会自动更新,然后在请求头中设置 Authorization 字段。

Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidG9kb191c2VyIn0.u5zRwwjJJFkMhYubeemYXq9iT_JTRERmM0V5CX1UiSE

如果使用 Postman 则没有这么麻烦,直接在 Authorization 中填入相关信息就可以了。

在这里插入图片描述

① 处选择 JWT Bearer,② 处保持默认,③ 处输入 jwt 密钥,④ 处填入角色,最后点击发送即可。不出意外的话,会看到一个没有内容的 201 Created 响应。

上面我们选择了 HS256,这是一种使用 HMAC-SHA256 算法的对称加密,签发方和验证方使用相同的密钥。有对称就有非对称,PostgREST 也是支持非对称加密的。

非对称加密

在非对称加密中使用的是 RS256 算法,密钥也不叫 JWT,而是叫 JWK,Json Web Key。RS256 使用了 RSA 非对称加密算法来生成公钥和私钥。客户端持有私钥进行加密,PostgREST 持有公钥进行解密。

不管是对称还是非对称,加密算法只能是这两个,不支持客户端指定算法,这也在一定程度上避免了恶意客户端选择不加密的风险。

开始之前,首先我们需要获得一对公钥和私钥。做为演示,我们可以去 mkjwk.org 网站在线生成。官方推荐了一个 latchset/jose 工具,不过貌似只有 Linux 版本。

在这里插入图片描述

① 处选择 Signature,② 处选择 RS256 算法,③ 处随便写点什么,左边下拉还能选择其他选项,比如再做一次加密,日期或时间戳等,④ 处我们选择 Yes 才能看到最下面的 X.509 PEM 格式的私钥,最后点击 Generat 生成即可。

接下来我们在 tutorial.conf 文件同级目录下创建一个名为 rsa.jwk.pub 文件,将上图中右上角的 Public Key 下的内容复制,粘贴到 rsa.jwk.pub 文件中。打开 tutorial.conf 文件,将 jwt-secret 配置修改如下:

jwt-secret = "@rsa.jwk.pub"

@ 表示从文件中读取内容,文件名可以随意。当然也可以直接将 jwk 做为字符串设置,注意 json 中的双引号需要转义,但是这样不便于修改,所以还是推荐从文件读取。

jwt-secret = "{ \"alg\":\"RS256\", … }"

最后别忘重启 PostgREST 服务。

然后我们回到 Postman,在 Authorization 选项中,使用非对称加密。

在这里插入图片描述

① 处选择 RS256,回到生成公私钥的网站,将左下角 Private Key (X.509 PEM Format) 下的内容复制粘贴到 ② 处,注意不要点最下面的复制到剪贴板,因为它会把私钥复制成一个字符串,换行会被替换成 \n ,这样是不对的,直接全选复制。

点击 Send 按钮,不出意外的话,会看到 201 Created 响应。除了 JWK,也同样支持 JWKS,格式为 {"keys": [{jwk2}, {jwk2}]}

其他字段

在 jwt 的负载中,除了 role ,PostgREST 还支持以下 jwt 字段:

  • exp :指定 token 的过期时间。
  • iat :Issued At,token 签发时间。
  • nbf :Not Before,token 生效的起始时间。
  • audAudience,受众。用来指定 token 的接收方,当 aud 与当前服务名不匹配时,可以拒绝该 token。它可以是单个字符串或者一个 json 字符串列表,表示有多个受众。

nbfexp 构成了一个时间区间,这个区间就是 token 的生效时间范围。以上都是 jwt 标准保留声明,也就是 jwt 规范的标准字段。但是 role 并不是,它是我们自定义的,在 PostgREST 配置文件中通过 jwt-role-claim-key 配置,默认是 role

## Jspath to the role claim key
jwt-role-claim-key = ".role"

此外我们还可以通过 jwt-aud 来配置 PostgREST 的服务名,不过这里只能配置单个字符串,毕竟一个服务只有一个名字是正常的。

jwt-aud = "service_a"

前置验证

按理来说,PostgREST 验证完 jwt token,下一步就是执行 SQL 查询了。但是这里有一个问题,假设我们不小心签发了一个 token 给恶意用户,由于 token 是有有效期的,在 token 的有效期内,恶意用户可以为所欲为,那么阁下又当如何应对呢?

PostgREST 允许通过 db-pre-request 配置一个函数,它会在执行 SQL 之前调用,实现拦截的效果。比如我们可以定义以下 PostgreSQL 函数。

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;

我们定义了一个叫 check_user 的函数,然后从 jwt 中提取 email 字段,为此我们在签发 token 的时候就要先把 email 加上。最后判断如果邮箱是 evil.user@malicious.com 就引发一个异常。

在配置文件中加上下面这行:

db-pre-request = "public.check_user"

这样我们就能立即撤回误发的 token 了,做为示例这里邮箱匹配是写死的字符串,更进一步也可以去查表来获取失效的邮箱地址。


以上就是本期的全部内容,但是我们并没有涉及 jwt token 的签发,我们可以选择使用第三方服务,比如 Auth0 ,也可以通过 PostgreSQL 函数来签发 token,不过这是后面的内容了。

最后只有对外提供服务时才需要用到 jwt 权限验证,如果是一个受信任的内网服务,那么直接给匿名角色足够的权限也是完全合理的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值