[译]Passport.js 文档 ——一般原则

[译]Passport.js 文档 ——一般原则

Overview 概览

Passport 是一个 Node 平台的身份认证中间件。它的设计服务于一个简单的目标:身份认证请求。编写模块时,封装是一个美德,所以 Passport 把所有其他功能交给应用自身去做。这种关注点的分离可以保持代码整洁并且可维护,同时使得 Passport 极其容易地集成到应用中。

在现代 web 应用中,身份认证可以采用多种形式。传统而言,用户通过提供用户名和密码登录。由于社交网络的兴起,使用类似于 Facebook 或者 Twitter 的 OAuth 提供商进行单点登录(single sign-on)已经成为一个流行的身份验证方式。暴露 API 的服务通常需要基于 token 的凭证来保护访问行为。

Passport 认为每一个应用都有独一无二的身份认证需求。身份认证机制,被称为策略(strategies),打包成独立的模块。应用可以选择应用哪些策略,同时避免创建不必要的依赖。

尽管身份验证过程很复杂,代码也可以很简洁。

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

Authenticate 身份认证

认证请求很简单,只需要调用 passport.authenticate() 并且指定要应用的策略。authenticate 的函数签名是一个标准的 Connect 中间件,因此可以方便地作为 Express 应用中的路由中间件。

app.post('/login', passport.authenticate('local'), function(req, res) {
    // 如果这个函数被调用了,说明认证成功。
    // `req.user` 包含已认证的用户
    res.redirect('/users/' + req.user.username);
});

默认情况下,如果认证失败,Passport 将响应一个 401 Unauthorized 状态,并且任何额外的路由处理器都不会被调用。如果认证成功,下一个处理器会被调用,同时 req.user 属性将被设置为已认证的用户。

注意:在路由中使用之前,策略必须被提前设置好。

Redirects 重定向

认证一个请求后通常会发出一个重定向。

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

在这种情况下,重定向选项覆盖了默认的行为。一旦认证成功,用户将被重定向到首页。如果认证失败,用户将被重定向回登陆页进行重试。

Flash Messages 快闪消息

重定向经常和快闪消息 (Flash Messages) 结合,用来展示向用户展示状态信息。

app.post('/login',
    passport.authenticate('local', {
        successRedirect: '/',
        failureRedirect: '/login',
        failureFlash: true })
);

设置 failureFlash 选项为 true 通知 Passport 快速展示一个error 消息,如果有的话,策略验证回调函数提供的消息。这通常是最佳的方式,因为验证回调函数可以最准确地确认为何认证失败。

另外,快闪消息可以被显式地设置。

passport.authenticate('local', { failureFlash: 'Invalid username or password.' });

successFlesh 选项可以用来在认证成功时显示一个 success 消息。

passport.authenticate('local', { successFlash: 'Welcome!' });

Disable Sessions 禁用会话

成功认证之后,Passport 将建立一个持续的登录会话。这在用户通过浏览器访问一个 web 应用的普遍场景中是有用的。然而,在某些情况下,会话支持是不必要的。例如,API 服务通常需要每一个请求都提供一个凭证。在这种情况下,会话支持可以通过设置 session 选项为 fasle 来被安全地禁用。

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

Custom Callback 自定义回调

如果内置的选项不足以处理身份认证请求,可以提供一个自定义的回调函数来允许应用自己处理成功或者失败。

app.get('/login', function(req, res, next) {
    passport.authenticate('local', function(err, user, info) {
        if (err) { return next(err); }
        if (!user) { return res.redirect('/login'); }
        req.logIn(user, function(err) {
        if (err) { return next(err); }
            return res.redirect('/users/' + user.username);
        });
    })(req, res, next);
});

在这个例子中,注意 authenticate 实在路由处理器内部被调用的,而不是作为一个路由中间件。通过闭包来使回调函数访问到 reqres 对象。

如果身份认证失败,user 将被设置为 false 。如果一个异常发生,err 将被设置。一个可选的 info 参数将被传入,包含这个策略的验证回调函数提供的其他细节。

回调函数可以根据需要使用提供的参数来处理认证的结果。注意在使用自定义回调函数时,应用需要负责建立回话(通过调用 req.login())和发送响应。

Configure 配置

使用 Passport 进行身份验证,有三个部分需要设置:

  1. 认证策略
  2. 应用中间件
  3. 会话(可选)

Strategies 策略

Passport 是使用所谓策略 (strategies) 来对请求进行身份认证。策略的范围包括验证用户名和密码,使用 OAuth 的委托认证,或者使用 OpenID 的联合认证。

在请求 Passport 对一个请求进行身份认证前,必需配置应用所使用的策略。

策略和他们的配置,通过 use 函数提供。例如,下面为用户名/密码认证使用了 LocalStrategy

