且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

为PHP REST API实现简单身份验证

更新时间:2023-09-07 18:06:22

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");