深入理解令牌认证机制(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 相关文章推荐
jquery 框架使用教程 AJAX篇
Oct 11 Javascript
超级24小时弹窗代码 24小时退出弹窗代码 100%弹窗代码(IE only)
Jun 11 Javascript
有关于eclipse配置spket需要注意的一些地方
Apr 07 Javascript
jQuery学习笔记之创建DOM元素
Jan 19 Javascript
JavaScript中的闭包介绍
Mar 15 Javascript
jQuery实现将页面上HTML标签换成另外标签的方法
Jun 09 Javascript
js 动态添加元素(div、li、img等)及设置属性的方法
Jul 19 Javascript
vue.js+boostrap项目实践(案例详解)
Sep 21 Javascript
bootstrap suggest搜索建议插件使用详解
Mar 25 Javascript
jquery获取链接地址和跳转详解(推荐)
Aug 15 jQuery
vue cli3 配置proxy代理无效的解决
Oct 30 Javascript
解决vue的touchStart事件及click事件冲突问题
Jul 21 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读取ACCESS数据到MYSQL的代码
2011/05/11 PHP
php学习笔记 数组的常用函数
2011/06/13 PHP
PHP表单递交控件名称含有点号(.)会被转化为下划线(_)的处理方法
2013/01/06 PHP
示例详解Laravel重置密码代码重构
2016/08/10 PHP
PHP使用PDO调用mssql存储过程的方法示例
2017/10/07 PHP
PHP7创建COOKIE和销毁COOKIE的实例方法
2020/02/03 PHP
Jquery Ajax学习实例3 向WebService发出请求,调用方法返回数据
2010/03/16 Javascript
IE8下Jquery获取select选中的值post到后台报错问题
2014/07/02 Javascript
JavaScript函数参数使用带参数名的方式赋值传入的方法
2015/03/19 Javascript
遍历json获得数据的几种方法小结
2017/01/21 Javascript
jQuery EasyUI Draggable拖动组件
2017/03/01 Javascript
详解react-webpack2-热模块替换[HMR]
2017/08/03 Javascript
JS实现获取汉字首字母拼音、全拼音及混拼音的方法
2017/11/14 Javascript
javascript字体颜色控件的开发 JS实现字体控制
2017/11/27 Javascript
详解vue移动端项目的适配(以mint-ui为例)
2018/08/17 Javascript
Vue开发实现吸顶效果的示例代码
2018/08/21 Javascript
浅谈ElementUI中switch回调函数change的参数问题
2018/08/24 Javascript
vue cli3.0结合echarts3.0与地图的使用方法示例
2019/03/26 Javascript
Vue 前端实现登陆拦截及axios 拦截器的使用
2019/07/17 Javascript
微信小程序npm引入vant-weapp的踩坑记录
2019/08/01 Javascript
layui+SSM的数据表的增删改实例(利用弹框添加、修改)
2019/09/27 Javascript
pyqt和pyside开发图形化界面
2014/01/22 Python
Python中的is和id用法分析
2015/01/26 Python
基python实现多线程网页爬虫
2015/09/06 Python
利用Python如何生成随机密码
2016/04/20 Python
浅谈flask截获所有访问及before/after_request修饰器
2018/01/18 Python
Python基本数据结构之字典类型dict用法分析
2019/06/08 Python
Python urlencode和unquote函数使用实例解析
2020/03/31 Python
使用python编写一个语音朗读闹钟功能的示例代码
2020/07/14 Python
We Fashion荷兰:一家国际时装公司
2018/04/18 全球购物
教师工作表现评语
2014/12/31 职场文书
华清池导游词
2015/02/02 职场文书
劳动仲裁调解书
2015/05/20 职场文书
哪类餐饮行业,最适合在高校创业?
2019/08/19 职场文书
XX部保密工作制度范本
2019/08/27 职场文书
Android studio 简单计算器的编写
2022/05/20 Java/Android