使用OAuth2.0协议的github、QQ、weibo第三方登录接入总结

第三方接入总结

本文主要讲解OAuth2.0协议和github、微博、QQ三个平台提供的接入流程,介绍nodejs下十分好用的认证授权插件passport.js。本文代码基于nodejs-express。

OAuth2.0介绍

 在说OAuth协议之前,想说一下OpenID。在2005年的夏天,一个开源社区为了解决一个其它现有web身份认证技术不容易解决的问题时,制定了OpenID,OpenID是一个认证(Authentication)协议,这个协议让第三方应用程序在不获取你密码的情况下认证你的身份,OpenID和OAuth是两个经常放在一起比较的web身份认证技术,但OAuth比起OpenID,更像是一个授权(Authorization)协议,是让第三方应用程序在不获取你密码的情况下获取你的授权去对授权提供商或者说是资源提供商请求你授权的资源,拿到OpenID就像给别人指,这栋房子是我的,我可以拿点东西给你看看,而拿到OAuth授权就像你给别人说,这栋房子是我的,这个钥匙给你,但只能开大门不能进卧室,哪些可以动哪些可以开都是你说了算,并且你可以随时方便的收回这把钥匙。

 OAuth2.0是2006年提出来的新一代OAuth版本,比起OAuth1.x,它简化了认证交互过程,增加了认证机制,使用了SSL,去掉了一个叫做客户端token secret的东西,这也导致了OAuth升级需要改动代码,并且没有了1.0版本的安全bug(1.0a修复),这个安全bug主要是回调地址设定方式导致的。

 OAuth2.0协议主要有四个角色,资源所有者即用户,资源服务器,授权服务器和用户代理即客户端,这四个角色主要是完成了这样一个流程,客户端与服务器提供商之前,有一个通过OAuth协议完成的授权服务器,客户端有从服务端获取的唯一ID,客户端不能直接登录服务器提供商,需要先通过用户在授权服务器取得授权码,并且拿着获得用户授权的授权码在授权层换取accessToken,通过这个token向资源服务器请求用户授权信息。

github OAuth2.0登录接入

 OAuth2.0客户端一共有4种授权模式,授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)和客户端模式(client credentials),在github提供的OAuth.v3接口中,使用的是授权码模式,在这种模式中,客户端需要引导用户重定向请求到Github的OAuth认证接口,带上在授权服务器申请的客户端 client_id,配置的回调地址 redirect_uri ,申请授权的scope,你也可以带上状态值state,服务器会在回调中原封不动地返回你这个值,这一步将会换取前面我们说到的授权码code,具体流程和代码实现如下:

1.首先我们需要在github上新建一个应用(记得登录),获取客户端的Client ID和Client Secret并配置好回调地址。
此处应有图

2.重定向到OAuth认证接口 GET https://github.com/login/OAuth/authorize

<a href= '/mygithub'></a>

3.发起重定向请求到授权服务器换取code

var express = require('express');
app.get('/mygithub', function(req, res) {
    var dataStr = (new Date()).valueOf();
    //重定向到认证接口,并配置参数
    //注意这里使用的是node的https模块发起的请求
    var path = "https://github.com/login/OAuth/authorize";
    path += '?client_id=' + OAuthConfig.GITHUB_CLIENT_ID;
    path += '&scope='+OAuthConfig.GITHUB_CLIENT_SCOPE;
    path += '&state='+ dataStr;
    //转发到授权服务器
    res.redirect(path);
});

 通过第一步的请求,如果用户正确授权,换取的code和上一步传输的state会被服务器作为回调参数,放在你在这里配置的回调地址上,但是如果授权服务器返回的state和第一步传入的不一致,根据OAuth2.0协议,开发者应该中止这个请求。拿到了code后,开发者应该在回调地址处重定向请求到授权服务器利用client_idclient_secretcode换取access_token,这个access_token就是你开启开门的钥匙,拿到钥匙后你可以本地存储钥匙或者根据scope马上向资源服务器请求数据,具体流程和代码实现如下:

app.get("/OAuth/github_v2/callback", function(req, res){
    var code = req.query.code;
    var state = req.query.state;
    var headers = req.headers;
    var path = "/login/OAuth/access_token";
    headers.host = 'github.com';

    path += '?client_id=' + OAuthConfig.GITHUB_CLIENT_ID;
    path += '&client_secret='+OAuthConfig.GITHUB_CLIENT_SECRET;
    path += '&code='+ code;
    console.log(path);
    var opts = {
        hostname:'github.com',
        port:'443',
        path:path,
        headers:headers,
        method:'POST'
    };
    //注意这里使用的是node的https模块发起的请求
    var req = https.request(opts, function(res){
        res.setEncoding('utf8');
         console.log(opts);
         //解析返回数据
        res.on('data', function(data){
            var args = data.split('&');
            var tokenInfo = args[0].split("=");
            var token = tokenInfo[1];
            console.log(data);
            //利用access_token向资源服务器请求用户授权数据
            var url = "https://api.github.com/user?access_token="+token&scope="user";
            https.get(url, function(res){
                res.on('data', function(userInfo){
                   console.log(userInfo);
                });
            });
          
        })
    });
  
  });

