创建 PHP API:检查 API 请求来自哪个服务器
我正在为网站创建 PHP API,并且希望限制对我们服务器上注册的域的 API 访问(以防止滥用 API 使用)。所以,这就是我现在的方法,而且,它在纸面上看起来应该很不错。
- API 在
api.example.com
上设置。 - 想要使用 API 的用户向我们注册,添加他的域并获取 API 密钥。
- API 的用户将使用其 API 密钥加密其请求数据(通过
mcrypt
)并通过cURL
将其发送到api.example.com.
- 我的服务器检查此 API 请求来自哪个域,并将该域与数据库中的 API 密钥进行匹配。如果存在 API 密钥,API 会使用该密钥通过
mcrypt
解密请求,然后使用相同的方法加密并发送结果。
我被困在第 4 步。最初,我计划使用 HTTP_REFERER 来检查它,但由于 cURL 默认情况下不发送,并且它很容易在用户端代码中伪造(据我所知是 CURLOPT_REFERER),我被困在这里了。
有没有办法知道这个API请求来自哪个域?我发现可以使用一些流行的 API(例如 reCAPTCHA)来完成此操作。检查 _SERVER["REMOTE_HOST"] 并不是真正的选择,因为共享主机(它们具有相同的 IP),因此这无法防止滥用(无论如何,这主要来自共享服务器)。
有这样的方法可以检查吗?谢谢!
I'm creating a PHP API for a website and I'd want to restrict the API access to domains that are registered on our server (in order to prevent abusing of API usage). So, this is my approach right now, and well, it should look pretty good on paper.
- The API is setup at
api.example.com
. - A user that wants to use the API registers with us, adds his domain and gets an API key.
- The user of the API will use his API key to encrypt his request data (via
mcrypt
) and sends it, viacURL
toapi.example.com
. - My server checks from which domain this API request comes from and matches that domain to an API key in the database. If there is an API key, the API decrypts the request via
mcrypt
with that key and then using the same method encrypts and sends the result.
I'm stuck on step 4. Originally, I planned to use HTTP_REFERER to check it, but since cURL doesn't send one by default and it could be easily faked in the user-side code (CURLOPT_REFERER as far as I remember), I am stuck here.
Is there a method to know from which domain this API request comes from? I see that it can be done with some popular APIs like the reCAPTCHA one. Checking the _SERVER["REMOTE_HOST"] isn't really an option because of shared hosts (they have the same IPs) so this would not be able to prevent abuse (which would originate mostly from shared servers anyway).
Is there such a method to check for it? Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
@Shafee 有一个好主意,只是需要一些调整。我们关注 API 调用的可见部分,即 API 密钥。这在 URL 中可见,并告诉 API 谁正在请求数据。我们可以“只是添加”另一个密钥,而不是试图阻止其他人窃取此密钥并使用他们拦截的域运行自己的 cURL 调用,而这个密钥对这些拦截器来说是不可见的。我并不是说停止检查请求来自哪里,这仍然是在脚本早期剔除无效请求的好方法,但是使用第二个密钥,您可以保证只有请求数据的人真正知道如何获取数据(您相信他们不会将其泄露给任何人)。
因此,当用户注册密钥时,您实际上为用户分配了两个不同的密钥。
API_KEY - 将您连接到您的域的公钥。系统查找提供的域和密钥,以便找到下一个密钥。
MCRYPT_KEY - 这是将用于通过 Mcrypt 实际加密该数据的密钥。由于它是加密数据,因此只有请求者和服务器才知道它是什么。您使用密钥来加密数据,并将加密的输入与 API 密钥一起发送到服务器,服务器通过提供的 API 密钥和域(和 IP)找到解密该输入所需的密钥。如果他们没有使用正确的密钥加密数据,那么使用正确的密钥解密将返回乱码,并且
json_decode()
调用将返回 NULL,从而允许脚本简单地返回“invalid_input”响应。最终使用这种方法,我们是否还需要检查请求来自哪里(域/IP)?使用这种方法实际上可以归结为 API 用户不会向其他用户泄露他们的 API/MCRYPT 密钥对,类似于不泄露您的用户名/密码。即便如此,任何网站都可以轻松注册以获得自己的密钥对并使用 API。另请注意,API 甚至不会向服务器返回任何有用的信息,除非用户端使用正确的用户名和密码登录,因此他们端已经拥有该信息。我们的服务器真正返回的唯一新内容是成功验证用户后的电子邮件地址。话虽如此,我们还需要使用 cURL 吗?我们不能简单地使用
file_get_contents('http://api.example.com/{$API_KEY}/{$MCRYPT_DATA}')
吗?我意识到我在回答中提出了更多问题......@Shafee has a good idea it just needed some tweaking. We're focusing on the visible part of the API call, which is the API key. This is visible in the URL and tells the API who is requesting the data. Rather than trying to prevent others from stealing this key and running their own cURL call with the domain they intercepted it from, we can 'just add' another key to the mix, this one not visible to those interceptors. I'm not saying stop checking where the request is coming from, it's still a good way to kick out invalid requests early on in the script, but with a second key, you guarantee that only the person requesting the data actually knows how to get the data (you're trusting them not to give it away to anyone).
So, when the user registers for a key, you're actually assigning two different keys to the user.
API_KEY - The public key that connects you to your domain. The system looks up the domain and key provided in order to find the next key.
MCRYPT_KEY - This is the key that will be used to actually encrypt that data via Mcrypt. Since it's encrypted data, only the requester and the server will know what it is. You use the key to encrypt the data and send the encrypted input with your API key to the server, which finds the key that it needs to decrypt that input via the API key and domain (and IP) that have been provided. If they did not encrypt the data with the proper key, then decrypting with the correct key will return gibberish and the
json_decode()
call will return NULL, allowing the script to simply return an 'invalid_input' response.Ultimately with this method, do we even need to check where (domain/IP) the request is coming from? Using this method it really comes down to the API users not giving away their API/MCRYPT key pair to other users, similar to not giving away your username/password. Even so, any website can easily just go sign up to get their own key pair and use the API. Also to note, the API will not even return anything useful to their server unless the user on their end logs in using the correct username and password, so their end will already have that information. The only thing new our server is really returning is their email address upon successful validation of the user. Having said that, do we even need to use cURL? Could we not simply use
file_get_contents('http://api.example.com/{$API_KEY}/{$MCRYPT_DATA}')
? I realize I'm asking more questions in my answer...你可以验证请求来自哪个IP,你通常可以进行ptr搜索来获取该IP的域名,但很可能该IP地址有多个域,你最终会得到错误的一个,所以我建议客户端在请求中发送他的域名,可能是 HTTP_REFERER,并且您进行 dns 检查该域是否指向请求它的 IP,但请注意一个域,例如google.com,可以指向多个IP。
(如果有一些良好的黑客技巧,IP可能会被伪造,但这超出了我的知识范围)
You can varify what ip the request comes from, and you ofen can do a ptr search to get a domain name for that ip, but probely the ip adress have more then one domain, and you end up whit the wrong one, so i recomendate that the client send his domainname in the reques, maybe whit HTTP_REFERER, and that you make a dns check if that domain points to the ip asking for it, but notice that a domain, like google.com, can point to more then one ip.
(the ip could probely be faked to, whit some good hacking skill, but thats out of my knowledge)
引入第二个变量(例如
app id
)怎么样?当用户注册其域时,将此 ID 与域相关联。用户需要在每个未加密的请求中提交app id
以及加密的 API 调用。然后您可以查找应用程序ID
获取应用程序秘密
并尝试解密?How about introducing a second variable like lets say an
app id
. When a user registers her domain, associate this id with the domain. The user needs to submit theapp id
with each request without encryption along with the encrypted api call. Then you can look up theapp id
get theapp secret
and try to decrypt?为了最好地防止 API 被滥用,请限制请求的速度,或限制它们可以发出的请求数量。如果有人愚蠢地分享了他们的 API 密钥,他们只会限制自己的 API 使用,这对于那些打算滥用 API 来获取自己的密钥的人来说会更经济。
另外,如果有人决定使用您的 API 实现桌面应用程序怎么办?他们肯定不会要求用户将 IP 地址发送给他们以便他们将其列入白名单吗?
此外,您还可以结合限制速度/限制请求,并根据请求数量限制速度,例如如果您超过一定的数据使用量,Verizon 如何限制其 3G 网络的速度。
In order to best prevent abuse of your API, limit either the speed of requests, or limit the number of requests they can make. If someone is stupid and shares their API key, they'll only be limiting their own API usage, making it more economical for people who intend on abusing the API to get their own key.
Plus, what if someone decides to implement a desktop application using your API? Surely they won't require their users to send their IP addresses to them so that they can whitelist them?
Also, you can combine limiting speed/limiting requests, and limit speed based on the number of requests like how Verizon limits the speed of their 3G network if you pass a certain amount of data usage.