通过微博登录案例来进行了解js调试
1.分析流程
(1)手动操作流程
- 访问首页
https://weibo.com
- 输入用户名和密码
- 点击登录
- 如果有验证码,就输入验证码验证
- 成功跳转到微博首页面
(2)微博首页请求流程分析过程
根据上面的手动操作流程,我们要分析出网站的http请求逻辑。
1.首页面请求分析
首先,打开谷歌浏览器开发者调试工具,查看在请求首页面时,请求回的响应是否包含cookie
,也即是看首页面的响应头中是否包含set-cookie
。如果包含,那么这个请求是登录过程中必须的。经过查看,发现在首页面的响应头中,包含set-cookie
,这个请求是登录的第一个请求。
根据经验,在请求页面的
html
之后,还会去请求页面中的js
,css
,图片等信息。css
,图片信息我们可以排除,一般情况,不会是登录的必须请求。一种常见的手法是,将后续请求需要用到的参数放到js中,然后通过js异步请求来完成登录。所以,我们在Network
中过滤出所有的js请求。然后一个请求一个请求的查看,查看请求返回的响应内容,排除一些功能性的js,例如jquery之类,找出可能是参数的js,一般你能看懂的,大概率是参数。没有特别有效的方法,只能一条一条的看,凭借经验,以及url的命名。果然,找到了一条可疑请求,url为:https://login.sina.com.cn/sso/prelogin.php
pubkey
,nonce
,rsakv
,这是rsa
加密用的参数,在后面的请求中一定会用到。分析请求参数,发现除了最后有个时间戳解决浏览器缓存之外,其他参数都是固定的。ajax
请求,调试工具里,也可以过滤出,选择XHR
过滤选项。根据流程,可以先实现代码此部分的代码。
import requests
import time
import re
import json
#创建一个浏览器对象
session = requests.session()
#将浏览器对象进行伪装
session.headers.update({'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'})
#请求首页面,获取cookie(看浏览器里面是否存在cookie值 )
response = session.get('https://weibo.com/')
# print(response.cookies)
# print(session.cookies)
#请求prelogin,获取响应内容
params = {'entry': 'weibo',
'callback': 'sinaSSOController.preloginCallBack',
'su':'',
'rsakt': 'mod',
'client': 'ssologin.js(v1.4.19)',
'_': int(time.time())}
rs = session.get('https://login.sina.com.cn/sso/prelogin.php', params=params)
#print(rs.text)
data = re.findall(r'\((.*?)\)', rs.text)[0]
data = json.loads(data)
print(data, type(data))
2.点击登录后请求的分析
先清空调试工具中的请求历史,然后勾选Preserver log
和Disable cache
,保证抓包的正确。
上图红框中是几个比较关键的参数,在其中,没有发现username
,password
的字样,但是nonce
,pwencode
,rsakv
,以及sp
的值都明确指明了参数被js加密了。需要分析页面js,最笨的办法是在查找此条请求之前的所有请求中的js,然后找到处理参数的js。有时候,笨办法是最好的办法。谷歌浏览器提供的开发者调试工具中有个js调试工具,它可以定位页面元组的js事件,然后通过断点的方式,一步一步定位到关键代码处。
退回到点击登录按钮的那一步。点击开发者工具Elements
选项卡,点击左上角的选中按钮
,然后用鼠标左定位页面中的登录按钮。
Events Listeners
, 找到click
事件,从而定位到js代码。sources
窗口,它由3个子窗口组成,从左往右依次为,文件目录窗口,js代码窗口和调试窗口。大多数情况,为了减少带宽的消耗,js会被压缩,点击代码窗口左下角的{}
可以格式化js。click
监听时间执行的入口函数处,它会高亮黄色显示。在对于的行号出用鼠标左键点击,给当前代码处打上断点,行号显示为蓝色箭头,代表打上了断点。但是,有一种情况,可以换另外一种方法定位。那就是如果你要找某个请求的触发函数,那么可以直接在networking
选项窗口直接定位。输入错误密码,然后点击登录,请求的Initiator
字段显示了,发送这条请求的发起对象。
e.submit
是发起http请求的函数,这不是分析目标,分析的目标是请求参数的构造。查看当前函数loginByIframe
,发现没有参数构造的步骤。通过函数栈图,找到调用这个函数的函数loginByConfig
,在栈图中点击这个函数,可以跳到loginByIframe
函数调用处,在此处打上断点。Scope
选项中查看当前变量。a
是用户名,形参b
是密码。经过分析找到如下代码su
,处理方式为,先urlencode
再base64encode
。继续往下调试urlencode
就是对用户名进行url编码,python中使用如下代码
import base64
from urllib import parse
username = '112064149@qq.com'
res = parse.quote(username)
print(res)
# 输出:
112064149%40qq.com
继续调试,得到username编码后的值通过python代码实现如下:
import base64
from urllib import parse
username = '112064149@qq.com'
res = parse.quote(username)
res = base64.b64encode(res.encode())
print(res.decode('utf-8'))
# 输出
MTEyMDY0MTQ5JTQwcXEuY29t
仔细观察代码,发现参数entry
,service
,nonce
,pwencode
,rsakv
,以及密码sp
和它的加密方式。
继续按照这样的分析方式可以找到所有剩余参数的构造方法。
继续上面的python代码,实现这一步操作。
# 2.提交登录请求
def get_username(username):
res = parse.quote(username)
res = base64.b64encode(res.encode())
return res.decode('utf-8')
def get_password(pubkey, password):
# publickey = rsa.PublicKey(int(pubkey, 16), int('10001', 16))
# res = rsa.encrypt(password.encode(), publickey)
rsa = Rsa(pubkey=pubkey)
res = rsa.encrypt(password.encode())
return binascii.b2a_hex(res).decode()
t2 = str(int(time.time()*1000))
form_data = {
'entry': 'weibo',
'gateway': '1',
'from': '',
'savestate': '7',
'qrcode_flag': 'false',
'useticket': '1',
'pagerefer': '',
'vsnf': 1,
'su': get_username('122064149@qq.com'),
'service': 'miniblog',
'servertime': data['servertime'],
'nonce': data['nonce'],
'pwencode': 'rsa2',
'rsakv': data['rsakv'],
'sp': get_password(data['pubkey'], str(data['servertime']) + '\t' + data['nonce']+'\n'+'pythonvip123'),
'sr': '1920*1080',
'encoding': 'UTF-8',
'prelt': '49',
'url': 'https://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
'returntype': 'META'
}
login_url = 'https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)'
res = session.post(url=login_url, data=form_data)
res.encoding = 'gbk'
print(res.text)
运行结果:返回的结果是一个html页面,结构比较简单,js代码中有一个location.replace的跳转语句,说明接下来浏览器会发送这个请求。通过正则表达式提取url,然后发送请求。
next_url = re.findall(r'replace\("(.*?)"\)', res.text)[0]
# print(parse.unquote(next_url, encoding='gbk'))
response = session.get(next_url)
response.encoding = 'gbk'
print(response.text)
运行结果:
仍然是一个html页面,分析其中的js,发现一个arrURL的列表,观察其中的url,对照浏览器抓包,发现都有请求,通过代码提取,然后依次请求。最后访问home页面
https://weibo.com/?wvr=5&lf=reg
。调试后发现,arrURL中除了第一个请求必须之外,其他的请求都可以不发送。到此微博登录成功实现。
response = session.get(next_url)
response.encoding = 'gbk'
arrurl = re.findall(r'setCrossDomainUrlList\((.*?)\)', response.text)[0]
arrurl = json.loads(arrurl)['arrURL']
res = session.get(arrurl[0])
res.encoding = 'gbk'
print(res.text)
# res = session.get(arrurl[1])
# res.encoding = 'gbk'
# print(res.text)
# res = session.get(arrurl[2])
# res.encoding = 'gbk'
# print(res.text)
# res = session.get(arrurl[3])
# res.encoding = 'gbk'
# print(res.text)
res = session.get('https://weibo.com/?wvr=5&lf=reg')
print(res.url)
print(res.text)
import requests
import time
import re
import json
from urllib import parse
import base64
from Cryptodome.Cipher import PKCS1_OAEP, PKCS1_v1_5 #用的是比较老的模式PKCS1_v1_5
from Cryptodome.PublicKey import RSA
import binascii
def encrypt_username(username):
temp = parse.quote(username)
res = base64.b64encode(temp.encode())
return res.decode('utf-8')
# print(encrypt_username('wswdd'))
# if encrypt_username('wswdd') == 'd3N3ZGQ=':
# print('是相等的')
def encrypt_password(password, pubkey):
public_key = RSA.RsaKey(n=int(pubkey, 16), e=65537)
cipher = PKCS1_v1_5.new(public_key)
res = cipher.encrypt(password.encode())
return binascii.b2a_hex(res).decode()
#创建一个浏览器对象
session = requests.session()
#将浏览器对象进行伪装
session.headers.update({'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'})
#请求首页面,获取cookie(看浏览器里面是否存在cookie值 )
response = session.get('https://weibo.com/')
# print(response.cookies)
# print(session.cookies)
#请求prelogin,获取响应内容
params = {'entry': 'weibo',
'callback': 'sinaSSOController.preloginCallBack',
'su':'',
'rsakt': 'mod',
'client': 'ssologin.js(v1.4.19)',
'_': int(time.time())}
rs = session.get('https://login.sina.com.cn/sso/prelogin.php', params=params)
#print(rs.text)
data = re.findall(r'\((.*?)\)', rs.text)[0]
data = json.loads(data)
# print(data, type(data))
# print('-'*100)
#发送用户名和密码(在谷歌中有一个door参数,是要验证码的)
login_data = {
'entry': 'weibo',
'gateway': 1,
'from': '',
'savestate': '7',
'qrcode_flag': 'false',
'useticket': '1',
'pagerefer': '',
'pcid': 'yf-a9af4495ac9f6a582caeb643033d45f3725e',
'vsnf': '1',
'su': encrypt_username('15115203567'),
'service': 'miniblog',
'servertime': data['servertime'],
'nonce': data['nonce'],
'pwencode': 'rsa2',
'rsakv': data['rsakv'],
'sp': encrypt_password(str(data['servertime'])+"\t" + data['nonce'] + "\n" + 'qwer4321', data['pubkey']),
'sr': '1366*768',
'encoding': 'UTF-8',
'prelt': '391',
'url': 'https://www.weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
'returntype': 'META',}
# print(login_data)
response = session.post('https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)', data=login_data)
response.encoding = 'gbk'
# print(response.text)
# print('--'*100)
redirect_url = re.findall(r'replace\("(.*?)"\)', response.text)[0]
# print(redirect_url, type(redirect_url))
# print('--'*100)
# print(parse.unquote(redirect_url, encoding='gbk'))
response = session.get(redirect_url)
response.encoding = 'gbk'
# print(response.text)
# print('-'*100)
#获取url列表
urls = re.findall(r'"arrURL":(\[.*?\])', response.text)[0]
# print(urls, type(urls))
urls = json.loads(urls)
# print('-'*100)
print(urls, type(urls))
for res in urls:
response = session.get(res) #只取第一个即可
print(response.text)
print('='*100)
print('='*100)
response = session.get('https://weibo.com/?wvr=5&lf=reg')
response.encoding = 'utf-8'
print(response.text)