一次讲清后端鉴权与访问控制:Session、JWT、RBAC、多租户与越权防护

一次讲清后端鉴权与访问控制:Session、JWT、RBAC、多租户与越权防护
一次讲清后端鉴权与访问控制Session、JWT、RBAC、多租户与越权防护很多项目的鉴权代码看起来像这样ifnottoken:raiseHTTPException(status_code401)于是很容易产生一个误解只要 Token 验证通过鉴权就完成了。实际上Token 有效最多只能帮助后端确认调用方身份。它并不能自动回答这个用户能否调用退款接口他能否读取这一张具体订单他在当前公司里是什么角色他能否审批自己提交的申请为什么一个租户不能看到另一个租户的数据附件、缓存、导出任务和 WebSocket 是否也做了权限检查真正的生产级访问控制是一条完整链路认证身份 ↓ 构造可信 AuthContext ↓ 校验接口权限 ↓ 校验资源归属 ↓ 校验业务 Policy ↓ 限制数据访问范围 ↓ 执行业务并记录审计日志 ↓ 通过越权测试持续验证本文会把 Session、JWT、RBAC、多租户、XSS、CSRF 和 RLS 放回这条链路中说明它们分别解决什么问题又不能解决什么问题。一、先分清认证、授权和数据隔离1. Authentication你是谁Authentication简称 AuthN即认证。它负责确认当前请求来自哪个用户、应用或服务例如用户名和密码登录Session CookieBearer Access TokenAPI KeyOAuth2 / OpenID Connect企业 SSO服务间身份凭证。认证成功后后端应得到一个可信身份而不是直接把客户端传来的user_id当作事实。2. Authorization你能做什么Authorization简称 AuthZ即授权。它基于已经确认的身份判断是否允许执行某个动作能否查看订单能否创建退款能否进入管理后台能否修改系统配置能否审批某笔申请。常见授权模型包括 RBAC、Scope、ABAC 和 Policy。3. Resource-level Authorization你能操作哪一个资源用户拥有orders:read权限并不等于他能读取系统里的所有订单。系统还要判断这张订单属于当前用户吗 属于当前租户吗 当前用户与资源有什么关系这通常称为 Ownership Check、对象级授权或资源级授权。4. Data Isolation查询层真正限制数据范围最后权限必须落实到查询和写入条件中例如SELECT*FROMordersWHEREid:order_idANDtenant_id:current_tenant_id;如果业务要求用户只能看自己的订单还要增加ANDuser_id:current_user_id所以可以记住认证你是谁 授权你能做什么 资源授权你能操作哪一个对象 数据隔离系统怎样确保你只能拿到允许的数据二、一条生产请求如何完成鉴权假设用户请求POST /api/v1/refunds/9001/approve Authorization: Bearer access_token后端不应只验证 Token而应依次执行第一步认证请求提取凭证校验签名或查询 Session检查有效期、签发者、受众等约束确认账号、会话和凭证没有失效。第二步构造 AuthContextAuthContext(user_iduser_1001,tenant_idtenant_a,roles{finance_approver},scopes{refunds:read,refunds:approve},session_idsession_001,)第三步检查入口权限当前身份是否包含refunds:approve第四步查询目标资源退款单9001是否属于tenant_a当前用户是否具备访问它的成员关系第五步检查业务 Policy退款单是否处于pending用户能否审批这个金额是否禁止提交人自我审批是否需要二次验证或双人复核第六步执行并审计记录操作者、租户、资源、动作、时间、结果和请求标识。这才是一条完整的鉴权链路。三、AuthContext后端可信身份的统一入口AuthContext 或 CurrentUser是后端从已验证登录态中解析出的当前身份上下文。常见字段包括fromdataclassesimportdataclassdataclass(frozenTrue)classAuthContext:user_id:strtenant_id:str|Noneroles:frozenset[str]scopes:frozenset[str]session_id:str|NoneNone它可以来自服务端 SessionAccess TokenJWTOAuth2 / OIDC 登录结果企业 SSO服务间认证。后续权限判断、查询隔离和审计日志都应从 AuthContext 开始。不要信任请求体里的身份字段危险做法{user_id:user_1001,tenant_id:tenant_a,role:admin,title:new order}客户端可以把role改成admin也可以把tenant_id改成其他公司。更安全的做法是orderorder_service.create(user_idctx.user_id,tenant_idctx.tenant_id,titlerequest.title,)请求可以携带“用户选择了哪个租户”但后端必须用可信身份验证该用户确实属于这个租户之后再写入 AuthContext不能直接相信选择结果。四、Session Cookie 是什么Session Cookie 是浏览器 Web 应用常见的认证方式。用户提交登录凭证 ↓ 服务端验证身份 ↓ 服务端创建随机 session_id ↓ Set-Cookie 返回给浏览器 ↓ 浏览器后续自动携带 Cookie ↓ 服务端查询 Session 并恢复 AuthContextCookie 通常只保存难以猜测的随机 Session ID用户信息和会话状态保存在服务端数据库或 Redis。示例Set-Cookie: __Host-sessionrandom_value; Path/; Secure; HttpOnly; SameSiteLax关键属性Secure只通过 HTTPS 发送HttpOnly禁止 JavaScript 直接读取SameSite限制跨站请求携带 CookiePath与Domain限制 Cookie 作用范围合理的过期时间限制会话生命周期。服务端还应支持登录后轮换 Session ID降低 Session Fixation 风险主动退出和管理员撤销密码修改、风险事件后失效旧会话闲置超时与绝对过期敏感操作重新认证。HttpOnly只能降低 Cookie 被脚本直接读取的风险。如果页面存在 XSS恶意脚本仍可能在当前页面中代用户发请求因此不能用HttpOnly代替 XSS 防护。五、Access Token 与 Refresh TokenToken 方案中客户端通常主动携带访问凭证Authorization: Bearer access_tokenAccess TokenAccess Token 用于调用受保护 API通常生命周期较短并限制可访问的资源服务器允许的 Scope有效时间客户端或用户身份。Refresh TokenRefresh Token 用于获取新的 Access Token通常比 Access Token 生命周期更长因此也更敏感。生产系统需要设计安全存储撤销轮换重放检测设备或客户端绑定策略账号风险事件后的统一失效。RFC 9700 是 OAuth 2.0 当前的安全最佳实践文档其中专门讨论了 Token 重放防护、权限限制和 Refresh Token 保护。不要把 Bearer Token 放进 URL不推荐https://example.com/profile?access_tokensecretURL 可能进入浏览器历史Web Server 日志代理和监控系统Referer截图和复制记录。Bearer Token 应通过安全传输并优先放在AuthorizationHeader 中。六、JWT 只是 Token 格式JWT 全称 JSON Web Token。RFC 7519 将它定义为一种承载 Claims 的紧凑格式可通过 JWS 签名或通过 JWE 加密。一个常见 JWT 看起来像header.payload.signaturePayload 可能包含{sub:user_1001,iss:https://auth.example.com,aud:invoice-api,tenant_id:tenant_a,scope:invoices:read invoices:write,exp:1785632400}需要特别注意JWT 不等于 Access TokenAccess Token 可以是 JWT也可以是不透明随机字符串Refresh Token 也可以采用不同格式签名 JWT 的 Payload 通常只是编码不是加密不要在其中放密码、密钥等敏感明文JWT 不是一套完整的登录、刷新、撤销和授权方案。验证 JWT 不能只解析 Payload资源服务器至少应根据协议与部署校验使用预期算法验证签名iss是否由可信签发者签发aud是否签发给当前 APIexp是否过期nbf是否已经生效Token 用途和类型是否正确必要时检查jti、账号、会话或撤销状态。不要让未验证的 Token Header 任意决定算法或密钥来源。JWT 的撤销问题JWT 可在本地验证但“本地可验证”不代表“永远不用查状态”。当出现以下情况时可能仍需要短有效期Refresh Token 轮换撤销列表Token Version会话状态查询关键操作二次认证。应该根据安全需求选择而不是为了“无状态”拒绝所有服务端状态。七、OAuth2、OIDC 和 SSO 不要混在一起OAuth 2.0OAuth 2.0 主要解决授权委托客户端如何在用户授权后获得受限凭证去访问资源服务器。它定义了 Access Token、Refresh Token、Scope、Authorization Server 和 Resource Server 等角色与概念。OpenID ConnectOpenID Connect简称 OIDC是构建在 OAuth 2.0 之上的身份层用于认证终端用户并通过 ID Token 等机制传递身份声明。简单记忆OAuth 2.0客户端被授权访问资源 OIDC客户端确认登录用户身份SSOSSO 是 Single Sign-On即单点登录体验用户完成一次登录后可以访问多个关联系统。OIDC、SAML 等协议都可以用于实现 SSO但 SSO 是目标或体验不是某一种固定 Token 格式。八、Session Cookie 与 Token 哪个更安全不能脱离场景说谁绝对更安全。维度Session CookieBearer Token常见场景浏览器 WebAPI、多端、服务间调用状态主要在服务端可本地验证或查服务端状态浏览器携带自动携带 Cookie通常由客户端代码主动加 Header撤销删除服务端 Session 较直接取决于 Token 设计和状态机制常见风险CSRF、Session 固定、Cookie 配置错误Token 泄露、存储不当、刷新与撤销复杂XSS 影响可代用户操作非 HttpOnly Cookie 还可能被读取存在 JS 可访问存储中的 Token 可能被直接窃取选择时应考虑客户端类型是否完全由自己控制是否需要跨域和多端撤销与审计要求XSS 与 CSRF 威胁身份提供方能力团队是否能正确实现复杂流程。对于浏览器应用OWASP 当前 Session 指南明确建议不要把认证 Token、Session ID、JWT 或 Refresh Token 放进localStorage/sessionStorage因为同源运行的恶意 JavaScript 可以读取它们。HttpOnly Cookie 或 Backend-for-Frontend 等模式通常能更好地保护凭证机密性但仍需结合 CSRF 与 XSS 防护。九、RBAC用户、角色与权限RBAC 是 Role-Based Access Control基于角色的访问控制。核心关系User → Role → Permission例如customer ├── orders:read └── refunds:create support_agent ├── tickets:read └── tickets:write finance_approver ├── refunds:read └── refunds:approveRBAC 适合表达粗粒度职责能否访问管理后台能否进入财务模块能否调用审批接口能否查看审计日志。多租户中的角色通常属于 Membership同一个用户可能在公司 A 是管理员在公司 B 是普通成员不属于公司 C。因此更合理的关系常常是User ↓ Tenant Membership ├── tenant_id └── role不要轻易把所有 Role 都设计成用户的全局属性。十、Scope、ABAC 与 PolicyScope允许哪类动作Scope 常用于表达动作或 API 权限orders:read orders:write refunds:create refunds:approve它适合回答当前凭证是否允许调用这类能力但有refunds:approve仍不代表可以审批任何退款。ABAC根据属性做决策ABAC 是 Attribute-Based Access Control基于属性的访问控制。决策可能综合Subject用户、角色、部门、租户 Object资源所有者、租户、状态、敏感级别 Action读取、修改、审批、导出 Environment时间、网络、设备、风险等级例如用户是 finance_approver AND 用户与退款单属于同一 tenant AND 退款状态是 pending AND 退款金额不超过审批上限 AND 用户不是申请提交人Policy把业务授权规则明确表达出来Policy 可以写成集中函数defcan_approve_refund(ctx:AuthContext,refund:Refund,)-bool:return(refunds:approveinctx.scopesandrefund.tenant_idctx.tenant_idandrefund.statuspendingandrefund.created_by!ctx.user_idandrefund.amountapproval_limit(ctx))RBAC、Scope 和 Policy 不是非此即彼Role 聚合职责 Scope 表达能力 Policy 结合资源和业务条件作最终决策OWASP 建议最小权限、默认拒绝并对每个请求执行授权检查。复杂、多租户场景仅靠 RBAC 往往不够还需要属性或关系级约束。十一、资源归属校验最容易漏掉的一层假设接口是GET /api/v1/orders/9001危险代码orderorder_repository.get_by_id(order_id)returnorder攻击者只要把9001换成别人的 ID就可能读取其他资源。这类问题常被称为 IDOR 或对象级越权。更安全的查询orderorder_repository.get_for_tenant(order_idorder_id,tenant_idctx.tenant_id,)如果普通用户只能看自己的订单orderorder_repository.get_for_owner(order_idorder_id,tenant_idctx.tenant_id,user_idctx.user_id,)把权限范围放进查询条件通常比先查出资源再到处补if更不容易泄露数据。十二、多租户隔离不能只靠前端传 tenant_id多租户系统常见模型users tenants tenant_memberships orders invoices filestenant_memberships表达用户属于哪些租户以及在各租户中的角色。一次请求选择tenant_a后后端要验证当前用户是否有 tenant_a 的有效 membership membership 是否被冻结或过期 当前动作是否在该租户角色权限内之后才能将tenant_a放入 AuthContext。每条数据访问链路都要携带租户边界不仅是详情查询还包括列表和搜索更新与删除批量接口附件下载导入导出后台任务消息队列WebSocket / SSE日志和 Trace数据分析备份与恢复流程。缓存 Key 也要包含权限维度危险order:9001更安全tenant:tenant_a:order:9001如果资源还受用户级权限约束缓存设计也要考虑用户或权限版本避免把一个人的结果返回给另一个人。十三、文件、导出和后台任务为什么高风险很多系统只保护 JSON API却遗漏了/files/{file_id}/download预签名下载地址导出结果文件批量操作定时任务和队列消费者管理员代操作实时连接订阅Agent Run、Trace 和中间产物。例如附件下载不能只做filefile_repository.get(file_id)returnstorage.download(file.path)还应验证文件所属租户、业务资源、当前用户关系和下载权限。后台任务也不能只接收一个裸order_id。任务消息应携带足够的租户和授权来源上下文并在执行时重新确认当前规则而不是默认“进入队列的任务都可信”。十四、401、403 和 404 怎么选择401 Unauthorized虽然名字容易误解401主要表示没有提供有效认证凭证未登录Token 缺失Token 无效或过期。使用 Bearer Token 时通常还应返回合适的WWW-AuthenticateHeader。403 Forbidden服务端识别了当前身份但不允许执行目标动作缺少 Scope角色不允许Policy 拒绝账号被限制。404 Not Found目标资源不存在时返回404。在某些场景中为避免暴露“这个 ID 对应的资源确实存在”访问其他用户或租户资源时也可以统一表现为404。选择应形成一致策略同时避免通过响应 Body、延迟和日志接口泄露资源存在性。十五、XSS、CSRF 与 CORS 的区别XSSCross-Site Scripting跨站脚本攻击。攻击者让恶意 JavaScript 在目标网站页面中执行可能读取localStorage中的 Token读取非 HttpOnly Cookie代用户调用同源 API修改页面和交易信息窃取页面中的敏感数据。主要防护包括上下文相关输出编码安全模板与框架默认转义避免危险 DOM API输入净化用于允许富文本的场景Content Security Policy 作为纵深防御依赖与前端供应链治理。CSRFCross-Site Request Forgery跨站请求伪造。攻击者通常不需要读取 Cookie而是诱导浏览器自动携带目标网站 Cookie 发出请求。防护要根据架构选择组合CSRF TokenSameSite CookieOrigin / Referer 校验自定义 Header对敏感操作重新认证避免用 GET 执行有副作用的操作。SameSite是重要防线但不应在所有架构中被当作唯一 CSRF 防护。CORSCORS 控制浏览器中的 JavaScript 是否被允许读取跨源响应。它不等于认证也不能完整替代 CSRF 防护。服务器接受了一个请求与浏览器允许脚本读取响应是两个不同问题。十六、数据库层兜底PostgreSQL RLSPostgreSQL Row Level Security简称 RLS可以在数据库层根据 Policy 限制哪些行可见或可修改。示意ALTERTABLEordersENABLEROWLEVELSECURITY;CREATEPOLICY tenant_orders_policyONordersUSING(tenant_idcurrent_setting(app.current_tenant_id)::uuid)WITHCHECK(tenant_idcurrent_setting(app.current_tenant_id)::uuid);其中USING控制哪些已有行可见或可修改WITH CHECK控制新插入或更新后的行是否允许存在。启用 RLS 后如果没有适用 PolicyPostgreSQL 会采用默认拒绝。但还要注意Superuser 和具有BYPASSRLS的角色会绕过 RLS表 Owner 通常也会绕过除非使用FORCE ROW LEVEL SECURITY连接池复用时必须正确设置并清理租户上下文上下文最好绑定事务防止请求之间串租户Policy 组合、迁移、备份和后台任务都要测试RLS 不能表达所有复杂业务 Policy。因此RLS 是数据库层的纵深防御不是应用层认证、Scope、Ownership 和业务授权的替代品。十七、在 FastAPI 中组织鉴权代码下面是简化示例重点是职责分离。1. 认证依赖构造 AuthContextfromtypingimportAnnotatedfromfastapiimportDepends,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer oauth2_schemeOAuth2PasswordBearer(tokenUrl/api/v1/auth/token)asyncdefget_auth_context(token:Annotated[str,Depends(oauth2_scheme)],)-AuthContext:claimsawaittoken_service.verify_access_token(token)ifclaimsisNone:raiseHTTPException(status_codestatus.HTTP_401_UNAUTHORIZED,detailinvalid credentials,headers{WWW-Authenticate:Bearer},)returnAuthContext(user_idclaims.subject,tenant_idclaims.tenant_id,rolesfrozenset(claims.roles),scopesfrozenset(claims.scopes),session_idclaims.session_id,)真实项目还要校验签发者、受众、时间、Token 用途以及必要的会话状态。2. Scope 依赖保护入口defrequire_scope(required_scope:str):asyncdefchecker(ctx:Annotated[AuthContext,Depends(get_auth_context)],)-AuthContext:ifrequired_scopenotinctx.scopes:raiseHTTPException(status_code403,detailforbidden)returnctxreturnchecker3. Route 获取可信上下文router.post(/refunds/{refund_id}/approve)asyncdefapprove_refund(refund_id:str,ctx:Annotated[AuthContext,Depends(require_scope(refunds:approve)),],service:Annotated[RefundService,Depends(get_refund_service)],):returnawaitservice.approve(refund_idrefund_id,ctxctx)4. Service 执行资源与业务 PolicyclassRefundService:asyncdefapprove(self,refund_id:str,ctx:AuthContext):refundawaitself.repository.get_for_tenant(refund_idrefund_id,tenant_idctx.tenant_id,)ifrefundisNone:raiseRefundNotFound()ifnotself.policy.can_approve(ctxctx,refundrefund):raiseRefundForbidden()approvedawaitself.repository.approve(refundrefund,approved_byctx.user_id,)awaitself.audit_log.record(actor_idctx.user_id,tenant_idctx.tenant_id,actionrefund.approve,resource_idrefund.id,resultsuccess,)returnapproved认证适合集中在中间件、依赖或 Guard 层资源授权和业务 Policy 应靠近业务动作。不要把所有规则都塞进 Token 解析函数或 API Gateway。十八、缓存、消息与实时连接中的权限上下文缓存缓存应考虑TenantUserRole 或权限版本数据可见性权限变化后的失效。消息队列消息中不要盲目信任生产者传来的user_id和tenant_id。需要明确生产者身份事件来源租户边界消费者能否执行该动作权限是在入队时还是执行时重新判断。WebSocket / SSE建立连接时认证一次并不总是足够。还要授权订阅目标验证 Channel / Topic 的租户归属处理凭证过期角色变化后断开或刷新权限避免广播跨租户数据。十九、审计日志应该记录什么审计日志用于回答谁在什么时间以什么身份操作了哪个租户的哪个资源结果如何建议记录Actor IDTenant IDSession / Client 标识动作资源类型和 ID时间成功或拒绝拒绝原因或稳定错误码Request ID / Trace ID必要的变更摘要。不要记录完整密码完整 TokenSession Secret私钥不必要的敏感个人信息。审计日志自身也需要访问控制、防篡改、保留周期和隐私治理。二十、越权测试应该怎么写访问控制最容易在业务迭代中退化因此不能只测试“管理员能成功”。需要建立否定测试矩阵。场景预期结果未登录访问受保护接口401普通用户访问管理员接口403Alice 读取 Bob 的资源拒绝或404Tenant A 读取 Tenant B 数据拒绝或404有读取 Scope 但执行写操作403审批人审批自己提交的请求Policy 拒绝直接猜测附件 ID不得下载批量接口混入其他租户 ID整体拒绝或安全过滤并明确语义导出任务访问其他租户数据不得生成或下载缓存命中其他租户结果永远不得发生WebSocket 订阅其他租户 Topic拒绝订阅角色被撤销后继续使用旧会话按既定失效策略拒绝OWASP 的授权回归测试指南强调应持续覆盖水平越权、垂直越权和多租户边界退化。二十一、生产级检查清单认证所有受保护入口是否都要求有效身份凭证是否只通过 HTTPS 传输Session、Access Token 和 Refresh Token 是否有明确生命周期JWT 是否校验签名、签发者、受众、时间和用途是否支持登出、撤销、轮换和风险事件失效登录、找回密码和敏感操作是否有防自动攻击与再认证策略授权是否默认拒绝是否在每个请求上检查权限Role、Scope 和 Policy 的职责是否清晰是否遵循最小权限业务规则是否集中、可测试资源与租户查询是否同时限制资源 ID 与 Tenant必要时是否限制 User 或 Membership是否覆盖附件、导出、批量、缓存、消息和实时连接是否禁止信任客户端提交的身份与角色后台和运维入口是否遵循同样边界Web 安全Cookie 是否设置Secure、HttpOnly和适合的SameSite是否有与架构匹配的 CSRF 防护是否系统性防御 XSSCORS 是否只允许必要 Origin、Method 和 Header凭证和敏感数据是否避免进入 URL 与日志数据库与基础设施是否需要用 RLS 作为第二道防线连接池和事务中的租户上下文是否安全设置与清理数据备份、分析和迁移是否保持租户边界Secret 是否进入专用管理机制并支持轮换验证与运行是否有水平越权、垂直越权和跨租户测试是否记录授权拒绝和敏感操作审计是否监控登录异常、凭证重放和越权探测权限变更后缓存与会话是否按策略失效二十二、常见误区误区一有 Token 就完成鉴权Token 只帮助建立身份和部分权限声明不能替代资源归属和业务 Policy。误区二JWT 天然比 Session 安全二者风险模型不同。JWT 使用不当同样会出现泄露、重放、长期有效、无法及时撤销和错误验签问题。误区三前后端分离必须用 JWT架构是否分离与凭证格式不是同一维度。Session Cookie、BFF、OIDC 和 Token API 都可能适合不同系统。误区四管理员接口加 Role 就够了Role 只能说明粗粒度职责。具体资源、租户、状态、金额和关系仍需 Policy。误区五ID 不可猜就不会越权UUID 只能降低枚举便利度不能替代授权。攻击者仍可能从日志、链接、导出和其他接口获得 ID。误区六CORS 可以防 CSRFCORS 主要控制浏览器是否允许脚本读取跨源响应不能完整阻止浏览器发送带 Cookie 的跨站请求。误区七开启 RLS 后应用层不用鉴权RLS 主要限制数据库行。接口权限、业务动作、附件、缓存、消息和外部系统仍需应用层控制。总结生产级鉴权可以概括成下面这条公式访问控制 身份认证 可信 AuthContext 接口权限 资源归属 业务 Policy 数据隔离 审计日志 越权测试其中Session、Access Token、JWT 主要帮助传递和验证身份RBAC、Scope、ABAC 和 Policy 用于授权决策Ownership Check 与 Tenant Filter 把权限落实到具体数据XSS、CSRF 和安全凭证存储保护浏览器侧链路PostgreSQL RLS 可以提供数据库层纵深防御审计与越权测试确保规则长期有效。设计权限时不要只问“这个接口要不要登录”还要继续追问当前身份来自哪里是否可信他是否具备目标动作的权限他能否操作这个具体资源资源是否属于当前租户当前业务状态是否允许操作数据查询、缓存、文件和后台任务是否保持同一权限边界我们有没有用否定测试证明别人无法越权如果这篇文章帮你理清了 Session、JWT、RBAC、多租户和资源级授权之间的关系别忘了点个赞、收藏一下方便以后设计鉴权链路或排查越权问题时随时回来复习。如果还有没看懂的地方或者你想看 FastAPI 鉴权、多租户权限或 PostgreSQL RLS 的完整实战案例欢迎在评论区告诉我。后续还会继续分享 Python、FastAPI、后端安全和系统设计相关内容感兴趣的话点个关注我们下一篇见参考资料OWASPAuthorization Cheat SheetOWASPAuthentication Cheat SheetOWASPSession Management Cheat SheetOWASPCross-Site Request Forgery Prevention Cheat SheetOWASPMulti Tenant Security Cheat SheetOWASPAuthorization Regression Testing Cheat SheetRFC 6750OAuth 2.0 Bearer Token UsageRFC 7519JSON Web TokenRFC 9700Best Current Practice for OAuth 2.0 SecurityOpenID Connect Core 1.0PostgreSQLRow Security PoliciesPostgreSQLCREATE POLICY