为 PHP REST API 实现简单的身份验证 [英] Implementing simple authentication for PHP REST API

查看:32
本文介绍了为 PHP REST API 实现简单的身份验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正致力于向旧版 PHP 站点添加 REST API.这是为了为内部应用提供一个端点,所以我可以很自由地设计事物以及支持和不支持的内容.

I am working on adding a REST API to a legacy PHP site. This is to provide an endpoint for an internal app, so I am quite free in how I design things and what do and don't support.

我现在需要添加到此 API 的是一种登录方式,然后以特定用户身份执行操作.该网站是多年前建立的,不一定采用当时的最佳实践,因此不幸的是,我在如何做到这一点方面受到了一些限制.所有这些都需要在 PHP 5.4 和 MySQL 5.6 中运行.

What I now need to add to this API is a way to login, and then perform actions as a specific user. The site has been built years ago and not necessarily with the best practices at the time, so I am unfortunately a bit restricted in how I do this. All of this needs to run in PHP 5.4 with MySQL 5.6.

我一直在阅读有关此的常见设计,OAuth1/2 看起来是最常见的标准.然而,这对我的目的来说似乎太过分了,因为它具有我不需要的各种功能,而且实现起来似乎非常复杂.

I have been reading up on common designs for this and OAuth1/2 looks like the most common standard. However, this seems like massive overkill for my purposes, since it has various features that I do not need and seems very complicated to implement.

相反,我打算做这样的事情:

Instead, I am planning on just doing something like this:

  • 客户端调用 get_session API 端点,该端点生成随机会话 ID,将其保存到数据库中的表中,并将其返回给客户端.
  • 客户端保存此会话 ID.
  • 然后客户端通过向 login 端点发送请求、发送用户名、密码和会话 ID(显然是通过 HTTPS)来进行身份验证.
  • 服务器将数据与用户表进行比较,如果登录正确,则更新会话表以将会话 ID 与相应的用户 ID 相关联.这需要以某种方式限制速率以防止暴力破解.
  • 现在客户端可以调用仅提供其会话 ID 进行授权的任何其他端点.
  • 在每个请求中,服务器都会查找会话 ID,查看它与哪个用户关联并执行正确的操作.
  • 客户端可以记住会话 ID 以备将来使用,直到它被手动删除或在一段时间后过期.
  • 为了注销,客户端向 logout 端点发送请求,服务器删除与用户帐户的关联.
  • The client calls a get_session API endpoint, which generates a random session ID, saves that to a table in the database and returns it to the client.
  • The client saves this session ID.
  • Then the client authenticates by sending a request to the login endpoint, sending the username, password and session ID (via HTTPS obviously).
  • The server compares the data to the user table and, if the login is correct, updates the session table to associate the session ID with the corresponding user ID. This needs to be rate-limited in some way to prevent brute forcing.
  • Now the client can call any other endpoints providing only its session ID for authorization.
  • On each request, the server looks up the session ID, sees which user it has been associated with and performs the correct action.
  • The client can remember the session ID for future use, until it either gets removed manually or expires after some amount of time.
  • To log out, the client sends a request to the logout endpoint and the server removes the association with the user account.

这是一个合理的设计吗?这显然不是很复杂,但我正在寻找可以实现的东西,而不会带来巨大的麻烦或需要第三方库.

Is this a reasonable design? It's obviously not very sophisticated, but I am looking for something that I can implement without a huge hassle or requiring third-party libraries.

推荐答案

REST 作为一个概念的主要观点之一是避免使用会话状态,以便更轻松地水平扩展 REST 端点的资源.如果您计划使用问题中概述的 PHP 的 $_SESSION,您会发现自己处于一个困难的境地,必须在要扩展的情况下实现共享会话存储.

One of the major points of REST as a concept is to avoid the use of session state so that it's easier to scale the resources of your REST endpoint horizontally. If you plan on using PHP's $_SESSION as outlined in your question you're going to find yourself in a difficult position of having to implement shared session storage in the case you want to scale out.

虽然 OAuth 将是您想要执行的操作的首选方法,但完整的实现可能比您想投入的工作要多.但是,您可以做出一些半途而废的事情,并且仍然保持会话-较少的.您以前甚至可能见过类似的解决方案.

