why token based auth?

此段摘自

  • http://zhuanlan.zhihu.com/FrontendMagazine/19920223

英文原文

  • http://code.tutsplus.com/tutorials/token-based-authentication-with-angularjs-nodejs–cms-22543

在讨论了关于基于 token 认证的一些基础知识后,我们接下来看一个实例。看一下下面的几点,然后我们会仔细的分析它:

  1. 多个终端,比如一个 web 应用,一个移动端等向 API 发送特定的请求。

  2. 类似 https://api.yourexampleapp.com 这样的请求发送到服务层。如果很多人使用了这个应用,需要多个服务器来响应这些请求操作。

  3. 这时,负载均衡被用于平衡请求,目的是达到最优化的后端应用服务。当你向https://api.yourexampleapp.com 发送请求,最外层的负载均衡会处理这个请求,然后重定向到指定的服务器。

  4. 一个应用可能会被部署到多个服务器上(server-1, server-2, …, server-n)。当有请求发送到https://api.yourexampleapp.com 时,后端的应用会拦截这个请求头部并且从认证头部中提取到 token 信息。使用这个 token 查询数据库。如果这个 token 有效并且有请求终端数据所必须的许可时,请求会继续。如果无效,会返回 403 状态码(表明一个拒绝的状态)。

基于 token 的认证在解决棘手的问题时有几个优势:

  • Client Independent Services 。在基于 token 的认证,token 通过请求头传输,而不是把认证信息存储在 session 或者 cookie 中。这意味着无状态。你可以从任意一种可以发送 HTTP 请求的终端向服务器发送请求。

  • CDN 。在绝大多数现在的应用中,view 在后端渲染,HTML 内容被返回给浏览器。前端逻辑依赖后端代码。这中依赖真的没必要。而且,带来了几个问题。比如,你和一个设计机构合作,设计师帮你完成了前端的 HTML,CSS 和 JavaScript,你需要拿到前端代码并且把它移植到你的后端代码中,目的当然是为了渲染。修改几次后,你渲染的 HTML 内容可能和设计师完成的代码有了很大的不同。在基于 token 的认证中,你可以开发完全独立于后端代码的前端项目。后端代码会返回一个 JSON 而不是渲染 HTML,并且你可以把最小化,压缩过的代码放到 CDN 上。当你访问 web 页面,HTML 内容由 CDN 提供服务,并且页面内容是通过使用认证头部的 token 的 API 服务所填充。

  • No Cookie-Session (or No CSRF) 。CSRF 是当代 web 安全中一处痛点,因为它不会去检查一个请求来源是否可信。为了解决这个问题,一个 token 池被用在每次表单请求时发送相关的 token。在基于 token 的认证中,已经有一个 token 应用在认证头部,并且 CSRF 不包含那个信息。

  • Persistent Token Store 。当在应用中进行 session 的读,写或者删除操作时,会有一个文件操作发生在操作系统的temp 文件夹下,至少在第一次时。假设有多台服务器并且 session 在第一台服务上创建。当你再次发送请求并且这个请求落在另一台服务器上,session 信息并不存在并且会获得一个“未认证”的响应。我知道,你可以通过一个粘性 session 解决这个问题。然而,在基于 token 的认证中,这个问题很自然就被解决了。没有粘性 session 的问题,因为在每个发送到服务器的请求中这个请求的 token 都会被拦截。

这些就是基于 token 的认证和通信中最明显的优势。基于 token 认证的理论和架构就说到这里。下面上实例。

这段本来想自己写,不过自己写也这些内容,节省点时间

jwt加密和解密

JWT 代表 JSON Web Token ,它是一种用于认证头部的 token 格式。这个 token 帮你实现了在两个系统之间以一种安全的方式传递信息。出于教学目的,我们暂且把 JWT 作为“不记名 token”。一个不记名 token 包含了三部分:header,payload,signature。

header 是 token 的一部分,用来存放 token 的类型和编码方式,通常是使用 base-64 编码。

payload 包含了信息。你可以存放任一种信息,比如用户信息,产品信息等。它们都是使用 base-64 编码方式进行存储。 signature 包括了 header,payload 和密钥的混合体。密钥必须安全地保存储在服务端。

nodejs实现的jwt代码

http://github.com/auth0/node-jsonwebtoken

主要3个方法

  • jwt.sign

  • jwt.verify

  • jwt.decode

需要小心的密钥在多线程或集群下的处理。

加解密一个对象的时间,远远比查询数据库的代价小,唯一可能有的是token有效期的校验,代价极其小。

优雅之写法

授权获取token

在app/routes/api/index.js里

