• 腾讯微博应用授权隐式登陆实现


    应用授权的请求地址格式,使用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','x00x00x00x00x00x00x00x00');

    第二个参数就是验证码,第三个参数是用户账号调用$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);
        }
    }
  • 相关阅读:
    css如何设置div中的内容垂直居中?
    有哪些sql优化工具
    XSS攻击
    java的HashSet 原理
    复杂度O(n)计算
    Kubernetes(K8s)基础知识(docker容器技术)
    Golang glog使用详解
    教你如何找到Go内存泄露【精编实战】
    Linux生产环境上,最常用的一套“AWK“技巧【转】
    Go 程序的性能监控与分析 pprof
  • 原文地址:https://www.cnblogs.com/junchu25/p/3183038.html
Copyright © 2020-2023  润新知