前后端分离session_前后端分离:基于JWT用户认证分析

作者:lion1ou

https://lion1ou.win/2017/01/18/

在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了。

于是我们的程序就不知道谁是谁,就要再验证一次。所以为了保证系统安全,我们就需要验证用户否处于登录状态。

传统方式

前后端分离通过Restful API进行数据交互时,如何验证用户的登录信息及权限。在原来的项目中,使用的是最传统也是最简单的方式,前端登录,后端根据用户信息生成一个token,并保存这个 token 和对应的用户id到数据库或Session中,接着把 token 传给用户,存入浏览器 cookie,之后浏览器请求带上这个cookie,后端根据这个cookie值来查询用户,验证是否过期。

但这样做问题就很多,如果我们的页面出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致用户 token 泄露,而作为后端识别用户的标识,cookie 的泄露意味着用户信息不再安全。尽管我们通过转义输出内容,使用 CDN 等可以尽量避免 XSS 注入,但谁也不能保证在大型的项目中不会出现这个问题。

在设置 cookie 的时候,其实你还可以设置 httpOnly 以及 secure 项。设置 httpOnly 后 cookie 将不能被 JS 读取,浏览器会自动的把它加在请求的 header 当中,设置 secure 的话,cookie 就只允许通过 HTTPS 传输。secure 选项可以过滤掉一些使用 HTTP 协议的 XSS 注入,但并不能完全阻止。

httpOnly 选项使得 JS 不能读取到 cookie,那么 XSS 注入的问题也基本不用担心了。但设置 httpOnly 就带来了另一个问题,就是很容易的被 XSRF,即跨站请求伪造。当你浏览器开着这个页面的时候,另一个页面可以很容易的跨站请求这个页面的内容。因为 cookie 默认被发了出去。

另外,如果将验证信息保存在数据库中,后端每次都需要根据token查出用户id,这就增加了数据库的查询和存储开销。若把验证信息保存在session中,又加大了服务器端的存储压力。那我们可不可以不要服务器去查询呢?

如果我们生成token遵循一定的规律,比如我们使用对称加密算法来加密用户id形成token,那么服务端以后其实只要解密该token就可以知道用户的id是什么了。不过呢,我只是举个例子而已,要是真这么做,只要你的对称加密算法泄露了,其他人可以通过这种加密方式进行伪造token,那么所有用户信息都不再安全了。

恩,那用非对称加密算法来做呢,其实现在有个规范就是这样做的,就是我们接下来要介绍的 JWT。

Json Web Token(JWT)

JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。

它具备两个特点:

1.简洁(Compact)

可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快

2.自包含(Self-contained)

负载中包含了所有用户所需要的信息,避免了多次查询数据库

JWT 组成

f1eadd98f2d8cae5832260b19c49a05d.png

Header 头部

头部包含了两部分,token 类型和采用的加密算法

{  "alg": "HS256",  "typ": "JWT"}

它会使用 Base64 编码组成 JWT 结构的第一部分,如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串。

Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

Payload 负载

这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。

{    "iss": "lion1ou JWT",    "iat": 1441593502,    "exp": 1441594722,    "aud": "www.example.com",    "sub": "lion1ou@163.com"}

同样的,它会使用 Base64 编码组成 JWT 结构的第二部分

Signature 签名

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

三个部分通过.连接在一起就是我们的 JWT 了,它可能长这个样子,长度貌似和你的加密算法和私钥有关系。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s

其实到这一步可能就有人会想了,HTTP 请求总会带上 token,这样这个 token 传来传去占用不必要的带宽啊。如果你这么想了,那你可以去了解下 HTTP2,HTTP2 对头部进行了压缩,相信也解决了这个问题。

签名的目的

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

信息暴露

在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。

因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。单点登录:图解JWT如何用于单点登录

JWT 使用

525387e768772adfb04afa3475823321.png
  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  • 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同lll.zzz.xxx的字符串。
  • 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
  • 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
  • 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
  • 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

和Session方式存储id的差异

Session方式存储用户id的最大弊病在于Session是存储在服务器端的,所以需要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些KV数据库和一系列缓存机制来实现Session的存储。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户id之外,还可以存储其他的和用户相关的信息,例如该用户是否是管理员、用户所在的分组等。

