使用Python模拟腾讯第三方认证-篇2

上篇分析了模拟登陆的流程,以及HTTP请求和回应,下面我们开始编码实现。

编码

准备

安装requests, pip install requests

使用requests请求比使用urllib方便了很多,下面简单封装了一个http请求函数fetch:

'''
默认为GET,如果赋值data后则为POST方式
'''
def fetch(self, url, data=None, **kw):
    if data is None:
        func = self.session.get
    else:
        kw['data'] = data
        func = self.session.post
    return func(url, **kw)

使用QQ登录

点击‘QQ登录’按钮后会以GET方式请求http://passport.jikexueyuan.com/connect/qq, 请求代码如下:

self.jkqqurl='http://passport.jikexueyuan.com/connect/qq'

response = self.fetch(self.jkqqurl)
self.state = re.findall('&state=(.*?)\&', response.url)[0]          #(.*?), 括号为返回匹配值,即=后面的
self.client_id = re.findall('&client_id=(.*?)\&', response.url)[0]

因为下面向腾讯请求认证需要学院返回的这两个参数:STATE, CLIENTID, 这里记录下来。

请求登录页面

通过上步请求后,虽然学院发起了向腾讯认证服务器的请求,但是因为没有登录会被重新定向至登录授权页面,
这里我们手动请求一次封装在IFrame中的登录页面:

self.xloginurl='http://xui.ptlogin2.qq.com/cgi-bin/xlogin'

g = self.fetch(self.xloginurl, params = {
        'appid': self.appid,
        'style': 23,
        'login_text': '授权并登录',
        'hide_title_bar': 1,
        'target': 'self',
        's_url': self.loginjumpurl,
        'pt_3rd_aid': 101119675,
        'pt_feedback_link': self.feedbackurl,
    }).text

这步操作理论上会产生一些登录请求的会话信息,还是尽量模拟浏览器操作,后面再作精简。

登录校验

分析来看,腾讯根据登录请求类型来产生校验码图片或文字型校验码(如果不需要校验码图片),看代码:

self.checkurl2='http://check.ptlogin2.qq.com/check'

g = self.fetch(self.checkurl2, params = {
        'regmaster': '',
        'pt_tea': 1,
        'pt_vcode': 1,
        'uin': self.user,
        'appid': self.appid,
        'js_ver': 10120,
        'js_type': 0,
        'login_sig': self.session.cookies['pt_login_sig'],
        'u1': self.loginjumpurl,
    }).text
v = re.findall('\'(.*?)\'', g)
vcode = v[1]
uin = v[2]

这里pt_login_sig是从session中取得,requests从session中获取参数还是很方便的。

返回值格式:

ptui_checkVC(
    '0',        #参数为是否需要校验码
    '!JCD',     #系统生成的校验码
    '\x00\x00\x00\x00\x3x\x2x\x3x\x3x', #用户ID进行转化后的值
    '47e6754aafb6138d4cdcdfdd1602bc8df9d29893cc705484d148adb079105ebba462f8cd7c18025441c5309c1b50378059fb5a0fcd18b206', #ptvfsession,后面登录使用
    '0');

登录

登录时判断是否需要校验码,如果需要则下载校验码图片。
开篇说过,腾讯第三方登录认证是不需要校验码的,从上面结果也能看出v[0]一直为0

self.loginurl2='http://ptlogin2.qq.com/login'

if v[0] == '1': # 需要校验码
    vcode = self.getVerifyCode(vcode) #获得校验码
g = self.fetch(self.loginurl2, params = {
    'u': self.user,
    'verifycode': vcode,
    'pt_vcode_v1': 0,
    'pt_verifysession_v1': self.session.cookies['ptvfsession'],
    'p': self.pwdencode(vcode, uin, self.pwd),
    'pt_randsalt': 0,
    'u1': self.loginjumpurl,
    'ptredirect': 0,
    'h': 1,
    't': 1,
    'g': 1,
    'from_ui': 1,
    'ptlang': 2052,
    'action': self.action,
    'js_ver': 10138,
    'js_type': 0,
    'login_sig': self.session.cookies['pt_login_sig'],
    'pt_uistyle': 33,
    'aid': self.appid,
    'pt_3rd_aid': 101119675,
}).text

登录处理这块处理的东西较多,分开看:

  • ptvfsession和pt_login_sig都是从cookies中取得

  • 密码加密

    为了防止密码被截取,肯定做足了混淆加密,具体算法可以参考c_login_2.js中的实现,这里参考了qqlib的代码http://www.jianshu.com/p/4217d8f3574b

def fromhex(self, s):
    # Python 3: bytes.fromhex
    return bytes(bytearray.fromhex(s))

#pip install rsa
pubKey=rsa.PublicKey(int(
    'F20CE00BAE5361F8FA3AE9CEFA495362'
    'FF7DA1BA628F64A347F0A8C012BF0B25'
    '4A30CD92ABFFE7A6EE0DC424CB6166F8'
    '819EFA5BCCB20EDFB4AD02E412CCF579'
    'B1CA711D55B8B0B3AEB60153D5E0693A'
    '2A86F3167D7847A0CB8B00004716A909'
    '5D9BADC977CBB804DBDCBA6029A97108'
    '69A453F27DFDDF83C016D928B3CBF4C7',
    16
), 3)

