Node with React: Fullstack Web Development 课程手记(二)——Google OAuth

OAuth

OAuth是一个关于受权(authorization)的开放网络标准,在全世界获得普遍应用,目前的版本是2.0版。常见的采用微信、QQ、微博、Facebook、Google帐号登录网站的过程都是采用了OAuth技术。这一章咱们会以使用Google帐号登录第三方网站为例,展现如何使用这项技术。node

Google OAuth工做流程

  • 整个OAuth过程主要设计三个方面,客户端(对于网站而言则是浏览器)、第三方服务器(对应网站的服务器)和Google服务器。当用户点击使用Google帐号登录网站时,第三方服务器会直接把这个请求传递给Google服务器,响应后页面跳转至Google的验证受权页面,询问用户是否赞成受权。用户赞成后,谷歌服务器会跳转至第三方服务器中,而且在跳转URL上会携带一个code参数,第三方服务器拿到code后会凭借这个code再次向Google服务器发送请求,并换取用户信息。拿到用户信息后,第三方服务器会检查数据库,若是没有这个用户则存入数据库,并登录成功,若是有则直接登录成功。与此同时,给浏览器种一个标识用户信息的cookie,此后在cookie的有效期内,浏览器接下来每次对第三方服务器的请求中都会携带cookie,所以能够表示用户身份,作一些须要权限才能作的事情。具体流程以下图所示:
  • 我使用passport这个库帮助咱们实现验证流程。

    passportJS

  • 两个问题:
    • passportJS会自动化OAuth流程,但须要代码深刻到流程细节中,并不能彻底自动化整个流程
    • 库的结构,实际上咱们须要两个库才能使用passportJS——passport、passport strategy,第一个是核心库,用以提供验证流程的工具方法,第二个是针对不一样的受权提供方(Google、Facebook、Wechat etc.)所须要的定制方法,也就是说你若是须要同时提供Google、Facebook、Wechat三种验证方式,那你就须要三个strategy库。在 passportjs.org中提供了不少strategies库。
  • 安装passport到项目中
    npm install --save passport passport-google-oauth20复制代码
  • 20的意思是版本为2.0,由于npm的包名称中不能有.,因此就起名为20了,其实这里也能够不加20,那么安装的就是一个1.02.0的组合版。鉴于如今基本知名的auth provider都已经支持OAuth2.0,因此这里采用2.0版本。详情参见passport-google-oauth github
  • 使用passport
const passport  = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.use(new GoogleStrategy());复制代码
  • 在使用Google OAuth以前,须要两个参数appid和api secrect,要获取这两个参数,须要在 console.developers.google.com 上建立项目(只要有Google帐号,very easy)
  • 建立完项目,进入项目面板,进入api板块,点击启用Google API,搜索Google +,选择 Google + API, 点击启用
  • 如今此API依然不能使用,须要点击点击建立凭据按钮,按照提示流程一直走到最后,生成凭据,主要包含两个信息clientID和client密钥。若是想要看详细步骤,参考这里git

    • 这个流程里须要注意的点是,设置JavaScript受权域和受权回调URL,由于咱们如今创建的是一个开发项目,二者分别设为 http://localhost:5000和http://localhost:5000/*
    • clientID: 用于生成登录用的URL
    • clientSecrect: 用于证实该APP是否有权访问token
  • 接下来要把刚才生成的clientID和clientSectrect,传入Google OAuth模块中。注意,clientSecrect不能公布,谨慎起见clientID也应该保密。因此咱们不但愿别人经过查看源代码的形式获取这两个值。目前咱们先经过不提交这部分代码的形式作到隐藏这部分信息。github

  • 建立cong/keys.js,存放clientID和clientSecrect
    module.exports = {
      googleClientId: '1229722414-eeujg12q0q9gvisar.apps.googleusercontent.com',
      googleClientSecret: 'ANPiCt5QFTa'
    };复制代码
  • 在.gitignore中写入keys.js,确保包含敏感信息的文件不会被提交
  • 在index.js中,引入keys模块,并将对应的clientKey和clientID传入GoogleStrategy模块中。
    • 注意这里还添加了回调URL。这是由于当用户点击受权后,Google服务器会返回一个code到应用的服务器,那咱们服务器应该如何接收并处理这个服务器的返回呢。Google服务器返回给app服务器信息,能够看作是一次请求(服务器不就是用来处理请求的吗),因此咱们必需要指定请求的route是什么,所以咱们须要一个回调URL的参数,google服务器会将code拼接到这个URL的参数里。
    • 这里还添加了一个回调函数,全部验证的目的就是为了拿到token,以便用户随后的操做,回调函数定义了拿到token作什么。目前仅仅先把token打出来看一下。
      const keys = require('./config/keys');
      passport.use(new GoogleStrategy({
      clientID: keys.googleClientId,
      clientSecret: keys.googleClientSecret,
      callbackURL: '/auth/google/callback'
      }, (accessToken, refreshToken, profile, done) => {console.log(accessToken)}));复制代码
  • 最后咱们须要添加一个route handler,用以接收用户login的请求,并进入Google OAuth流程,以下面的代码所示。
    • 首先解释一下代码的意思:若是服务器接收到/auth/google的请求,使用passport启用Google OAuth的验证流程,须要获取的信息有用户资料和邮箱。
    • 这里面的字符串'google'看起来很让人费解,由于在以前的代码中咱们并无任何用这个字符串表明Google OAuth strategy的意思。这是passport广为人诟病的一点。事实上,Google OAuth strategy模块中设定了这一点,也就是说这个模块告诉passport若是passport.authenticate方法第一个参数传入了google,那么就采用Google OAuth strategy模块验证。
