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

OAuth

OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。常见的采用微信、QQ、微博、Facebook、Google账号登陆网站的过程都是采用了OAuth技术。这一章我们会以使用Google账号登陆第三方网站为例,展示如何使用这项技术。

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密钥。如果想要看详细步骤,参考这里
    • 这个流程里需要注意的点是,设置JavaScript授权域和授权回调URL,因为我们现在建立的是一个开发项目,两者分别设为 http://localhost:5000和http://localhost:5000/*
    • clientID: 用于生成登陆用的URL
    • clientSecrect: 用于证明该APP是否有权访问token
  • 接下来要把刚才生成的clientID和clientSectrect,传入Google OAuth模块中。注意,clientSecrect不能公布,谨慎起见clientID也应该保密。所以我们不希望别人通过查看源代码的形式获取这两个值。目前我们先通过不提交这部分代码的形式做到隐藏这部分信息。
  • 创建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);
时间: 2024-08-02 10:19:15

Node with React: Fullstack Web Development 课程手记(二)——Google OAuth的相关文章

萧井陌WEB前端课程

详情请交流  QQ  709639943 00.萧井陌WEB前端课程 00.微信小程序 美甲商城 00.微服务的入门级微框架Spring Boot快速入门 00.基于java的微信公众号二次开发视频教程 00.leetcode 算法 面试 00.北风网 零基础到数据(大数据)分析专家-首席分析师 00.快速上手JMeter 00.Jmeter 00.2017年Java web开发工程师成长之路 00.R语言速成实战 00.R语言数据分析实战 00.Python+Django+Ansible Pl

《Node.js+MongoDB+AngularJS Web开发》读书笔记及联想

总体介绍 <Node.js+MongoDB+AngularJS Web开发>,于2015年6月出版,是一本翻译过来的书,原书名为<Node.js,MongoDB and AngularJS Web Development>,总的来说是一本讲述如何用Javascript进行B/S架构全栈开发的书. 该书主要讲解4种技术(框架),分为6个部分29个章节.4种技术即Node.js.MongoDB.Express.AngularJS,业内称为MEAN:6个部分我个人理解为: 基础(引言).

Facebook React 和 Web Components(Polymer)对比优势和劣势

目录结构 译者前言 Native vs. Compiled 原生语言对决预编译语言 Internal vs. External DSLs 内部与外部 DSLs 的对决 Types of DSLs - explanation DSLs 的种类 - 解释 Data binding 数据绑定 Native vs. VM 原生对决 VM(虚拟机) 译者前言 这是一篇来自 StackOverflow 的问答,提问的人认为 React 相比 WebComponents 有一些"先天不足"之处,列举

【转】Facebook React 和 Web Components(Polymer)对比优势和劣势

原文转自:http://segmentfault.com/blog/nightire/1190000000753400 译者前言 这是一篇来自 StackOverflow 的问答,提问的人认为 React 相比 WebComponents有一些“先天不足”之处,列举如下: 原生浏览器支持 原生语法支持(意即不把样式和结构混杂在 JS 中) 使用 Shadow DOM 封装样式 数据的双向绑定 这些都是确然的.不过他还是希望听听大家的看法,于是就有了这篇精彩的回答. 需要说明的是,这篇回答并没有讨

Flask Web Development - Flask 模板1 - 模板机制&Jinja2引擎

节选自PartI Chapter3,这个chapter主要讲模板工作原理,这里讲的就是Jinja2这个模板,另外还提到了Flask-Bootstrap及Flask-Moment两个插件,前者对Flask使用Bootstrap做了些封装,后者对moment.js做了些封装.内容较多,估计分开搞. 模板存在的意义 可维护性高的代码是结构良好且整洁的. 当用户在网站注册一个账户时,他在表单里填入邮箱跟密码,并点击提交按钮.在server端就收到一个包含这些数据的request,再由Flask分发到相应

Plan for Perl Web Development

Plan for Perl Web Development [2015/2/8 to 2015/4/1] Perl Foundation (Task::Kensho,DBDev: Database Development,XML: XML Development) Web Foundation (Task::Kensho::WebCrawling: Web Crawling) Web Development (Task::Kensho::WebDev: Web Development) Web

Flask Web Development - Flask插件机制&Flask-Script

本节取自part I chapter 2的后半部分,跳过了关于request与response具体交互设计细节内容.主要通过Flask-Script插件让读者对于插件系统有个简单认识. Flask注重拓展性,社区里已经有很多插件可供选择,当然也可以使用python标准库或者其他的各种库. Flask-Script Flask-Script这个插件,是用来增加Flask应用的命令行参数的,它本身自带了一些通用的选项,也支持自定义的命令.这功能可能类似于python标准库中的argparse. 之前

學習 React.js:用 Node 和 React.js 創建一個實時的 Twitter 流

Build A Real-Time Twitter Stream with Node and React.js By Ken Wheeler (@ken_wheeler) 簡介 歡迎來到學習 React 的第二章,該系列文章將集中在怎麼熟練並且有效的使用臉書的 React 庫上.如果你沒有看過第一章,概念和起步,我非常建議你繼續看下去之前,回去看看. 今天我們準備創建用 React 來創建一個應用,通過 Isomorphic Javascript. Iso-啥? Isomorphic. Java

学习web前端课程必掌握技能总结

前端开发是近几年来兴起的新兴行业,是IT行业中要求相对较低的职业,同时就业薪资相对较高,很多人通过参加web前端培训实现了高薪就业梦,那web前端课程到底包括哪些技术呢? Web前端开发技术包括三个要素:HTML.CSS和JavaScript,但随着RIA的流行和普及,Flash/Flex.Silverlight.XML和服务器端语言也是前端开发工程师应该掌握的.随着时代的发展,前端开发技术的三要素也演变成为现今的:html5,css3,jquery. 学习html,这个是最简单的,也是最基础的