深入理解令牌认证机制(token)


Posted in Javascript onAugust 22, 2019

以前的开发模式是以MVC为主,但是随着互联网行业快速的发展逐渐的演变成了前后端分离,若项目中需要做登录的话,那么token成为前后端唯一的一个凭证。

token即标志、记号的意思,在IT领域也叫作令牌。在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。

token其实说的更通俗点可以叫暗号,在一些数据传输之前,要先进行暗号的核对,不同的暗号被授权不同的数据操作。例如在USB1.1协议中定义了4类数据包:token包、data包、handshake包和special包。主机和USB设备之间连续数据的交换可以分为三个阶段,第一个阶段由主机发送token包,不同的token包内容不一样(暗号不一样)可以告诉设备做不同的工作,第二个阶段发送data包,第三个阶段由设备返回一个handshake包。

在HTTP请求中使用承载令牌来访问OAuth 2.0受保护的资源。拥有承载令牌的任何一方(“承载方”)都可以使用它访问相关资源(无需证明拥有加密密钥)。为了防止误用,需要防止在存储和传输中泄露承载令牌。

OAuth允许客户端通过获取访问令牌,它在“OAuth 2.0授权”中定义框架“[RFC6749]作为”表示访问的字符串而不是使用资源直接服务的凭证。

该令牌由服务端允许的情况下,由客户端通过某种方式向服务端发出请求,由服务端向客户端发出,客户机使用访问令牌访问由资源服务器承载的受保护的资源。该规范描述了当OAuth访问令牌是承载令牌时,如何发出受保护的资源请求。

客户端只需要拥有token可以以任何一种方法传递token,客户端需要知道参数加密的密钥,只需要存储token即可。

OAuth为客户端提供了一种方法来代表资源所有者访问受保护的资源。在一般情况下,客户机在访问受保护的资源之前,必须首先从资源所有者获得授权,然后将授权交换为访问令牌。访问令牌表示授权授予授予的范围、持续时间和其他属性。客户机通过向资源服务器显示访问令牌来访问受保护的资源。在某些情况下,客户端可以直接向服务端显示的发送自己的凭证。

+--------+                +---------------+
 |    |--(A)- Authorization Request ->|  Resource  |
 |    |                |   Owner   |
 |    |<-(B)-- Authorization Grant ---|        |
 |    |                +---------------+
 |    |
 |    |                +---------------+
 |    |--(C)-- Authorization Grant -->| Authorization |
 | Client |                |   Server  |
 |    |<-(D)----- Access Token -------|        |
 |    |                +---------------+
 |    |
 |    |                +---------------+
 |    |--(E)----- Access Token ------>|  Resource  |
 |    |                |   Server  |
 |    |<-(F)--- Protected Resource ---|        |
 +--------+                +---------------+

此方案的Authorization头字段的语法遵循[RFC2617]第2节中定义的基本方案的用法。注意,与Basic一样,它不符合[RFC2617]第1.2节中定义的通用语法,但与正在为HTTP 1.1 [HTTP- auth]开发的通用身份验证框架兼容,尽管它没有遵循其中列出的反映现有部署的首选实践。承载凭证的语法如下

b64toke = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
credentials = "Bearer" 1*SP b64token

客户端应该使用带有承载HTTP授权方案的Authorization请求头字段使用承载令牌发出经过身份验证的请求。资源服务器必须支持此方法。

在Internet Engineering Task Force (IETF)的白皮书中介绍Bearer Token的使用方法,那么我们平时使用token的时候姿势是否正确。应该如何正确使用token呢?

import axios from "axios";
axios.interceptors.request.use(config => {
 if (store.state.token) {
  config.headers.authorization = `Basic ${store.state.token}`;
 }
 return config;
});

照白皮书所说这样才是正确使用token的姿势。小伙伴们平时你们使用token的时候是这样的吗?

那么除了前端有明确的使用规范,那么服务端又应该怎样有效的做好后端数据防护?在白皮书中同样也有提到过。