至此,你就可以拿着token获取用户授权的信息了,但是token也会过期,需要重新请求。

scope是OAuth协议提供权限范围选项,避免让第三方应用有了一个钥匙就打开了用户所有的门,在github中,你可以打开的门如下:
scope

国内第三方应用商SDK使用

 除了用原生的OAuth协议去获取用户授权,我们也可以使用一些第三方应用提供商的SDK来实现快速地接入,特别是国内的许多平台都提供了集成OAuth协议的SDK,使用这种SDK可以帮你快速对现有网站进行集成,但是缺点对开发测试很不友好,比如需要你是上线的网站并且在验证的时候它会默认去访问服务器80端口,如果你的那台测试服务器刚好http端口在做其他事情,这就很尴尬了。

 下面我就微博和腾讯QQ的接入来简单介绍一下。

微博SDK

 使用微博SDK之前,你需要先在微博开放平台验证网站所有权,微博有一点特别不好的就是在这里接入的地址不能指定端口,再在这里下载相应的SDK

 准备工作做好之后,开发起来就相当便利了。

  • 首先在网站的首页添加一个meta标签。
meta(property="wb:webmaster" content="YOUR_KEY")

 添加一个meta标签以便微博服务器识别你的身份,微博会对你在验证网站所有权配置的地址进行http访问,来查询是否有正确的meta,不过微博的实现好像不是很好,经常会告诉你验证失败,当然QQ也一样。

  • 引入js SDK

 在appkey填入你申请的appkey,并且开启debug模式方便调试

script(src="http://tjs.sjs.sinajs.cn/open/api/js/wb.js?appkey=YOUR_APPKEY&debug=true" type="text/javascript" charset="utf-8")
  • 简单地调用

 完成了上面的资源引用,对于我们的授权接入,只需简单放置如下代码即可

<div id="wb_connect_btn"></div>

WB2.anyWhere(function (W) {
    W.widget.connectButton({
        id: "wb_connect_btn",//按钮id
        type: '3,2',//按钮样式,[这里](http://open.weibo.com/widget/loginbutton.php)可以设置
        callback: {
            login: function (o) { //登录后的回调函数
                alert("login: " + o.screen_name)
            },
            logout: function () { //退出后的回调函数
                alert('logout');
            }
        }
    });
});
  • 相关文档

微博开发平台新手指南

微博API文档

JS SDK文档

腾讯QQ SDK

 腾讯SDK的使用方式和微博SDK差别不大,最好的一点就是腾讯的SDK如果回调地址是当前页面,可以不用配置回调地址就可以快速接入。当然你也需要在这里注册申请appkey

  • 首先在网站的首页添加一个meta标签。
meta(property="qc:admins" content="YOUR_KEY")
  • 引入js SDK
script(type="text/javascript" src="http://qzonestyle.gtimg.cn/qzone/openapi/qc_loader.js"  data-appid="YOUR_APPKEY" charset="utf-8")

 腾讯的SDK会默认有很多日志在控制台输出。你可以看到你有什么错误。

  • 简单地调用
<span id="qqLoginBtn"></span>
 QC.Login({
                btnId: "qqLoginBtn", //插入按钮的节点id
                size: "C_S" //按钮样式
            }, function(reqData, opts) {
                var dom = document.getElementById('user_login'),
                    _logoutTemplate = [
                        //头像
                        '<span><img src="{figureurl}" class="{size_key}"/></span>',
                        //昵称
                        '<span>{nickname}</span>',
                        //退出
                        '<span><a style="color:white" href="javascript:QC.Login.signOut();">退出</a></span>'
                    ].join("");
                dom && (dom.innerHTML = QC.String.format(_logoutTemplate, {
                    nickname: QC.String.escHTML(reqData.nickname), //做xss过滤
                    figureurl: reqData.figureurl
                }));
                $('#user_login').removeClass('hide');

            }, function(opts) { //注销成功
                console('QQ登录 注销成功');
            });

  • 相关文档

QQ网站接入流程

JS SDK文档