虽说JWT方式让服务器有一些计算压力(例如加密、编码和解码),但是这些压力相比磁盘存储而言可能就不算什么了。具体是否采用,需要在不同场景下用数据说话。

单点登录

Session方式来存储用户id,一开始用户的Session只会存储在一台服务器上。对于有多个子域名的站点,每个子域名至少会对应一台不同的服务器,例如:www.taobao.com,nv.taobao.com,nz.taobao.com,login.taobao.com。

所以如果要实现在login.taobao.com登录后,在其他的子域名下依然可以取到Session,这要求我们在多台服务器上同步Session。使用JWT的方式则没有这个问题的存在,因为用户的状态已经被传送到了客户端。

总结

JWT的主要作用在于

(一)可附带用户信息,后端直接通过JWT获取相关信息。

(二)使用本地保存,通过HTTP Header中的Authorization位提交验证。

但其实关于JWT存放到哪里一直有很多讨论,有人说存放到本地存储,有人说存 cookie。个人偏向于放在本地存储,如果你有什么意见和看法欢迎提出。

参考

https://segmentfault.com/a/1190000005783306
http://blog.rainy.im/2015/06/10/react-jwt-pretty-good-practice/
https://ruiming.me/archives/41

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/245487.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我的世界服务器开启显示坐标的指令,我的世界坐标怎么看 坐标指令

在我的世界游戏中,坐标这个问题如果玩的很6的话能够帮助我们瞬间移动,去到任何想去的地方,这时有不少玩家不禁想问了,我的世界坐标怎么看,坐标指令又是什么呢?坐标(coordinates)在数字上反映了您在主世界中…

扑克牌图片一张一张_培养孩子的数学力,不妨试试这五个扑克牌游戏

宝宝的数学认知会随着年龄的增长而逐渐增强,3-4岁的宝宝能感知和区分物体的大小、多少等量方面的特点;4-5岁的宝宝能理解数与数之间的关系;5-6岁的宝宝能够理解加和减的实际意义,并进行10以内的加减运算。而扑克牌是家中常备的娱乐…

三十二楼层选几层最好_买房楼层怎么选?建筑学家建议:一栋楼不管几层,最好避开这3层...

对于很多人买房,可以说是一生最重要的事情之一。大部分人买房都可能和自己的家人居住一生,特别是现在房价那么高的情况下,买了房如果不如意的话,想再换房可就非常难了。特别是楼层的选择,很多人都不会选择楼层&#xf…

服务器文件相对路径,服务器文件的相对路径

服务器文件的相对路径 内容精选换一换编译构建服务提供了常用的构建模板(构建环境),不同的构建模板中预装了对应构建所需工具集,MSBuild构建镜像一般预装了msbuild、nuget、.NET Frameword等常用工具。使用msbuild构建工具执行引擎、构造工程&#xff0c…

每天九点十分开始每半小时一次执行一个cron_每天通勤4小时!西咸双城生活的上班族,不简单...

在西安,你每天上班路上花费多长时间?大多数人的答案可能都在一个小时之间。然而,在西安,有一群辗转于西安和咸阳两座城市的上班族,他们平均每天早上六点出门,晚上八点回家,在公交、地铁、电动车…

时间设置偏移秒_零偏移有源低通滤波器,第2部分

本系列文章分为4个部分,分别讨论了:使用Sallen-Key有源滤波器时应避免的陷阱、Sallen-Key低通滤波器所建议的补救措施、接近零偏移的Chebyshev低通滤波器,以及采纳网友建议所带来的不利后果。 本系列文章的第1部分解决了使用Sallen-Key有源滤…

线程与进程的区别_Java线程和PC进程的区别

进程和线程的区别进程:资源分配的基本单位,多存在于操作系统任务中线程:资源调度的基本单位,一般存在于某个程序中JVM主内存与工作内存间具体的交互A:作用范围主内存变量B:作用范围工作内存变量lock(锁定)A…

linux nand 坏块_Nand Flash 中的坏块(Bad Block)

Nand Flash 中,一个块中含有 1 个或多个位是坏的,就称为其为坏块 Bad Block。坏块的稳定性是无法保证的,也就是说,不能保证你写入的数据是对的,或者写入对了,读出来也不一定对的。与此对应的正常的块,肯定是写入读出都是正常的。坏块有两种:(1) 出厂时就有存在的坏块:一种是出厂…

