http://www.passportjs.org/docs/downloads/html/ 原文地址

概览

Passport 是 NodeJS 的认证中间件。他的唯一设计目的是:验证请求。书写模块化的、封装代码是一种美德,所以 Passport 将除了验证请求之外的功能都分发给应用程序来实现。关注点分离使代码能够更加整洁、可维护,同时也使 Passport 能够极易集成到应用中。

现在 Web 程序,认证有多种形式。传统的,用户通过用户名、密码登录。随着社交网络使用上升,使用 OAuth 的单点登录,例如 Facebook 或者 Twitter 已经成为了一种流行的认证方式。暴露一个 API 的服务通常需要基于 token 的证书来保护访问。

Passport 认识到每个应用有自己独特的认证需求。认证机制,也被成为策略,被打包成单独的模块。应用能够选择采用的策略,无需创建不需要的依赖。

不管认证的复杂性,代码能够不变的复杂。

  1. app.post('/login', passport.authentication('local', {successRedirect: '/', failureRedirect: '/login'}));

安装

  1. npm install passport

认证

认证请求就像调用 passport.authenticate() 和指定采用哪种策略一样简单。authenticate() 的函数签名是标准的 Connect 中间件,所以能够方便的作为路由中间件在 Express 应用中使用。

  1. app.post('/login',
  2. passport.authenticate('local'),
  3. function(req, res) {
  4. // 如果这个函数被调用,说明认证成功。
  5. // `req.user` 包含认证的 user
  6. res.redirect('/user', + req.user.username);
  7. }
  8. )

默认情况下,如果认证失败,Passport 将返回 401 Unauthorized,后续其他的路由处理器将不会执行。如果认证成功,下个处理器将调用,req.user 属性将设置为认证 user。

注意:在路由使用策略时,一定要预先配置。继续阅读详细配置 todo 章节。

重定向

重定向通常是在认证请求之后发出的。

  1. app.post('/login',
  2. passport.authenticate('local', {
  3. successRedirect: '/',
  4. failureRedirect: '/login',
  5. })
  6. )

这种情况下,重定向选项将覆盖默认行为。若成功认证,用户将重定向到主页。若认证失败,用户将重定向返回到登录页去再次尝试。

即时消息(Flash Messages)

重定向通常和即时消息结合来展示用户的状态信息。

  1. app.post('/login',
  2. passport.authenticate('local', {
  3. successRedirect: '/',
  4. failureRedirect: '/login',
  5. failureFlash: true
  6. });
  7. );

设置 failureFlash 选项为 true,指示 Passport 去发送 来自策略验证回调的 error 信息。这通常是最好的方法,因为验证回调能够最准确的判断为什么认证失败。

或者,即时消息能够自定义设置。

  1. passport.authenticate('local', {
  2. failureFlash: '无效用户名或者密码。'
  3. })

successFlash 选项能够发送一个 success 的消息,在认证成功的时候。

  1. passport.authenticate('local', {
  2. successFlash: '欢迎!'
  3. })

注意:使用即时消息需要 req.flash() 函数。Express 2.x 提供了这个功能,不过 Express 3.x 移除了。在使用 Express 3.x 时,建议使用 connect-flash 中间件,它提供了这个功能。

禁止 Sessions

成功授权后,Passport 将建立一个持久的登录 session。对于用户通过浏览器访问 web 应用的场景这是有用的。然后,其他情况下,不需要 session 支持。例如,API 服务器通常需要每个请求携带凭证。这种情况下,session 支持能够通过设置 session 选项为 false 来安全的禁用。

  1. app.get('/api/users/me',
  2. passport.authenticate('basic', { session: false }),
  3. function(req, res) {
  4. res.json({id: req.user.id, username: req.user.username});
  5. }
  6. )

自定义回调

如果内部选项不足够处理认证请求,自定义回调能够让应用处理成功和失败的情况。

  1. app.get('/login', function(req, res, next){
  2. passport.authenticate('local', function(err, user, info) {
  3. if(err) {
  4. return next(err);
  5. }
  6. if(!user) {
  7. return res.redirect('/login');
  8. }
  9. req.logIn(user, function(err) {
  10. if(err) {
  11. return next(err)
  12. }
  13. return res.redirect('/users' + user.username)
  14. })
  15. })(req, res, next);
  16. })