passport.js插件使用

 如果对于每个接入都要去使用这个第三方应用提供商的SDK,那么对于一个开发者,花在学习调试开发上面的时间会成倍增加,既然现在主流的第三方登录都适用的OAuth协议,那么我们可不可以根据这个流程封装一套通用的SDK呢?答案当然是可以的,并且前人已经为我们造好了轮子。

 在Node平台,最出名的认证插件当然非passport.js莫属了,在它的官网上你可以很容易的搜索到各种第三方应用登录策略,只需简单的npm install,你就可以进行OAuth认证授权了。点击官网上面的Strategies字样搜索你想要的登录策略。

 passport目前拥有300+的第三方应用接入策略,支持持久化session,拥有简单易用的回调处理函数,同时支持OpenIDOAuth

安装

github v3 接口实现、qq、weibo的passport.js插件

npm install passport-github2
npm install passport-qq
npm install passport-weibo

相关中间件、路由

 我们使用express来做为我们的web框架,使用passport.js做我们的认证插件,在这个例子中我们仅仅介绍passport-github2的使用,QQ和weibo插件的使用和passport-github2一模一样。需要引入如下几个库

var express = require('express');
var passport = require('passport');
var util = require('util');
var session = require('express-session');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var GitHubStrategy = require('passport-github2').Strategy;

var partials = require('express-partials');

第一步,序列化与反序列化session值,以支持持久化登录,这步以后你可以方便的在passport的session模块中根据你序列化的唯一值(一般为ID),来处理用户的登录状态和登录信息等

passport.serializeUser(function(user, done) {
    done(null, user);
});

passport.deserializeUser(function(obj, done) {
    done(null, obj);
});

第二步,使用在passport.js中使用GitHub的认证策略GitHubStrategy,形如这样的策略函数是passport中的一个‘认证’函数,它的回调接受accessToken, refreshToken和第三方返回的用户信息,这里是GitHub profile

passport.use(new GitHubStrategy({
        clientID: YOUR_GITHUB_CLIENT_ID,
        clientSecret: YOUR_GITHUB_CLIENT_SECRET,
        callbackURL: "http://127.0.0.1/auth/github/callback",//第三方应用申请页面填写的回调地址
        passReqToCallback: true//会传输req对象
    },
    function(req,accessToken, refreshToken, profile, done) {
        console.log(profile);
        process.nextTick(function() {
            //在这里你可以对profile进行处理,比如进行存储或者方便
            if(req.user){
                //...绑定
                 app.db.models.User.findOne({...},function(){
                    ....
                    })
            }
            else{
                //...新建一个用户
                var user = new app.db.models.User();
                user.github = profile;
                user.save();
            }
            //...或者存储accessToken

            return done(null, profile);
        });
    }
));

第三步,对express进行配置,和以往的express配置没有太多不同,只需要调用中间价passport的initializesession去初始化passport和它的session模块。

var app = express();

// configure Express
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(partials());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(methodOverride());
app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(__dirname + '/public'));

第四步,配置路由

需要引导用户点击/auth/github,使用passport.authenticate()做为路由中间件去授权认证请求,passport.js插件会重定向用户到github的授权页,用户点击授权后github会把用户重定向到你之前申请应用时填写的回调地址,我们在回调地址/auth/github/callback中接受用户信息,并且利用passport.authenticate()做为路由中间件去检查授权是否成功以及引导相应路由。

app.get('/', function(req, res) {
    res.render('index', { user: req.user });
});

app.get('/account', ensureAuthenticated, function(req, res) {
    res.render('account', { user: req.user });
});

app.get('/login', function(req, res) {
    res.render('login', { user: req.user });
});
// GET /auth/github
app.get('/auth/github',
    passport.authenticate('github', { scope:YOUR_GITHUB_CLIENT_SCOPE }),
    function(req, res) {
        // The request will be redirected to GitHub for authentication, so this
        // function will not be called.
    });

// GET /auth/github/callback
app.get('/auth/github/callback',
    passport.authenticate('github', { failureRedirect: '/login' }),
    function(req, res) {
        //这里可以重定向到用户界面等页面,现在已经拿到用户信息和存储了session。
        res.redirect('/');
    });

返回字段(2016年8月23日获取)

_json值是格式化的raw值,故所有返回字段皆省略raw字段

  • QQ

注意QQ的_json字段中并没有id值,与github和weibo不同

