JWT
一、Token
-
什么是Token?
Token指访问资源的凭据,是一种身份认证的方式,它是解决跨域认证的最流行的一种方式。
-
为什么用Token?
以前较为流行的是通过session去做身份认证,session是通过服务器中保存会话数据来做身份认证,这种方式会导致在高并发中服务器压力过大的情况,还有就是,如果是服务器集群,那么就需要这些服务器session共享。
Token不在服务器中保存会话数据,而是保存在客户端。每次请求的headers中存入Token,在服务器中判断Token的有效性,是否可以访问资源。
-
传统Token和JWT的区别
-
传统Token
用户发起登录请求,登录成功之后返回Token,并且存于数据库,用户访问资源的时候需要携带Token,服务端获取Token之后和数据库中的对比。
-
JWT
用户发起登录请求,登录成功之后返回Token,但是不存于数据库,用户访问资源的时候需要携带Token,服务端获取Token之后去校验Token的合法性。
-
二、JWT实现过程
-
JWT分为三个部分header、payload、verify signature
-
header
内部包含有签名算法、Token类型,然后通过base64url算法转成字符串
//明文例子: { "alg":"HS256", "typ":"JWT" }
-
payload
内部包含JWT标准数据和自定义数据,然后通过base64url算法转成字符串
JWT标准数据常见的有:
- iss:提供方。
- sub:主题,一般是用户ID。
- exp:过期时间。
- iat:创建时间。
- jti:token的唯一标识。
可选择性使用以上标准数据
//明文例子: { "id": 3, "name": "Bmongo", "age": 18, "iat": 1588139323, "exp": 1588139333 }
注意:由于JWT是默认不加密的,所以在这边不要存敏感信息
-
verify signature
这部分是对前两部分的签名,防止数据的篡改
secret是服务器端保存的密钥,只有服务器端知道,再使用header中所指定的签名算法对上面的俩部分进行签名,按照以下公式生成签名
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
算出签名之后,把三部分通过.分隔开返回给用户就行了
JWT例子:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTAsIm5hbWUiOiLlvKDkuIkiLCJhZ2UiOjE2LCJpYXQiOjE1ODgxMzkzMjMsImV4cCI6MTU4ODEzOTMzM30.WzZp_aNgiw4iTsX7buxMhZe0z0e94Ve6ImEZ8L8L78c
-
客户端请求
每次客户端的请求都需要带上这个token,一般是把token写入到请求的headers中
三、Node.js中使用
Node.js中使用JWT
1.开始使用
通过npm包jsonwebtoken来完成token的生成和验证
npm install --save jsonwebtoken
2.生成、验证Token
const jwt = require("jsonwebtoken")
//撒盐,加密时候混淆
const secret = '113Bmongojsdalkfnxcvmas'
//生成token
//info也就是payload是需要存入token的信息
function createToken(info) {
let token = jwt.sign(info, secret, {
//Token有效时间 单位s
expiresIn:60 * 60 * 10
})
return token
}
//验证Token
function verifyToken(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, secret, (error, result) => {
if(error){
reject(error)
} else {
resolve(result)
}
})
})
}
3.使用
const express = require("express")
const app = express()
const jwt = require("jsonwebtoken")
//撒盐,加密时候混淆
const secret = '113Bmongojsdalkfnxcvmas'
const user = {
id:10,
name:"Bmongo",
age:16,
}
//生成token
//info也就是payload是需要存入token的信息
function createToken(info) {
let token = jwt.sign(info, secret, {
//Token有效时间 单位s
expiresIn:60 * 60 * 10
})
return token
}
//验证Token
function verifyToken(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, secret, (error, result) => {
if(error){
reject(error)
} else {
resolve(result)
}
})
})
}
//设置允许跨域
app.use(function(req, res, next) {
//指定允许其他域名访问 *所有
res.setHeader("Access-Control-Allow-Origin", "*");
//允许客户端请求头中带有的
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
//允许请求的类型
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.setHeader("X-Powered-By",' 3.2.1')
//让options请求快速返回
if(req.method=="OPTIONS") res.send(200);
else next();
});
//白名单
const whiteList = ['/login']
app.use((req,res,next) => {
if(!whiteList.includes(req.url)) {
verifyToken(req.headers.authorization).then(res => {
next()
}).catch(e => {
res.status(401).send('invalid token')
})
} else {
next()
}
})
app.post('/login',(req,res) => {
let token = createToken(user)
res.json({token})
})
app.get("/api/info", (req,res) => {
res.send({
result:1,
data:{
"name":"Bmongo",
"id":1
}
})
})