这个例子中,注意 authenticate() 是在路由处理器中被调用,而不是作为路由中间件。这通过闭包给了 reqres 对象回调权限。

如果认证失败,user 将被设置为 false。如果发生异常,err 将被设置。一个可选的 info 参数将传入,包括策略验证回调所提供的附加的详细信息。

这个回调能够使用提供的参数处理预期的认证结果。注意,当使用自定义回调时,需要应用来建立 session(通过调用 req.login()) 和发送响应。

配置

使用 Passport 来认证需要配置三个方面:

1、认证策略
2、应用中间件
3、Session(可选的)

策略

Passport 使用被称为策略的东西来认证请求。策略从验证用户名、密码,使用 OAuth 委托认证或者使用 OpenID 联合认证。

在让 Passport 认证请求前,应用使用的某个策略(或者某些策略)必须要先配置。

策略和他们的配置通过 use() 函数提供。例如,下面的例子使用 LocalStrategy 来进行用户名、密码认证。

  1. var passport = require('passport')
  2. , LocalStrategy = require('passport-local').Strategy;
  3. passport.use(new LocalStrategy(
  4. function(username, password, done) {
  5. User.findOne({ username: username }, function (err, user) {
  6. if (err) {return done(err)}
  7. if (!user) {
  8. return done(null, false, { message: '用户名错误' })
  9. }
  10. if (!user.validPassword(password)) {
  11. return done(null, false, { message: '密码错误'})
  12. }
  13. return done(null, user)
  14. })
  15. }
  16. ))

验证回调

这个例子引入了一个重要的概念。策略需要一个称为回调的东西。验证回调的目的是找到拥有一套凭证的用户。

当 Passport 认证请求时,它解析请求中的凭证。然后将凭证作为调用回调函数的参数,这个例子中就是 usernamepassword。如果凭证有效,回调函数将调用 done 将已认证的用户的信息传入 Passport。

  1. return done(null, user);

如果验证失败(本例中,比如密码错误),done 函数应该传入 false 而不是用户信息来表明认证失败。

  1. return done(null, false);

可以提供额外的消息来表明失败原因。这对于展示即时消息,来提示用户再次尝试很有用。

  1. return done(null, false, { message: '密码错误' });

最后,当验证凭证时发生异常(例如,数据库服务不可用),在常规的 Node 操作中 done 应该被调用来传入错误信息。

  1. return done(err);

注意,对于区分两种能够发生失败的情况是很重要的。后者是服务端异常,这种情况下 err 被设置为非 null 的值。在服务器正常运行时,认证失败也是很自然的情况。确认 err 包含 null,使用最后一个参数传递详细信息。

验证回调通过委派的方式使 Passport 可以无需数据库支持。应用可以自己决定如何存储用户信息,没有验证层强加的任何假设。

中间件

在基于 Connect 或者 Express 的应用中,需要使用 passport.initialize() 中间件来初始化 Passport。如果你的应用使用了持久化登录 Session,passport.session() 中间件也需要使用。

  1. app.configure(function() {
  2. app.use(express.static('public'));
  3. app.use(express.cookieParser());
  4. app.use(express.bodyParser());
  5. app.use(express.session({ secret: 'keyboard cat' }));
  6. app.use(passport.initialize());
  7. app.use(passport.session());
  8. app.use(app.router);
  9. })

注意,开启 session 支持完全是可选的,尽管建议将其用于大多数应用中。如果开启,确认在 passport.session() 前使用 session(),从而确保登录 session 能够按正确的顺序存储。

在 Express 4.x 中,Connect 中间件不再包含于 Express 核心模块中,app.configure() 也被移除了。相同的中间件能够在 npm 模块找到。

  1. var session = require('express-session'),
  2. bodyParser = require('body-parser');
  3. app.use(express.static('public'));
  4. app.use(session({ secret: 'cats' }));
  5. app.use(bodyParser.urlencoded({ extended: false }));
  6. app.use(passport.initialize());
  7. app.use(passport.session());

sessions 会话

在常规的 web 应用中,用于认证用户的凭证仅在登录请求时被发送。如果认证成功,一个session 将被建立和保持,通过设置在浏览器中的 cookie。

每个随后的请求将不再包含凭证,但是会有唯一的 cookie 来确认 session。为了支持登录 session,Passport 将序列化和反序列化 user 实例经由 session。

  1. passport.serializeUser(function(user, done) {
  2. done(null, user.id);
  3. })
  4. passport.deserializeUser(function(id, done) {
  5. User.findById(id, function(err, user) {
  6. done(err, user);
  7. })
  8. })