{ provider: 'qq',
  id: '',
  nickname: '',
  _raw: '...',
  _json:
   { ret: 0,
     msg: '',
     is_lost: 0,
     nickname: '',
     gender: '男',
     province: '四川',
     city: '成都',
     year: '1994',
     figureurl: '',
     figureurl_1: '',
     figureurl_2: '',
     figureurl_qq_1: '',
     figureurl_qq_2: '',
     is_yellow_vip: '0',
     vip: '0',
     yellow_vip_level: '0',
     level: '0',
     is_yellow_year_vip: '0' } 
 }
  • Weibo
{ provider: 'weibo',
  id: '',
  displayName: '',
  _raw: 
   { ...},
  _json: 
   { id: ,
     idstr: '',
     class: 1,
     screen_name: '',
     name: '',
     province: '44',
     city: '3',
     location: ' ',
     description: '',
     url: '',
     profile_image_url: '',
     cover_image: '',
     profile_url: '',
     domain: '',
     weihao: '',
     gender: 'm',
     followers_count: ,
     friends_count: ,
     pagefriends_count: ,
     statuses_count: ,
     favourites_count: ,
     created_at: '',
     following: false,
     allow_all_act_msg: false,
     geo_enabled: true,
     verified: true,
     verified_type: 2,
     remark: '',
     status: 
      { created_at: 'Tue Aug 23 15:22:14 +0800 2016',
        id: ,
        mid: '',
        idstr: '',
        text: '',
        textLength: 60,
        source_allowclick: 0,
        source_type: 1,
        source: '',
        favorited: false,
        truncated: false,
        in_reply_to_status_id: '',
        in_reply_to_user_id: '',
        in_reply_to_screen_name: '',
        pic_urls: [Object],
        thumbnail_pic: '',
        bmiddle_pic: '',
        original_pic: '',
        geo: null,
        reposts_count: 0,
        comments_count: 0,
        attitudes_count: 0,
        isLongText: false,
        mlevel: 0,
        visible: [Object],
        biz_feature: 0,
        page_type: 32,
        hasActionTypeCard: 0,
        darwin_tags: [],
        hot_weibo_tags: [],
        text_tag_tips: [],
        userType: 0,
        positive_recom_flag: 0,
        gif_ids: '',
        is_show_bulletin: 2 },
     ptype: 0,
     allow_all_comment: true,
     avatar_large: '',
     avatar_hd: '',
     verified_reason: '',
     verified_trade: '',
     verified_reason_url: '',
     verified_source: '',
     verified_source_url: '',
     verified_state: 0,
     verified_level: 3,
     verified_type_ext: 0,
     verified_reason_modified: '',
     verified_contact_name: '',
     verified_contact_email: '',
     verified_contact_mobile: '',
     follow_me: false,
     online_status: 0,
     bi_followers_count: 94,
     lang: 'zh-cn',
     star: 0,
     mbtype: 0,
     mbrank: 0,
     block_word: 0,
     block_app: 0,
     credit_score: 80,
     user_ability: 4,
     urank: 19 } }

  • github
{ id: '',
  displayName: null,
  username: '',
  profileUrl: '',
  emails: [ { value: '' } ],
  provider: 'github',
  _raw: '{...}',
  _json: 
   { login: '',
     id: ,
     avatar_url: '',
     gravatar_id: '',
     url: '',
     html_url: '',
     followers_url: '',
     following_url: '',
     gists_url: '',
     starred_url: '',
     subscriptions_url: '',
     organizations_url: '',
     repos_url: '',
     events_url: '',
     received_events_url: '',
     type: 'User',
     site_admin: false,
     name: null,
     company: null,
     blog: null,
     location: null,
     email: '',
     hireable: null,
     bio: null,
     public_repos: 0,
     public_gists: 0,
     followers: 0,
     following: 0,
     created_at: '2016-07-15T06:17:35Z',
     updated_at: '2016-08-18T08:06:49Z',
     private_gists: 0,
     total_private_repos: 0,
     owned_private_repos: 0,
     disk_usage: 0,
     collaborators: 0,
     plan: 
      { name: 'free',
        space: ,
        collaborators: 0,
        private_repos: 0 } } }

QQ和微博申请和审核

 最后简单介绍一下QQ和weibo的应用申请和审核。

微博weibo

  • 第一步,新建应用
  • 第二步,填写应用信息,尽量详细,特别是应用图标,以方便通过审核。
    信息
  • 第三步,填写回调地址,可以配置非80端口
    回调
  • 第四步,点击审核,进入审核状态,等待weibo通过。
    审核

腾讯QQ

  • 第一步,新建应用,点击创建应用,选择网站
  • 第二步,填写应用信息,注意回调地址不能有端口号,默认访问80。
    信息
  • 第三步,在信息编辑中,填写配置协作者账号,方便开发测试。
    协作者
  • 第四步,点击审核,进入审核状态,等待QQ通过。
    审核

相关文档

posted @ 2016-08-23 19:01  GabrielChenCN  阅读(22361)  评论(6编辑  收藏  举报