OAuth是什么
OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
OAuth 2.0
OAuth的版本有v1.0, v1.0a 和 v2.0。OAuth 2.0 的出现主要是解决1.0+中的几个问题,提升开发简易度和应用安全性:
- 更好的支持非浏览器APP(移动和桌面客户端,可以省略v1.0的交换token过程,增强用户体验)
- 给访问令牌(Access-Token)添加了续期概念,对令牌泄露多了一层防御
- 不再强制客户端使用Token Secret,有令牌就够了
- 引入Bearer 验证机制
OAuth 2 是目前被广泛支持的版本,但不向下兼容1.0。
术语
简单说,OAuth 2涉及到三方
- Client APP:要访问用户的程序
- Resource Owner :用户,由他来给APP授权
- Authorization Server:也称为 API Provider(Google/Facebook/Twitter…)
- client_id 和 client_secret:Server颁发给 APP 的身份凭证
- Access Token:Server颁发的令牌,访问资源API需要带着它
- Scope:APP 向Server 提供需要调用的API种类
- Redirect URL:APP 向 Server提供的回调地址
OAuth 2 交互模型
OAuth 2的 交互模型比较灵活,主要的交互模型分为如下几种:
- 服务端Authorization code授权:被 Web服务端编程广泛采用,也是本文重点介绍的模型
- 客户端隐式授权:Web客户端编程(JavaScript APP)使用这种方式交互,在用户授权后直接取到令牌
- Resource Owner登录授权:需要用户输入用户名和密码来交换令牌
- 客户端凭证:这种模型下,APP 可能就是Owner,代表自己进行API调用
准备工作
首先,开发人员需要去API Provider处,为APP 创建应用信息,申请权限,以Google为例:
- 在 Google Developers Console 创建项目
- 在 APIs & Auth -> APIs 中,选择打开需要调用的API
- 在 APIs & Auth -> Credentials 中,填写 Redirect URIS(OAuth 2 回调的地址),创建Client ID。成功后,会得到需要的 CLIENT ID 和 CLIENT SECRET,保存好不要泄露 🙂
- 在 APIs & Auth -> Consent screen 中,填写 Email/ Product Name/ Homepage/ Logo等 APP元信息,这些是给用户看的,在交互过程中会出现在确认授权页
服务端交互流程
OAuth 2 的服务端交互流程,是一个对用户透明的三方过程:
如果用Node.js 实现前述的 Google API OAuth 2访问,编程模型大概如此:
- 判断是否已有 access_token,过期了吗?如果不存在或过期,一步步来:
- 将用户页面跳向到https://accounts.google.com/o/oauth2/auth(附上一系列Query参数:response_type/ client_id/ redirect_uri/ scope,视需要追加参数:access_type/ approval_prompt/ state…)
- 为 redirect_uri 提供 HTTP GET 方法的处理,以响应回调
- 第3步的回调会收到一个code参数,用它向 https://accounts.google.com/o/oauth2/token 发起一个 POST请求,这次需要提供的参数:code / client_id/ client_secret/ redirect_uri/ grant_type
- 为 第4步 的redirect_uri 提供HTTP GET 方法的处理,以响应回调
- 第5步的回调会收到一个 JSON对象,里面有access_token 和它的过期时间expires_in,将它存下来
- 访问API,附上得到的 access_token
引入Passport
显然,自己实现OAuth 流程将需要写不少东西,且每增加一个API Provider,这个过程需要再来一次。这行里有句黑话:写得越多,错得就越多 🙂 这时候,应该找个合适的框架
Passport 框架就是为解决类似问题而生的:它以中间件的形式为Node 程序提供身份认证,框架本身将一般形式的认证过程(Basic & Digest/ OAuth/ Open ID)、回调及错误处理进行了封装,而将具体的认证实现抽象为Strategy(策略),与框架本身并无关系,只要是符合Passport 的Strategy都能以插件的形式加入项目被Passport使用。比如基于Google的OAuth 2认证,我们可以用 passport-google-oauth,基于Facebook的OAuth 2认证我们可以用passport-facebook,当然也可以用别的或者自己写。 这种基于策略的抽象大大简化了编程模型,所以Passport 有自信称 ” Simple, unobtrusive authentication for Node.js”,诚不欺我。
Passport 实现 Google 用户登录
Google 作为具体的 API Provider,用Passport 对其进行OAuth 2访问,需要一个 Strategy 来提供它的交互流程实现,本例中使用 passport-google-oauth
在package.json中确定引用,并用 npm install 安装模块:
1 2 3 4 5 |
"dependencies": { //... more libraries "passport": "*", "passport-google-oauth": "*", } |
将从 Google Developers Console 获得的client_id 和 client_secret 存到配置文件中:
1 2 3 4 5 6 |
{ //...more configuration "GOOGLE_CLIENT_ID" : "xxxx.apps.googleusercontent.com", "GOOGLE_CLIENT_SECRET" : "wdsfas3_-safdsafasf", "GOOGLE_RETURN_URL" : "http://www.xxx.com/auth/google/return", } |
上篇提到的配置读取器configUtils 将能自动读取到它,结合这些实现Google OAuth 2认证(authUtils.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
var passport = require('passport') , GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; var config = require('../configUtils'); passport.use(new GoogleStrategy({ authorizationURL: 'https://accounts.google.com/o/oauth2/auth', tokenURL: 'https://accounts.google.com/o/oauth2/token', clientID: config.getConfigs().GOOGLE_CLIENT_ID, clientSecret: config.getConfigs().GOOGLE_CLIENT_SECRET, callbackURL: config.getConfigs().GOOGLE_RETURN_URL }, function (accessToken, refreshToken, profile, done) { var userInfo = { 'type': 'google', 'userid': profile.id, 'name': profile.displayName, 'email': profile.emails[0].value, 'avatar': profile._json.picture }; return done(null, userInfo); } passport.serializeUser(function (user, done) { done(null, user); }); passport.deserializeUser(function (obj, done) { done(null, obj); }); )); |
代码中的 function (accessToken, refreshToken, profile, done) 就是用户授权后的回调,Passport 会将获取到的用户信息包装成profile,里面的转换代码可以自由发挥,最后记得调用done,并将用户信息返回。done 就是我们定义在路由里的回调,稍后会提到。
serializeUser 和 deserializeUser 是转换用的序列化/ 反序列化。
结合Express
在 authUtils.js 中导出给 Express 用的路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
module.exports = function (app, session) { app.use(passport.initialize()); app.get('/auth/google', passport.authenticate('google', { scope: 'https://www.google.com/m8/feeds' + ' https://www.googleapis.com/auth/userinfo.email' + ' https://www.googleapis.com/auth/userinfo.profile' })); app.get('/auth/google/return', passport.authenticate('google', { failureRedirect: '/login' }), function (req, res) { if (req.user) { //...some user login handling code } res.redirect('/'); }); }; |
第1个 get 路由是我们可以在UI 上引用的地址,点击它开始OAuth 2 流程,即 Passport 的入口;第2个 get路由是Passport 的出口,即上节提到被 done 方法回调的地址,此时整个 OAuth 2 流程完成,程序得到了 user 信息(Passport将它注入到了 req上,但仅此一次可用,阅后即焚)。在认证过程发生任何错误,将跳转到 failureRedirect 指定的地址。Passport为我们做了诸如 code 交换 token的一系列琐碎事情。
app.js 中调用,So easy:
1 2 |
var authUtils = require('../authUtils'); authUtils(app); |
小结
OAuth 2 协议为用户资源的授权提供了一个安全的、开放而又简易的标准,但即便如此,在Node.js 的 OAuth 2交互实现中,仍需 为Owner/App/Server的三方交互流程编写很多代码。使用Passport框架,将大大简化这一编程模型,使我们可以将更多精力投入到业务实现上。
本文提供的仅是一个基于Passport框架的OAuth 2方案思路,代码部分也仅是个骨架,望抛砖引玉。
你好:
我現在完成了一小部分,也就是 當使用者在google授權登入並redirect回我的站之後,我得到了userinfo對象( 裡面包含了使用者許多個人資料 )。
但我該如何使用passport存入我的session中呢? 使用者關閉頁面之後再開啟似乎又需要再次登入? 我該如何判斷使用者是否已經登入? 如果已經登入了,要怎麼再次取得這個使用者的userinfo?
初學node.js不好意思問了這麼麻煩的問題 還希望您有空能幫忙解惑 謝謝
例子在这儿:
https://github.com/rockdragon/cloud-drive/blob/master/modules/auth/authUtils.js
来转转看看