app.get('/login', function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    if (err) { return next(err); }
    if (!user) { return res.redirect('/login'); }
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      return res.redirect('/users/' + user.username);
    });
  })(req, res, next);
});

Verify Callback 验证回调函数

这个例子介绍了一个重要的概念。策略需要所谓验证回调函数。验证回调函数的目的是找到拥有一组凭证的用户。

当 Passport 对一个请求进行身份认证时,它解析包含在请求中的凭证。然后将这些凭证作为参数调用验证回调函数,在这个例子中是 usernamepassword 。如果凭证有效,验证回调调用 done 来把这个认证了的用户提供给 Passport。

return done(null, user);

如果凭证无效(例如,密码不正确),应该在调用 done 时传入 false 而不是一个用户来告诉 Passport 一个身份认证失败。

return done(null, false);

可以提供一个额外的信息消息来说明失败的原因。这有助于显示一个快闪消息提示用户重试时。

return done(null, false, { message: 'Incorrect password.' });

最后,在验证凭证时,如果发生一个异常(例如,数据库不可用),应该按照常规的 Node 风格,在调用 done 时传入一个错误。

return done(err);

注意区分这两种失败情况是很重要。后者是一个服务器异常,err 是一个非 null 值。身份认证失败是自然的情况,服务器运行正常。确保 err 保持 null ,同时使用最后的参数来传递额外的细节。

通过这种方式的委托,验证回调函数保持 Passport 独立于数据库。身份认证层不会强加任何前提条件,应用可以自由地选择如何存储用户信息。

Middleware 中间件

在一个基于 Connect 或 Express 的应用中,需要使用 passport.initailize() 中间件来初始化 Passport。如果你的应用使用持续的登录会话,passport.sesson() 中间件也必须使用。

app.configure(function() {
  app.use(express.static('public'));
  app.use(express.cookieParser());
  app.use(express.bodyParser());
  app.use(express.session({ secret: 'keyboard cat' }));
  app.use(passport.initialize());
  app.use(passport.session());
  app.use(app.router);
});

注意启用会话支持是完全可选的,虽然在大多数应用中推荐使用它。如果启用,确保在 passport.session 之前使用 session() 来保证登录回话按照正确的顺序保存。

在 Express 4.x 中 Connect 中间件不在包含在 Express 核心中,app.configure() 方法被移除。相同的中间件可以在与其等价的 npm 模块中找到。

var session = require("express-session"),
    bodyParser = require("body-parser");

app.use(express.static("public"));
app.use(session({ secret: "cats" }));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.initialize());
app.use(passport.session());

Sessions 会话

在一个典型 web 应用中,用来认证一个用户的凭证只在登录请求时传递。如果登录成功,一个会话将被建立同时通过用户浏览器的一个 cookie 设置来维护。

随后的每一个请求将不再包含凭证,但是带有标识回话的唯一 cookie。为了支持登录回话,Passport 会将user 实例序列化到回话并从回话中反序列化 user 实例 。

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

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

在这个例子中,只有用户的 ID 被序列化到会话中,保持了会话中存储最少的数据。接受到随后的请求时,这个 ID 被用来查找用户,用户将被存储到 req.user

这个序列化和反序列化的逻辑是由应用提供的,允许应用不受身份认证的限制,选择一个合适的数据库或对象映射器。

Username & Password 用户和密码

在网站中使用范围最广的用户身份认证方式是通过一个用户名和密码。对这种机制的支持由 passport-local 模块提供。

Configuration 配置

var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function(err, user) {
      if (err) { return done(err); }
      if (!user) {
        return done(null, false, { message: 'Incorrect username.' });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { message: 'Incorrect password.' });
      }
      return done(null, user);
    });
  }
));

本地身份认证的验证回调接受 usernamepassword 参数,通过登录表单提交给应用。

Form 表单

表单放置在 web 页面上,允许用户输入他们的凭证然后登录。

<form action="/login" method="post">
    <div>
        <label>Username:</label>
        <input type="text" name="username"/>
    </div>
    <div>
        <label>Password:</label>
        <input type="password" name="password"/>
    </div>
    <div>
        <input type="submit" value="Log In"/>
    </div>
</form>

Route 路由

登录表单通过 POST 提交给服务器。使用 authenticate()local 策略可以处理这个登录请求。

app.post('/login',
  passport.authenticate('local', { successRedirect: '/',
                                   failureRedirect: '/login',
                                   failureFlash: true })
);

设置 failureFlash 选项为 true 命令 Passport 使用上面的验证回调设置的 message 选项来快速展示一个 error 消息。这有助于提示用户重试。

Parameters 参数

默认地,LocalStrategy 期望使用名为 usernamepassword 的参数中查找凭证。如果你的网站想要给这些字段重新命名,可以通过选项来改变默认值。

