发展史
传统session使用
1、很久以前,web大多是文档的浏览,所以服务器不需要记录谁访问了什么文档,每一次http请求都是一次全新的请求,不需要记录。
2、交互式web应用兴起,如在线购物网站,需要登录的网站,这些就需要管理回话,记录谁登陆了,做了什么事。所以服务器就想到给每一个人发一个会话标识(session id),就是一个随机的字符串,每个人的都不一样,发起http请求时携带上这个字符串,就可以区分谁是谁了。
3、这种方式对用户友善,但对服务器就很不友善了,成千上万的session严重影响了服务器的性能。使用不同的机器访问需要session的复制,非常麻烦;有人提出将session id 集中存储到一个地方,所有的机器都能访问,不用复制了,但如果该机器挂掉,所有人都需要重新登录一遍。一直没有很好地解决方案。
token使用
1、有服务器想,我为什么要保存session id,让客户端去保存该多好,但如果没有session id 如何验证合法用户哪?关键在于验证,假如A登录了,我给他一个令牌(token),里面包含了user id ,A下次http请求时通过http header带上token就可以验证了。但这和session id 没有本质区别,人人都可以伪造。
2、可以使用某个算法和一个只有我知道的密钥,对数据做一个签名,签名和数据一起作为token,因为别人不知道密钥,就无法伪造token了。
3、token我不保存,我只保存算法和密钥,当A带着token访问时,我用相同的算法和密钥对数据再次签名,如果得到的签名和A携带的签名一样,就说明A是合法用户,已经登录过了;如果不同,说明数据被人篡改过。
4、token 中的数据时铭文保存的,可以被别人看到,因此不能保存像密码那样的敏感信息。当然,如果token被偷走了,我会认为小偷是合法用户,这和session id被偷走是一样的。
认证授权凭证
什么是认证(Authentication)
通俗地讲就是验证当前用户的身份,证明“你是你自己”(比如:你每天上下班打卡,都需要通过指纹打卡,当你的指纹和系统里录入的指纹相匹配时,就打卡成功)
互联网中的认证:
- 用户名密码登录
- 邮箱发送登录链接
- 手机号接收验证码
- 只要你能收到邮箱/验证码,就默认你是账号的主人
什么是授权(Authorization)
用户授予第三方应用访问该用户某些资源的权限
- 你在安装手机应用的时候,APP 会询问是否允许授予权限(访问相册、地理位置等权限)
- 你在访问微信小程序时,当登录时,小程序会询问是否允许授予权限(获取昵称、头像、地区、性别等个人信息)
实现授权的方式有:cookie、session、token、OAuth
什么是凭证(Credentials)
实现认证和授权的前提是需要一种媒介(证书) 来标记访问者的身份
- 在现实生活中,每个人都会有一张专属的居民身份证,是用于证明持有人身份的一种法定证件。通过身份证,我们可以办理手机卡/银行卡/个人贷款/交通出行等等,这就是认证的凭证。
- 在互联网应用中,一般网站(如掘金)会有两种模式,游客模式和登录模式。游客模式下,可以正常浏览网站上面的文章,一旦想要点赞/收藏/分享文章,就需要登录或者注册账号。当用户登录成功后,服务器会给该用户使用的浏览器颁发一个令牌(token),这个令牌用来表明你的身份,每次浏览器发送请求时会带上这个令牌,就可以使用游客模式下无法使用的功能。
会话机制和http协议
在一次会话中,可以向服务器发出N次请求。通过某个机制,可以让服务器和客户端建立联系。这种机制叫会话机制
如果没有会话机制,就没有现的互联网。
会话机制也不属于任何一门一样,是一门独立的技术。
解决的是保存用户浏览页面状态的问题。
任何一门后台语言,都实现了会话机制的。
会话
会话:在多次HTTP连接间维护用户与同一用户发出的不同请求之间关联的情况称为维护一个会话(session)。
cookie是基于浏览器端的会话技术
session是基于服务端的会话技术
session通常都会依赖cookie。
http协议
HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每次请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法辨别上一次请求的发送者和这一次请求的发送者是否是同一人。所以服务器和浏览器为了进行会话跟踪(知道是谁在访问我),就必须去维护一个状态,这个状态用于告知服务器前后两次请求是否来自统一浏览器。
Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性。
cookie
什么是cookie
cookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据。
Cookie是基于浏览端的会话技术。将数据保存在浏览器端。
cookie由服务器生成,发送给浏览器,浏览器把cookie以KV形式存储到某个目录下的文本文件中,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间。所以每个域的cookie数量是有限制的。
cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain)。
cookie的重要属性
属性 | 说明 |
---|---|
name=value | 名值对,表示cookie的名称,必选。//键值对,设置Cookie 的名称及相对应的值,都必须是字符串类型。如果值为 Unicode 字符,需要为字符编码。 如果值为二进制数据,则需要使用 BASE64 编码。 |
domain=xx.com | 控制域名访问,localhost:3000和17.0.0.1:3000不同。指定cookie被发送到哪台计算机上。正常情况下,cookie只被送回最初向用户发送cookie的计算机。如果设置domain=xx.com,则cookie会被发送到任何在xx.com域中的主机。如果domain为空,domain就设置为和提供cookie的服务器相同。如果domain不为空,但它的值和提供cookie的web服务器域名不符,这个cookie将被忽略。//指定 cookie 所属域名,默认是当前域名。 |
path=/directory | 控制后面路由路径访问。 只有访问/directory下面的页面时,cookie才被发送。如果指定了path,但path与当前访问的url不符,则此cookie被忽略。如果缺省,path的属性值为web服务器传给浏览器的资源的路径名。//指定 cookie 在哪个路径(路由)下生效,默认是 ‘/’。如果设置为 /abc,则只有 /abc 下的路由可以访问到该 cookie,如:/abc/read。 |
expires=date | 指定cookie失效的时间,如果没有指定,则cookie不会写入磁盘,只持续到当前会话结束(通常就是关闭浏览器)。该属性值DATE必须以特定的格式来书写:星期几,DD-MM-YY HH:MM:SS GMT,GMT表示这是格林尼治时间。反之,不以这样的格式来书写,系统将无法识别。// 过期时间,在设置的某个时间点后该 cookie 就会失效。一般浏览器的 cookie 都是默认储存的,当关闭浏览器结束这个会话的时候,这个 cookie 也就会被删除 |
secure | 如果设置了,则cookie只能通过ssl通道,即https。//该 cookie 是否仅被使用安全协议传输。安全协议有 HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效。 |
httpOnly | 如果给某个 cookie 设置了 httpOnly 属性,则无法通过 JS 脚本 读取到该 cookie 的信息,但还是能通过 Application 中手动修改 cookie,所以只是在一定程度上可以防止 XSS 攻击,不是绝对的安全 |
maxAge | 设置cookie 失效的时间,单位秒。如果为整数,则该 cookie 在 maxAge 秒后失效。如果为负数,该 cookie 为临时 cookie ,关闭浏览器即失效,浏览器也不会以任何形式保存该 cookie 。如果为 0,表示删除该 cookie 。默认为 -1。- 比 expires 好用。 |
cookie的原理及有效期
cookie的原理
其中,从浏览器端–>服务器端,cookie是以请求头的方式传递。
从服务端–>浏览器端,cookie是以响应头的方式来传递的。
cookie的有效期
任何一个cookie,都是有有效期的。
默认情况下,有效期是到浏览器关闭,浏览器一关掉,就失效。
Cookie的格式中,有一个expires键,就是用来设置有效期的。
在express中的res.cookie方法中,可以有如下两种设置方式:
都是一个时间戳。从1970-1-1到指定时间的毫秒数。
其中,maxAge用法更加简单,推荐使用。
关掉浏览器之后,只要是1分钟之内,再次打开浏览器,这个cookie是仍然有效的。
如果我们设置了有效期,此时cookie数据是保存到浏览器所在的计算机中的某个磁盘目录中。
cookie的设置
服务端种植cookie
使用res对象,cookie是作为响应头信息,从服务端发送到浏览器端的。
发送的标准格式:SetCookie:name=value;expires=date;path=/directory;domain=.xx.com;secure
使用原生的setHeader方法,来设置cookie:
const express = require('express');
const app = express();
app.get('/',(req,res)=>{
res.setHeader('set-cookie','username = 'ange');
res.send('响应中种植了cookie')
})
在express中,cookie的发送,其实已经封装了相应的方法 res.cookie
res.cookie('username','ange')
cookie是作为响应头信息从服务器发送到浏览器端的,需要遵循cookie的格式
获取cookie
浏览器端cookie的获取:
可以使用js ,使用document.cookie
这种cookie的用法,现在逐渐的被localStorage所取代。
服务器端cookie的获取:
任何一次http请求,浏览器都会自己携带cookie,作为请求头,向服务端发送http请求。
服务器接收cookie需要用到req 对象,默认情况下,req对象中没有对cookie进行解析,所以不能直接获取,所以需要第三方中间间 cookie-parser
-
安装cookie-parser
npm install cookie-parser
-
引入使用中间件
const cookieParser = require('cookie-parser'); app.use(cookieParser());
-
获取cookie
console.log(req.cookies)
cookie的应用
记录用户上次浏览的时间
const express = require("express");
var cookieParser = require('cookie-parser')
const app = express();
app.use(cookieParser())
app.get("/",(req,res)=>{
let last = req.cookies.last;
res.cookie("last",new Date().toLocaleString(),{maxAge:60000*60*24*365});
if(last){
res.send(`<h1>你上一次访问的时间是:${last}</h1>`)
}else{
res.send("<h1>这是你第1次访问本网站</h1>")
}
})
app.listen(3000)
用户登录控制
const express = require("express");
const path = require("path");
var bodyParser = require('body-parser')
var cookieParser = require('cookie-parser')
const app = express();
app.use(cookieParser())
// 配置表单提交的数据
app.use(bodyParser.urlencoded({ extended: false }))
// 配置前端提交的json数据
app.use(bodyParser.json())
app.get("/",(req,res)=>{
res.sendFile(path.join(__dirname,"./views/index.html"))
})
app.get("/mine",(req,res)=>{
if(req.cookies.isLogin){
res.sendFile(path.join(__dirname,"./views/mine.html"))
}else{
res.redirect("/login")
}
})
app.get("/login",(req,res)=>{
res.sendFile(path.join(__dirname,"./views/login.html"))
})
app.post("/doLogin",(req,res)=>{
var username = req.body.username.trim();
var pwd = req.body.pwd.trim();
if(username == "admin" && pwd == "admin"){
res.cookie("isLogin",true,{maxAge:60000*60*24*7})
res.redirect("/mine")
}else{
res.redirect("/login")
}
})
app.get("/logout",(req,res)=>{
res.cookie("isLogin",true,{maxAge:-1})
res.redirect("/login")
})
app.listen(3000)
session
什么是session
- session是将数据保存在服务器端的一种会话技术
- session 是另一种记录服务器和客户端会话状态的机制
- session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中
session认证流程:
- 用户第一次请求服务器是,服务器根据用户提交的信息,创建对应的session。
- 请求返回时将session的唯一标识信息sessionID返回给浏览器。
- 浏览器接收到服务器返回的sessionID后,存储到cookie中,同时cookie记录此sessionID属于哪个域名。
- 当用户第二次访问服务器时,请求会自动判断此域名下是否有cookie信息,如果存在自动将cookie信息也发送到服务端,服务端会从cookie中获取sessionID,再根据sessionID查找对应的session信息,如果没有说明用户没有登录或登录失效,如果找到session证明用户已经登录可以执行后面的操作。
SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态
session的基本原理和有效期
- 1.用户向服务器发送用户名和密码
- 2.服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色, 登陆时间等;
- 3.服务器向用户返回一个session_id, 写入用户的cookie
- 4.用户随后的每一次请求, 都会通过cookie, 将session_id传回服务器
- 5.服务端收到 session_id, 找到前期保存的数据, 由此得知用户的身份
默认的有效期就是当前会话结束(浏览器关闭)。
原因在于,session是基于cookie的,session的有效期其实就是cookie的有效期。
session的使用
安装
引入并使用
var session = require('express-session')
app.use(session({
secret: 'wangcai', //任意字符串
resave: false, //默认
saveUninitialized: true, //默认
cookie:{ maxAge:60000*10 }
})) // 因为session是基于cookie的,可以为空
设置session
设置,req.session.session名称 = 值
req.session.username = 'ange'
会自动生成一个sessionid,且作为响应头发送给浏览器端
将session信息,保存到服务端的session文件中,文件名就是sessionid
接下来后续的每一次请求,浏览器都会自己携带这个cookie信息,如下:
获取session
获取,req.session.session名称
删除session
删除对应的session,可以使用delete操作。
如果要删除所有的,可以直接将req.session设置为{}。或者调用destroy方法。
session实现登录控制
const express = require("express");
const path = require("path");
var bodyParser = require('body-parser')
var session = require('express-session')
const app = express();
// 配置session 不需要再配置cookie相关的
app.use(session({
secret: 'wangcai',
resave: false,
saveUninitialized: true,
cookie:{ maxAge:60000*10 } // 因为session是基于cookie的
}))
// 配置表单提交的数据
app.use(bodyParser.urlencoded({ extended: false }))
// 配置前端提交的json数据
app.use(bodyParser.json())
app.get("/",(req,res)=>{
res.sendFile(path.join(__dirname,"./views/index.html"))
})
app.get("/login",(req,res)=>{
res.sendFile(path.join(__dirname,"./views/login.html"))
})
app.post("/doLogin",(req,res)=>{
var username = req.body.username.trim();
var pwd = req.body.pwd.trim();
if(username=="admin" && pwd=="admin"){
req.session.isLogin = true;
res.redirect("/mine")
}else{
res.redirect("/login")
}
})
app.get("/mine",(req,res)=>{
if(req.session.isLogin){
res.sendFile(path.join(__dirname,"./views/mine.html"))
}else{
res.redirect("/login")
}
})
app.get("/logout",(req,res)=>{
req.session.destroy((err)=>{
res.redirect("/login")
})
})
app.listen(3000)
cookie和session的区别
- 安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
- 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
- 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
- 存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于Cookie,但是当访问量过多,会占用过多的服务器资源。
cookie是存储在客户端,session存储在服务端,客户端只存储sessionID。
建议: 将登陆信息等重要信息存放为session, 其他信息如果需要保留,可以放在cookie中。
token(令牌)
token优点
- 简洁:可以通过URL,post参数或是在HTTP头参数发送,因为数据量小,传输速度也快。
- 自包含:由于字符串包含了用户所需要的信息,避免了多次查询数据库。
- 因为token是以json的形式保存在客户端的,所以JWT是跨语言的。
- 不需要在服务端保存会话信息,特别适用于分布式微服务。
Acesss Token
访问资源接口(API)时所需要的资源凭证
简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
特点:
- 服务端无状态化、可扩展性好
- 支持移动端设备
- 安全
- 支持跨程序调用
token的身份验证流程
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
- 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
- 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
token 完全由应用管理,所以它可以避开同源策略
Refresh Token
refresh token 是专用于刷新 access token 的 token。如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。
- Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token
就可以获取到新的 Token,如果 Refresh Token 也失效了,用户就只能重新登录了。 - Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Acesss Token时才会验证,不会对业务接口响应时间造成影响,也不需要向 Session 一样一直保持在内存中以应对大量的请求。
Token 和 Session 的区别
- Session是一种HTTP储存机制, 为无状态的HTTP提供持久机制; Token就是令牌,比如你授权(登录)一个程序时,它就是个依据,判断你是否已经授权该软件;
- Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token是令牌,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。
- Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session
好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。 - 所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID的不可预测性,暂且认为是安全的。而 Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证 和 授权,认证是针对用户,授权是针对 App 。其目的是让某 App 有权利访问某用户的信息。这里的 Token 是唯一的。不可以转移到其它App上,也不可以转到其它用户上。Session 只提供一种简单的认证,即只要有此 SessionID ,即认为有此 User的全部权利。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方App。所以简单来说:如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token。如果永远只是自己的网站,自己的 App,用什么就无所谓了。
session是将要验证的信息存储在服务端,并以sessionID和数据进行对应,sessionID由客户端存储,在青丘时将sessionID也带过去,因此实现了状态的对应。
而token是在服务端将用户信息经过Base64Url斑马过后传给客户端,每次用户请求的时候都会带上这一段信息,因此客户端拿到此信息进行解密后就知道此用户是谁了,这个方法叫JWT。
token相较于session的优点在于,当后端系统有多台是,由于客户端访问时直接带着数据,因此不用做共享数据的操作。
JWT
什么是JWT
- JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案(鉴权)。
- 是一种认证授权机制。
- JWT 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准(RFC 7519)。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。
- 可以使用 HMAC 算法或者是 RSA 的公/私秘钥对 JWT 进行签名。因为数字签名的存在,这些传递的信息是可信的。
详见阮一峰老师的 JSON Web Token 入门教程
JWT构成
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJuYW1lIjoiend6IiwiYWdlIjoiMTgifQ.
UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。
header:
- 声明类型
- 声明的加密算法
payload:
存放有效信息的地方
- 标准中注册的声明
- 公共的声明
- 私有的声明
signature:
签证信息
- header (base64后的)
- payload (base64后的)
- secret(密钥)
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。
JWT原理
JWT 认证流程:
- 用户输入用户名/密码登录,服务端认证成功后,会返回给客户端一个 JWT
- 客户端将 token 保存到本地(通常使用localstorage,也可以使用 cookie)
- 当用户希望访问一个受保护的路由或者资源的时候,需要请求头的Authorization 字段中使用Bearer 模式添加 JWT
服务端的保护路由将会检查请求头 Authorization 中的 JWT 信息,如果合法,则允许用户的行为
因为 JWT 是自包含的(内部包含了一些会话信息),因此减少了需要查询数据库的需要
因为 JWT 并不使用 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不·需要担心跨域资源共享问题(CORS)
因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制
JWT特点
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token
的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。 - JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT
的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。 - 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
JWT使用
安装jwt
npm i jsonwebtoken
后端使用jwt
//安装jwt 模块
let jwt = require('jsonwebtoken');
//定义密钥
const secretKey = 'wangcai';
//生成token,保存两分钟
let tokenStr = jwt.sign({username:username},secreKey,{expiresIn:'120s'});
//token返回客户端
res.send({
token:tokenStr,})
在服务端使用 req.headers 可以获取请求头信息,请求头信息中就有token信息
但一般我们会使用express-jwt 中间件,可以将token字符串解析还原成json对象。
安装
npm i express-jwt@5.3.3
let expressJWT = require('express-jwt');
//配置express-jwt
app.use(expressJwt({secret:secretKey}).unless({path:[/^\/api\//]}))
使用 req.user 就可以还原token中的信息。得到用户信息。
前端使用jwt
前端获取jwt
后台页面都需要携带 token 验证,可以使用请求拦截器
token和JWT区别
异同
相同:
- 都是访问资源的令牌
- 都可以记录用户的信息
- 都是使服务端无状态化
- 都是只有验证成功后,客户端才能访问服务端上受保护的资源
区别:
- Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。
- JWT: 将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。
token和jwt详解
区别:
Token验证方式:由于服务器没有存储token数据,因此需要先从数据库中查询当前token,服务器再是验证否有效;
JWT验证方式:直接在服务端进行校验,并且不用查库。因为用户的信息及加密信息,在第二部分payload和第三部分签证中已经生成,只要在服务端进行校验就行,并且校验也是JWT自己实现的。
1.Token
1.1 概念: 令牌, 是访问资源的凭证。
1.2 Token的认证流程:
- 用户输入用户名和密码,发送给服务器。
- 服务器验证用户名和密码,正确的话就返回一个签名过的token,浏览器客户端拿到这个token。
- 客户端自己保存token,后续每次请求中,浏览器会把token作为http
header发送给服务器,服务器验证签名是否有效,如果有效那么认证就成功,可以返回客户端需要的数据。
1.3 特点:
这种方式的特点就是客户端的token中自己保留有大量信息,服务器没有存储这些信息。
2.Jwt
**2.1 概念:**JWT是json web token缩写。可以使用在RESTFUL接口定义,也可以使用在普通的web。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。
2.2 组成:
JWT包含三个部分: Header头部,Payload负载和Signature签名。由三部分生成token,三部分之间用“.”号做分割。 列如 : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
-
Header 声明信息。 在Header中通常包含了两部分:type:代表token的类型,这里使用的是JWT类型。
alg:使用的Hash算法,例如HMAC SHA256或RSA. { “alg”: “HS256”, “typ”: “JWT” }这会被经过base64Url编码形成第一部分 -
Payload token的第二个部分是荷载信息,它包含一些声明Claim(实体的描述,通常是一个User信息,还包括一些其他的元数据)声明分三类: 1)Reserved Claims,这是一套预定义的声明,并不是必须的,这是一套易于使用、操作性强的声明。包括:iss(issuer) exp(expirationntime)sub(subject)、aud(audience)等 2)Plubic Claims, 3)Private Claims,交换信息的双方自定义的声明 { “sub”: “1234567890”, “name”: “John Doe”, “admin”: true } 同样经过Base64Url编码后形成第二部分
-
signature 使用header中指定的算法将编码后的header、编码后的payload、一个secret进行加密。例如使用的是HMAC SHA256算法,大致流程类似于: HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)这个signature字段被用来确认JWT信息的发送者是谁,并保证信息没有被修改 。
2.3 验证流程: -
在头部信息中声明加密算法和常量,然后把header使用json转化为字符串
-
在载荷中声明用户信息,同时还有一些其他的内容,再次使用json把在和部分进行转化,转化为字符串
-
使用在header中声明的加密算法来进行加密,把第一部分字符串和第二部分的字符串结合和每个项目随机生成的secret字符串进行加密,生成新的字符串,此字符串是独一无二的
-
解密的时候,只要客户端带着jwt来发起请求,服务端就直接使用secret进行解密,解签证解出第一部分和第二部分,然后比对第二部分的信息和客户端穿过来的信息是否一致。如果一致验证成功,否则验证失败。
2.4 特点:
- 三部分组成,每一部分都进行字符串的转化
- 解密的时候没有使用数据库,仅仅使用的是secret进行解密
- Jwt使用的secret千万不能丢失
常见问题:
使用 cookie 时需要考虑的问题
- 因为存储在客户端,容易被客户端篡改,使用前需要验证合法性
- 不要存储敏感数据,比如用户密码,账户余额
- 使用 httpOnly 在一定程度上提高安全性
- 尽量减少 cookie 的体积,能存储的数据量不能超过 4kb
- 设置正确的 domain 和 path,减少数据传输
- cookie 无法跨域
- 一个浏览器针对一个网站最多存 20 个Cookie,浏览器一般只允许存放 300 个Cookie
- 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
使用 session 时需要考虑的问题
- 将 session 存储在服务器里面,当用户同时在线量比较多时,这些 session 会占据较多的内存,需要在服务端定期的去清理过期的
session - 当网站采用集群部署的时候,会遇到多台 web 服务器之间如何做 session 共享的问题。因为 session
是由单个服务器创建的,但是处理用户请求的服务器不一定是那个创建 session 的服务器,那么该服务器就无法拿到之前已经放入到
session 中的登录凭证之类的信息了。 - 当多个应用要共享 session 时,除了以上问题,还会遇到跨域问题,因为不同的应用可能部署的主机不一样,需要在各个应用做好 cookie
跨域的处理。 - sessionId 是存储在 cookie 中的,假如浏览器禁止 cookie 或不支持 cookie 怎么办? 一般会把sessionId 跟在 url 参数后面即重写 url,所以 session 不一定非得需要靠 cookie 实现
- 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
使用 token 时需要考虑的问题
- 如果你认为用数据库来存储 token 会导致查询时间太长,可以选择放在内存当中。比如 redis 很适合你对 token 查询的需求。
- token 完全由应用管理,所以它可以避开同源策略
- token 可以避免 CSRF 攻击(因为不需要 cookie 了)
- 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
使用 JWT 时需要考虑的问题
- 因为 JWT 并不依赖 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 最大的优势是服务器不再需要存储 Session,使得服务器认证鉴权业务可以方便扩展。但这也是 JWT最大的缺点:由于服务器不需要存储 Session 状态,因此使用过程中无法废弃某个 Token 或者更改 Token 的权限。也就是说一旦JWT 签发了,到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- JWT 适合一次性的命令认证,颁发一个有效期极短的 JWT,即使暴露了危险也很小,由于每次操作都会生成新的 JWT,因此也没必要保存JWT,真正实现无状态。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
只要关闭浏览器 ,session 真的就消失了?
不对。对 session 来说,除非程序通知服务器删除一个 session,否则服务器会一直保留,程序一般都是在用户做 log off 的时候发个指令去删除 session。
然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分 session 机制都使用会话 cookie 来保存 session id,而关闭浏览器后这个 session id 就消失了,再次连接服务器时也就无法找到原来的 session。如果服务器设置的 cookie 被保存在硬盘上,或者使用某种手段改写浏览器发出的 HTTP 请求头,把原来的 session id 发送给服务器,则再次打开浏览器仍然能够打开原来的 session。
恰恰是由于关闭浏览器不会导致 session 被删除,迫使服务器为 session 设置了一个失效时间,当距离客户端上一次使用 session 的时间超过这个失效时间时,服务器就认为客户端已经停止了活动,才会把 session 删除以节省存储空间。