// auth
router.post('/auth', function(req, res, next) {
  User.one({username: req.body.username},function(err, user){
    if (err) throw err;
    console.log(user);     if (!user) {
        res.json({ success: false, message: '认证失败,用户名找不到' });
    } else if (user) {       // 检查密码
      if (user.password != req.body.password) {
          res.json({ success: false, message: '认证失败,密码错误' });
      } else {
        // 创建token
        var token = jwt.sign(user, 'app.get(superSecret)', {
            'expiresInMinutes': 1440 // 设置过期时间
        });         // json格式返回token
        res.json({
            success: true,
            message: 'Enjoy your token!',
            token: token
        });
      }
    }
  });
});
  • post请求

  • 地址http://127.0.0.1:3019/api/auth

  • 参数”username=sang&password=000000”

测试

curl -d "username=sang&password=000000" http://127.0.0.1:3019/api/auth

返回

{"success":true,"message":"Enjoy your token!","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NTc4MzJkZjk0ZTFjN2YyMDJmYTVlNGUiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.Wv5za6GpJSMi346o625_8FxfoM4dJ1cWNuezG10zQG4"}%

路由处理

app/routes/api/groups.js

var express = require('express');
var router = express.Router(); var $ = require('../../controllers/groups_controller');
var $middlewares = require('mount-middlewares'); router.get('/list', $middlewares.check_api_token, $.api.list); module.exports = router;

核心代码

router.get('/list', $middlewares.check_api_token, $.api.list);

说明

  • 使用了$middlewares.check_api_token中间件

  • 核心业务逻辑在$.api.list

  • 和其他的express路由用法一样,无他

中间件$middlewares.check_api_token

/*!
 * Moajs Middle
 * Copyright(c) 2015-2019 Alfred Sang <shiren1118@126.com>
 * MIT Licensed
 */ var jwt = require('jsonwebtoken');//用来创建和确认用户信息摘要
// 检查用户会话
module.exports = function(req, res, next) {
  console.log('检查post的信息或者url查询参数或者头信息');
  //检查post的信息或者url查询参数或者头信息
  var token = req.body.token || req.query.token || req.headers['x-access-token'];
  // 解析 token
  if (token) {
    // 确认token
    jwt.verify(token, 'app.get(superSecret)', function(err, decoded) {
      if (err) {
        return res.json({ success: false, message: 'token信息错误.' });
      } else {
        // 如果没问题就把解码后的信息保存到请求中,供后面的路由使用
        req.api_user = decoded;
        console.dir(req.api_user);
        next();
      }
    });
  } else {
    // 如果没有token,则返回错误
    return res.status(403).send({
        success: false,
        message: '没有提供token!'
    });
  }
};

这个很容易解释,只要参数有token或者头信息里有x-access-token,我们就认定它是一个api接口,

校验通过了,就把token的decode对象,也就是之前加密的用户对象返回来,保存为req.api_user

业务代码

app/controllers/groups_controller.js

exports.api = {
  list: function (req, res, next) {
    console.log(req.method + ' /groups => list, query: ' + JSON.stringify(req.query));
  
    var user_id = req.api_user._id;     Group.query({ower_id: user_id}, function(err, groups){
      console.log(groups);
      res.json({
        data:{
          groups : groups
        },
        status:{
          code  : 0,
          msg   : 'success'
        }
      })
    });
  }
}

让scaffold生成代码和api共存,清晰明了

说明一下

  • req.api_user是$middlewares.check_api_token里赋值的

  • 写一个下查询接口,返回json即可

测试接口