passport.use(new LocalStrategy({
    usernameField: 'email',
    passwordField: 'passwd'
  },
  function(username, password, done) {
    // ...
  }
));

OpenID

OpenID 是一个联合认证的开放标准。当访问一个网站时,用于提交他们的 OpenID 来登录。然后用户使用他们选择的 OpenID 提供者进行身份认证,提供者发出一个声明来确认这个用户的身份。网站验证这个声明来登记用户。

对 OpenID 的支持由 passport-openid 模块提供。

Configuration 配置

使用 OpenID 时,需要指定一个返回 URL 和范围。returnURL 是用户在他们的 OpenID 提供者认证后重定向的目标。realm 指示了身份认证生效的 URL 空间部分。通常它是网站的根 URL。

var passport = require('passport')
  , OpenIDStrategy = require('passport-openid').Strategy;

passport.use(new OpenIDStrategy({
    returnURL: 'http://www.example.com/auth/openid/return',
    realm: 'http://www.example.com/'
  },
  function(identifier, done) {
    User.findOrCreate({ openId: identifier }, function(err, user) {
      done(err, user);
    });
  }
));

OpenID 认证的验证回调函数接受一个 identifier 参数,包含了用户的身份标识。

Form 表单

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

<form action="/auth/openid" method="post">
    <div>
        <label>OpenID:</label>
        <input type="text" name="openid_identifier"/><br/>
    </div>
    <div>
        <input type="submit" value="Sign In"/>
    </div>
</form>

Routes 路由

OpenID 认证需要两个路由。第一个路由接受表单提交,包含 OpenID 身份标识。在认证过程中,用户将被重定向到他们的 OpenID 提供商。第二个路由是用户与 OpenID 提供商认证后返回的 URL。

// 接受 OpenID 身份标识,然后将用户重定向到他们的 OpenID 提供商进行认证。
// 完成后,提供商会把用户定向到应用的地址:
//     /auth/openid/return
app.post('/auth/openid', passport.authenticate('openid'));

// OpenID 提供商已经将用户重定向到应用。通过校验这个声明来完成认证过程。
// 如果有效,这个用户将会登录。否则,认证失败。
app.get('/auth/openid/return',
        passport.authenticate('openid',
                              { successRedirect: '/',
                               failureRedirect: '/login' }));

Profile Exchange

可以选择性地配置 OpenID 来检索被认证用户信息。通过设置 profile 选项为 true 来启用用户信息交换。

passport.use(new OpenIDStrategy({
    returnURL: 'http://www.example.com/auth/openid/return',
    realm: 'http://www.example.com/',
    profile: true
  },
  function(identifier, profile, done) {
    // ...
  }
));

用户信息交换被启用时,验证回调函数的签名接受一个额外的 profile 参数,包含 OpenID 提供商提供的用户信息。

OAuth

OAuth 是一个允许用户授权 web 和桌面或移动应用进行 API 访问的标准协议。一旦访问被批准,被授权应用可以以这个用户的名义使用 API。OAuth 也成为了一个流行的委托认证机制。

OAuth 有两种风格,都被广泛部署。

初始版本的 OAuth 作为一个开放标准,由一个松散的 web 开发者组织开发。他们的工作最终成为 OAuth 1.0 ,后来被 OAuth 1.0a 替代。这项工作成果已被 IETF 标准化为 RFC 5849

Web Authorization Protocol Working Group 接手的新的工作计划,已经致力于定义 OAuth 2.0 。鉴于漫长的标准化工作计划,服务提供者已经开始部署符合多个草案的实现,包含了轻微的语义差别。

幸好,Passport 使应用规避了处理 OAuth 变体的复杂性。在很多情况下,可以使用一个提供者相关的策略,而不是上述的通用 OAuth 策略。这减少了一些必要设置,并能快速适配任何提供商特定的怪异模式。

对 OAuth 的支持由 passport-oauth 模块提供。

OAuth 1.0

OAuth 1.0 是一个包含了多个步骤的委托认证策略。第一步,获取一个 request token。然后,用户被重定向到服务提供商进行访问授权。最后,授权被批准后,用户被定向回应用,同时可以用 request token 换取 access token。请求访问的应用,被称为消费者(consumer),通过一个 consumer key 和 consumer secret 标识。

Configuration 配置

使用通用的 OAuth 策略时,key, secret 和终端通过选项指定。

var passport = require('passport')
  , OAuthStrategy = require('passport-oauth').OAuthStrategy;

passport.use('provider', new OAuthStrategy({
    requestTokenURL: 'https://www.provider.com/oauth/request_token',
    accessTokenURL: 'https://www.provider.com/oauth/access_token',
    userAuthorizationURL: 'https://www.provider.com/oauth/authorize',
    consumerKey: '123-456-789',
    consumerSecret: 'shhh-its-a-secret'
    callbackURL: 'https://www.example.com/auth/provider/callback'
  },
  function(token, tokenSecret, profile, done) {
    User.findOrCreate(..., function(err, user) {
      done(err, user);
    });
  }
));

