Keep on going never give up.

Let's Go

Asp.Net MVC 接入第三方登录(QQ、新浪等)

Asp.NetLonely2019-09-03 13:43:40101次6条

Asp.Net MVC接入第三方登录,目前比较广泛使用的有QQ登录、新浪登录、微信登录、百度登录,下面我们来说说怎么实现网站第三方登录功能。

一开始没开始动手做之前,我反复看了官方文档十几遍都没搞清楚到底咋回事,我该如何实现?用户登录成功后我能拿到的数据有哪些?文档看不明白,那怎么办?找个Demo看一下呗,百度了一圈之后发现网上的Demo都比较旧或者比较简陋,有些还是WebForm的,最后在CSDN花币下了两个Demo配合官网文档一看,顿然醒悟,不过有些Demo比较过分啊,就拼了个链接字符串跳转到登录授权页后面就没了,浪费币#aru_39#

官方文档传送

Q Q登录文档:传送门

新浪登录文档:传送门

微信登录文档:传送门

百度登录文档:传送门

前期准备工作

基本上接入第三方登录就那几个步骤,有个别平台流程不太一样但都大同小异,弄清楚了流程举一反三,有了思路实现起来代码就好写了。以QQ登录为例,流程为:

(1)、申请AppId和AppKey,有些平台叫法不一样如新浪的叫AppKey、AppSecret但都是同一个东西。

(2)、获取Authorization Code。

(3)、通过Authorization Code获取授权Access Token。

(4)、使用Access Token来获取用户的OpenID。(这一步新浪没有,新浪的叫Uid,在上一步直接和Token等信息一起返回了)

(5)、使用Access Token以及OpenID获取登录用户信息。到这一步拿到用户信息基本上就已经算结束了啊,注册用户绑定那些按照自己的业务流程来就行了。

在申请AppKey、AppSecret之前,作为开发者,你需要准备以下资料:

1、基本信息:主要为网站名称、网站类别、网站简介、网站Logo。

2、平台信息:主要为网站地址、网站回调域、主板单位名称、网站备案号(没备案其实也可以的)。

在这里就不得不说腾讯有点恶心了,要认证开发者身份还必须手持身份证上传,认证开发者身份完之后才能创建应用申请Key。我就特别讨厌要一堆个人信息的网站,这点还是新浪比较友好,申请Key新浪这边门槛就低很多了,直接有一个新浪账号就能申请到Key了,只不过是没通过审核的,能测试用不就好了。

另外这些Key,某宝上可以买到,30RMB左右,提供网站信息,回调域就可以了。微信这边个人好像申请不了,也不废话这么多了,这一步略过,在有AppKey、AppSecret的情况下,我们来到第二步开始编码。

开始编写代码

本文节选代码,完整Deom文章底部提供下载地址。

C#是一门面向对象语言,既然是面向对象就得用上面向对象思想啊,还记得面向对象三大特性是什么吗?对没错就是封装、继承、多态,必须章口就莱#newtieba_10#

image.png

(1)、新建ClassLibrary(类库)取名为OAuth,把各种平台的登录写在这里。

(2)、新建一个接口,取名为IOAuthConfig.cs,代码如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OAuth
{
    public interface IOAuthConfig
    {
        /// <summary>
        /// 请求的基本网址
        /// 如:https://api.weibo.com
        /// </summary>
        string BaseUrl { get; }

        /// <summary>
        /// AppKey
        /// </summary>
        string AppKey { get; }

        /// <summary>
        /// AppSecret
        /// </summary>
        string AppSecret { get; }

        /// <summary>
        /// 回调域名
        /// </summary>
        string Domain { get; }
    }
}

(3)、新建一个抽象类,取名为BaseOAuthConfig.cs,代码如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OAuth
{
    internal abstract class BaseOAuthConfig : IOAuthConfig
    {
        private Dictionary<string, string> dicConfig = null;

        /// <summary>
        /// 构造函数
        /// </summary>
        protected BaseOAuthConfig()
        {
            dicConfig = GetConfig();
        }

        protected abstract Dictionary<string, string> GetConfig();

        #region 实现 IOAuthConfig 接口成员

        public string BaseUrl => dicConfig["BaseUrl"];

        public string AppKey => dicConfig["AppKey"];

        public string AppSecret => dicConfig["AppSecret"];

        public string Domain => dicConfig["Domain"];

        #endregion

    }
}

