欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ASP.NET实现QQ、微信、新浪微博OAuth2.0授权登录 原创

程序员文章站 2022-03-21 15:31:18
不管是腾讯还是新浪,查看他们的api,php都是有完整的接口,但对c#支持似乎都不是那么完善,都没有,腾讯是完全没有,新浪是提供第三方的,而且后期还不一定升级,nnd,用第...

不管是腾讯还是新浪,查看他们的api,php都是有完整的接口,但对c#支持似乎都不是那么完善,都没有,腾讯是完全没有,新浪是提供第三方的,而且后期还不一定升级,nnd,用第三方的动辄就一个类库,各种配置还必须按照他们约定的写,烦而且乱,索性自己写,后期的扩展也容易,看过接口后,开始以为很难,参考了几个源码之后发现也不是那么难,无非是get或post请求他们的接口获取返回值之类的,话不多说,这里只提供几个代码共参考,抛砖引玉了。。。

我这个写法的特点是,用到了session,使用对象实例化之后调用 login() 跳转到登录页面,在回调页面调用callback() 执行之后,可以从session也可以写独立的函数(如:getopenid())中获取access_token或用户的唯一标识,以方便做下一步的操作。所谓绑定就是把用户的唯一标识取出,插入数据库,和帐号绑定起来。

1.首先是所有oauth类的基类,放一些需要公用的方法

public abstract class baseoauth
{
  public httprequest request = httpcontext.current.request;
  public httpresponse response = httpcontext.current.response;
  public httpsessionstate session = httpcontext.current.session;

  public abstract void login();
  public abstract string callback();

  #region 内部使用函数

  /// <summary>
  /// 生成唯一随机串防csrf攻击
  /// </summary>
  /// <returns></returns>
  protected string getstatecode()
  {
    random rand = new random();
    string data = datetime.now.tostring("yyyymmddhhmmssffff") + rand.next(1, 0xf423f).tostring();

    md5cryptoserviceprovider md5 = new md5cryptoserviceprovider();

    byte[] md5byte = md5.computehash(utf8encoding.default.getbytes(data));

    return bitconverter.tostring(md5byte).replace("-", "");

  }

  /// <summary>
  /// get请求
  /// </summary>
  /// <param name="url"></param>
  /// <returns></returns>
  protected string getrequest(string url)
  {
    httpwebrequest httpwebrequest = system.net.webrequest.create(url) as httpwebrequest;
    httpwebrequest.method = "get";
    httpwebrequest.servicepoint.expect100continue = false;

    streamreader responsereader = null;
    string responsedata;
    try
    {
      responsereader = new streamreader(httpwebrequest.getresponse().getresponsestream());
      responsedata = responsereader.readtoend();
    }
    finally
    {
      httpwebrequest.getresponse().getresponsestream().close();
      responsereader.close();
    }

    return responsedata;
  }

  /// <summary>
  /// post请求
  /// </summary>
  /// <param name="url"></param>
  /// <param name="postdata"></param>
  /// <returns></returns>
  protected string postrequest(string url, string postdata)
  {
    httpwebrequest httpwebrequest = system.net.webrequest.create(url) as httpwebrequest;
    httpwebrequest.method = "post";
    httpwebrequest.servicepoint.expect100continue = false;
    httpwebrequest.contenttype = "application/x-www-form-urlencoded";

    //写入post参数
    streamwriter requestwriter = new streamwriter(httpwebrequest.getrequeststream());
    try
    {
      requestwriter.write(postdata);
    }
    finally
    {
      requestwriter.close();
    }

    //读取请求后的结果
    streamreader responsereader = null;
    string responsedata;
    try
    {
      responsereader = new streamreader(httpwebrequest.getresponse().getresponsestream());
      responsedata = responsereader.readtoend();
    }
    finally
    {
      httpwebrequest.getresponse().getresponsestream().close();
      responsereader.close();
    }

    return responsedata;
  }

  /// <summary>
  /// 解析json
  /// </summary>
  /// <param name="strjson"></param>
  /// <returns></returns>
  protected namevaluecollection parsejson(string strjson)
  {
    namevaluecollection mc = new namevaluecollection();
    regex regex = new regex(@"(\s*\""?([^""]*)\""?\s*\:\s*\""?([^""]*)\""?\,?)");
    strjson = strjson.trim();
    if (strjson.startswith("{"))
    {
      strjson = strjson.substring(1, strjson.length - 2);
    }

    foreach (match m in regex.matches(strjson))
    {
      mc.add(m.groups[2].value, m.groups[3].value);
    }
    return mc;
  }

  /// <summary>
  /// 解析url
  /// </summary>
  /// <param name="strparams"></param>
  /// <returns></returns>
  protected namevaluecollection parseurlparameters(string strparams)
  {
    namevaluecollection nc = new namevaluecollection();
    foreach (string p in strparams.split('&'))
    {
      string[] ps = p.split('=');
      nc.add(ps[0], ps[1]);
    }
    return nc;
  }

  #endregion

}

