深入理解令牌认证机制(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 相关文章推荐
JavaScript Event学习第十一章 按键的检测
Feb 10 Javascript
JS添加删除一组文本框并对输入信息加以验证判断其正确性
Apr 11 Javascript
浅析JS动态创建元素【两种方法】
Apr 20 Javascript
基于Vue实现拖拽功能
Jul 29 Javascript
Node.js中Koa2在控制台输出请求日志的方法示例
May 02 Javascript
详解vue项目中实现图片裁剪功能
Jun 07 Javascript
vue实现移动端省市区选择
Sep 27 Javascript
浅探express路由和中间件的实现
Sep 30 Javascript
分享JS表单验证源码(带错误提示及密码等级)
Jan 05 Javascript
vue中destroyed方法的使用说明
Jul 21 Javascript
解决element-ui的下拉框有值却无法选中的情况
Nov 07 Javascript
Vue仿Bibibili首页的问题
Jan 21 Vue.js
聊聊鉴权那些事(推荐)
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
Search Engine Friendly的URL设计
2006/10/09 PHP
PHP实现MVC开发得最简单的方法――模型
2007/04/10 PHP
php5.3 注意事项说明
2013/07/01 PHP
php解决约瑟夫环示例
2014/04/09 PHP
PHP的Json中文处理解决方案
2016/09/29 PHP
Linux平台php命令行程序处理管道数据的方法
2016/11/10 PHP
Track Image Loading效果代码分析
2007/08/13 Javascript
Javascript 倒计时源代码.(时.分.秒) 详细注释版
2011/05/09 Javascript
js 页面关闭前的出现提示的实现代码
2011/05/25 Javascript
JS模块与命名空间的介绍
2013/03/22 Javascript
用js实现in_array的方法
2013/11/05 Javascript
《JavaScript DOM 编程艺术》读书笔记之JavaScript 图片库
2015/01/09 Javascript
javascript简单实现类似QQ头像弹出效果的方法
2015/08/03 Javascript
js实现仿qq消息的弹出窗效果
2016/01/06 Javascript
Bootstrap免费字体和图标网站(值得收藏)
2017/03/16 Javascript
node安装--linux下的快速安装教程
2017/03/21 Javascript
vue2.0 父组件给子组件传递数据的方法
2018/01/15 Javascript
Vue 莹石摄像头直播视频实例代码
2018/08/31 Javascript
angular6 填坑之sdk的方法
2018/12/27 Javascript
JS实现查找数组中对象的属性值是否存在示例
2019/05/24 Javascript
了解JavaScript函数中的默认参数
2019/05/30 Javascript
vue的滚动条插件实现代码
2019/09/07 Javascript
Python中运行并行任务技巧
2015/02/26 Python
在Python中关于中文编码问题的处理建议
2015/04/08 Python
Python异常处理知识点总结
2019/02/18 Python
pandas中的series数据类型详解
2019/07/06 Python
python 抓取知乎指定回答下视频的方法
2020/07/09 Python
Selenium Webdriver元素定位的八种常用方式(小结)
2021/01/13 Python
html Table 表头固定的实现
2019/01/22 HTML / CSS
Falconeri美国官网:由羊绒和羊毛制成的针织服装
2018/04/08 全球购物
银行求职推荐信范文
2013/11/30 职场文书
植树节口号
2014/06/21 职场文书
党员自我剖析材料(群众路线)
2014/10/06 职场文书
怎样写好工作计划
2019/04/10 职场文书
Apache压力测试工具的安装使用
2021/03/31 Servers
Dashboard管理Kubernetes集群与API访问配置
2022/04/01 Servers