这个例子中,仅 user ID 被序列化到 session 中,从而保持存储在 session 中的数据量较小。当后续请求到达时,这个 ID 将用来找到存储在 req.user 中的 user。

序列化和反序列化逻辑由应用来提供,允许应用选择一个合适的数据库和(或者)对象mapper,无需认证层强加。

用户名 和 密码

最广泛使用的网站认证用户的方式是通过用户名和密码。对这种机制的支持是通过提供 passport-local 模块。

安装

  1. npm install passport-local

配置

  1. var passport = require('passport'),
  2. LocalStrategy = require('passport-local').Strategy;
  3. passport.use(new LocalStrategy(
  4. function(username, password, done) {
  5. User.findOne({ username: username }, function(err, user) {
  6. if(err) {
  7. return done(err)
  8. }
  9. if(!user) {
  10. return done(null, false, { message: '用户名错误。' })
  11. }
  12. if(!user.validPassword(password)) {
  13. return done(null, false, { message: '密码错误。' })
  14. }
  15. return done(null, user);
  16. })
  17. }
  18. ))

这个本地认证的验证回调接受 usernamepassword 参数,通过应用的登录表单提交上来的。

表单

Web 页面的一个表单,允许用户输入他们凭证然后登录。

  1. <form action="/login" method="post">
  2. <div>
  3. <label>Username:</label>
  4. <input type="text" name="username"/>
  5. </div>
  6. <div>
  7. <label>Password:</label>
  8. <input type="password" name="password"/>
  9. </div>
  10. <div>
  11. <input type="submit" value="Log In"/>
  12. </div>
  13. </form>

路由

登录表单通过 POST 方法提交到服务器。使用 local 策略的 authenticate 函数来处理登录请求。

  1. app.post('/login',
  2. passport.authenticate('local', {
  3. successRedirect: '/',
  4. failureRedirect: '/login',
  5. failureFlash: true
  6. })
  7. )

设置 failureFlash 选项为 true 表明 Passport 使用验证回调提供的 message 选项来发送一个 error 消息。这对于提示用户再试一次很有用。

参数

默认的,LocalStrategy 预期在参数中找到命名为 usernamepassword 的凭证。如果你的网站更喜欢用其他字段命名,有可用的选项支持修改默认值。

  1. passport.use(new LocalStrategy({
  2. usernameField: 'email',
  3. passwordField: 'passwd',
  4. },
  5. function(username, password, done) {
  6. // ...
  7. }
  8. ))

OpenID

OpenID 是一个联合认证的开放标准。当访问网站时,用户使用 OpenID 登录。用户通过他们选择 OpenID 提供者(它发出一个断言来确认用户身份)来认证。网站验证这个断言来让用户登录。

OpenID 的支持通过 passport-openid 模块提供。

安装

  1. npm install passport-openid

配置

当使用 OpenID 时,返回地址和领域必须设置。 returnURL 是用户在使用 OpenID 提供者认证后重定向的地址。realm 表明 URL空间中验证有效的部分。通常它会是网站的根 URL。

  1. var passport = require('passport'),
  2. OpenIDStrategy = require('passport-openid').Strategy;
  3. passport.use(new OpenIDStrategy({
  4. returnURL: 'http://www.example.com/auth/openid/return',
  5. realm: 'http://www.example.com/'
  6. },
  7. function(identifier, done) {
  8. User.findOrCreate({openId: identifier}, function(err, user) {
  9. done(err, user);
  10. })
  11. }
  12. ))

OpenID认证的验证回调接受一个 identifier 参数,包含用户的声明识别码。

表单

web 页面的表单,允许用户输入 OpenID 然后登录。

  1. <form action="/auth/openid" method="post">
  2. <div>
  3. <label>OpenID:</label>
  4. <input type="text" name="openid_identifier"/><br/>
  5. </div>
  6. <div>
  7. <input type="submit" value="Sign In"/>
  8. </div>
  9. </form>

路由

