整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

使用 API 代理服务器隐藏您的 API 密钥

文/教程的主要目的是演示如何通过使用 NodeJS 构建 API 代理服务器来隐藏公共 API 密钥。


在这篇文章/教程中,我们将使用 NodeJS 创建一个 API 代理服务器,这背后的主要原因是向您展示如何隐藏公共 API 密钥,而不是像我过去那样公开它们。我们所有人都创建了一个应用程序,只是为了了解一个新的库或框架,而实现这一点的最佳方法是使用开放 API(如TMDB)。

这些开放 API 中的大多数都需要在发出请求之前在请求 URL 或标头中添加 API 密钥。无论哪种方式,API 密钥都可能被实际上不拥有此密钥的人窃取和使用。因此,您最终可能会被暂停帐户,或者您的应用程序无法按预期运行。

请记住:API 密钥属于应用程序的服务器端。

话不多说,让我们创建这个 API 代理服务器。为了这篇文章/教程的缘故,我们将使用Wea​ther API。

要求

在开始编码之前,我们需要具备以下条件:

  • API 密钥
    打开 OpenWeatherMapAPI 网站,创建一个免费帐户,转到
    我的 API 密钥并创建一个。
  • JavaScript和NodeJS的基础知识
  • java

依赖项和脚本

创建应用程序的项目文件夹

cd ~/Documents/tutorials
mkdir api-proxy-server && cd api-proxy-server
npm init -y


安装所需的 NPM 包


npm install -S express cors dotenv axios
npm install -D nodemon # for faster development


在 package.json 文件中准备脚本

JSON

{
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon --config nodemon.json"
  }
}


让我们看一下项目的文件夹和文件,以便更清晰地了解结构。

server/
├── src
│   ├── utils
│   │   └── env.js
│   └── routes
│       └── index.js
├── package.json
├── nodemon.json
├── index.js
└── .env
3 directories, 6 files


在项目的根目录下创建一个index.js和一个文件。nodemon.json

JSON

// nodemon.json
{
  "verbose": true,
  "ignore": ["node_modules/"],
  "watch": ["./**/*"]
}


在我们开始编辑 entry ( index.js) 文件之前,我们需要创建另外两个有用的文件。第一个是.env我们所有环境变量所在的文件。它将有点可重用,以便能够与除 OpenWeatherMap API 之外的其他公共 API 一起使用。

API_PORT = 1337
API_BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
API_KEY = "1d084c18063128c282ee3b41e91b6740" # not actually api key; use a valid one


另一个文件是src/utils/env.js负责读取和导出所有环境变量的文件。

JavaScript

require("dotenv").config();

module.exports = {
  port: Number(process.env.API_PORT) || 3000,
  baseURL: String(process.env.API_BASE_URL) || "",
  apiKey: String(process.env.API_KEY) || "",
};


现在是时候编辑主文件并向这个代理服务器应用程序添加一些逻辑了。

JavaScript

// imports
const express = require("express");
const cors = require("cors");
const env = require("./src/utils/env");

// Application
const app = express();

// Middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());

// Routes
app.use("/api", require("./src/routes")); // Every request that starts with /api will be handled by this handler

// This route will handle all the requests that are not handled by any other route handler
app.all("*", (request, response, next) => {
  return response.status(404).json({ message: "Endpoint not found!" });
});

// Bootstrap server
app.listen(env.port, () => {
  console.log(`Server is up and running at http://localhost:${env.port}`);
});


这里的事情很简单;它是一个基本的 ExpressJs 服务器应用程序。在顶部,我们有所需的导入。然后我们有一些需要的中间件,例如JSON中间件,它负责解析带有 JSON 有效负载的传入请求,并且基于body-parser. 接下来,我们在此处管理端点及其处理程序的路由。最后,我们得到了服务器的引导。

让我们看一下/api路由的处理程序。

JavaScript

// server/src/routes/index.js
const router = require("express").Router();
const { default: axios } = require("axios");
const env = require("../utils/env");

// [GET] Current weather data
router.get("/", async (request, response, next) => {
  try {
    const query = request.query || {}; 
    const params = {
      appid: env.apiKey, // required field
      ...query,
    };
    const { data } = await axios.get(env.baseURL, { params });

    return response.status(200).json({
      message: "Current weather data fetched!",
      details: { ...data },
    });
  } catch (error) {
    const {
      response: { data },
    } = error;
    const statusCode = Number(data.cod) || 400;
    return response
      .status(statusCode)
      .json({ message: "Bad Request", details: { ...data } });
  }
});

module.exports = router;


请求处理函数将GET我们在调用它时提供的查询字段作为输入。

