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

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

问题描述

我正在努力将REST API添加到旧版PHP网站.这是为内部应用程序提供一个终结点,因此我在设计事物的方式以及支持和不支持的东西方面完全自由.

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的方法是登录,然后以特定用户身份执行操作.该网站建于多年前,并不一定具有当时的最佳实践,因此很遗憾,我的操作方式受到限制.所有这些都需要在带有MySQL 5.6的PHP 5.4中运行.

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. 客户提出请求时,他们会提供:
    • 令牌,以纯文本格式.
    • 根据唯一但已知的值和秘密"计算得出的值.例如:HMAC或密码签名

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

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天全站免登陆