(4)、新建一个密封类QQOAuthConfig.cs继承于BaseOAuthConfig,并重写抽象方法GetConfig()。这里的Config.GetValue(),Config是封装的一个类,用于读取和设置配置文件的信息,配置信息存进数据库也可以,因为这些数据不会经常改动,所以我把它存到配置文件,我想读取配置文件应该要比查数据库速度要快吧。

using Common.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OAuth.QQ
{
    internal sealed class QQOAuthConfig : BaseOAuthConfig
    {
        /// <summary>
        /// 获取QQ登录API配置
        /// </summary>
        /// <returns>返回配置信息</returns>
        protected override Dictionary<string, string> GetConfig()
        {
            Dictionary<string, string> dic = new Dictionary<string, string>(4);
            dic.Add("BaseUrl", Config.GetValue("QQBaseUrl"));
            dic.Add("AppKey", Config.GetValue("QQAppKey"));
            dic.Add("AppSecret", Config.GetValue("QQAppSecret"));
            dic.Add("Domain", Config.GetValue("CallBackDomain"));
            return dic;
        }


        


    }
}

(5)、建立Model,代码如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OAuth.QQ
{
    public class QQModel
    {
        /// <summary>
        /// 接口调用凭证
        /// </summary>
        public string Access_token { get; set; }
        /// <summary>
        /// access_token接口调用凭证超时时间,单位(秒)
        /// </summary>
        public string Expires_in { get; set; }
        /// <summary>
        /// 用户刷新access_token
        /// </summary>
        public string Refresh_token { get; set; }
    }

    public class QQUser
    {
        /// <summary>
        /// OpenID是此网站上或应用中唯一对应用户身份的标识,网站或应用可将此ID进行存储,便于用户下次登录时辨识其身份,或将其与用户在网站上或应用中的原有账号进行绑定。
        /// </summary>
        public string Openid { get; set; }
    }

    public class QQUserInfo
    {
        /// <summary>
        /// 返回码,0: 正确返回,其它: 失败。
        /// </summary>
        public int Ret { get; set; }
        /// <summary>
        /// 如果ret小于0,会有相应的错误信息提示,返回数据全部用UTF-8编码。
        /// </summary>
        public string Msg { get; set; }
        /// <summary>
        /// 用户在QQ空间的昵称。
        /// </summary>
        public string Nickname { get; set; }
        /// <summary>
        /// 大小为30×30像素的QQ空间头像URL。
        /// </summary>
        public string Figureurl { get; set; }
        /// <summary>
        /// 大小为50×50像素的QQ空间头像URL。
        /// </summary>
        public string Figureurl_1 { get; set; }
        /// <summary>
        /// 大小为100×100像素的QQ空间头像URL。
        /// </summary>
        public string Figureurl_2 { get; set; }
        /// <summary>
        /// 大小为40×40像素的QQ头像URL。
        /// </summary>
        public string Figureurl_qq_1 { get; set; }
        /// <summary>
        /// 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100x100的头像,但40x40像素则是一定会有。
        /// </summary>
        public string Figureurl_qq_2 { get; set; }
        /// <summary>
        /// 性别,如果获取不到则默认返回"男"。
        /// </summary>
        public string Gender { get; set; }
    }
}

(6)、把登录流程封装成一个类,在需要的地方调用就可以了,代码如下所示:

using Common.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace OAuth.QQ
{
    public class QQ
    {
        private IOAuthConfig config = new QQOAuthConfig(); //获取配置信息

        /// <summary>
        /// 获取Authorization Code请求地址,请求方法:Get
        /// </summary>
        /// <param name="callback">回调函数名称</param>
        /// <param name="state">client端的状态值。用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回。请务必严格按照流程检查用户与state参数状态的绑定。</param>
        /// <returns>返回Authorization Code请求地址</returns>
        public string GetAuthCodeUrl(string callback, out string state)
        {
            string api = "/oauth2.0/authorize";
            string callbackUrl = HttpUtility.UrlEncode(config.Domain + callback);
            state = RandomHelper.GetRandomString(16);
            string url = string.Format("{0}{1}?response_type=code&client_id={2}&redirect_uri={3}&state={4}",
                config.BaseUrl, api, config.AppKey, callbackUrl, state);
            return url;
        }

        /// <summary>
        /// 获取Access Token请求地址,请求方法:Get
        /// </summary>
        /// <param name="code">Authorization Code</param>
        /// <param name="callback">回调函数名称</param>
        /// <returns>返回Access Token请求地址</returns>
        public string GetAccessTokenUrl(string code, string callback)
        {
            string api = "/oauth2.0/token";
            string callbackUrl = HttpUtility.UrlEncode(config.Domain + callback);
            string url = string.Format("{0}{1}?grant_type=authorization_code&client_id={2}&client_secret={3}&code={4}&redirect_uri={5}",
                config.BaseUrl, api, config.AppKey, config.AppSecret, code, callbackUrl);
            return url;
        }

        /// <summary>
        /// 获取Access Token
        /// </summary>
        /// <param name="url">Access Token请求地址</param>
        /// <returns>返回access_token</returns>
        public string GetAccessToken(string url)
        {
            try
            {
                string data = RequestHelper.HttpGet(url, Encoding.UTF8);
                //成功返回数据内容:access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14s
                //失败情况和其他需求,自行根据文档返回码完善
                string[] arr = data.Split('&');
                string[] temp = arr[0].Split('=');
                return temp[1];
            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// 获取用户OpenID
        /// </summary>
        /// <param name="access_token">获取到的access token</param>
        /// <returns>返回OpenID</returns>
        public string GetOpenID(string access_token)
        {
            try
            {
                string api = "/oauth2.0/me";
                string url = string.Format("{0}{1}?access_token={2}", config.BaseUrl, api, access_token);
                string data = RequestHelper.HttpGet(url, Encoding.UTF8);
                // 返回数据内容:callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
                int startIndex = data.IndexOf("(") + 1;
                int endIndex = data.IndexOf(")");
                int length = endIndex - startIndex;
                QQUser qqUser = data.Substring(startIndex, length).Trim().ToObject<QQUser>(); 
                return qqUser.Openid;
            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// 获取用户信息
        /// </summary>
        /// <param name="access_token">获取到的access token</param>
        /// <param name="openid">用户的ID,与QQ号码一一对应。 </param>
        /// <returns>返回QQUserInfo</returns>
        public QQUserInfo GetQQUserInfo(string access_token, string openid)
        {
            try
            {
                string api = "/user/get_user_info";
                string url = string.Format("{0}{1}?access_token={2}&oauth_consumer_key={3}&openid={4}&format=json",
                    config.BaseUrl, api, access_token, config.AppKey, openid);
                string data = RequestHelper.HttpGet(url, Encoding.UTF8);
                return data.ToObject<QQUserInfo>(); 
            }
            catch
            {
                throw;
            }
        }




    }
}

(7)、流程已经写好了,下面在登录的控制器调用就好了,代码如下所示:

[HttpGet]
public ActionResult QQLogin()
{
    QQ qqRequest = new QQ();
    string salt;
    string url = qqRequest.GetAuthCodeUrl("Test/QQCallback", out salt);
    Session["QQLoginSalt"] = salt;
    return Redirect(url); 
}

[HttpGet]
public ActionResult QQCallback()
{
    //图演示方便直接写这了
    string backSalt = Request.QueryString["state"];
    string code = Request.QueryString["code"]; //此code会在10分钟内过期
    //判断防止跨站请求伪造(CSRF)攻击等……
    QQ qqRequest = new QQ();
    string url = qqRequest.GetAccessTokenUrl(code, "Test/QQCallback");
    string access_token = qqRequest.GetAccessToken(url);
    string openid = qqRequest.GetOpenID(access_token);
    QQUserInfo qqUserInfo = qqRequest.GetQQUserInfo(access_token, openid);
    //根据你的业务逻辑自己完善
    if (qqUserInfo.Ret == 0 && string.IsNullOrWhiteSpace(qqUserInfo.Msg)) //成功
    {
        return Content("登录成功!欢迎您:"+ qqUserInfo.Nickname);
    }
    else //失败
    {
        return Content("登录失败!返回码:" + qqUserInfo.Ret);
    }
}

在这里登录成功以后就能拿到用户的信息了,自己再根据业务需要完成其他的一些操作例如注册绑定账号等等,搞清楚以后很容易举一反三将别的平台的接入方法也写进来,这里我只写了QQ登录和新浪登录,因为我搞不到微信登录的Key所以就懒得写了,有兴趣的同学自行完善其他平台的接入。

完整Deom戳这里 --> 传送门

暗锚,解决锚点偏移

文章评论

    嘿,来试试登录吧!