OpenID 认证需要2个路由。第一个路由接受表单提交中包含的 OpenId 识别码。认证期间,用户将被重定向到 OpenID 提供者。第二个路由是用户在使用 OpenID 提供者认证后,将返回的 URL。

  1. // 接受 OpenID 识别码,将用户重定向到 OpenID 提供者处去认证。完成后,提供者将用户重定向到应用:
  2. // /auth/openid/return
  3. app.post('/auth/openid', passport.authenticate('openid'));
  4. // OpenID 提供者已经将用户重定向到应用。
  5. // 通过验证断言来完成认证过程。如果有效,用户将登录。
  6. // 否则,认证失败。
  7. app.get('/auth/openid/return',
  8. passport.authenticate('openid', {
  9. successRedirect: '/',
  10. failureRedirect: '/login',
  11. })
  12. )

个人资料交换

OpenID 能够可选择的设置为取回已认证用户的个人信息。个人资料交换通过设置 profile 选项为 true 开启。

  1. passport.use(new OpenIDStrategy({
  2. returnURL: 'http://www.example.com/auth/openid/return',
  3. realm: 'http://www.example.com/',
  4. profile: true
  5. },
  6. function(identifier, profile, done) {
  7. // ...
  8. }
  9. ))

当个人资料交换开启后,验证回调函数签名接收额外的 profile 参数,包含 OpenID 提供者提供的用户个人信息;通过 User Profile 来了解更多信息。

OAuth

OAuth 是个标准协议,它允许用户授权 API 使用权给网站、桌面程序或者移动应用。一旦被授权,被授权的应用能够代表用户使用 API。OAuth 也已经成为流行的委托授权机制。

OAuth 有两种主要形式,两种都被广泛的部署。

OAuth 初始版本被一群组织松散的 Web 开发者开发作为开放标准。他们开发了 OAuth 1.0,被 OAuth 1.0a 取代。这项工作现在被 IEFT 作为 RFC 5849 准备被标准化。

最近的工作 — 专注定义 OAuth 2.0,已经被 web 授权协议工作组 承担。由于长期的标准化工作,提供者已经开始部署符合各种草案的实现,每种语义略有不同。

谢天谢地,Passport 隔离了应用处理 OAuth 变体的复杂性。许多情况下,特定策略的提供者能够被使用。而不是下面描述的通用策略。这减少了必须的配置,并且能容纳任何提供者的特定怪癖。查看 Facebook, Twitter 或者列表中的提供者的首选用法。

OAuth 的支持通过 passport-oauth 模块实现

安装

  1. npm install passport-oauth

OAuth 1.0

OAuth 1.0 是包含多个步骤的代理认证策略。首先,需要获得请求 token。然后,用户被重定向到服务提供者处授权。最后,授权后,用户被重定向回应用并且请求 token 能够用来交换访问 token。请求访问的应用程序(称为消费者)由消费者 key 和 消费者 secret 标识。

配置

当使用通用 OAuth 策略时,key, secret 和 endpoints 作为选项定义。

  1. var passport = require('passport'),
  2. OAuthStrategy = require('passport-oauth').OAuthStrategy;
  3. passport.use('provider', new OAuthStrategy({
  4. requestTokenURL: 'https://www.provider.com/oauth/request_token',
  5. accessTokenURL: 'https://www.provider.com/oauth/access_token',
  6. userAuthorizationURL: 'https://www.provider.com/oauth/authorize',
  7. consumerKey: '123-456-789',
  8. consumerSecret: 'shhh-its-a-secret',
  9. callbackURL: 'https://www.example.com/auth/provider/callback'
  10. },
  11. function(token, tokenSecret, profile, done) {
  12. User.findOrCreate(..., function(err, user) {
  13. done(err, user);
  14. })
  15. }
  16. ))

基于 OAuth 策略的验证回调接受 tokentokenSecretprofile 参数。token 是访问 token,tokenSecret 是它对应的秘钥。profile 包含服务提供者提供的用户个人信息。通过 User Profile 了解更多信息。

路由

OAuth 认证需要2个路由。第一个路由发起一个 OAuth 交换和重定向用户到服务提供者处。第二个路由是个URL,用户在提供者认证后重定向的 URL。

  1. // 重定向用户到用于认证的 OAuth 提供者处。当认证完成后,
  2. // 提供者将重定向用户回到应用:
  3. // /auth/provider/callback
  4. app.get('/auth/provider', passport.authenticate('provider'));
  5. // OAuth 提供者已经重定向用户回到应用。
  6. // 通过获取访问 token 来结束认证过程。如果已经授权,用户将登录。
  7. // 否则,认证失败。
  8. app.get('/auth/provider/callback',
  9. passport.authenticate('provider', {
  10. successRedirect: '/',
  11. failureRedirect: '/login'
  12. })
  13. )

