Web 应用安全,了解 BFF 模式的含义

2025-06-08

Web 应用安全,了解 BFF 模式的含义

身份和安全概述

具有 BFF 流的身份服务器

说到安全,有很多指南、建议和评论,告诉你为什么应该或不应该使用每种方法。很难理解所有不同的选择以及它们所面临的安全问题。

您还拥有不同的客户端(移动/网络/应用程序),并且每个客户端对以令牌、cookie 或会话形式管理、存储和处理数据都有不同的安全要求。

最后,您的应用程序可能会扩展,并且并非所有选项都可以部署为多个实例节点,而无需对应用程序和基础架构进行额外的更改。

这意味着您确实需要在一开始就做出决定,并提出以下问题:您的客户是谁?您的要求是什么?将来可以在系统中添加什么?如何处理所选择的方法?等等。

本文是开源培训的一部分。

身份验证与授权

  • Authentication- 验证用户身份(身份)的过程。
  • Authorization- 检查用户有权访问什么内容的过程(检查访问特定资源的权限)。

Tokens 与 Cookie 和 Sessions

令牌、cookie 和会话可以被解释为各种协议/标准/模式(OAuth、OpenID、BFF)用于执行与身份相关的任务的资源或工具。

理解基础知识很重要,因为它们最终会被组合使用。

最后,您可以将 JWT 令牌包装在 cookie 中,并将其与会话数据一起使用以进行“授权”和“身份验证”。

曲奇饼

Cookie 可以理解为由服务器创建的小数据块,它们在响应中写入一次,并在 Web 浏览器的每个后续请求中自动重新发送,直到其生命周期到期。

它们可用于身份验证/授权、追踪和营销目的。以及更多……

IndentityServer cookie示例:
网络浏览器中的 IdnetityServer Cookies

Cookie 有 3 个主要属性:

  • HttpOnly- 仅限 http 的 Cookie 无法通过客户端 API(例如 JavaScript)访问。浏览器不允许您从前端代码访问此 Cookie。
  • Secure安全 cookie 只能通过加密的 HTTPS 连接传输。
  • SameSite3 个选项值StrictLax或者None- 这告诉浏览器可以将 cookie 发送到哪个域和地址。
起源是什么?

来源一般是ip和端口或者域名和子域名。

// This are different origins since subdomain are different
https://developer.mozilla.org
https://mozilla.org

// This are also different origins since port number is different
https://localhost:5001
https://localhost:7001
Enter fullscreen mode Exit fullscreen mode
另一个cookie定义:
  • Session cookies- 仅为浏览器会话(在内存中)创建,关闭后删除/丢失。
  • Third-party cookies- 通常,cookie 的域属性与 Web 浏览器地址栏中显示的域匹配。 为first-party cookies。 与third-party cookies当前域不匹配,并用作tracking cookies跟踪用户活动。

会议

Session 用于在服务器上临时存储信息,以供网站多个页面使用。它通常与 Cookie 相关联,Cookie 用于标识存储在服务器上的会话,但不包含任何数据。

会话图

代币

令牌是允许应用系统执行授权和身份验证过程的数据元素。它们通常编码为 base64 字符串。

有几种类型的令牌:

  • access token- 包含用户声明并使用密钥进行签名。它使用 JWT 令牌。
  • refresh token- 用于在其生命周期到期后“刷新”并获取新的“访问令牌”。
  • id token- 有关用户个人资料信息的 JSON 编码数据
  • 等等等等...
JWT 令牌

JSON Web Token 是一种开放标准,它定义了如何以 JSON 对象的形式在各​​方之间安全地传输信息。

它们用于authorization提供information exchange安全证明,证明其中包含的信息是有效的并且由受信任的来源编写。

您可以轻松地将任意数据写入令牌,并对其进行签名,然后让客户端使用它来访问服务器资源。服务器可以验证令牌是否已签名且仍然有效。

基本 JWT 令牌流示例:

授权图 JWT 令牌

JWT 内容:

JWT 令牌

JWT 由 3 个部分组成:

  • Header- 包含令牌类型(JWT)和使用的签名算法等信息,例如 HMAC SHA256 或 RSA。

    {
    "alg": "HS256",
    "typ": "JWT"
    }
    
  • Payload- 安全签名的数据(声明)

    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }
    
  • Signature- 加密的标头、加密的有效负载、秘密并由标头中指定的算法签名。

    HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
    

    有关 JWT 令牌的更多附加信息,请参阅:官方文档

    最常用的授权访问API的令牌是Bearertoken。

