目录javascript
本文主要讲解OAuth2.0协议和github、微博、QQ三个平台提供的接入流程,介绍nodejs下十分好用的认证受权插件passport.js。本文代码基于nodejs-express。php
在说OAuth协议以前,想说一下OpenID。在2005年的夏天,一个开源社区为了解决一个其它现有web身份认证技术不容易解决的问题时,制定了OpenID,OpenID是一个认证(Authentication)协议,这个协议让第三方应用程序在不获取你密码的状况下认证你的身份,OpenID和OAuth是两个常常放在一块儿比较的web身份认证技术,但OAuth比起OpenID,更像是一个受权(Authorization)协议,是让第三方应用程序在不获取你密码的状况下获取你的受权去对受权提供商或者说是资源提供商请求你受权的资源,拿到OpenID就像给别人指,这栋房子是个人,我能够拿点东西给你看看,而拿到OAuth受权就像你给别人说,这栋房子是个人,这个钥匙给你,但只能开大门不能进卧室,哪些能够动哪些能够开都是你说了算,而且你能够随时方便的收回这把钥匙。html
OAuth2.0是2006年提出来的新一代OAuth版本,比起OAuth1.x,它简化了认证交互过程,增长了认证机制,使用了SSL,去掉了一个叫作客户端token secret的东西,这也致使了OAuth升级须要改动代码,而且没有了1.0版本的安全bug(1.0a修复),这个安全bug主要是回调地址设定方式致使的。java
OAuth2.0协议主要有四个角色,资源全部者即用户,资源服务器,受权服务器和用户代理即客户端,这四个角色主要是完成了这样一个流程,客户端与服务器提供商以前,有一个经过OAuth协议完成的受权服务器,客户端有从服务端获取的惟一ID,客户端不能直接登陆服务器提供商,须要先经过用户在受权服务器取得受权码,而且拿着得到用户受权的受权码在受权层换取accessToken,经过这个token向资源服务器请求用户受权信息。node
OAuth2.0客户端一共有4种受权模式,受权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)和客户端模式(client credentials),在github提供的OAuth.v3接口中,使用的是受权码模式,在这种模式中,客户端须要引导用户重定向请求到Github的OAuth认证接口,带上在受权服务器申请的客户端 client_id
,配置的回调地址 redirect_uri
,申请受权的scope
,你也能够带上状态值state
,服务器会在回调中原封不动地返回你这个值,这一步将会换取前面咱们说到的受权码code
,具体流程和代码实现以下:git
1.首先咱们须要在github上新建一个应用(记得登陆),获取客户端的Client ID和Client Secret并配置好回调地址。
github
2.重定向到OAuth认证接口 GET https://github.com/login/OAuth/authorize
web
<a href= '/mygithub'></a>
3.发起重定向请求到受权服务器换取code
express
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_id
、client_secret
和code
换取access_token
,这个access_token
就是你开启开门的钥匙,拿到钥匙后你能够本地存储钥匙或者根据scope
立刻向资源服务器请求数据,具体流程和代码实现以下:npm
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中,你能够打开的门以下:
除了用原生的OAuth协议去获取用户受权,咱们也可使用一些第三方应用提供商的SDK来实现快速地接入,特别是国内的许多平台都提供了集成OAuth协议的SDK,使用这种SDK能够帮你快速对现有网站进行集成,可是缺点对开发测试很不友好,好比须要你是上线的网站而且在验证的时候它会默认去访问服务器80端口,若是你的那台测试服务器恰好http端口在作其余事情,这就很尴尬了。
下面我就微博和腾讯QQ的接入来简单介绍一下。
使用微博SDK以前,你须要先在微博开放平台验证网站全部权,微博有一点特别很差的就是在这里接入的地址不能指定端口,再在这里下载相应的SDK。
准备工做作好以后,开发起来就至关便利了。
meta(property="wb:webmaster" content="YOUR_KEY")
添加一个meta标签以便微博服务器识别你的身份,微博会对你在验证网站全部权配置的地址进行http访问,来查询是否有正确的meta,不过微博的实现好像不是很好,常常会告诉你验证失败,固然QQ也同样。
在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'); } } }); });
腾讯SDK的使用方式和微博SDK差异不大,最好的一点就是腾讯的SDK若是回调地址是当前页面,能够不用配置回调地址就能够快速接入。固然你也须要在这里注册申请appkey
meta(property="qc:admins" content="YOUR_KEY")
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登陆 注销成功'); });
若是对于每一个接入都要去使用这个第三方应用提供商的SDK,那么对于一个开发者,花在学习调试开发上面的时间会成倍增长,既然如今主流的第三方登陆都适用的OAuth协议,那么咱们可不能够根据这个流程封装一套通用的SDK呢?答案固然是能够的,而且前人已经为咱们造好了轮子。
在Node平台,最出名的认证插件固然非passport.js莫属了,在它的官网上你能够很容易的搜索到各类第三方应用登陆策略,只需简单的npm install,你就能够进行OAuth认证受权了。点击官网上面的Strategies
字样搜索你想要的登陆策略。
passport目前拥有300+的第三方应用接入策略,支持持久化session,拥有简单易用的回调处理函数,同时支持OpenID
和 OAuth
。
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的initialize
和session
去初始化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('/'); });
_json
值是格式化的raw
值,故全部返回字段皆省略raw
字段
注意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' } }
{ 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 } }
{ 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和weibo的应用申请和审核。