生产管理erp系统源码_仁和ERP企业管理系统提高生产管理流程

传统的制造企业管理理念只会阻碍公司经营发展,在管理流程根本上去改变,还得从企业各部门管理思想的转变做基础,优化公司核心业务流程做标准,双向进行改革创新,将管理流程提升为企业的核心竞争力!这将是企业…

date oracle 显示毫秒_Oracle date timestamp 毫秒 - 时间函数总结

yyyy-mm-dd hh24:mi:ss.ff 年-月-日 时:分:秒.毫秒关于毫秒:Oracle 毫秒的存储必须字段类型为 timestamp(6) –数字表示存储的毫秒位数--当前毫秒级时间select to_char(current_timestamp,yyyy-mm-dd hh24:mi:ss.ff6) from dual;--字符串转为…

怎么把字符串变成数组_字符串哈希:从零开始的十分钟包会教程

大家吼啊!这是我下定决心写专栏以来的第二篇文章,请大家多多资瓷!!同样我们先以上次的话起头吧!恭喜你找到了这篇博客!虽然这个标题看起来非常像是nc营销号的标题但是!请相信我一次这是真的&…

body 没有被撑开_body没有高度设置背景色为什么可以全屏显示?

原标题:body没有高度设置背景色为什么可以全屏显示?--- 关于html和body的那些事还记得我们开发全屏页面或者是移动端页面时经常会设置一句话html, body { height: 100%; }是不是只知道用?却不知道怎么回事?原因是,当没…

python抽象工厂模式_Python设计模式之抽象工厂模式

Python设计模式之抽象工厂模式这篇文章主要为大家详细介绍了Python设计模式之抽象工厂模式,感兴趣的小伙伴们可以参考一下python面向对象编程入门,我们需要不断学习进步"""抽象工厂模式的实现"""import randomclass PetSh…

岗位理解_当面试官问求职者对应聘岗位的理解情况

虽然求职是看的是岗责是否匹配自己的经历和能力,但是很多面试官还是会问这样的问题。对于求职者来说,对岗位的理解其实就是把岗位职责背一遍,但是面试官既然问了,就要说出一些其他内容来。想回答好这个问题,就要清楚面…

指针和引用的区别_浅析指针与引用

关注“杜明c”,每天进步一点点!已经时隔一个多月没有发文章了,主要是太忙了,绝不是偷懒今天给大家聊一聊指针和引用的不同之处在哪里。摘要指针的使用引用的使用二者的区别后话指针的使用在C语言中,我们怎么样在子函数…

python打印所有花数_Python中使用while循环实现花式打印乘法表

废话不多说,直接上代码吧! #python中,while语句用于循环执行程序,即在某个条件下,循环执行某段程序,以处理需要重复处理的相同任务。#while是“当型”循环结构。i1while i20: print(i,end" ") i1…

cad线性标注命令_CAD线性标注如何使用的

我们应该知道每次画完图之后都会进行标注,所以在CAD标注也要活灵活用。但是很多同学还不知道。那么接下来下面是学习啦小编整理的一些关于CAD线性标注如何使用,供您参阅。CAD线性标注使用的方法您可以使用 DIM 命令创建水平、垂直、对齐和半径标注。标注…

转行学python后悔_月薪13k的我为什么要转行学Python?

在职业规划包括人生方向上,选择很重要。一句很经典的话做总结“方向不对,努力白费”。选择自己感兴趣的工作,全力以赴投入其中,“唯有爱的深,才能做的好”当你付出不亚于任何人的努力,出彩是必然的。作为职…

python流式下载处理_流式下载 - 对象存储 OSS - 阿里云

如果要下载的文件太大,或者一次性下载耗时太长,您可以通过流式下载,一次处理部分内容,直到完成文件的下载。以下代码用于流式下载文件:# -*- coding: utf-8 -*-import oss2# 阿里云主账号AccessKey拥有所有API的访问权…

python程序如何封装成接口_python接口自动化如何封装获取常量的类

这篇文章主要介绍了python接口自动化如何封装获取常量的类,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下背景:一、执行case的过程:首先需要,我们能够通过excel获取单元格的内…