使用Python优雅地处理带有JWT认证的接口提交

Written by

in

文章目录
  • 在传统的网页爬虫开发中,我们习惯于直接请求目标 URL,解析 HTML,提取数据。然而,随着现代 Web 应用架构的演进,前后端分离(SPA)已成为主流。这意味着数据不再是直接嵌入在 HTML 中,而是通过 API 接口以 JSON 格式交互。为了保护这些接口的安全,开发者往往会设置一道“门卫”——JWT (JSON Web Token)。 对于爬虫工程师而言,理解 JWT 的工作原理是绕过这道门卫的关键。
  • 在掌握了理论基础后,我们需要动手操作。本章节将使用 Python 的 requests 库,演示如何从零开始获取一个 JWT Token。
  • 拿到 Token 后,万里长征走完了一半。现在我们需要将这个 Token 应用到后续的数据提交和获取请求中。
  • 作为一名资深的爬虫开发者,不仅要学会攻,还要懂防。了解 JWT 的弱点,能帮助我们应对更复杂的反爬机制;了解防御策略,则能帮助我们开发更健壮的应用。
  • 目录
    • 第一章:当爬虫遇上“隐形门卫”——JWT 认证机制解析
      • 1.1 什么是 JWT?
      • 1.2 为什么爬虫需要特别关注 JWT?
      • 1.3 JWT 的“双刃剑”对爬虫的影响
    • 第二章:Python 实战:模拟登录与 Token 获取
      • 2.1 准备工作:分析目标 API
      • 2.2 编写获取 Token 的 Python 代码
      • 2.3 常见登录陷阱与应对
    • 第三章:带着“通行证”出发:在请求中注入 JWT
      • 3.1 构造带认证的 Header
      • 3.2 封装为通用爬虫类(Class)
    • 第四章:进阶技巧与安全防御视角
      • 4.1 应对高级防御:Token 存储与刷新
      • 4.2 从防御者角度看 JWT 爬虫
      • 4.3 总结

    在传统的网页爬虫开发中,我们习惯于直接请求目标 URL,解析 HTML,提取数据。然而,随着现代 Web 应用架构的演进,前后端分离(SPA)已成为主流。这意味着数据不再是直接嵌入在 HTML 中,而是通过 API 接口以 JSON 格式交互。为了保护这些接口的安全,开发者往往会设置一道“门卫”——JWT (JSON Web Token)

    对于爬虫工程师而言,理解 JWT 的工作原理是绕过这道门卫的关键。

    JWT 是一种开放标准(RFC 7519),用于在网络应用环境间传递声明。它将用户信息加密成一个 Token,通常由三部分组成:

    1. Header(头部):包含令牌类型和签名算法(如 HMAC SHA256 或 RSA)。
    2. Payload(负载):包含用户身份信息(如用户ID、过期时间)。
    3. Signature(签名):这是安全性的核心。服务器使用 Header 和 Payload,配合只有服务器知道的密钥(Secret)进行签名。

    在传统的 Session/Cookie 机制中,服务器会在客户端保留一个 Session ID,爬虫只需保持 Cookie 即可。但在 JWT 模式下,服务器是无状态的。客户端(浏览器)在登录成功后,会收到一个长字符串(Token),之后的每一次请求,都必须在 HTTP Header 中携带这个 Token(通常是 Authorization: Bearer <token>)。

    如果我们的爬虫忽略了这个机制,直接去请求需要登录的接口,服务器会直接返回 401 Unauthorized,就像被门卫直接拦在门外一样。

    • 优势:Token 可以跨域使用,且不需要服务器查询数据库验证 Session,速度极快。
    • 挑战:Token 有过期时间(exp),一旦过期必须重新获取。此外,为了安全,很多应用会使用 Refresh Token 机制,或者对 Token 进行加密处理(JWE),这大大增加了爬虫模拟的复杂度。

    在掌握了理论基础后,我们需要动手操作。本章节将使用 Python 的 requests 库,演示如何从零开始获取一个 JWT Token。

    假设我们要爬取一个名为 “DataHub” 的虚构平台数据。首先,我们需要打开浏览器的开发者工具(F12),切换到 Network 面板,进行一次真实的登录操作。

    观察结果

    • 请求 URLhttps://api.datahub.com/v1/auth/login
    • 请求方法POST
    • 请求体(Payload){"username": "your_user", "password": "your_password"}

    利用 requests 库,我们可以轻松模拟这个过程。

    import requests
    
    # 1. 定义登录接口和凭证
    login_url = "https://api.datahub.com/v1/auth/login"
    headers = {
        "Content-Type": "application/json",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
    }
    payload = {
        "username": "your_username",
        "password": "your_password"
    }
    
    # 2. 发送登录请求
    try:
        response = requests.post(login_url, json=payload, headers=headers)
        
        # 检查响应状态
        if response.status_code == 200:
            # 3. 解析响应数据
            data = response.json()
            
            # 假设服务器返回的数据结构如下:
            # {"code": 0, "msg": "success", "data": {"access_token": "eyJhbGciOiJIUz...", "refresh_token": "..."}}
            
            access_token = data.get("data", {}).get("access_token")
            
            if access_token:
                print(f"成功获取 Token: {access_token[:20]}...")
            else:
                print("响应中未找到 access_token 字段")
        else:
            print(f"登录失败,状态码: {response.status_code}, 原因: {response.text}")
            
    except Exception as e:
        print(f"发生错误: {e}")
    

    • 验证码(Captcha):如果登录接口带有图形验证码,简单的 requests 就失效了。此时,你需要引入 OCR 库(如 ddddocr)或者接入打码平台。
    • CSRF Token:部分老式网站在登录时会要求携带一个隐藏的 CSRF Token。你需要先请求一次登录页,解析 HTML 获取该 Token,再发起登录。
    • 加密参数:现在的前端为了安全,往往会将密码进行 JS 加密(如 RSA 或 AES)。你需要使用 Python 的 pycryptodome 库复现该加密逻辑,或者直接使用 PyExecJS 执行原生 JS 代码。

    拿到 Token 后,万里长征走完了一半。现在我们需要将这个 Token 应用到后续的数据提交和获取请求中。

    这是最关键的一步。绝大多数 API 规范要求将 Token 放入 HTTP Header 的 Authorization 字段中,格式通常为 Bearer <token>

    import requests
    
    # 假设我们已经获取到了 token
    access_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    
    # 1. 构造请求头
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
        "Accept": "application/json"
    }
    
    # 2. 访问需要认证的接口(例如:提交数据或获取用户信息)
    target_url = "https://api.datahub.com/v1/user/profile"
    # 或者是一个 POST 提交接口
    # target_url = "https://api.datahub.com/v1/data/submit"
    
    try:
        # 发起 GET 请求获取数据
        resp = requests.get(target_url, headers=headers)
        
        if resp.status_code == 200:
            print("数据获取成功:", resp.json())
        elif resp.status_code == 401:
            print("错误:Token 失效或未提供,请重新登录!")
        elif resp.status_code == 403:
            print("错误:Token 有效,但权限不足(Scope 不够)!")
        else:
            print(f"未知错误: {resp.status_code}")
    
    except Exception as e:
        print(f"请求异常: {e}")
    

    为了代码的复用性和可维护性,建议将 Token 管理封装成一个类:

    class JWTCrawler:
        def __init__(self, username, password):
            self.username = username
            self.password = password
            self.base_url = "https://api.datahub.com"
            self.token = None
            self.session = requests.Session() # 使用 Session 保持连接
    
        def login(self):
            """登录并获取 Token"""
            url = f"{self.base_url}/v1/auth/login"
            payload = {"username": self.username, "password": self.password}
            try:
                resp = self.session.post(url, json=payload)
                self.token = resp.json().get("data", {}).get("access_token")
                return True if self.token else False
            except Exception as e:
                print(f"登录失败: {e}")
                return False
    
        def request(self, method, endpoint, **kwargs):
            """通用请求方法,自动注入 Token"""
            if not self.token:
                if not self.login():
                    raise Exception("无法获取有效 Token")
            
            url = f"{self.base_url}{endpoint}"
            headers = kwargs.get("headers", {})
            headers["Authorization"] = f"Bearer {self.token}"
            kwargs["headers"] = headers
            
            response = self.session.request(method, url, **kwargs)
            
            # 处理 Token 过期的情况(通常状态码为 401)
            if response.status_code == 401:
                print("Token 已过期,尝试重新登录...")
                if self.login():
                    # 重新发起请求
                    headers["Authorization"] = f"Bearer {self.token}"
                    kwargs["headers"] = headers
                    response = self.session.request(method, url, **kwargs)
                else:
                    raise Exception("刷新 Token 失败")
                    
            return response
    
    # 使用示例
    # crawler = JWTCrawler("user", "pass")
    # resp = crawler.request("GET", "/v1/data/list")
    # print(resp.json())
    

    作为一名资深的爬虫开发者,不仅要学会攻,还要懂防。了解 JWT 的弱点,能帮助我们应对更复杂的反爬机制;了解防御策略,则能帮助我们开发更健壮的应用。

    • Refresh Token 机制:为了安全,很多应用不会给 Access Token 很长的过期时间(如 15 分钟)。此时,服务器会同时返回一个 Refresh Token(有效期 7 天)。当 Access Token 过期时,客户端应使用 Refresh Token 去请求 /refresh 接口换取新的 Access Token。
    • 爬虫应对:在 JWTCrawler 类中增加 refresh_token 字段,并在 request 方法捕获 401 错误后,优先尝试调用刷新接口,而不是直接重新登录(避免频繁触发登录风控)。
    • HttpOnly Cookie:有些应用虽然使用了 JWT,但为了防止 XSS 攻击,会将 Token 存储在 HttpOnly 的 Cookie 中,而不是 JS 可读的 localStorage。这种情况下,requests 直接读取 JS 变量是行不通的。
    • 爬虫应对:使用 SeleniumPlaywright 等浏览器自动化工具。先用浏览器自动化工具完成登录,然后通过 get_cookies() 提取 Token,再转交给 requests 使用。

    如果你是 API 的开发者,该如何防止他人通过模拟 JWT 爬取数据?

    1. 签名算法选择:严禁使用 None 算法。推荐使用 HS256(对称)或 RS256(非对称)。如果密钥泄露,攻击者可以伪造任何用户的 Token。
    2. 增加黑名单机制:JWT 的特性是“一旦签发,无法撤回”。为了解决这个问题,可以在 Redis 中维护一个 Token 黑名单(用户注销或修改密码时将旧 Token 加入黑名单)。
    3. IP 与 User-Agent 绑定:在 Payload 中加入 IP 或设备指纹信息,并在验证时检查。如果一个 Token 突然在不同的 IP 下使用,判定为异常。
    4. 动态 Token(混淆 Token):像 WeChat 这样的应用,会使用动态 Token,每次请求后 Token 都会变化,或者使用加密的二进制格式,大大增加了逆向难度。

    处理带有 JWT 认证的 Python 爬虫,核心在于模拟“客户端行为”。这不仅仅是发送 HTTP 请求,更是一个对认证流程的逆向工程。

    • 初级阶段:手动抓包,硬编码 Token。
    • 中级阶段:编写登录脚本,自动获取并管理 Token。
    • 高级阶段:处理 Token 过期刷新、应对加密参数、结合浏览器自动化工具解决复杂渲染问题。

    随着反爬技术的升级,单纯的 HTTP 请求越来越难以满足需求,将 requests 的高效与 playwright 的渲染能力相结合,是未来爬虫开发的必然趋势。

    以上就是使用Python优雅地处理带有JWT认证的接口提交的详细内容,更多关于Python处理WT认证接口提交的资料请关注风君子博客其它相关文章!

    您可能感兴趣的文章:

    • Python中实现JWT认证的完整指南(从生成到验证)
    • Python FastAPI实现JWT校验的完整指南
    • Python实现JWT加解密的示例代码
    • Python结合jwt实现登录权限校验认证
    • Python使用JWT的超详细教程

    站内搜索