美文网首页python学习python加油站程序员
使用Python模拟腾讯第三方认证-篇2

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

作者: LittleFat | 来源:发表于2015-11-18 11:49 被阅读1638次

    上篇分析了模拟登陆的流程,以及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

    相关文章

      网友评论

      • 2767486c229c:能提供下源码吗?目前卡在密码加密上。
      • 真依然很拉风:请问github上有没有完整的代码
      • 15877752003:感觉看乱了。有代码吗
      • LittleFat:不要把时间浪费在选择上,你可以:
        1.搜索对比两种语言的优劣
        2.任何编程语言只是表达逻辑思维的工具,尝试使用两种语言,写写东西
      • 又又鱼:我现在面临一个选择,是python还是R

      本文标题:使用Python模拟腾讯第三方认证-篇2

      本文链接:https://www.haomeiwen.com/subject/mjclhttx.html