While OAuth would be the preferred method for what you want to do, a full implementation can be more work than you'd like to put in. However, you can carve out something of a half-measure, and still remain session-less. You've probably even seen similar solutions before.

  1. 在配置 API 帐户时生成 2 个随机值:令牌和秘密.
  2. 当客户提出请求时,他们会提供:
    • 明文形式的令牌.
    • 根据唯一但已知的值和 Secret 计算出的值.例如:HMAC 或加密签名

通过这种方式,您可以保持无会话"REST 的理想状态,而且您在交换的任何部分都不会真正传输 Secret.

In this way you maintain the "sessionless" REST ideal, and also you never actually transmit the Secret during any part of the exchange.

客户端示例:

$token  = "Bmn0c8rQDJoGTibk";                 // base64_encode(random_bytes(12));
$secret = "yXWczx0LwgKInpMFfgh0gCYCA8EKbOnw"; // base64_encode(random_bytes(24));
$stamp  = "2017-10-12T23:54:50+00:00";        // date("c");
$sig    = hash_hmac('SHA256', $stamp, base64_decode($secret));
// Result: "1f3ff7b1165b36a18dd9d4c32a733b15c22f63f34283df7bd7de65a690cc6f21"

$request->addHeader("X-Auth-Token: $token");
$request->addHeader("X-Auth-Signature: $sig");
$request->addHeader("X-Auth-Timestamp: $stamp");

服务器示例:

$token  = $request->getToken();
$secret = $auth->getSecret($token);
$sig    = $request->getSignature();

$success = $auth->validateSignature($sig, $secret);

值得注意的是,如果决定使用时间戳作为随机数,您应该只接受最近几分钟内生成的时间戳,以防止重放攻击.大多数其他身份验证方案将在签名数据中包含其他组件,例如资源路径、标头数据的子集等,以进一步锁定签名,使其仅适用于单个请求.

It's worth noting that if decide to use a timestamp as a nonce you should only accept timestamps generated within the last few minutes to prevent against replay attacks. Most other authentication schemes will include additional components in the signed data such as the resource path, subsets of header data, etc to further lock down the signature to only apply to a single request.

当这个答案最初是在 2013 年编写时,JWT 还很新,[我没有听说过],但到 2020 年,它们已经牢固地确立了自己的实用性.下面是一个手动实现的例子来说明它们的简单性,但是有大量的库可以为您进行编码/解码/验证,可能已经融入您选择的框架中.

When this answer was originally written in 2013 JWTs were quite new, [and I hadn't heard of them] but as of 2020 they've solidly established their usefulness. Below is an example of a manual implementation to illustrate their simplicity, but there are squillions of libs out there that will do the encoding/decoding/validation for you, probably already baked into your framework of choice.

function base64url_encode($data) {
  $b64 = base64_encode($data);
  if ($b64 === false) {
    return false;
  }
  $url = strtr($b64, '+/', '-_');
  return rtrim($url, '=');
}

$token  = "Bmn0c8rQDJoGTibk";                 // base64_encode(random_bytes(12));
$secret = "yXWczx0LwgKInpMFfgh0gCYCA8EKbOnw"; // base64_encode(random_bytes(24));

// RFC-defined structure
$header = [
    "alg" => "HS256",
    "typ" => "JWT"
];

// whatever you want
$payload = [
    "token" => $token,
    "stamp" => "2020-01-02T22:00:00+00:00"    // date("c")
];

$jwt = sprintf(
    "%s.%s",
    base64url_encode(json_encode($header)),
    base64url_encode(json_encode($payload))
);

$jwt = sprintf(
    "%s.%s",
    $jwt,
    base64url_encode(hash_hmac('SHA256', $jwt, base64_decode($secret), true))
);

var_dump($jwt);

产量:

string(167) "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6IkJtbjBjOHJRREpvR1RpYmsiLCJzdGFtcCI6IjIwMjAtMDEtMDJUMjI6MDA6MDArMDA6MDAifQ.8kvuFR5xgvaTlOAzsshymHsJ9eRBVe-RE5qk1an_M_w"

和可以是由任何人验证了附着到标准,这是非常受欢迎的 atm.

and can be validated by anyone that adheres to the standard, which is pretty popular atm.

无论如何,大多数 API 将它们添加到标头中:

Anyhow, most APIs tack them into the headers as:

$request->addHeader("Authorization: Bearer $jwt");

这篇关于为 PHP REST API 实现简单的身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