騰訊微博應用授權隱式登陸實現


應用授權的請求地址格式,使用Fiddler2捕捉一次完整的授權操作就可以大致了解騰訊微博的登陸原理(重點在h_login_11.js)

https://open.t.qq.com:443/cgi-bin/oauth2/authorize?client_id=[appKey]&redirect_uri=[callbackUrl]&response_type=code

對於https的請求服務器證書回調驗證我們都返回true

ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);

1、獲取臨時驗證碼

請求鏈接格式如下:

https://ssl.ptlogin2.qq.com/check?uin={0}&appid=46000101&r={1}

uid:用戶賬號

r:0~1之間的隨機數(允許忽略)

var userUid = "[userUid]";
var random = new Random(); var vcUrl = String.Format("https://ssl.ptlogin2.qq.com/check?uin={0}&appid=46000101&r={1}", userUid, random.NextDouble()); var cookieContainer = new CookieContainer(); var webRequest = WebRequest.CreateHttp(vcUrl); webRequest.CookieContainer = cookieContainer;
var webResponse = webRequest.GetResponse(); var responseStream = webResponse.GetResponseStream(); var responseReader = new StreamReader(responseStream, Encoding.UTF8, true); var response = responseReader.ReadToEnd();

返回數據如下:

ptui_checkVC('0','!MMC','\x00\x00\x00\x00\x00\x00\x00\x00');

第二個參數就是驗證碼,第三個參數是用戶賬號調用$str.uin2hex(h_login_11.js)的結果,它會被用來后續參與用戶密碼的加密參數。

2、用戶密碼加密

查看h_login_11.js源代碼,用戶密碼會被$.Encryption.getEncryption函數加密

if (B[E].name=="p") {
    var K = B.p.value;
    var G = B.verifycode.value.toUpperCase();
    var F = $.Encryption.getEncryption(K, pt.login.saltUin, G);
    I += F
}

三個參數分別是用戶密碼、加密賬號、驗證碼,加密算法步驟如下:

1)md5([用戶密碼])

2)hexchar2bin([上一步返回字符串])

3)md5([上一步返回字符串] + [加密賬號])

4)md5([上一步返回字符串] + [驗證碼大寫])

之前考慮到該算法的復雜程度,使用.NET javascript 引擎Jint執行$.Encryption.getEncryption,但返回的結果始終不對。在仔細閱讀了$.Encryption內部源代碼后參考編寫了一個C#.NET版本(需要注意字符編碼為ISO-8859-1)。

/// <summary>
/// 騰訊加密輔助類
/// </summary>
internal static class QQEncryptionHelper
{
    /// <summary>
    /// MD5字符編碼
    /// </summary>
    private static readonly Encoding MD5Encoding = Encoding.GetEncoding("ISO-8859-1");

    /// <summary>
    /// 獲取加密字符
    /// </summary>
    /// <param name="password">密碼</param>
    /// <param name="uin">用戶賬號,請使用明文</param>
    /// <param name="vcode">驗證碼</param>
    /// <returns>加密字符</returns>
    public static String GetEncryption(String password, String uin, String vcode)
    {
        var str = HexChar2Bin(ToMD5(password));
        var str2 = ToMD5(str + Uin2Hex(uin));
        var str3 = ToMD5(str2 + vcode.ToUpper());

        return str3;
    }

    /// <summary>
    /// 十六進制字符串轉換為二進制字符串
    /// </summary>
    /// <param name="value">十六進制字符串</param>
    /// <returns>二進制字符串</returns>
    private static String HexChar2Bin(String value)
    {
        var buffer = new StringBuilder(value.Length >> 1);

        for (var i = 0; i < value.Length; i += 2)
            buffer.Append((Char)Convert.ToByte(value.Substring(i, 2), 16));

        return buffer.ToString();
    }

    /// <summary>
    /// 用戶賬號轉換為十六進制字符串
    /// </summary>
    /// <param name="value">用戶賬號</param>
    /// <returns>十六進制字符串</returns>
    private static String Uin2Hex(String value) 
    {
        var str = Convert.ToString(Convert.ToInt64(value), 16).PadLeft(16, '0');

        return HexChar2Bin(str);
    }

    /// <summary>
    /// 轉換為MD5加密字符串
    /// </summary>
    /// <param name="value">字符串</param>
    /// <returns>MD5加密字符串</returns>
    private static String ToMD5(String value)
    {
        var data = QQEncryptionHelper.MD5Encoding.GetBytes(value);

        using (var md5 = new MD5CryptoServiceProvider())
            return String.Concat(md5.ComputeHash(data).Select(p => p.ToString("X").PadLeft(2, '0')));
    }
}

調用示例:

var userUid = "[userUid]";
var password = "[password]";
var verifyCode = "[verifyCode]";

password = QQEncryptionHelper.GetEncryption(password, userUid, verifyCode);

3、HTTP GET完成登陸

騰訊並沒有像新浪、網易使用HTTP POST的方式,而是HTTP GET。請求登陸鏈接格式:

https://ssl.ptlogin2.qq.com:443/login?ptlang=2052&u=[userUid]&p=[password]&verifycode=[verifyCode]&low_login_enable=1&low_login_hour=720&aid=46000101&u1=[authorizationUrl]&ptredirect=1&h=1&from_ui=1&dumy=&fp=loginerroralert&action=1-2-11125&g=1&t=1&dummy=

u:用戶名

p:用戶密碼(需要使用固定加密算法)

verifyCode:驗證碼(第一步請求獲取)

u1:應用授權鏈接

var userUid = "[userUid]";
var password = "[password]";
var verifyCode = "[verifyCode]";

password = QQEncryptionHelper.GetEncryption(password, userUid, verifycode);

var authorizeUrl = new UriBuilder("https://open.t.qq.com:443/cgi-bin/oauth2/authorize")
{
    Query = new UriQueryBuilder()
        .Append("client_id", appKey)
        .Append("redirect_uri", callbackUrl)
        .Append("response_type", "code")
        .ToString()
}.ToString();
var loginUrl = new UriBuilder("https://ssl.ptlogin2.qq.com/login") 
{ 
    Query = new UriQueryBuilder()
        .Append("ptlang", "2052")
        .Append("u", userUid)
        .Append("p", password)
        .Append("verifycode", verifycode)
        .Append("low_login_enable", "1")
        .Append("low_login_hour", "720")
        .Append("aid", "46000101")
        .Append("u1", authorizeUrl)
        .Append("ptredirect", "1")
        .Append("h", "1")
        .Append("from_ui", "1")
        .Append("dumy")
        .Append("fp", "loginerroralert")
        .Append("action", "1-2-11125")
        .Append("g", "1")
        .Append("t", "1")
        .Append("dummy")
        .ToString()    
}.ToString();

var webRequest = WebRequest.CreateHttp(loginUrl);
var webRequest.CookieContainer = cookieContainer;
var webResponse = webRequest.GetResponse();
var responseStream = webResponse.GetResponseStream();
var responseReader = new StreamReader(responseStream, Encoding.UTF8, true);
var response = responseReader.ReadToEnd();

返回數據如下:

ptuiCB('0','0','https://open.t.qq.com:443/cgi-bin/oauth2/authorize?client_id=[appKey]&redirect_uri=[callbackUrl]','1','登錄成功!', '[userNickName]');

4、獲取AuthorizationCode

再次請求授權鏈接會發現響應還是返回登陸內容,其實我們已經完成了登陸,只需要將返回內容中隱藏域u1的值獲取並再次請求就可以獲得授權代碼。其中最重要的一個查詢參數就是sessionKey。

<input type="hidden" name="u1" value="https://open.t.qq.com/cgi-bin/oauth2/authorize?client_id=[appKey]&response_type=code&redirect_uri=http%3A%2F%2Fwww.qq.com&checkStatus=yes&appfrom=&g_tk=&sessionKey=5dcad9615be24c288cfe03eeca4e9e0d&checkType=showAuth&state=" id="u1">

5、獲取access_token

請求地址格式:

https://open.t.qq.com:443/cgi-bin/oauth2/access_token?client_id=[appKey]&client_secret=[appSecret]&redirect_uri=[callbackUrl]&grant_type=authorization_code&code=[authorizationCode]

返回數據如下:

access_token=[accessToken]&expires_in=8035200&refresh_token=[refreshToken]&openid=[openId]&name=[userName]&nick=[nickName]&state=

 

示例代碼中UriQueryBuilder類型是自己編寫的,附上源代碼:

/// <summary>
/// 鏈接查詢建造器
/// </summary>
internal sealed class UriQueryBuilder
{
    /// <summary>
    /// 查詢參數格式
    /// </summary>
    /// <value>{0}={1}</value>
    public const String QUERY_FORMAT = "{0}={1}";

    /// <summary>
    /// 查詢參數分隔符
    /// </summary>
    /// <value>&</value>
    public const String QUERY_SEPARATOR = "&";

    private Dictionary<String, String> _buffer = new Dictionary<String, String>();

    /// <summary>
    /// 添加
    /// </summary>
    /// <param name="name">參數名稱</param>
    /// <param name="value">參數值</param>
    public UriQueryBuilder Append(String name, String value = "")
    {
        if (String.IsNullOrWhiteSpace(name))
            throw new ArgumentException("name不能為空.");

        _buffer[name] = Uri.EscapeDataString(value);

        return this;
    }

    /// <summary>
    /// 清空
    /// </summary>
    public void Clear()
    {
        _buffer.Clear();
    }

    /// <summary>
    /// 轉換字符串
    /// </summary>
    /// <returns>拼接查詢參數</returns>
    public override String ToString()
    {
        var values = _buffer.Select(p => String.Format(QUERY_FORMAT, p.Key, p.Value));

        return String.Join(QUERY_SEPARATOR, values);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM