为 PHP REST API 实现简单的身份验证
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/46719676/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Implementing simple authentication for PHP REST API
提问by Nils
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.
我正在向旧版 PHP 站点添加 REST API。这是为内部应用程序提供一个端点,所以我在如何设计事物以及支持和不支持的方面非常自由。
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.
我现在需要添加到此 API 的是一种登录方式,然后以特定用户身份执行操作。该网站是多年前建立的,不一定采用当时的最佳实践,因此不幸的是,我在如何做到这一点方面受到了一些限制。所有这些都需要在 PHP 5.4 和 MySQL 5.6 中运行。
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.
我一直在阅读有关此的常见设计,OAuth1/2 看起来是最常见的标准。但是,对于我的目的来说,这似乎是一种过度杀伤,因为它具有我不需要的各种功能,而且实现起来似乎非常复杂。
Instead, I am planning on just doing something like this:
相反,我打算做这样的事情:
- 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.
- 客户端调用
get_session
API 端点,该端点生成随机会话 ID,将其保存到数据库中的表中并将其返回给客户端。 - 客户端保存此会话 ID。
- 然后客户端通过向
login
端点发送请求、发送用户名、密码和会话 ID(显然是通过 HTTPS)来进行身份验证。 - 服务器将数据与用户表进行比较,如果登录正确,则更新会话表以将会话 ID 与相应的用户 ID 相关联。这需要以某种方式限制速率以防止暴力破解。
- 现在,客户端可以调用仅提供其会话 ID 进行授权的任何其他端点。
- 对于每个请求,服务器查找会话 ID,查看它与哪个用户关联并执行正确的操作。
- 客户端可以记住会话 ID 以备将来使用,直到它被手动删除或在一段时间后过期。
- 要注销,客户端向
logout
端点发送请求,服务器删除与用户帐户的关联。
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.
这是一个合理的设计吗?它显然不是很复杂,但我正在寻找可以在没有巨大麻烦或不需要第三方库的情况下实现的东西。
回答by Sammitch
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.
REST 作为一个概念的主要观点之一是避免使用会话状态,以便更轻松地水平扩展 REST 端点的资源。如果您计划使用$_SESSION
问题中概述的PHP ,您会发现自己处于一个困难的境地,必须在要扩展的情况下实现共享会话存储。
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.
虽然 OAuth 将是您想要执行的操作的首选方法,但完整的实现可能比您想投入的工作更多。但是,您可以制定一些半措施,并且仍然保持无会话。您以前甚至可能见过类似的解决方案。
- When an API account is provisioned generate 2 random values: a Token and a Secret.
- When a client makes a request they provide:
- The Token, in plaintext.
- A value computed from a unique, but known value, and the Secret. eg: an HMAC or a cryptographic signature
- The REST endpoint can then maintain a simple, centralized key-value store of Tokens and Secrets, and validate requests by computing the value.
- 配置 API 帐户时,生成 2 个随机值:令牌和秘密。
- 当客户提出请求时,他们提供:
- 令牌,以明文形式。
- 根据唯一但已知的值和 Secret 计算出的值。例如:HMAC 或加密签名
- 然后,REST 端点可以维护一个简单、集中的 Tokens 和 Secrets 键值存储,并通过计算值来验证请求。
In this way you maintain the "sessionless" REST ideal, and also you never actually transmit the Secret during any part of the exchange.
通过这种方式,您可以保持“无会话”REST 的理想状态,而且您在交换的任何部分都不会真正传输 Secret。
Client Example:
客户端示例:
$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");
Server Example:
服务器示例:
$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.
值得注意的是,如果决定使用时间戳作为随机数,您应该只接受最近几分钟内生成的时间戳,以防止重放攻击。大多数其他身份验证方案将在签名数据中包含其他组件,例如资源路径、标头数据的子集等,以进一步锁定签名以仅适用于单个请求。
2020 Edit: This is basically what JSON Web Tokens [JWT]are.
2020 编辑:这基本上就是JSON Web 令牌 [JWT]。
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.
当这个答案最初是在 2013 年编写时,JWT 还很新,[而且我没有听说过它们],但到 2020 年,它们已经牢固地确立了它们的实用性。下面是一个手动实现的例子来说明它们的简单性,但是有大量的库可以为您进行编码/解码/验证,可能已经融入您选择的框架中。
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);
Yields:
产量:
string(167) "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6IkJtbjBjOHJRREpvR1RpYmsiLCJzdGFtcCI6IjIwMjAtMDEtMDJUMjI6MDA6MDArMDA6MDAifQ.8kvuFR5xgvaTlOAzsshymHsJ9eRBVe-RE5qk1an_M_w"
and can be validated by anyonethat adheres to the standard, which is pretty popular atm.
Anyhow, most APIs tack them into the headers as:
无论如何,大多数 API 将它们添加到标头中:
$request->addHeader("Authorization: Bearer $jwt");
回答by Alejandro Iván
I would say you should generate a unique token and use that for communication. Basically:
我会说您应该生成一个唯一的令牌并将其用于通信。基本上:
- The client sends username/password to the
login
resource. - The server verifies the username/password combination. If it's correct, it generates a unique toquen, saves it in the
sessions
table and sends it back to the user, along with a status update likelogged_in = TRUE
. - Now, every other request sent by the user should include a
token
field (either as aPOST
field or aGET
parameter). At this point, I would re-consider using REST and only usePOST
requests for everything, with theoperation
as a POST field. That would not add the token to the URL and, thus, letting it be registered on a web browsing historial, routers and stuff. - On every request, the server should check if the token exists and it's valid. If not, simply return an error message like
403 Forbidden
andlogged_in = FALSE
.
- 客户端向
login
资源发送用户名/密码。 - 服务器验证用户名/密码组合。如果它是正确的,它会生成一个唯一的toquen,将其保存在
sessions
表中并将其发送回用户,以及状态更新,如logged_in = TRUE
. - 现在,用户发送的每个其他请求都应该包含一个
token
字段(作为POST
字段或GET
参数)。在这一点上,我会重新考虑使用 REST,并且只使用POST
请求operation
作为 POST 字段。这不会将令牌添加到 URL 中,因此,让它在网络浏览历史记录、路由器和其他东西上注册。 - 对于每个请求,服务器都应该检查令牌是否存在且有效。如果没有,只需返回一条错误消息,如
403 Forbidden
andlogged_in = FALSE
。
The system could also require to send another data to make it more secure, like a client-generated unique id
and stuff like that, which should be sent with the token and checked server-side.
系统还可能需要发送另一个数据以使其更安全,例如客户端生成的数据unique id
和类似的东西,这些数据应该与令牌一起发送并在服务器端进行检查。
回答by Fritz M. Neumann
The points in your plan are essentially the basic features of OAuth. It depends on your requirements. If your APIs are for internal use only, you could send a secret key with HMAC-SHA authentication.
您计划中的要点本质上是 OAuth 的基本功能。这取决于您的要求。如果您的 API 仅供内部使用,您可以发送带有 HMAC-SHA 身份验证的密钥。
回答by user1597430
Well you wrote a lot how to invent cookies from the scratch and how to store them in the database.
好吧,你写了很多如何从头开始发明 cookie 以及如何将它们存储在数据库中。
In the same time you already have usernames, passwords and HTTPS for security data transfer. Why won't you use just cookies?
同时,您已经拥有用于安全数据传输的用户名、密码和 HTTPS。你为什么不只使用cookies?