根据OAuth 2.0动态客户端注册协议该规范定义了向授权服务器动态注册OAuth 2.0客户端的机制。注册请求向授权服务器发送一组所需的客户端元数据值(token)。结果的注册响应返回要在授权服务器上使用的客户机标识符和为客户机注册的客户机元数据值。然后,客户机可以使用此注册信息使用OAuth 2.0协议与授权服务器通信。该规范还定义了一组通用客户端元数据字段和值,供客户端在注册期间使用。

为了让OAuth 2.0 [RFC6749]客户机利用OAuth 2.0授权服务器,客户机需要与服务器交互的特定信息,包括在该服务器上使用的OAuth 2.0客户端标识符。该规范描述了如何通过授权服务器动态注册OAuth 2.0客户端来获取此信息。

抽象的动态客户端注册流程

+--------(A)- Initial Access Token (OPTIONAL)
  |
  |  +----(B)- Software Statement (OPTIONAL)
  |  |
  v  v
+-----------+                   +---------------+
|      |--(C)- Client Registration Request -->|  Client   |
| Client or |                   | Registration |
| Developer |<-(D)- Client Information Response ---|  Endpoint  |
|      |    or Client Error Response   +---------------+
+-----------+

图中所示的抽象OAuth 2.0客户机动态注册流描述了客户机或开发人员与此规范中定义的端点之间的交互。此图没有显示错误条件。这个流程包括以下步骤

  1. 可选地,向客户端或开发人员发出初始访问令牌,允许访问客户端注册端点。向客户端或开发人员发出初始访问令牌的方法超出了本规范的范围。
  2. 客户端或开发人员可以选择发布一个软件声明,以便与客户端注册端点一起使用。向客户端或开发人员发出软件声明的方法超出了本规范的范围。
  3. 客户端或开发人员使用客户端所需的注册元数据调用客户端注册端点,如果授权服务器需要初始访问令牌,则可以选择包含来自(A)的初始访问令牌。
  4. 授权服务器注册客户机并返回客户端注册的元数据, 在服务器上唯一的客户端标识符,以及一组客户端凭据,如客户端机密(如果适用于此客户端)。

授权类型与响应类型之间的关系

描述的grant类型和响应类型值是部分正交的,因为它们引用传递到OAuth协议中不同端点的参数。但是,它们是相关的,因为客户机可用的grant类型影响客户机可以使用的响应类型,反之亦然。例如,包含授权代码的授权类型值意味着包含代码的响应类型值,因为这两个值都定义为OAuth 2.0授权代码授权的一部分。因此,支持这些字段的服务器应该采取步骤,以确保客户机不能将自己注册到不一致的状态,例如,通过向不一致的注册请求返回无效的客户机元数据错误响应。

下表列出了这两个字段之间的相关性。

+-----------------------------------------------+-------------------+
| grant_types value includes:          | response_types  |
|                        | value includes:  |
+-----------------------------------------------+-------------------+
| authorization_code              | code       |
| implicit                   | token       |
| password                   | (none)      |
| client_credentials              | (none)      |
| refresh_token                 | (none)      |
| urn:ietf:params:oauth:grant-type:jwt-bearer  | (none)      |
| urn:ietf:params:oauth:grant-type:saml2-bearer | (none)      |
+-----------------------------------------------+-------------------+

向授予类型或响应类型参数引入新值的此文档的扩展和概要文件必须记录这两种参数类型之间的所有通信。

如果发送任何人类可读的字段时没有使用语言标记,那么使用该字段的各方不能对字符串值的语言、字符集或脚本做出任何假设,而且字符串值必须按照在用户界面中显示的位置使用。为了促进互操作性,建议客户端和服务器除了使用任何特定于语言的字段外,还使用不使用任何语言标记的人可读字段,并且建议发送的任何不使用语言标记的人可读字段包含适合在各种系统上显示的值。