def pwdencode(self, vcode, uin, pwd):
    # uin is the bytes of QQ number stored in unsigned long (8 bytes)
    salt = uin.replace(r'\x', '')
    h1 = hashlib.md5(pwd.encode()).digest()
    s2 = hashlib.md5(h1 + self.fromhex(salt)).hexdigest().upper()
    rsaH1 = binascii.b2a_hex(rsa.encrypt(h1, self.pubKey)).decode()
    rsaH1Len = hex(len(rsaH1) // 2)[2:]
    hexVcode = binascii.b2a_hex(vcode.upper().encode()).decode()
    vcodeLen = hex(len(hexVcode) // 2)[2:]
    l = len(vcodeLen)
    if l < 4:
        vcodeLen = '0' * (4 - l) + vcodeLen
    l = len(rsaH1Len)
    if l < 4:
        rsaH1Len = '0' * (4 - l) + rsaH1Len
    pwd1 = rsaH1Len + rsaH1 + salt + vcodeLen + hexVcode
    saltPwd = base64.b64encode(
        tea.encrypt(self.fromhex(pwd1), self.fromhex(s2))
    ).decode().replace('/', '-').replace('+', '*').replace('=', '_')
    return saltPwd
  • 获取校验码:
#这里只是给出给参考,从WebQQ登录查询校验码过程获得
self.imgurl='http://captcha.qq.com/getimage'
def getVerifyCode(self, vcode):
    r = self.fetch(self.imgurl, params = {
        'r':0,
        'appid':self.appid,
        'uin':self.user,
        'vc_type':vcode,
    })
    tmp = tempfile.mkstemp(suffix = '.jpg')
    os.write(tmp[0], r.content)
    os.close(tmp[0])
    os.startfile(tmp[1])
    vcode = input('Verify code: ')
    os.remove(tmp[1])
    return vcode

如果登录成功则返回值如下:
ptuiCB('0','0','http://openapi.qzone.qq.com/oauth/login_jump','0','登录成功!', 'XXXX');

登录跳转

代码很简单:

self.loginjumpurl='http://openapi.qzone.qq.com/oauth/login_jump'
g = self.fetch(self.loginjumpurl).text

这一步会返回这样一个页面:

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <script>
        document.domain = 'qq.com';
        if(parent.agree){
            parent.agree()
        }else{
            parent.isAgreed = true;
        }
    </script>
</head>
<body>
</body>
</html>

这一步,对于模拟情况作用不大,对于浏览器的话会执行上报报告,表格提交等操作,后面会模拟。
重点是JS块中的parent.agree详细操作流程,可以从qlogin_v2.js中分析到。

上传报告

这两个报告没有特别的:

self.appsupporturl='http://appsupport.qq.com/cgi-bin/appstage/mstats_report'
self.reporturl='http://cgi.connect.qq.com/report/report_vm'

g = self.fetch(self.appsupporturl, params = {
    'report_type': '4',
    'platform': 8,
    'app_id': '101119675',
    'result': 0,
    'act_type': 2,
    'uin': self.user,
    'login_status': 2,
    'via': 1,
    't': int(time.time()),
    }).text
#正确的话返回{"ret":0,"msg":"成功"}

g = self.fetch(self.reporturl, params = {
        'tag': 0,
        'log': '101119675_10613_0',
        't': int(time.time()),
    }).text

#正确的话返回{"ec":0}

获取授权

self.authurl='https://graph.qq.com/oauth2.0/authorize'
#更新请求头,防止被屏蔽
self.session.headers.update({'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240'})

token = self.gettoken()
data = {
    'response_type': 'code',
    'client_id': 101119675,
    'redirect_uri': 'http://passport.jikexueyuan.com/connect/success?t=qq',
    'scope': 'get_user_info',
    'state': self.state,
    'src': '1',
    'update_auth': '1',
    'openapi': '80901010',
    'g_tk': token,
    'auth_time': int(time.time()),
    'ui': '556F791F-682E-47B7-BC66-CE179327DB04',#UID,可以随机生成,这里简单使用了抓包结果
}
response = self.fetch(self.authurl, data = data)

gettoken定义如下:

def gettoken(self):
    str = self.session.cookies['skey']
    hash = 5381
    for i in str:
        t = (hash << 5) + ord(i)
        hash = hash + t
    return hash & 0x7fffffff

如果成功的话,则会跳转至学院主页。
如果失败,需要分析是哪一步错了,主要有:密码,STATE, pt_login_sig. ptvfsession。

试验

self.session.headers.update({'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240'})
g = self.fetch('http://www.jikexueyuan.com/course/2185_1.html?ss=1').text
print g

查看打印信息,看是否已经登录即可。

总结

编码流程基本是针对登录过程抓包的实现,比较原始,并且刚开始写python程序,后面再整理出一个干净的版本。

到现在为止所做的足够对学院进行爬虫分析了,后续更新。

需要注明的是,代码主要参考了qqlib, 作者@JetLu http://www.jianshu.com/users/4d556dbb651e/latest_articles

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容