身份协议

有几种协议/规范可用于管理您的身份或授权过程。

这对于标准化服务和客户端之间的身份验证和授权至关重要。这样,我们就可以采用不同的全球身份/身份验证提供商,例如 Facebook、Google(外部/内部),并标准化流程的实施方式。

Oauth 和 OpenId 徽标

该演示重点介绍最常用的协议OAuthOpenID Connect

这两种协议默认使用 JWT 令牌来加密和签名敏感数据,或验证请求是否来自可信来源。您也可以在前端使用 Cookie,让后端为您完成会话和令牌授权。

您还可以观看和学习各种演讲:

OAuth

主要用于授权应用访问特定资源。无需与外部网站共享密码即可完成此操作。

Oauth Slack 授权提示示例

如果您曾经登录过某个新应用并同意访问您的联系人、日历等信息,那么您就使用了OAuth 2.0 。该协议不提供任何有关用户终端的信息,仅提供访问特定资源的令牌。您可以阅读此文档,了解更多关于OAuth 的信息

Oauth 是授权而不是身份验证

OAuth 通常为客户端提供对某些资源的“安全委托访问”。假设您是 Google 用户,某个应用想要访问您的日历数据。以下流程可以作为示例:

OAuth流程示例:

Oauth 流程解释

在上面的例子中,像 Slack、Jira 等应用程序只获得访问特定资源(例如日历)的权限,但没有用户本身的权限,因此用户名和电子邮件等配置文件数据不会被传输并保持受保护。

如果您想了解有关 OAuth 的更多信息,可以观看以下演示:

代币兑换流程

有几种方法grant可以替代。选择哪种方法取决于请求访问的客户端类型以及该客户端的可信度。

  • 授权码流程
  • 使用 PKCE 的授权码流程
  • 隐式流
  • 客户端凭证流

Oauth 授权流程选择图片来自Okta

OpenID 连接

OpenID是一种去中心化身份验证协议

多个内部/外部应用程序使用的登录名。如果您使用 Google 或 Facebook 等登录到外部网站或应用程序,那么您就使用了OpenID Connect

OpenID Connect 基于OAuth 2.0。(OAuth 是底层协议,OpenId 是构建在其上的身份层)并使用名为 的 JWT 令牌,id_token该令牌以 JSON 格式封装身份声明。有关 OpenId 的更多信息,请参阅本规范

id_token例子:

{
"iss": "http://server.example.com",
"sub": "248289761001",
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"name": "Dalibor Kundrat",
"given_name": "Dalibor",
"family_name": "Kundrat",
"gender": "male",
"birthdate": "0000-10-31",
"email": "d.kundrat@example.com",
"picture": "http://example.com/somepicture_of_dalibor.jpg"
}
Enter fullscreen mode Exit fullscreen mode

OpenId流程示例:

OpenId 流程

有几种流程可供使用。您可以在此文章中OpenId阅读更多相关信息

每个OpenId服务器根据规范提供多个端点进行交互。

可以使用全局发现端点 (global discovery endpoint)探索所有端点的 URL 。通常称为disco。它位于路径:并返回与指定授权服务器相关的/.well-known/openid-configurationJSON OpenID Connect元数据。