2.qq的oauth类

public class qqoauth : baseoauth
{
  public string appid = configurationmanager.appsettings["oauth_qq_appid"];
  public string appkey = configurationmanager.appsettings["oauth_qq_appkey"];
  public string redirecturl = configurationmanager.appsettings["oauth_qq_redirecturl"];

  public const string get_auth_code_url = "https://graph.qq.com/oauth2.0/authorize";
  public const string get_access_token_url = "https://graph.qq.com/oauth2.0/token";
  public const string get_openid_url = "https://graph.qq.com/oauth2.0/me";

  /// <summary>
  /// qq登录,跳转到登录页面
  /// </summary>
  public override void login()
  {
    //-------生成唯一随机串防csrf攻击
    string state = getstatecode();
    session["qc_state"] = state; //state 放入session

    string parms = "?response_type=code&"
      + "client_id=" + appid + "&redirect_uri=" + uri.escapedatastring(redirecturl) + "&state=" + state;

    string url = get_auth_code_url + parms;
    response.redirect(url); //跳转到登录页面
  }

  /// <summary>
  /// qq回调函数
  /// </summary>
  /// <param name="code"></param>
  /// <param name="state"></param>
  /// <returns></returns>
  public override string callback()
  {
    string code = request.querystring["code"];
    string state = request.querystring["state"];

    //--------验证state防止csrf攻击
    if (state != (string)session["qc_state"])
    {
      showerror("30001");
    }

    string parms = "?grant_type=authorization_code&"
      + "client_id=" + appid + "&redirect_uri=" + uri.escapedatastring(redirecturl)
      + "&client_secret=" + appkey + "&code=" + code;

    string url = get_access_token_url + parms;
    string str = getrequest(url);

    if (str.indexof("callback") != -1)
    {
      int lpos = str.indexof("(");
      int rpos = str.indexof(")");
      str = str.substring(lpos + 1, rpos - lpos - 1);
      namevaluecollection msg = parsejson(str);
      if (!string.isnullorempty(msg["error"]))
      {
        showerror(msg["error"], msg["error_description"]);
      }

    }

    namevaluecollection token = parseurlparameters(str);
    session["qc_accesstoken"] = token["access_token"]; //access_token 放入session
    return token["access_token"];
  }


  /// <summary>
  /// 使用access token来获取用户的openid
  /// </summary>
  /// <param name="accesstoken"></param>
  /// <returns></returns>
  public string getopenid()
  {
    string parms = "?access_token=" + session["qc_accesstoken"];

    string url = get_openid_url + parms;
    string str = getrequest(url);

    if (str.indexof("callback") != -1)
    {
      int lpos = str.indexof("(");
      int rpos = str.indexof(")");
      str = str.substring(lpos + 1, rpos - lpos - 1);
    }

    namevaluecollection user = parsejson(str);

    if (!string.isnullorempty(user["error"]))
    {
      showerror(user["error"], user["error_description"]);
    }

    session["qc_openid"] = user["openid"]; //openid 放入session
    return user["openid"];
  }

  /// <summary>
  /// 显示错误信息
  /// </summary>
  /// <param name="code">错误编号</param>
  /// <param name="description">错误描述</param>
  private void showerror(string code, string description = null)
  {
    if (description == null)
    {
      switch (code)
      {
        case "20001":
          description = "<h2>配置文件损坏或无法读取,请检查web.config</h2>";
          break;
        case "30001":
          description = "<h2>the state does not match. you may be a victim of csrf.</h2>";
          break;
        case "50001":
          description = "<h2>可能是服务器无法请求https协议</h2>可能未开启curl支持,请尝试开启curl支持,重启web服务器,如果问题仍未解决,请联系我们";
          break;
        default:
          description = "<h2>系统未知错误,请联系我们</h2>";
          break;
      }
      response.write(description);
      response.end();
    }
    else
    {
      response.write("<h3>error:<h3>" + code + "<h3>msg:<h3>" + description);
      response.end();
    }
  }

}

3.新浪微博的oauth类

public class sinaoauth : baseoauth
{
  public string appkey = configurationmanager.appsettings["oauth_sina_appkey"];
  public string appsecret = configurationmanager.appsettings["oauth_sina_appsecret"];
  public string redirecturl = configurationmanager.appsettings["oauth_sina_redirecturl"];

  public const string get_auth_code_url = "https://api.weibo.com/oauth2/authorize";
  public const string get_access_token_url = "https://api.weibo.com/oauth2/access_token";
  public const string get_uid_url = "https://api.weibo.com/2/account/get_uid.json";

  /// <summary>
  /// 新浪微博登录,跳转到登录页面
  /// </summary>
  public override void login()
  {
    //-------生成唯一随机串防csrf攻击
    string state = getstatecode();
    session["sina_state"] = state; //state 放入session

    string parms = "?client_id=" + appkey + "&redirect_uri=" + uri.escapedatastring(redirecturl)
      + "&state=" + state;

    string url = get_auth_code_url + parms;
    response.redirect(url); //跳转到登录页面
  }