基于 OAuth 的策略的验证回调函数接受 token , tokenSecretprofile 参数。token 是 access token ,tokenSecret 是它对应的 secret。profile 将包含服务提供商提供的用户信息。

Routes 路由

OAuth 认证需要两个路由。第一个路由创建一个 OAuth 事务并将用户定向到服务提供商。第二个路由是用户与提供商认证后返回的 URL。

// 将用户重定向到 OAuth 提供商进行认证。完成后,提供商将用户定向回应用的地址:
//     /auth/provider/callback
app.get('/auth/provider', passport.authenticate('provider'));

// OAuth 提供商已经将用户重定向回应用。
// 试图获取 access token 来完成认证。如果授权被许可,用户将登录。否则,认证失败
app.get('/auth/provider/callback',
  passport.authenticate('provider', { successRedirect: '/',
                                      failureRedirect: '/login' }));

Link 链接

链接或者按钮可以放置在 web 页面中,点击即开始认证过程。

<a href="/auth/provider">Log In with OAuth Provider</a>

OAuth 2.0

OAuth 2.0 是 OAuth 1.0 的继任者,为解决早期版本中的一些已知的缺陷而设计。认证的过程基本相同。用户先被重定向到服务提供商进行访问授权。授权被许可后,用户被重定向回到应用,携带一个用于交换 access token 的 code。请求访问的应用,被称为 client,通过 ID 和 secret 标识。

var passport = require('passport')
  , OAuth2Strategy = require('passport-oauth').OAuth2Strategy;

passport.use('provider', new OAuth2Strategy({
    authorizationURL: 'https://www.provider.com/oauth2/authorize',
    tokenURL: 'https://www.provider.com/oauth2/token',
    clientID: '123-456-789',
    clientSecret: 'shhh-its-a-secret'
    callbackURL: 'https://www.example.com/auth/provider/callback'
  },
  function(accessToken, refreshToken, profile, done) {
    User.findOrCreate(..., function(err, user) {
      done(err, user);
    });
  }
));

基于 OAuth 2.0 的策略的验证回调函数接受 accessTokenrefreshTOkenprofile 参数。refreshToken 可以用来获取新的 access token,如果提供商没有发送 refresh token 时可能为 undefinedprofile 将包含服务提供商提供的用户信息。

Routes 路由

OAuth 2.0 认证需要两个路由。第一个路由将用户重定向到服务提供商。第二个路由是用户与提供商认证结束后重定向的 URL。

// 将用户重定向到 OAuth2.0 提供商进行认证。
// 完成后,提供商将用户重定向回到应用的地址:
//     /auth/provider/callback
app.get('/auth/provider', passport.authenticate('provider'));

// OAuth 2.0 提供商已经将用户重定向回到应用。
// 尝试获取 access token 来完成认证过程。如果授权被许可,用户将成功登录。否则,认证失败。
app.get('/auth/provider/callback',
  passport.authenticate('provider', { successRedirect: '/',
                                      failureRedirect: '/login' }));

Scope 作用域

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

app.get('/auth/provider',
  passport.authenticate('provider', { scope: 'email' })
);
Multiple scopes can be specified as an array.

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

scope 选项的值是提供商相关的。查阅提供商的文档来获取关于所支持的作用域的细节。

Link 链接

链接或者按钮可以放置到 web 页面中,点击即开始认证过程。

<a href="/auth/provider">Log In with OAuth 2.0 Provider</a>

User Profile 用户信息

当使用类似于 Facebook 或 Twitter 的第三方服务进行认证时,用户信息通常是可以访问的。每一个服务往往使用一个不一样的方式来编码这个信息。为了方便集成,Passport 最大程度地标准化了用户信息。

标准化后的用户信息遵循 Joseph Smarr 定制的 contact schema 。可用的通用字段可以概括为下表。

provider {String} 用户认证的提供商 (facebook, twitter, etc.)

id {String} 一个唯一的用户标识,由服务提供商生成。

displayName {String} 这个用户适合展示的名字。

name {Object}

  • familyName {String} 这个用户的姓
  • givenName {String} 这个用户的名
  • middleName {String} 这个用户的中间名

emails {Array} [n]

  • value {String} 实际的 email 地址
  • type {String} email 地址的类型(home, work, etc.)

photos {Array} [n]

  • value {String} 图片的 URL

注意所有上述字段不是对每一个服务提供商都可用。有些提供商可能包含额外的没有在这里描述的字段。查阅服务商相关的文档获取更多细节。

编辑于 2018-04-20 21:32