迪斯科响应示例

  {
   "issuer":"https://localhost:5001",
   "jwks_uri":"https://localhost:5001/.well-known/openid-configuration/jwks",
   "authorization_endpoint":"https://localhost:5001/connect/authorize",
   "token_endpoint":"https://localhost:5001/connect/token",
   "userinfo_endpoint":"https://localhost:5001/connect/userinfo",
   "end_session_endpoint":"https://localhost:5001/connect/endsession",
   "check_session_iframe":"https://localhost:5001/connect/checksession",
   "revocation_endpoint":"https://localhost:5001/connect/revocation",
   "introspection_endpoint":"https://localhost:5001/connect/introspect",
   "device_authorization_endpoint":"https://localhost:5001/connect/deviceauthorization",
   "frontchannel_logout_supported":true,
   "frontchannel_logout_session_supported":true,
   "backchannel_logout_supported":true,
   "backchannel_logout_session_supported":true,
   "scopes_supported":["profile","openid","email","role", "offline_access" //etc..],
   "claims_supported":["name","family_name","profile","email", etc..],
   "grant_types_supported":["authorization_code","client_credentials", "refresh_token", //etc..],
   "response_types_supported":["code","token", "id_token","id_token token" //etc..],
   "response_modes_supported":["form_post","query","fragment"],
   "token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post" ],
   "id_token_signing_alg_values_supported":["RS256"],
   "subject_types_supported":["public" ],
   "code_challenge_methods_supported":["plain","S256"],
   "request_parameter_supported":true,
   "request_object_signing_alg_values_supported":["RS256","RS384" //etc..],
   "authorization_response_iss_parameter_supported":true
}
Enter fullscreen mode Exit fullscreen mode

主要 OpenId 端点:

  • /authorization_endpoint- 与资源所有者交互并获得授权许可。
  • /token_endpoint- 通过提供授权许可(代码)或刷新令牌获取访问和/或ID令牌
  • /revocation_endpoint- 撤销访问或刷新令牌。
  • /end_session_endpoint- 结束与指定 ID 令牌关联的会话。
  • /userinfo_endpoint- 提供有关经过身份验证的最终用户的信息。


注意:发现端点中的所有值均指当前服务器配置。您可以在 idnetityserver 配置期间调整或启用/禁用代码中的某些选项
。⠀

前端后端模式(BFF)

BBF 是特定前端应用程序使用的后端。

由于端点 API 可能有多个具有不同请求的客户端,因此 BFF 可以提供特定于客户端的后端中介,并充当将多个请求转发和合并到不同服务 API 的代理。

前端的后端 - BFF 示例


好的,我们有 Cookie、Token 和 Session。我们将它们用于各种身份验证/授权协议(OpenId、OAuth 等),那么 hack BFF 有什么用处呢

答案是:

  • 安全原因
  • 架构原因

安全原因

近年来,用 Javascript(React、Angular、Vue……)为 SPA 实现 OpenID Connect 很常见,现在不再推荐这样做:

  • 在浏览器中使用访问令牌比使用安全 cookie 具有更多的安全风险。
  • SPA 是一个公共客户端,无法保守秘密,因为这样的秘密将成为 JavaScript 的一部分,任何检查源代码的人都可以访问。
  • 最近浏览器为了防止跟踪而做出的改变可能会导致“第三方 cookie”被删除。
  • 不可能将某些内容长期安全地存储在浏览器中,因为它可能会被各种攻击窃取。

由于上述问题,SPA 的最佳安全建议是避免在浏览器中存储令牌,并创建一个轻量级后端来帮助完成此过程,称为前端模式的后端(BFF)。

这样,您仍然可以使用它acces_tokens来授权访问所有 API,但在移动设备的情况下,客户端将使用会话 cookie 或令牌,而 BFF 将代理这一方。

BFF 可以是:

  • statefull- 将令牌存储在内存中并使用会话来管理它们。
  • stateless- 将令牌存储在仅加密的 HTTP、同一页面 cookie 中。

建筑原因

在设计应用程序时,您可以选择多种方式从客户端(Web/移动/外部)访问 API。

1) 为所有客户端提供单一 API 的单一 API 网关
2) 为每种类型的客户端提供 API 的单一 API 网关
3) 为每个客户端提供 API 的每个客户端 API 网关。

BFF 与 API 网关

虽然是所有客户端进入系统的API Gateway 单一入口点BFF,但 仅负责单一类型的客户端

前端后端 vs API 网关

BBF cookies 终止和令牌隔离

正如文中提到的,最重要的是:

  • 避免在浏览器中存储令牌。(浏览器策略中没有令牌)。
  • 在服务器端存储令牌并使用加密/签名的仅 HTTP cookie。

推荐使用 BFF 模式来保护 SPA 前端:

前端后端 (BFF) cookie 和令牌流 + 代理

  • 使用此功能,从 SPA 前端到授权服务器的所有通信现在都通过 BFF,并且令牌不会到达 SPA。
  • BFF 现在会发出会话 cookie。这些 cookie 是 API 请求的一部分,并在代理级别交换访问令牌。
  • 客户端 cookie 由 BFF 代理终止。

下一步是什么?

下次我将向大家展示BFF的具体实现。

存储库

您可以在开源 Github 仓库中找到该应用程序的完整源代码,包括身份、分布式日志记录、跟踪和监控:

https://github.com/damikun/trouble-training

鏂囩珷鏉ユ簮锛�https://dev.to/damikun/web-app-security-understanding-the-meaning-of-the-bff-pattern-i85
PREV
像建筑师一样进行测试
NEXT
为什么选择 Docker?Docker 到底能做什么?虚拟机不是慢吗?难道我不能直接把应用程序上传到一堆云服务器上吗?这里有一个更简单的解决方案。但这在开发过程中能给我带来什么帮助呢?总结一下……