例如,软件声明可以包含以下声明:

{
 "software_id": "4NRB1-0XZABZI9E6-5SM3R",
 "client_name": "Example Statement-based Client",
 "client_uri": "https://client.example.net/"
}

以下非标准示例JWT包括这些声明,并且使用RS256(仅用于显示目的)进行了非对称签名。并等到如下加密字符串。

eyJhbGciOiJSUzI1NiJ9.
eyJzb2Z0d2FyZV9pZCI6IjROUkIxLTBYWkFCWkk5RTYtNVNNM1IiLCJjbGll
bnRfbmFtZSI6IkV4YW1wbGUgU3RhdGVtZW50LWJhc2VkIENsaWVudCIsImNs
aWVudF91cmkiOiJodHRwczovL2NsaWVudC5leGFtcGxlLm5ldC8ifQ.
GHfL4QNIrQwL18BSRdE595T9jbzqa06R9BT8w409x9oIcKaZo_mt15riEXHa
zdISUvDIZhtiyNrSHQ8K4TvqWxH6uJgcmoodZdPwmWRIEYbQDLqPNxREtYn0
5X3AR7ia4FRjQ2ojZjk5fJqJdQ-JcfxyhK-P8BAWBd6I2LLA77IG32xtbhxY
fHX7VhuU5ProJO8uvu3Ayv4XRhLZJY4yKfmyjiiKiPNe-Ia4SMy_d_QSWxsk
U5XIQl5Sa2YRPMbDRXttm2TfnZM1xx70DoYi8g6czz-CPGRi4SW_S2RKHIJf
IjoI3zTJ0Y2oe0_EJAiXbL6OyF9S5tKxDXV8JIndSA

加密字符串由头,载荷以及密钥通过一系列的速算法生成,加密字符串与头,载荷以及密钥息息相关,一但加密字符串稍有改动,则无法解析正确解析无法通过验证。

通过加密字符串向授权服务器注册客户端。授权服务器为该客户端分配一个惟一的客户端标识符,可选地分配一个客户端机密,并将请求中提供的元数据与已发布的客户端标识符关联起来。该请求包括在注册期间为客户端指定的任何客户端元数据参数。授权服务器可以为客户端元数据中遗漏的任何项提供默认值。

注册端点,内容类型为application/json。该HTTP Entity Payload是一个由JSON组成的JSON文档对象和所有请求的客户端元数据值作为顶级成员那个JSON对象。

示例:

const Koa = require("koa");
const Router = require("koa-router");
const jwt = require("jsonwebtoken");
const jwtAuth = require("koa-jwt");

const secret = "it's a secret";   // 密钥
const app = new Koa();
const router = new Router();

router.get('/api/login',async (ctx) => {
  const {username,passwd} = ctx.query;
  if(username === "aaron" && passwd == "123456"){
    const token = jwt.sign({
      data:{name:"Aaron",userId:"1"},     // 用户信息
      exp:Math.floor(Date.now()/1000)+60*60  // 过期时间
    },secret);
    ctx.body = {code:200,token};
  }
  else{
    ctx.status = 401;
    ctx.body = {code:0,message: "用户名密码错误"};
  }
});

router.get("/api/userinfo",jwtAuth({secret}),async (ctx) => {  // jwtAuth受保护路由
  ctx.body = {code:200,data:{name:"Aaron",age:18}}
});

app.use(router.routes());
app.listen(3000);

