人人和微博登录模块的实现
发布于 11 年前 作者 sxyizhiren 20276 次浏览 最后一次编辑是 8 年前

最近因为要实现一个树洞(就是一种匿名发布秘密的主页)。研究了人人和微博的登录过程,通过firebug抓取http请求,然后用node模拟发送。 微博和人人的登录流程是类似的,或者几乎所有网站的登录都是类似的。如下: 1.获取登陆时加密密码的加密参数,登录一般都会把密码进行rsa加密(rsa是一种非对称加密方法,需要用公钥加密)。 2.查询当前用户名是否需要验证码(这个一般在焦点离开用户名的输入框时触发查询)。 3.对于需要验证码的,会显示验证码,同时设置与该验证码一一对应的COOKIE值。 4.提交用户名和密码,密码是rsa加密后的。对于需要验证码的,必须带上cookie和输入验证码。 5.根据返回值判断是否登录成功。登录成功的话一般会有个跳转地址,或是指向网站首页或是指向一个让你获取用户信息的地址(比如人人会跳至首页,在首页中为你的cookie写入用户信息,微博则是跳转到一个页面,这个页面的返回值中包含用户id等信息)。

上面这几个步骤有严格先后关系,用node这种异步的机制实在很难处理。好在有step这个模块。可以借助step实现顺序执行的一序列功能。 step的基本用法如下: var step=require(‘step’); step( function(){ fn(xx,this);//this的传入的回调函数,this被回调之时也就是下一个函数执行之时。 }, function(){ fn(xx,this); }, function(){

}

);