  /// <summary>
  /// 新浪微博回调函数
  /// </summary>
  /// <returns></returns>
  public override string callback()
  {
    string code = request.querystring["code"];
    string state = request.querystring["state"];

    //--------验证state防止csrf攻击
    if (state != (string)session["sina_state"])
    {
      showerror("the state does not match. you may be a victim of csrf.");
    }

    string parms = "client_id=" + appkey + "&client_secret=" + appsecret
      + "&grant_type=authorization_code&code=" + code + "&redirect_uri=" + uri.escapedatastring(redirecturl);

    string str = postrequest(get_access_token_url, parms);

    namevaluecollection user = parsejson(str);

    session["sina_accesstoken"] = user["access_token"]; //access_token 放入session
    session["sina_uid"] = user["uid"]; //uid 放入session
    return user["access_token"];
  }


  /// <summary>
  /// 显示错误信息
  /// </summary>
  /// <param name="description">错误描述</param>
  private void showerror(string description = null)
  {
    response.write("<h2>" + description + "</h2>");
    response.end();
  }
}

4.微信的oauth类

public class weixinoauth : baseoauth
{
  public string appid = configurationmanager.appsettings["oauth_weixin_appid"];
  public string appsecret = configurationmanager.appsettings["oauth_weixin_appsecret"];
  public string redirecturl = configurationmanager.appsettings["oauth_weixin_redirecturl"];

  public const string get_auth_code_url = "https://open.weixin.qq.com/connect/qrconnect";
  public const string get_access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token";
  public const string get_userinfo_url = "https://api.weixin.qq.com/sns/userinfo";

  /// <summary>
  /// 微信登录,跳转到登录页面
  /// </summary>
  public override void login()
  {
    //-------生成唯一随机串防csrf攻击
    string state = getstatecode();
    session["weixin_state"] = state; //state 放入session

    string parms = "?appid=" + appid
      + "&redirect_uri=" + uri.escapedatastring(redirecturl) + "&response_type=code&scope=snsapi_login"
      + "&state=" + state + "#wechat_redirect";

    string url = get_auth_code_url + parms;
    response.redirect(url); //跳转到登录页面
  }

  /// <summary>
  /// 微信回调函数
  /// </summary>
  /// <param name="code"></param>
  /// <param name="state"></param>
  /// <returns></returns>
  public override string callback()
  {
    string code = request.querystring["code"];
    string state = request.querystring["state"];

    //--------验证state防止csrf攻击
    if (state != (string)session["weixin_state"])
    {
      showerror("30001");
    }

    string parms = "?appid=" + appid + "&secret=" + appsecret
      + "&code=" + code + "&grant_type=authorization_code";

    string url = get_access_token_url + parms;
    string str = getrequest(url);


    namevaluecollection msg = parsejson(str);
    if (!string.isnullorempty(msg["errcode"]))
    {
      showerror(msg["errcode"], msg["errmsg"]);
    }

    session["weixin_accesstoken"] = msg["access_token"]; //access_token 放入session
    session["weixin_openid"] = msg["openid"]; //access_token 放入session
    return msg["access_token"];
  }


  /// <summary>
  /// 显示错误信息
  /// </summary>
  /// <param name="code">错误编号</param>
  /// <param name="description">错误描述</param>
  private void showerror(string code, string description = null)
  {
    if (description == null)
    {
      switch (code)
      {
        case "20001":
          description = "<h2>配置文件损坏或无法读取,请检查web.config</h2>";
          break;
        case "30001":
          description = "<h2>the state does not match. you may be a victim of csrf.</h2>";
          break;
        case "50001":
          description = "<h2>接口未授权</h2>";
          break;
        default:
          description = "<h2>系统未知错误,请联系我们</h2>";
          break;
      }
      response.write(description);
      response.end();
    }
    else
    {
      response.write("<h3>error:<h3>" + code + "<h3>msg:<h3>" + description);
      response.end();
    }
  }

}

5.web.config配置信息

<appsettings>
    <!--qq登录相关配置-->
    <add key="oauth_qq_appid" value="123456789" />
    <add key="oauth_qq_appkey" value="25f9e794323b453885f5181f1b624d0b" />
    <add key="oauth_qq_redirecturl" value="http://www.domain.com/oauth20/qqcallback.aspx" />

    <!--新浪微博登录相关配置-->
    <add key="oauth_sina_appkey" value="123456789" />
    <add key="oauth_sina_appsecret" value="25f9e794323b453885f5181f1b624d0b" />
    <add key="oauth_sina_redirecturl" value="http://www.domain.com/oauth20/sinacallback.aspx" />

    <!--微信登录相关配置-->
    <add key="oauth_weixin_appid" value="wx123456789123" />
    <add key="oauth_weixin_appsecret" value="25f9e794323b453885f5181f1b624d0b" />
    <add key="oauth_weixin_redirecturl" value="http://www.domain.com/oauth20/weixincallback.aspx" />
</appsettings>