例如,如果我们没有这个 API 代理服务器,我们的请求看起来像这样https://api.openweathermap.org/data/2.5/weather?q=Thessaloniki,Greece&appid={API key},但是现在我们已经有了它,我们的请求看起来像这样http://localhost/api?q=Thessaloniki,Greece。不需要添加必填字段appid,因为代理服务器自己将其添加为请求参数。当我们在 OpenWeatherMap API 上发出请求时,我们尽量不显示 API 密钥。

概括

主要概念是将您的公共 API 密钥隐藏在 API 代理服务器后面,以防止用户窃取您的密钥。为了实现这一点,它是创建端点来包装实际开放 API 的端点,在需要此密钥的每个请求中提供代理服务器内的 API 密钥作为环境变量。这种方法的明显缺点是这是一个耗时的过程,在某些情况下甚至不需要。

您可以在此处获取此 API 代理服务器的源代码。

下一步是什么

我正在考虑向这个代理服务器添加一些功能。其中之一是添加缓存功能,以便更快地提供最近请求的数据。另一种是添加速率限制器,以防止用户过度使用 API 并导致服务器崩溃。

如果您想到了另一种隐藏 API 密钥的解决方案,或者您有任何提示或技巧要告诉我,请发表评论。

效果图:

2 html骨架:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <!--网页标题-->
    <title>detection</title>
    <!--内部css部分-->
    <style>
    ......
    </style>
</head>

<body>
    <!--div部分-->
    <div class="contain">
    ......
    </div>

    <!--内部js部分-->
    <script>    
      ......
    </script>
</body>
</html>

3 css部分:

 <!--内部css部分-->
    <style>
        /*网页和body整体设置*/
        html,body{
            margin:0;
            padding:0;
            /*网页背景颜色设置*/
            background-color: rgb(96, 94, 212);
        }
        
        /*class='contain'在css的前面有点*/
        .contain{
            width:200px;
            height: 200px;
            font-size:25px;
            text-align: center;
            position: absolute;
            left:50%;
            top:50%;
            transform:translate(-50%,-50%);
            z-index:30;
        }

        /*警示label和结果label*/
        .alertInfo,.resultInfo{
            color:rgb(12, 231, 213);
            font-weight: bold;
            /*可以写在这里,注意数字和px紧紧相邻*/
            width: 350px;
            height: 10px;

        }
        
        /*输入框的设置*/
        .text{
            width:150px;
            /*文本框默认显示,下面就是不显示*/
            /*outline:none;*/ 
            text-align: center;
            font-size:25px;
            color:blue;
        }

        /*显示框的设置*/
        .show{
            font-size:25px;
            color:red;
        }
    </style>

4 body的div部分:

    <!--div部分-->
    <div class="contain">
        <!--注意:style可以嵌套在里面,但不推荐,也可以单独写在上面的css内-->
        <!--p class="alertInfo" style="width: 350px; height: 10px">显示倒5个字符串的输入框:</!--p-->
        <p class="alertInfo" >显示倒5个字符串的输入框:</p>
        <!--placeholder是指输入框默认显示文字-->
        <input type="text" class="text" placeholder="请输入内容">
        <!--p class="resultInfo" style="width: 350px; height: 10px">显示倒4个字符串的显示区:</!--p-->
        <p class="resultInfo" >显示倒4个字符串的显示区:</p>
        <p class="show"></p>
    </div>

5 body的js=JavaScript=script部分:

    <!--内部js部分-->
    <script>
        // 被let声明的变量不会作为全局对象window的属性,而被var声明的变量却可以
        //text和show均是class,所以前面有一个点
        let input=document.querySelector(".text");
        let show= document.querySelector(".show");
        input.addEventListener('keyup',debounce(handle,100));
        // 防抖处理
        function debounce(func,wait){
            let timeflag;
            return function(){
                clearTimeout(timeflag); //清除100ms之内之前触发的定时器。
                let arg=arguments;
                let timethis = this;
                timeflag = setTimeout(func.bind(timethis,arg),wait);
            }
        }
        //回调函数
        function handle(){
            //输入框内倒取5个字符串
            input.value=input.value.slice(-5);
            //输入框内倒取4个字符串
            show.textContent=input.value.slice(-4);
            
        }
    </script>

6 html部分基础学习,自己整理并分享出来。

文将介绍验证码的历史与发展、验证码破解的历史与发展,验证码破解全流程实战。

验证码的历史与发展

验证码,全称为“Completely Automated Public Turing test to tell Computers and Humans Apart”,即全自动区分计算机和人类的图灵测试,Captcha。早在上个世纪90年代,为了防止恶意的网络机器人行为,像邮件轰炸、暴力破解密码等,验证码应运而生。

最初的验证码是简单的文本字符,如用户只需输入一组扭曲的字母和数字。然后验证码发展到图像验证码,例如,要求用户识别哪些图片中包含某个特定对象(比如猫、狗或汽车等)。随着技术的发展,更为复杂的验证码类型出现了,例如逻辑验证码(例如,3+4=?),音频验证码(用户必须听音频然后输入听到的字符)和3D验证码(用户需要解读3D对象或者场景)。

此外,也有一些新的验证码设计,为了提高用户体验同时维护网站安全,它们需要用户进行更为人性化的操作。例如,滑动验证码让用户通过滑动解锁,点击验证码让用户点击特定的图片或文字,旋转验证码则要求用户调整图片到正确的方向。

一些大公司也开发了自己的验证码系统。例如,Google的reCAPTCHA v2引入了复杂的图像识别任务,需要用户选择包含特定物体(如汽车,交通灯)的图片;而Google的reCAPTCHA v3则摒弃了用户交互的方式,通过分析用户的行为模式来确定是人类还是机器。同样,第三方验证服务如GeeTest CAPTCHA和hCaptcha等,也为网站提供了验证服务,使得他们可以更好地防止自动化的恶意行为。


验证码破解的历史与发展

验证码破解的历史,与验证码的发展紧密相连。早期的验证码破解主要依赖于OCR(Optical Character Recognition,光学字符识别)技术,这是一种将图像中的文本转换为机器可读的字符的技术,用于识别简单的文本验证码。

然而,随着验证码的复杂性的增加,验证码破解也需要更为复杂的技术。例如,对于图像验证码,可能需要使用图像处理技术来处理噪声和扭曲。这可能包括灰度化(将图像转换为黑白),二值化(将图像进一步简化为只有黑和白两种颜色),边缘检测(识别图像中的边缘)等步骤。

对于更为复杂的验证码,例如点击验证码和旋转验证码,可能需要使用更复杂的机器视觉技术。这可能涉及到特征提取(识别图像中的重要特征),对象识别(识别特定的对象或形状),甚至深度学习(训练模型来识别复杂的模式)。

近年来,随着人工智能的发展,机器学习和深度学习等技术也被应用于验证码破解中。例如,卷积神经网络(CNN)已经被用来识别复杂的图像验证码,而递归神经网络(RNN)可以用于识别音频验证码。这些模型通过在大量的数据上进行训练,可以学习到识别验证码的复杂模式,大大提高了验证码破解的准确性和效率。


新时代高精准识别验证码的人工服务

人工验证码识别服务是一种基于人工智能或人工劳动力的验证码识别解决方案。当机器无法识别复杂的验证码时,这种服务能够提供相对高效且准确的解决方案。

2Captcha

2Captcha是一种基于人工劳动力的验证码识别服务。它提供了一个API接口,允许开发者将无法识别的验证码发送到2Captcha服务。然后2Captcha的工人会手动识别并返回结果。这种服务对处理图像验证码、文本验证码、点击类验证码、GeeTest、reCAPTCHA、FunCaptcha等复杂验证码有很高的准确率,并且提供多种编程语言的接口文档Python、PHP、Java、Go、Ruby、C++、C#。2Captcha的主要优点是其优异的精确性和灵活的API,使得开发者可以轻松集成并在不同环境中使用。

云码

云码基于图像识别技术和人工辅助提供验证码识别服务,提供在线普通图片、滑动、点选、谷歌、HCaptcha、数字计算题验证码识别服务。其对于图像类的验证码有比较好的效果,尤其是各种不同类型的图像验证码。但其对于复杂的验证码存在准确率下降和识别时间较长的情况、验证码种类跟进相较也会慢一些。

冰拓

冰拓可识别各种常见图片验证码,AI识别 + 真人识别双模式,可高效识别坐标题、计算题、字符题、滑块题、拼图题等各种图片。API支持Python、JAVA、PHP、JAVASCRIPT调用,支持按键精灵集成。对于多样化的滑块、拼图、旋转、坐标有自己独特的处理方法和提供定制服务,不支持谷歌验证码。

超级鹰

超级鹰是专业的人工打码平台,对图片数据进行精准、快速分类处理,并实时返还分类结果。支持英文数字、中文汉字、坐标选择计算等多种类型图片验证码,并且提供定制化的验证码识别服务。对于通用的验证码、传统验证码有较好的识别效果,但对于复杂验证码尚未提供更多服务。


验证码破解实战

以2Captcha破解reCAPTCHA v2为例

1. 注册2Captcha,https://cn.2captcha.com/ ,支持支付宝充值

2. 目标破解https://www.scrapebay.com/spam 网站reCAPTCHA v2

3. 拿到2Captcha API_KEY

4. 拿到google sitekey

5. 破解验证码

安装2captcha-python

pip3 install 2captcha-python

破解验证码

# 导入BeautifulSoup、TwoCaptcha、requests库
from bs4 import BeautifulSoup
from twocaptcha import TwoCaptcha
import requests

# TwoCaptcha服务的API秘钥,你需要使用自己的
API_KEY = 'xxxxxxxxxxxxxx'
# 利用TwoCaptcha库,使用提供的API秘钥初始化一个solver对象,该对象可以解决ReCAPTCHA问题
solver = TwoCaptcha(API_KEY)
# 要抓取的网页的URL
url = "https://www.scrapebay.com/spam"
# 这是ReCAPTCHA的site key,可以从网页源码中找到。
site_key='6LfGNEoeAAAAALUsU1OWRJnNsF1xUvoai0tV090n'

# 这个函数用来获取CSRF token和cookies。它首先通过requests.get()获取页面内容,然后通过BeautifulSoup找到CSRF token。最后返回CSRF token和cookies。
def get_csrf_cookie(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, "lxml")
    csrf_el = soup.select_one('[name=csrfmiddlewaretoken]')
    csrf = csrf_el['value']
    cokkies = response.cookies
    return csrf, cokkies

# 这个函数用来解决ReCAPTCHA问题。它使用TwoCaptcha solver对象的recaptcha()方法,如果发生异常则打印错误并退出。
def solve(url,sitekey):
    try:
        result = solver.recaptcha(sitekey=sitekey, url=url)
    except Exception as e:
        print(e)
        exit()
    return result

# 首先通过get_csrf_cookie(url)获取CSRF token和cookies,然后通过solve(url,site_key)解决ReCAPTCHA问题,获得ReCAPTCHA的验证码结果
def main():
    csrf,cokkies = get_csrf_cookie(url)
    print("csrf:",csrf)
    print("cokkies:",cokkies)
    result = solve(url,site_key)
    print("captcha:",result)


if __name__ == "__main__":
    main()

运行结果:

6. 获得验证码后的页面数据

包含破解验证码的全部代码如下:

# 导入BeautifulSoup、TwoCaptcha、requests库
from bs4 import BeautifulSoup
from twocaptcha import TwoCaptcha
import requests

# 2Captcha服务的API秘钥,你需要使用自己的
API_KEY = 'xxxxxxxxxxxxxx'
# 利用TwoCaptcha库,使用提供的API秘钥初始化一个solver对象,该对象可以解决ReCAPTCHA问题
solver = TwoCaptcha(API_KEY)
# 要抓取的网页的URL
url = "https://www.scrapebay.com/spam"
# 这是ReCAPTCHA的site key,可以从网页源码中找到。
site_key='6LfGNEoeAAAAALUsU1OWRJnNsF1xUvoai0tV090n'

# 这个函数用来获取CSRF token和cookies。它首先通过requests.get()获取页面内容,然后通过BeautifulSoup找到CSRF token。最后返回CSRF token和cookies。
def get_csrf_cookie(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, "lxml")
    csrf_el = soup.select_one('[name=csrfmiddlewaretoken]')
    csrf = csrf_el['value']
    cokkies = response.cookies
    return csrf, cokkies

# 这个函数用来解决ReCAPTCHA问题。它使用TwoCaptcha solver对象的recaptcha()方法,如果发生异常则打印错误并退出。
def solve(url,sitekey):
    try:
        result = solver.recaptcha(sitekey=sitekey, url=url)
    except Exception as e:
        print(e)
        exit()
    return result

# 这个函数用来提交解决ReCAPTCHA后的页面。它首先构建一个POST请求的payload,然后通过requests.post()方法发送请求。最后返回网页的最后一列的文本。
def post_page(url, csrf, cookie, result):
    payload = 'csrfmiddlewaretoken={}&g-recaptcha-response={}'
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Referer': 'https://www.scrapebay.com/spam'
    }
    response = requests.post(url,data=payload.format(csrf,result),headers=headers,cookies=cookie)
    soup = BeautifulSoup(response.text, "lxml")
    el = soup.select_one('td:last-child')
    return el.get_text()

# 先通过get_csrf_cookie(url)获取CSRF token和cookies,然后通过solve(url,site_key)解决ReCAPTCHA问题,最后通过post_page(url,csrf,cokkies,result)提交页面并打印出结果。
def main():
    csrf,cokkies = get_csrf_cookie(url)
    print("csrf:",csrf)
    print("cokkies:",cokkies)
    result = solve(url,site_key)
    print("captcha:",result)
    data = post_page(url,csrf,cokkies,result)
    print("result:",data)

if __name__ == "__main__":
    main()

网站验证后的页面:

运行结果:


7. 结束

至此我们使用2Captcha服务破解了reCAPTCHA v2,并获得了需要爬取的内容。2Captcha服务包含多种验证码格式,均可以使用上述的流程,修改其中不同验证码的细节部分,攻克验证码的识别难点。