app.get(
    "/auth/google",
    passport.authenticate("google", {
        scope: ["profile", "email"]
    })
);复制代码
  • 如今启动咱们的本地服务器,访问localhost:5000/auth/google,按理应该会弹出google认证的页面,可是不幸的是并无,这时弹出的是一个400页面,大概的意思是说实际提供的验证回调地址和在console.developers.google.com中设定的不一致。还提供了一个连接,直接访问这个连接就进入了修改验证回调URL的页面。
    • 为何会出现这个错误页?还记得以前咱们把已获受权的重定向 URI这一项设为http://localhost:5000/*,事实上这里须要严格匹配。以前在代码中咱们设定callbackURL/auth/google/callback,因此咱们应该在这个修改页面中将已获受权的重定向 URI这一项设为http://localhost:5000/auth/google/callback,这样以后应该就能正常弹出受权页面了。
    • 为何须要回调验证URL匹配?咱们访问google服务器要求提供受权时,提供的参数是clientID,而且明文传输。攻击者拿到clientID,并把redirect_uri改成恶意网站,那么用户受权后就肯能会跳转到恶意网站,并提供全部的受权信息。显然,这种状况是坚定不能发生的,因此咱们须要在google那边配置容许的回调URL,并严格匹配。若是不匹配是不会成功回调的。
  • 点击对应的Google帐户登录,会跳到一个错误页显示Cannot GET /auth/google/callback。咱们尚未设置针对回调route的handler,因此固然会报错了。在这个页面的URL中,会看到一个参数code,这就是在以前流程图中提到的Google服务器返回的code。咱们app的服务器拿到code后,就能够经过code再次向Google服务器发请求,并拿到用户的资料、邮箱等信息了。因此接下来须要补上对应的route handler。
    app.get('/auth/google/callback', passport.authenticate('google'));复制代码
  • 再次访问localhost:5000/auth/google,点击帐户登陆,能够看到在启动server的控制台中打印出了一坨东西。以前咱们在配置passport中传入了一个回调函数,在回调函数中打印出了token。这一坨就是取到的token。
    • 实际上passport在回调URL的handler中自动将code传递给了google服务器,并换取了token、用户信息(资料、邮箱等)。这些信息时经过函数参数的形式传递回来的。 所以,在这以后,那个打印token的函数被调用,咱们的app能够在这个回调函数中利用这些信息作一些不可描述的事情。
    • 在继续以前,咱们能够先把这些返回的信息打印出来,看看长什么样子。修改代码,重启server,从新访问登录链接,能够看到控制台中打印出了token(string)、profile(object)、done(function)。
      • accessToken: app后续访问用户信息的凭证。
      • accessToken过一段时间就会过时,refreshToken会容许咱们刷新获得最新的token。
      • profile:用户全部的资料。
      • done函数的参数有三个:err(错误信息),user(用户信息),info(其余信息)
    • 为何会pending?回调函数中,咱们并无给出响应response。
      passport.use(
      new GoogleStrategy(
        {
            clientID: keys.googleClientId,
            clientSecret: keys.googleClientSecret,
            callbackURL: "/auth/google/callback"
        },
        (accessToken, refreshToken, profile, done) => {
            console.log('accessToken', accessToken);
            console.log('refreshToken', refreshToken);
            console.log('profile', profile);
            console.log('done', done);
        }
      )
      );复制代码
  • 至此,全部受权的工做(在passport的帮助下)已经完成,接下来是建立用户信息到数据库、登录完成。

使用nodemon使开发自动化

  • 至此,应该已经厌倦了修改代码,重启server的过程。幸运的是已经有工具使这一切自动化,这个工具就是nodemon
  • npm install --save-dev nodemon
  • 修改package.json
    "scripts": {
      "start": "node index.js",
      "dev": "nodemon index.js"
    },复制代码
  • 以后只须要在命令行中输入npm run dev,就能够启动服务器,而且每次修改代码保存后,nodemon都会帮咱们自动重启服务器了。

    重构目前的代码

  • 以前咱们把全部的逻辑都写在index.js文件中,为了便于维护和迭代,咱们把逻辑分散在不一样的目录下。目前咱们把逻辑分为三个部分config,routes,services。三个部分的含义以下图所示。重构以后的目录以下所示。基本的工做就是把routehandler的逻辑移动到authRoutes.js中,把配置passport的逻辑,移动到passport.js中,而后在两个文件中引入依赖的包或者其余模块。再在index中引入这两个文件。
    ├── config
    │   └── keys.js
    ├── index.js
    ├── package-lock.json
    ├── package.json
    ├── routes
    │   └── authRoutes.js
    └── services
      └── passport.js复制代码

  • routes/authRoutes.js
const passport = require('passport');
module.exports =  (app) => {
    app.get(
        "/auth/google",
        passport.authenticate("google", {
            scope: ["profile", "email"]
        })
    );
    app.get("/auth/google/callback", passport.authenticate("google"));
}复制代码
  • servics/passport.js
const passport = require('passport');
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const keys = require("../config/keys");

passport.use(
    new GoogleStrategy(
        {
            clientID: keys.googleClientId,
            clientSecret: keys.googleClientSecret,
            callbackURL: "/auth/google/callback"
        },
        (accessToken, refreshToken, profile, done) => {
            console.log('accessToken', accessToken);
            console.log('refreshToken', refreshToken);
            console.log('profile', profile);
            console.log('done', done);
        }
    )
);复制代码
  • index.js
    const express = require("express");
    const app = express();
    require('./services/passport');
    require('./routes/authRoutes')(app);
    app.get("/", (req, res) => {
      res.send({ hi: "there" });
    });
    )
    const PORT = process.env.PORT || 5000;
    app.listen(PORT);复制代码

next section数据库

相关文章
相关标签/搜索