深入理解令牌认证机制(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 相关文章推荐
js不是基础的基础
Dec 24 Javascript
基于Jquery的仿Windows Aero弹出窗(漂亮的关闭按钮)
Sep 28 Javascript
node.js中的fs.readdirSync方法使用说明
Dec 17 Javascript
jQuery中contents()方法用法实例
Jan 08 Javascript
Jquery中offset()和position()的区别分析
Feb 05 Javascript
JS实现5秒钟自动封锁div层的方法
Feb 20 Javascript
jQuery绑定事件-多种实现方式总结
May 09 Javascript
原生JavaScript实现AJAX、JSONP
Feb 07 Javascript
基于jquery实现多选下拉列表
Aug 02 jQuery
JS实现颜色的10进制转化成rgba格式的方法
Sep 04 Javascript
Vue 应用中结合vux使用微信 jssdk的方法
Aug 28 Javascript
vue实现拖拽效果
Dec 23 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
php下使用SMTP发邮件的代码
2008/01/10 PHP
Zend Framework校验器Zend_Validate用法详解
2016/12/09 PHP
php连接mysql数据库
2017/03/21 PHP
PHP+swoole+linux实现系统监控和性能优化操作示例
2019/04/15 PHP
php如何把表单内容提交到数据库
2019/07/08 PHP
jQuery 获取和设置select下拉框的值实现代码
2013/11/08 Javascript
javascript实现时间格式输出FormatDate函数
2015/01/13 Javascript
JQuery复制DOM节点的方法
2015/06/11 Javascript
javascript实现tab切换的两个实例
2015/11/05 Javascript
详解JavaScript逻辑And运算符
2015/12/04 Javascript
JS中Array数组学习总结
2017/01/18 Javascript
极简主义法编写JavaScript类
2017/11/02 Javascript
vue头部导航动态点击处理方法
2018/11/02 Javascript
使用JavaScript保存文本文件到本地的两种方法
2019/01/22 Javascript
nodejs同步调用获取mysql数据时遇到的大坑
2019/03/02 NodeJs
angular 实现下拉列表组件的示例代码
2019/03/09 Javascript
微信小程序实现form表单本地储存数据
2019/06/27 Javascript
ionic+html5+API实现双击返回键退出应用
2019/09/17 Javascript
EXTJS7实现点击拖拉选择文本
2020/12/17 Javascript
跟老齐学Python之Python文档
2014/10/10 Python
Python中使用插入排序算法的简单分析与代码示例
2016/05/04 Python
PyQt5每天必学之弹出消息框
2018/04/19 Python
python实现简单登陆系统
2018/10/18 Python
Windows系统下PhantomJS的安装和基本用法
2018/10/21 Python
[原创]Python入门教程5. 字典基本操作【定义、运算、常用函数】
2018/11/01 Python
关于box-sizing的全面理解
2016/07/28 HTML / CSS
html5适合移动应用开发的12大特性
2014/03/19 HTML / CSS
HTML5本地存储localStorage、sessionStorage基本用法、遍历操作、异常处理等
2014/05/08 HTML / CSS
英国领先的奢侈品零售商之一:CRUISE
2016/12/02 全球购物
创联软件面试题笔试题
2012/10/07 面试题
英语硕士生求职简历的自我评价
2013/10/15 职场文书
办公室岗位职责
2014/02/12 职场文书
实验室的标语
2014/06/20 职场文书
购房个人委托书范本
2014/10/11 职场文书
担保书格式范文
2015/09/22 职场文书
退休劳动合同怎么写?
2019/10/25 职场文书