因为最后生成的token是通过base64加密的,有些内容是可以反解的,所以千万不要在数据里面添加有关数据的敏感信息。注意注意。。。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
你的编程语言可以这样做吗?
Sep 07 Javascript
jquery $.each 和for怎么跳出循环终止本次循环
Sep 27 Javascript
jQuery的$.proxy()应用示例介绍
Apr 03 Javascript
javascript setinterval 的正确语法如何书写
Jun 17 Javascript
jquery实现增加删除行的方法
Feb 03 Javascript
javascript中的Base64、UTF8编码与解码详解
Mar 18 Javascript
js实现浏览本地文件并显示扩展名的方法
Aug 17 Javascript
详解Node.js实现301、302重定向服务
Apr 07 Javascript
详解开发react应用最好用的脚手架 create-react-app
Apr 24 Javascript
JS选取DOM元素常见操作方法实例分析
Dec 10 Javascript
vue-week-picker实现支持按周切换的日历
Jun 26 Javascript
VUE 实现动态给对象增加属性,并触发视图更新操作示例
Nov 29 Javascript
聊聊鉴权那些事(推荐)
Aug 22 #Javascript
在Node.js中将SVG图像转换为PNG,JPEG,TIFF,WEBP和HEIF格式的方法
Aug 22 #Javascript
vue使用高德地图根据坐标定位点的实现代码
Aug 22 #Javascript
ES6基础之字符串和函数的拓展详解
Aug 22 #Javascript
node实现爬虫的几种简易方式
Aug 22 #Javascript
vue router动态路由设置参数可选问题
Aug 21 #Javascript
Element ui 下拉多选时新增一个选择所有的选项
Aug 21 #Javascript
You might like
解析mysql left( right ) join使用on与where筛选的差异
2013/06/18 PHP
destoon实现商铺管理主页设置增加新菜单的方法
2014/06/26 PHP
php导入大量数据到mysql性能优化技巧
2014/12/29 PHP
thinkPHP模板中for循环与switch语句用法示例
2016/11/30 PHP
thinkPHP5.0框架命名空间详解
2017/03/18 PHP
PHP get_html_translation_table()函数用法讲解
2019/02/16 PHP
JS类的封装及实现代码
2009/12/02 Javascript
windows系统下简单nodejs安装及环境配置
2013/01/08 NodeJs
jquery+ajax+C#实现无刷新操作数据库数据的简单实例
2014/02/08 Javascript
D3.js 从P元素的创建开始(显示可加载数据)
2014/10/30 Javascript
JavaScript日期类型的一些用法介绍
2015/03/02 Javascript
javascript常见数据验证插件大全
2015/08/03 Javascript
js+css实现上下翻页相册代码分享
2015/08/18 Javascript
基于BootStrap Metronic开发框架经验小结【六】对话框及提示框的处理和优化
2016/05/12 Javascript
用jQuery获取table中行id和td值的实现代码
2016/05/19 Javascript
微信小程序 loading 详解及实例代码
2016/11/09 Javascript
Mac下使用charles遇到的问题以及解决办法
2017/01/10 Javascript
js通过Date对象实现倒计时动画效果
2017/10/27 Javascript
基于Vue实现微信小程序的图文编辑器
2018/07/25 Javascript
JavaScript设计模式之构造器模式(生成器模式)定义与用法实例分析
2018/07/26 Javascript
使用Angular-CLI构建NPM包的方法
2018/09/07 Javascript
jquery分页优化操作实例分析
2019/08/23 jQuery
python网络编程调用recv函数完整接收数据的三种方法
2017/03/31 Python
python的re正则表达式实例代码
2018/01/24 Python
python 通过 socket 发送文件的实例代码
2018/08/14 Python
Python实现的字典排序操作示例【按键名key与键值value排序】
2018/12/21 Python
django 取消csrf限制的实例
2020/03/13 Python
实例代码讲解Python 线程池
2020/08/24 Python
socket.io 和canvas 实现的共享画板功能
2019/05/22 HTML / CSS
澳大利亚网上买书:Angus & Robertson
2019/07/21 全球购物
大学校园活动策划书
2014/02/04 职场文书
保险公司年会主持词
2014/03/22 职场文书
课外活动实习计划
2015/01/19 职场文书
出纳试用期自我评价
2015/03/10 职场文书
KTV员工管理制度
2015/08/06 职场文书
2016干部作风整顿心得体会
2016/01/22 职场文书