ACL 除了限制命令和 key,还限制频道(&pattern)。发布订阅出了名的没有认证之外的隔离,所以这一层要看清:哪些命令受它管、模式订阅怎么匹配、撤销权限时已经订阅的连接怎么办。全部在本机实例上实测。
> CONFIG GET acl-pubsub-default 1) "acl-pubsub-default" 2) "resetchannels"
> ACL SETUSER u1 on nopass ~* +@all "OK" user u1 on nopass sanitize-payload ~* resetchannels +@all > AUTH u1 x "OK" > PUBLISH news:1 hi (error) NOPERM No permissions to access a channel > SUBSCRIBE news:1 (error) NOPERM No permissions to access a channel
命令权限(+@all)和频道权限(&…)是两套独立的东西。只给命令,不给频道,PUBLISH 和 SUBSCRIBE 都会被拒。ACL LIST 里看得见 resetchannels(即没有任何频道)。
> ACL SETUSER u2 on nopass ~* +@all resetchannels &news:* "OK" user u2 on nopass sanitize-payload ~* resetchannels &news:* +@all DRYRUN u2 PUBLISH news:1 hi → "OK" DRYRUN u2 PUBLISH other hi → "User u2 has no permissions to access the 'other' channel" DRYRUN u2 SUBSCRIBE news:1 → "OK" DRYRUN u2 SUBSCRIBE news:* → "OK" DRYRUN u2 SUBSCRIBE other → "User u2 has no permissions to access the 'other' channel" DRYRUN u2 PSUBSCRIBE news:* → "OK" DRYRUN u2 PSUBSCRIBE news:a* → "User u2 has no permissions to access the 'news:a*' channel" DRYRUN u2 PSUBSCRIBE * → "User u2 has no permissions to access the '*' channel" DRYRUN u2 SSUBSCRIBE news:1 → "OK"
DRYRUN 的判定见上表。要点:授权的是一个 glob 模式 news:*,普通订阅 news:1 命中它;而 PSUBSCRIBE 本身带模式,必须与已授权的模式完全相同——news:a* 虽然范围更小也被拒。SUBSCRIBE news:* 是把 news:* 当作一个普通频道名,能被 &news:* 匹配,所以放行。(实测得到的行为;背后的匹配实现本文没有翻源码。)
> AUTH u2 x "OK" 订阅: 1) "subscribe" 2) "news:1" 3) (integer) 1 管理员发布一条: 1 个订阅者收到 -> 1) "message" 2) "news:1" 3) "before" > ACL SETUSER u2 resetchannels "OK" 撤销之后订阅连接:服务端已断开连接(EOF) 之后发布,订阅者数: 0 PUBSUB CHANNELS: (empty array)
管理员 resetchannels 之后,服务端直接把这条订阅连接断开(客户端读到 EOF),不会留一个「还在订阅但没权限」的连接。所以客户端库要做好订阅连接被踢后重连(并重新鉴权,会被拒)的处理。
> ACL SETUSER u3 on nopass ~* +@all allchannels "OK" user u3 on nopass sanitize-payload ~* &* +@all > CONFIG SET acl-pubsub-default allchannels "OK" > ACL SETUSER u4 on nopass ~* +@all "OK" user u4 on nopass sanitize-payload ~* &* +@all > CONFIG SET acl-pubsub-default resetchannels "OK"
> CONFIG SET notify-keyspace-events KEA "OK" > ACL SETUSER u5 on nopass ~* +@all resetchannels &news:* "OK" DRYRUN u5 PSUBSCRIBE __keyspace@0__:* → "User u5 has no permissions to access the '__keyspace@0__:*' channel"
想让新用户默认能用发布订阅,可以把 acl-pubsub-default 改成 allchannels——影响之后新建的用户。键空间通知的频道(__keyspace@0__:*)也归 ACL 管:没有授权的用户订阅不了,这是个天然的「谁能看到 key 变化」的开关。