链接

在网页中可以放置一个链接或者按钮,它们在点击时将开始认证过程。

  1. <a href="/auth/provider">使用 OAuth 提供者登录</a>

OAuth 2.0

OAuth 2.0 是 OAuth 1.0 的接班人,被设计来克服早期版本的已知缺陷。认证流程本质上是一样的。用户首先被重定向到服务提供者处授权,授权后,用户携带一个能够获取访问 token 的码被重定向回应用。请求访问的应用(作为客户端)由 ID 和 秘钥标识。

配置

使用通用 OAuth 2.0 策略时,client ID,client secret 和 endpoints 作为选项定义。

  1. var passport = require('passport'),
  2. OAuth2Strategy = require('passport-oauth').OAuth2Strategy;
  3. passport.use('provider', new OAuth2Strategy({
  4. authorizationURL: '',
  5. tokenURL: '',
  6. clientID: '',
  7. clientSecret: '',
  8. callbackURL: '',
  9. },
  10. function(accessToken, refreshToken, profile, done) {
  11. User.findOrCreate(..., function(err, user) {
  12. done(err, user);
  13. })
  14. }
  15. ))

基于 OAuth 2.0 策略的验证回调接受 accessTokenrefreshTokenprofile 参数。refreshToken 能够用来获取新的访问 token,也有可能是 undefined 如果提供者不发行刷新 token。profile 将包含服务提供者提供的用户个人信息。查看 User Profile 了解更多信息。

路由

OAuth 2.0 认证需要两个路由。第一个路由重定向用户到服务提供者。第二个路由是个 URL,用户在提供者处认证后重定向的URL。

  1. // 重定向用户到 OAuth 2.0 认证提供者。
  2. // 认证完成后,提供者将重定向用户返回应用:
  3. // /auth/provider/callback
  4. app.get('/auth/provider', passport.authenticate('provider'));
  5. // OAuth 2.0 提供者已经重定向用户到应用中。
  6. // 通过尝试获取访问 token 来完成认证过程。
  7. // 如果已授权,用户将登录。
  8. // 否则,认证失败。
  9. app.get('/auth/provider/callback',
  10. passport.authenticate('provider', {
  11. successRedirect: '/',
  12. failureRedirect: '/login',
  13. })
  14. )

作用域

当使用 OAuth 2.0 请求访问时,访问的作用域通过 scope 选项控制。

  1. app.get('/auth/provider',
  2. passport.authenticate('provider', { scope: 'email' })
  3. )

能够使用数组定义多个作用域。

  1. app.get('/auth/provider',
  2. passport.authenticate('provider', { scope: ['email', 'sms'] })
  3. )

scope 选项的值时提供者特定的。详情参考提供者文档,了解支持的作用域。

链接

Web 页面的链接或者按钮,当点击的时候能够开始认证过程。

  1. <a href="/auth/provider">使用 OAuth 2.0 提供者登录</a>

用户个人信息

当使用第三方服务,例如 Facebook 或者 Twitter 认证时,用户个人信息通常会可用。每个服务倾向于有个不同的方式编码这些信息。为了更易集成,Passport 尽可能规范化个人信息。

规范化个人信息符合 [Joseph Smarr][schema-author] 建立的联系模式。下表概述了可用的公共字段。

provider {String}

  提供者,用户认证的地方(facebook, twitter, 等)。

id {String}

  用户的唯一标识,通常由服务提供者生成。

displayName {String}

  用户名,适合展示

name {Object}

familyName 用户的 family name,或者大多数西方语言的 “last name”。

givenName 用户的 given name,或者大多数西方语言的 “first name”。

middleName 用户的 middle name。

emails {Array} [n]

value {String} 地址的 email 地址。

type {String} email 地址的类型(家里、工作单位,等)。

photos {Array} [n]

value {String} 图片地址

注意,上面的字段不是全部都能从每个服务提供者处获得。一些提供者可能包含没在这列出的其他信息。参考特定提供者的文档来了解更多详细信息。

参考

感谢阅读

感谢你阅读到这里,翻译的不好的地方,还请指点。希望我的内容能让你受用,再次感谢。by llccing 千里