#api-key #auth-token #api-token #dynamic #characters #uuid #timestamp

dynamic-token

基于共享 API 密钥、时间戳、一些随机噪声字符和可选的 UUID 编码和评估基于时间的动态认证令牌

1 个不稳定版本

0.1.7 2024 年 3 月 15 日
0.1.6 2024 年 3 月 15 日

#956 in 网页编程

GPL-2.0-or-later WITH Bison-exception-2…

25KB
344

mirror crates.io docs.rs

动态令牌

基于时间的动态令牌通过共享 API 密钥、时间戳、一些随机字符和可选的 UUID(通用唯一标识符)为第二阶段授权对客户端应用程序进行认证。与 JWT 令牌不同,动态令牌不可重复使用,不验证或维护用户的会话。它们是防止未经授权请求的第一道防线。

它们可以作为 JWT 令牌的额外安全层。由于动态令牌每毫秒都会随机打乱字符,因此没有详细了解算法就无法轻易分解。

如果解码的时间戳落在默认的 5 分钟的窄时间范围内,则将被拒绝。这允许相对较长的请求时间和系统时钟时间的微小差异。然而,唯一重要的是初始请求时间,而不是处理和发送响应所需的时间。理论上,相同的令牌可以在这一有限期间工作。

示例

  • 共享 API 密钥:Opus;Magna-897
  • 毫秒级时间戳: 1710105367868
  • 随机噪声或分割字符: %.,
  • UUID:6061f78686f34f52da3ef464
  • 示例编码令牌:OHR5a09wdXM7TWFnbmEtODk3MG10bDAwLDAwcjRfXzExa2JxOGloMnJfdXd6MjU2M2o4LjAwazk=

与其他认证系统的比较

  • 静态令牌 许多微服务使用单个静态令牌。虽然这可以阻止匿名用户,但它们可以通过分析从浏览器发送到后端的网络请求来轻松拦截。
  • 访问密钥和客户端密钥 这些最适合面向不同平台广泛消费者的 Web 服务。凭证直接暴露在标题、有效载荷或查询字符串凭证中。访问令牌可能需要与本地数据库进行比较。
  • OAuth 2.0 中,客户端最初向服务器呈现其静态 API 密钥以启动认证过程。如果成功,授权服务器向客户端颁发访问令牌。此访问令牌作为凭证,客户端可以使用它代表用户访问受保护的资源。
  • 动态令牌与之相反,可以通过非可重用令牌限制一组特定客户的访问,而无需握手或数据库查询。如果需要用户特定的身份验证,可以使用嵌入的UUID进行第二次基于数据库的授权步骤。

混淆而非单向加密

由于base-64编码非常常见,因此有可能通过暴力破解来解码动态令牌,并猜测哪些部分可能是共享的API令牌,通过比较两个通过标准base-64解码函数生成的动态令牌。然而,潜在的攻击者仍然需要猜测如何解码和重新组装时间戳以及排除随机控制字符。

自定义选项

在基本层面上,动态令牌只需要一个共享的API密钥。服务器和客户端都必须使用兼容的动态令牌算法。Rust crate非常适合作为HTTP头添加并作为中间件进行身份验证的服务器到服务器通信。动态令牌不会以易于被黑客攻击的格式暴露API密钥或时间戳。即使动态令牌被截获,其生命周期也是有限的。

建议配置

轻量级API端点,具有小型负载和简单响应

let options = AuthOptions::new("my_cryptic_shared_api_key").set_tolerance_secs(15);

如果客户端和服务器时钟同步,共享相同的API密钥,并且初始HTTP请求不超过15秒,这将有效。短的超时有助于防止DDoS攻击

重量级API端点,具有大型负载,如文件上传

let options = AuthOptions::new("my_cryptic_shared_api_key").set_tolerance_mins(5);

实际上,不建议允许超过5分钟的时间,因为大型上传将以数据包的形式到达服务器,而包含令牌的HTTP头只会在文件传输过程的开始进行身份验证。

定义自定义随机控制字符

let options = AuthOptions::new("my_cryptic_shared_api_key").set_rand_char_str("%@,.?£$");

服务器和客户端必须共享相同的API密钥和自定义分隔符。这些可以是除了字母、数字或下划线(_)之外的有效utf-8字符。这些字符将被base-64编码,从而增加了编码令牌的随机性。

请注意,如果随机字符序列包含中文字符,则序列将无效,但只要它们不被解释为希腊字母,emoji和数学符号就可以。

需要有效的UUID

UUID是一个通用唯一标识符,可以是至少24个字符长度的任何十六进制字符串。编码函数会删除任何连字符。这些被许多常见数据系统使用,如MongoDB,并且可以在使用十进制整数作为主键的其他数据库系统中生成,例如MySQL或PostGres。

let options = AuthOptions::new("api_key_with_an_emoji_😎☀︎").check_uuid(true);

// The client generates a key that may be added to the request header
let to_key = to_dynamic_key(&options, Some("5d00012de43dcd165cceb295"));

// The sever decodes the key using the shared API key and control characters
// You may use different options for different endpoints
let result = from_dynamic_key(&to_key, &options);

if result.valid() {
  // We have a valid result with a hexadecimal string that may be a UUID
  // Let's see if it matches a user in our database
  if let Some(user) = fetch_user_by_id(&result.uuid()).await {
    if user.is_admin() {
      // Ok proceed
    } else {
      // not authorised for this endpoint
    }
  } else {
    // user not found
  }
}

如果需要UUID,客户端必须发送有效的十六进制UUID。《from_dynamic_token()`函数将解码注入的UUID并验证其存在和格式。任何至少有24个字符的十六进制字符串都是有效的,但也支持32个字符的字符串。您可以使用提取的UUID进行后续的用户特定身份验证。如果客户端发送了UUID,但服务器不需要它,则UUID组件将被忽略。

开发备注

这是一个alpha版本,将与同一实用程序的Node JS和Web版本一起发布。

版本1.5

  • 未编码UUID的最小长度为24。大多数系统使用24或32,可选的连字符作为分隔符。
  • 编码器现在从UUID中删除连字符,只留下十六进制字符可用于验证。
  • 该crate公开了《encode_base64》和《decode_base64》函数。这些是《base64::engine::general_purpose::STANDARD.encode(&str)》及其《decode》兄弟的包装器。

依赖关系

~0.6–10MB
~93K SLoC