我代码中是这么写的: var ajaxLogin=function(){ Step( getEncryptKey, //rsa加密参数 getCaptcha, //是否需要验证码 getICode, //读取验证码 login, //登录 browserHomepage //跳转至个人主页 ); }

这个就是登录的流程。 ps:这个依赖的库中iconv-lite不能用最新的,要用0.2.9 下面我贴人人网登录的代码,微博登录的要的话可以找我要(786647787@qq.com). /** *人人网登录 *登录接口为onekeyLogin *先尝试原cookie是否有效, *无效的话走ajaxLogin() **/ var fs=require(‘fs’); var http=require(‘http’); var path=require(‘path’); var querystring=require(‘querystring’); var RSATool=require(‘simple-rsa’); var Request=require(‘node-request’); var Step=require(‘step’); var toughCookie=require(‘tough-cookie’); var CookieJar=toughCookie.CookieJar; var CookiePair=toughCookie.Cookie; var assert=require(‘assert’);

var Login=function(){ var accountInfo; //账户信息 var encryptkey; //rsa加密参数 var captcha; //是否需要验证码 var icode; //验证码 var uname; //账户信息 var homepage; //主页的html var token; //页面的token var loginInfo; //登陆信息 var otherAccounts; //其他用户身份信息【switchAccount】 var pageState; //页面状态【switchAccount】 var newAccount; //切换到另一用户身份【switchAccount】 var callbackFn; //登录后的回调函数

/**
 * 把json形式的对象转成用toughCookie.Cookie表示的对象
 * [@param](/user/param) storeIdx
 */
var makeJsonToCookieObject=function(storeIdx){
    if(typeof storeIdx !== 'object'){
        storeIdx = {};
    }
    Object.keys(storeIdx).forEach(function(domain){
        var domainGroup=storeIdx[domain];
        Object.keys(domainGroup).forEach(function(path){
            var pathGroup=domainGroup[path];
            Object.keys(pathGroup).forEach(function(key){
                var obj=pathGroup[key];
                obj.expires=toughCookie.parseDate(obj.expires);
                obj.creation=toughCookie.parseDate(obj.creation);
                obj.lastAccessed=toughCookie.parseDate(obj.lastAccessed);
                pathGroup[key]=new CookiePair(obj);
            });
        });
    });
}

/**
 * 设置登录的账户
 * [@param](/user/param) account
 */
this.setAccount=function(account){
    accountInfo={};
    accountInfo.email=account.email || '';
    accountInfo.passwd=account.passwd || '';
    accountInfo.Cookie=new CookieJar();
    //要么account.Cookie为空,要么是完整的store.idx
    assert(!(account.Cookie) || (account.Cookie.store && account.Cookie.store.idx));
    if(account.Cookie){
        makeJsonToCookieObject(account.Cookie.store.idx);
        accountInfo.Cookie.store.idx=account.Cookie.store.idx;
    }
    //
    accountInfo.isPage=('true'==account.isPage)?'true':'false';
}

/**
 * 尝试cookie是否有效
 */
var testCookie=function(){
    //console.log('stepCookieLogin');
    uname={};
    Step(
        function(){
            var url='http://notify.renren.com/wpi/getonlinecount.do';
            Request.get(url,accountInfo.Cookie,null,uname,'txt',this);
        },
        function(){
            var loginOk=false;
            try{
                var tmpJson=JSON.parse(uname.Content.trim());
                if(tmpJson.hostid>0){
                    console.log('Cookie LOGIN OK!');
                    loginOk=true;
                }else{
                    console.log('Cookie LOGIN FAIL!');
                }

            }catch(e){
                console.log(uname.Content);
                console.log('Cookie LOGIN FAIL!');
            }
            if(loginOk){
                loginInfo.Content={
                    code:true,
                    homeUrl:'http://www.renren.com/home'
                };
                console.log(tmpJson);
                //浏览主页解析token,等后续操作
                browserHomepage();
            }else{
                //cookie无效,重新登录
                ajaxLogin();
            }

        }
    );

};



/**
 * 设置RSA加密的参数
 */
var getEncryptKey=function(){
    //console.log('stepEncryptKey');
    encryptkey={};
    var url='http://login.renren.com/ajax/getEncryptKey';
    Request.get(url,null,null,encryptkey,'json',this);

}

/**
 * 检测该账号是否需要验证码
 */
var getCaptcha=function(){
    //console.log('stepCaptcha');
    captcha={};
    var postData=querystring.stringify({
        'email': accountInfo.email
    });
    var url='http://www.renren.com/ajax/ShowCaptcha';
    var headers={
        'Referer':'www.renren.com'
        ,'Accept-Language': 'zh-cn'
        ,'Content-Type':'application/x-www-form-urlencoded'
        ,'Host': 'www.renren.com'
        ,'Content-Length':postData.length
        ,'Connection': 'Keep-Alive'
        ,'Cache-Control': 'no-cache'
    };
    Request.post(url,null,postData,headers,captcha,'txt',this);
}

/**
 * 获取用户输入验证码
 * [@param](/user/param) getter
 * [@param](/user/param) callbackfn
 */
function getInputIcode(getter,callbackfn){
    process.stdin.resume();
    process.stdin.setEncoding('utf8');

    process.stdin.on('data', function (chunk) {
        process.stdout.write('data: ' + chunk);
        var codeLen=4;
        getter.str=chunk.substr(0,codeLen);
        process.stdin.pause();
        callbackfn();
    });

    process.stdin.on('end', function () {
        process.stdout.write('end');
        process.stdin.pause();
    });
}

/**
 * 取验证吗图片,以及读取用户输入的验证码
 */
var getICode=function(){
    //console.log('stepICode');
    processStep='stepICode';
    icode={str:''};
    var __this=this;
    //console.log(captcha);
    if(1==captcha.Content || 4==captcha.Content){
        Step(
            function(){
                var url='http://icode.renren.com/getcode.do?t=web_login&rnd='+Math.random();
                Request.get(url,accountInfo.Cookie,null,icode,'buf',this);
            },
            function(){
                fs.writeFile('icode.png', icode.Content, 'binary',this);
            },
            function(){
                console.log('输入验证码,请看当前目录下的icode.png');
                getInputIcode(icode,__this);
            }
        );
    }else{
        __this();
    }

}

/**
 * 前期工作准备好后,提交登录信息
 */
var login=function(){
    //console.log('stepLogin');
    assert(accountInfo);
    var pass=accountInfo.passwd;
    if(encryptkey.Content && encryptkey.Content.isEncrypt){
        RSATool.setMaxDigits(encryptkey.Content.maxdigits*2);
        var key=new RSATool.RSAKeyPair(encryptkey.Content.e,"null",encryptkey.Content.n);
        pass=RSATool.encryptedString(key, encodeURIComponent(pass));//encodeURIComponent可以转化中文成基本字符
    }
    //console.log(pass);
    var postData=querystring.stringify({
        'email': accountInfo.email,
        'origURL': 'http://www.renren.com/home',
        'icode': icode.str,
        'domain': 'renren.com',
        'key_id': 1,
        'captcha_type': 'web_login',
        'password': pass,
        'rkey': encryptkey.Content.rkey
    });
    //console.log(postData);
    var url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp='+Math.random();
    var headers={
        'Referer':'www.renren.com'
        ,'Accept-Language': 'zh-cn'
        ,'Content-Type':'application/x-www-form-urlencoded'
        ,'Connection': 'Keep-Alive'
        ,'Cache-Control': 'no-cache'
    };

    Request.post(url,accountInfo.Cookie,postData,headers,loginInfo,'json',this);
}

/**
 * 密码步骤步骤
 */
var ajaxLogin=function(){
    Step(
        getEncryptKey,
        getCaptcha,
        getICode,
        login,
        browserHomepage
    );
}

/**
 * Cookie登录步骤
 */
var cookieLogin=function(){
    testCookie();
}
/**
 * 留言home页面,便于提取token
 * 这一步很关键,承前启后
 */
var browserHomepage=function(){
    homepage={};
    //console.log('stepHomepage');
    var url=loginInfo.Content.homeUrl;
    accountInfo.logined=loginInfo.Content.code;//用于外部判断是否登录成功
    assert(true === accountInfo.logined,'Username And Password Not Match');
    homepage.url=url;
    Request.get(url,accountInfo.Cookie,null,homepage,'txt',processRealHomepage);
}

/**
 * home页面可能多次jump,这里处理jump
 */
var processRealHomepage=function(){
    if(301 == homepage.Status || 302 == homepage.Status){
        var url=homepage.Location;
        console.log('homepage location:'+url);
        homepage.url=url;
        Request.get(url,accountInfo.Cookie,null,homepage,'txt',processRealHomepage);/*可能还要跳转*/
    }else{
        switchUser();
    }
}

/**
 * 切换用户步骤
 */
var switchUser=function(){
    Step(
        getUname,
        parseToken,
        getOtherAccount,
        getOtherPageState,
        switchNewAccount,
        checkNewAccount
    );
}

/**
 * 获取id和name,公共主页的name和id一样,获取不到
 */
var getUname=function(){
    //console.log('stepUname');
    assert(accountInfo);
    var url='http://notify.renren.com/wpi/getonlinecount.do';
    Request.get(url,accountInfo.Cookie,null,uname,'json',this);
}

/**
 * 从home的html中解析出token
 */
var parseToken=function(){
    //console.log('stepToken');
    token={};
    html=homepage.Content;
    var tokenREG=/\{get_check:'(.+)',get_check_x:'(.+)',env:\{/;
    var ret;
    if(ret=tokenREG.exec(html)){
        token.requestToken=ret[1];
        token._rtk=ret[2];
        console.log('token:requestToken['+token.requestToken+'],_rtk['+token._rtk+']');
    }else{
        console.log('get token error!');
        token.requestToken='';
        token._rtk='';
        //失败会一直发不出状态
        //标记登录失败,外层做重启等操作
        //accountInfo.logined = false;
    }
    assert(''!=token.requestToken && ''!=token._rtk);
    this();
}


/**
 * 读取别的账号,用户切换普通账号和公共主页
 */
var getOtherAccount=function(){
    //console.log('stepOtherAccounts');
    otherAccounts={};
    var url='http://www.renren.com/getOtherAccounts';
    Request.get(url,accountInfo.Cookie,null,otherAccounts,'json',this);
}

/**
 * 读取账户的状态信息,切换账号时要检测它的值
 */
var getOtherPageState=function(){
    //console.log('stepPageState');
    pageState={};
    var needSwitch = (otherAccounts.Content && (accountInfo.isPage != otherAccounts.Content.self_isPage)
        && otherAccounts.Content.otherAccounts && otherAccounts.Content.otherAccounts[0])?true:false;
    pageState.needSwitch=needSwitch;
    if(needSwitch){
        console.log('Need to switch account!');
        var pids=otherAccounts.Content.otherAccounts[0].transId;
        var url='http://page.renren.com/api/pageState';

        var postData=querystring.stringify({
            '_rtk': token._rtk,
            'pids':pids,
            'requestToken':token.requestToken
        });
        var headers={
            'Referer':'www.renren.com'
            ,'Accept-Language': 'zh-cn'
            ,'Content-Type':'application/x-www-form-urlencoded'
            ,'Connection': 'Keep-Alive'
            ,'Cache-Control': 'no-cache'
        };

        Request.post(url,accountInfo.Cookie,postData,headers,pageState,'json',this);
    }else{
        //console.log('Need NOT to switch account!');
        this();
    }
}

/**
 * (确认要切换后)执行切换普通用户和主页的身份
 */
var switchNewAccount=function(){
    //console.log('stepNewAccount');
    newAccount={};

    if(pageState.needSwitch && pageState.Content && (pageState.Content.code == 0)){
        var destId=otherAccounts.Content.otherAccounts[0].id;
        var url='http://www.renren.com/switchAccount';

        var postData=querystring.stringify({
            '_rtk': token._rtk,
            'destId': destId ,
            'origUrl':homepage.url,
            'requestToken':token.requestToken
        });
        var headers={
            'Referer':'www.renren.com'
            ,'Accept-Language': 'zh-cn'
            ,'Content-Type':'application/x-www-form-urlencoded'
            ,'Connection': 'Keep-Alive'
            ,'Cache-Control': 'no-cache'
        };
        Request.post(url,accountInfo.Cookie,postData,headers,newAccount,'json',this);
    }else{
        this();
    }
}

/**
 * 检测是否切换成功,成功后重新读取home页面
 */
var checkNewAccount=function(){
    //console.log('stepCheckNewAccount');
    if(newAccount.Content && newAccount.Content.isJump){
        loginInfo.Content.homeUrl=newAccount.Content.url;
        //用户切换后重新解析token
        browserHomepage();
    }else{
        assert(typeof callbackFn === 'function');
        accountInfo.token=token;
        accountInfo.homeUrl=homepage.url;
        accountInfo.uid=getUid(accountInfo);
        callbackFn(null,accountInfo);
    }

}

/**
 * 获取用户id,在uname中的不是有用的id,
 * 这里先从home链接中提取,失败再从cookie中提取
 * [@return](/user/return) {*}
 */
var getUid=function(accountInfo){
    var uidReg;
    var ret;
    uidReg=/www\.renren\.com\/([\d]+)/;
    ret=uidReg.exec(accountInfo.homeUrl);
    if(ret){
        return ret[1];
    }else{
        uidReg=/feedType=([\d]+)_hot/;
        ret=uidReg.exec(accountInfo.Cookie.store.idx['www.renren.com']['/']['feedType'].cookieString());
        if(ret){
            return ret[1];
        }
    }
    return '';
}

this.onekeyLogin=function(callback){
    assert(typeof callback === 'function');
    callbackFn=callback;
    loginInfo={};   //初始化
    cookieLogin();
}

}

module.exports.INST=Login;

8 回复

很好的案例,相同的思路可以扩展到新浪微博,和豆瓣等平台。

试试这个,包括登录和其它api调用,https://github.com/sumory/social_oauth

你这个是 基于oatuh2 的API使用,LZ的是模拟登录,不一样的。

可以弄个taobao,豆瓣的吗?

@janry木有时间,你来pull下

@sumory qq登录有问题啊 登录成功后是这个url http://www.smartac.co/?code=9C2A2E8928A3F0559BB18E6361FAF07C 不对吧??

楼主是模拟form登陆

回到顶部