第一章:当爬虫遇上“隐形门卫”——jwt 认证机制解析
在传统的网页爬虫开发中,我们习惯于直接请求目标 url,解析 html,提取数据。然而,随着现代 web 应用架构的演进,前后端分离(spa)已成为主流。这意味着数据不再是直接嵌入在 html 中,而是通过 api 接口以 json 格式交互。为了保护这些接口的安全,开发者往往会设置一道“门卫”——jwt (json web token)。
对于爬虫工程师而言,理解 jwt 的工作原理是绕过这道门卫的关键。
1.1 什么是 jwt?
jwt 是一种开放标准(rfc 7519),用于在网络应用环境间传递声明。它将用户信息加密成一个 token,通常由三部分组成:
- header(头部):包含令牌类型和签名算法(如 hmac sha256 或 rsa)。
- payload(负载):包含用户身份信息(如用户id、过期时间)。
- signature(签名):这是安全性的核心。服务器使用 header 和 payload,配合只有服务器知道的密钥(secret)进行签名。
1.2 为什么爬虫需要特别关注 jwt?
在传统的 session/cookie 机制中,服务器会在客户端保留一个 session id,爬虫只需保持 cookie 即可。但在 jwt 模式下,服务器是无状态的。客户端(浏览器)在登录成功后,会收到一个长字符串(token),之后的每一次请求,都必须在 http header 中携带这个 token(通常是 authorization: bearer <token>)。
如果我们的爬虫忽略了这个机制,直接去请求需要登录的接口,服务器会直接返回 401 unauthorized,就像被门卫直接拦在门外一样。
1.3 jwt 的“双刃剑”对爬虫的影响
- 优势:token 可以跨域使用,且不需要服务器查询数据库验证 session,速度极快。
- 挑战:token 有过期时间(
exp),一旦过期必须重新获取。此外,为了安全,很多应用会使用 refresh token 机制,或者对 token 进行加密处理(jwe),这大大增加了爬虫模拟的复杂度。
第二章:python 实战:模拟登录与 token 获取
在掌握了理论基础后,我们需要动手操作。本章节将使用 python 的 requests 库,演示如何从零开始获取一个 jwt token。
2.1 准备工作:分析目标 api
假设我们要爬取一个名为 “datahub” 的虚构平台数据。首先,我们需要打开浏览器的开发者工具(f12),切换到 network 面板,进行一次真实的登录操作。
观察结果:
- 请求 url:
https://api.datahub.com/v1/auth/login - 请求方法:
post - 请求体(payload):
{"username": "your_user", "password": "your_password"}
2.2 编写获取 token 的 python 代码
利用 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}")
2.3 常见登录陷阱与应对
- 验证码(captcha):如果登录接口带有图形验证码,简单的
requests就失效了。此时,你需要引入 ocr 库(如ddddocr)或者接入打码平台。 - csrf token:部分老式网站在登录时会要求携带一个隐藏的 csrf token。你需要先请求一次登录页,解析 html 获取该 token,再发起登录。
- 加密参数:现在的前端为了安全,往往会将密码进行 js 加密(如 rsa 或 aes)。你需要使用 python 的
pycryptodome库复现该加密逻辑,或者直接使用pyexecjs执行原生 js 代码。
第三章:带着“通行证”出发:在请求中注入 jwt
拿到 token 后,万里长征走完了一半。现在我们需要将这个 token 应用到后续的数据提交和获取请求中。
3.1 构造带认证的 header
这是最关键的一步。绝大多数 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}")
3.2 封装为通用爬虫类(class)
为了代码的复用性和可维护性,建议将 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 的弱点,能帮助我们应对更复杂的反爬机制;了解防御策略,则能帮助我们开发更健壮的应用。
4.1 应对高级防御:token 存储与刷新
- 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 变量是行不通的。 - 爬虫应对:使用 selenium 或 playwright 等浏览器自动化工具。先用浏览器自动化工具完成登录,然后通过
get_cookies()提取 token,再转交给requests使用。
4.2 从防御者角度看 jwt 爬虫
如果你是 api 的开发者,该如何防止他人通过模拟 jwt 爬取数据?
- 签名算法选择:严禁使用
none算法。推荐使用hs256(对称)或rs256(非对称)。如果密钥泄露,攻击者可以伪造任何用户的 token。 - 增加黑名单机制:jwt 的特性是“一旦签发,无法撤回”。为了解决这个问题,可以在 redis 中维护一个 token 黑名单(用户注销或修改密码时将旧 token 加入黑名单)。
- ip 与 user-agent 绑定:在 payload 中加入 ip 或设备指纹信息,并在验证时检查。如果一个 token 突然在不同的 ip 下使用,判定为异常。
- 动态 token(混淆 token):像 wechat 这样的应用,会使用动态 token,每次请求后 token 都会变化,或者使用加密的二进制格式,大大增加了逆向难度。
4.3 总结
处理带有 jwt 认证的 python 爬虫,核心在于模拟“客户端行为”。这不仅仅是发送 http 请求,更是一个对认证流程的逆向工程。
- 初级阶段:手动抓包,硬编码 token。
- 中级阶段:编写登录脚本,自动获取并管理 token。
- 高级阶段:处理 token 过期刷新、应对加密参数、结合浏览器自动化工具解决复杂渲染问题。
随着反爬技术的升级,单纯的 http 请求越来越难以满足需求,将 requests 的高效与 playwright 的渲染能力相结合,是未来爬虫开发的必然趋势。
以上就是使用python优雅地处理带有jwt认证的接口提交的详细内容,更多关于python处理wt认证接口提交的资料请关注代码网其它相关文章!
发表评论