然后让我们来测试一下

  • get请求

  • url = http://127.0.0.1:3019/api/groups/list

  • 参数token

    curl http://127.0.0.1:3019/api/groups/list\?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NTc4MzJkZjk0ZTFjN2YyMDJmYTVlNGUiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.Wv5za6GpJSMi346o625_8FxfoM4dJ1cWNuezG10zQG4

    {"data":{"groups":[{"_id":"557d32a282f9ddcc76a540e8","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d32b082f9ddcc76a540e9","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d32f082f9ddcc76a540ea","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d33804f5905de78e1c25a","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d33984f5905de78e1c25b","name":"anan","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"2323","is_public":"232","__v":0}]},"status":{"code":0,"msg":"success"}}

模型,查询以及其他

模型,查询以及其他,沿用之前的东西,仍然以mongoosedao为主

  • one

  • all

  • query

基本上够用了

如果还想玩的更high一点,可以增加一个service层,把多个model的操作放到里面。

总结

以后写api,可以这样玩

1) 在app/routes/api/目录下建立对应的api文件,比如groups.js,topics.js,users.js等

2) 然后在对应的controller里,增加

exports.api = {
  aa:function(req, res, next){
    var user_id = req.api_user._id;
  },
  bb:function(req, res, next){
    var user_id = req.api_user._id;
  }
}

3) 简单写点模型的查询方法就可以了

是不是很简单?

  • 使用mount-routes自动挂载routes

  • 使用mongoosedao更简单的接口

如果以后再提供生成器呢?

想想就很美好,美好就继续美好吧~

补一下

  • Nodejs RESTFul架构实践 http://nodeonly.com/2015/06/09/expressjs-rest.html

全文完

Nodejs RESTFul架构实践之api篇(转)的更多相关文章

  1. Restful架构思想

    java作为一门后端语言,其厉害之处在于web,大家比较熟知的各种网络应用,java都能做,那么在这个移动优先的时代,如何继续发挥java的强大呢.通常是让java作为一个app的服务端,为app客户 ...

  2. Django高级篇一RESTful架构及API设计

    一.什么是RESTful架构? 通过互联网通信,建立在分布式体系上"客户端/服务器模式”的互联网软件,具有高并发和高延时的特点. 简单的来说,就是用开发软件的模式开发网站.网站开发,完全可以 ...

  3. 玩转 SpringBoot 2 快速搭建 | RESTful Api 篇

    概述 RESTful 是一种架构风格,任何符合 RESTful 风格的架构,我们都可以称之为 RESTful 架构.我们常说的 RESTful Api 是符合 RESTful 原则和约束的 HTTP ...

  4. 理解RESTful架构——Restful API设计指南

    理解RESTful架构 Restful API设计指南 理解RESTful架构 越来越多的人开始意识到,网站即软件,而且是一种新型的软件. 这种"互联网软件"采用客户端/服务器模式 ...

  5. Yii2 基于RESTful架构的 advanced版API接口开发 配置、实现、测试 (转)

    环境配置: 开启服务器伪静态 本处以apache为例,查看apache的conf目录下httpd.conf,找到下面的代码 LoadModule rewrite_module modules/mod_ ...

  6. Yii2 基于RESTful架构的 advanced版API接口开发 配置、实现、测试

    环境配置: 开启服务器伪静态 本处以apache为例,查看apache的conf目录下httpd.conf,找到下面的代码 LoadModule rewrite_module modules/mod_ ...

  7. Yii2 基于RESTful架构的 advanced版API接口开发 配置、实现、测试【转】

    环境配置: 开启服务器伪静态 本处以apache为例,查看apache的conf目录下httpd.conf,找到下面的代码 LoadModule rewrite_module modules/mod_ ...

  8. 理解restful 架构 && RESTful API设计指南

    restful是前端和后端接口中都会使用的设计思想. 网站即软件,我们也常说的webapp,这种互联网软件采用的是“客户端/服务器”模式,建立在分布式体系上. 网站开发,也可以完全采用软件开发的模式, ...

  9. Restful架构API编码规范

    Restful API 目前比较成熟的一套互联网应用程序的API设计理论 一.协议 API与用户的通信协议,总是使用HTTPs协议. 二.域名 应该尽量将API部署在专用域名之下. https://a ...

随机推荐

  1. ASP.NET中的Webconfig 和 Global.asax区别

    Web.Config与Global.asax的区别: Config可以根据不同的错误类型定义不同的错误页,网站重定义转向新的错误页面. Global,在全局错误中写入应用程序事件错误信息,并在当前页输 ...

  2. 洛谷—— P1074 靶形数独

    https://www.luogu.org/problem/show?pid=1074 题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他 们想用数独来一比高低.但 ...

  3. 洛谷—— P1126 机器人搬重物

    https://www.luogu.org/problem/show?pid=1126 题目描述 机器人移动学会(RMI)现在正尝试用机器人搬运物品.机器人的形状是一个直径1.6米的球.在试验阶段,机 ...

  4. mysql开发之---每日一得01

    2015年7月7日------------------------- 1.truncate表会清空建表语句auto_increment的值:某个表的id即是主键也是自增,你能够选择插入随意id值,假设 ...

  5. Spring整合TimerTask实现定时任务调度

    一. 前言 近期在公司的项目中用到了定时任务, 本篇博文将会对TimerTask定时任务进行总结, 事实上TimerTask在实际项目中用的不多, 由于它不能在指定时间执行, 仅仅能让程序依照某一个频 ...

  6. 最好的PHP框架有哪些

    最好的PHP框架有哪些 首推 Laravel ,其次是 Yii,Laravel 的设计思想以及在工程实践上的支持,超过 ThinkPHP 好几个世代.如果说原生的 PHP 是火枪, ThinkPHP ...

  7. [SCOI 2008] 奖励关

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1076 [算法] f[i][S]表示当前第i次抛出宝物,目前集合为S,所能获得的最高分 ...

  8. Java基础之关键字

    一.Java关键字总览 基本数据类型(9):boolean.char.byte.short.int.long.float.double.null 变量引用(2):super.this 类.方法.变量修 ...

  9. spring-boot系列:(一)整合dubbo

    spring-boot-2整合dubbo 新框架学习,必须上手干.书读百遍,其义自见. 本文主要介绍spring-boot-2整合dubbo,使用xml配置实现一个provider和consumer. ...

  10. HttpClient简单操作

    HttpClient 这个框架主要用来请求第三方服务器,然后获取到网页,得到我们需要的数据: HttpClient设置请求头消息User-Agent模拟浏览器 比如我们请求 www.tuicool.c ...