一、Jwt的理论知识
1.什么是Jwt
Jwt:JSON Web Token 的缩写,是一种跨域认证方案。
2.Jwt解决了什么问题
- 数据传输简单,高效
- jwt会生成签名,保证传输安全
- jwt具有时效性
- jwt更高效利用集群做好单点登录
时效性:比如说token5分钟过期,或者1小时过期,1天过期。如果设置的是1小时过期,那么1小时后拿到token就是失效的token,不能再用了,需要重新生成。
服务端集群,比如说100台服务器,服务端也不需要往内存里去写入token,客户端每次自带上去就好了,服务器自动去解析token来识别用户的身份。以前的话,需要服务器去写入token,当换一台服务器时就无法识别登录了,因此无法实现单点登录。将数据保存在客户端,也因此减少了服务器的压力。
3.Jwt原理
服务器认证通过后,生成一个JSON对象,后续通过JSON进行通信。 (Jwt本来就是一个JSON对象,JSON对象里面存储着通信过程中需要用到的数据)
4.Jwt数据结构
Jwt由3部分组成,分别是Header(头部),Payload(负载),Signature(签名)
(1). Header(头部)
{
"alg": "HS256",
"typ": "JWT"
}
alg:告诉服务器加密的算法 typ:告诉服务器加密的类型 该JSON由Base64Url编码成JWT的第一部分 (2). Payload(负载) Payload包含的是请求体和其它一些数据。它有7个官方的字段,分别如下: iss (issuer):签发人 sub (subject):主题 aud (audience):受众 exp (expiration time):过期时间 nbf (Not Before):生效时间,在此之前是无效的 iat (Issued At):签发时间 jti (JWT ID):编号 也可自定义字段,如用户姓名,用户id等:
"user_name":"wmh",
"user_id":123456
(3). Signature(签名) 签名是jwt的第三部分。主要是把头部的base64UrlEncode与负载的base64UrlEncode拼接起来,再进行HMACSHA256加密,加密结果再进行base64url加密,最终得到的结果作为签名部分。加密过程如下:
base64url(
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
your-256-bit-secret (秘钥加盐)
)
)
当客户端带着jwt向服务端发起请求时,服务端要确保这个请求是合法并且没有被篡改,那么服务端就需要一个规则来进行验证。上述签名的加密过程就是服务端的使用的规则。当用户再次登录成功之后,服务端就会进行加密,将加密结果再和客户端携带过来的jwt的第三部分(即签名)进行匹配,确定是否相同,如果相同则为合法请求。如果不相等,则为非法请求。 非法者非法请求的时候,可以通过同样的规则来伪造一个jwt,但是客户端一般是不知道服务端的密钥的,所以非法者几乎不能伪造出合法的请求。
5.Jwt使用方式
Jwt的使用方式有3种,分别如下:
- /api?token=xxx
- cookie写入token
- storage写入token,请求头添加Authorization:Bearer
第一种有时效性 第二种可以在服务端设置一个拦截器去拦截token,并解析token看有没有效 第三种每次请求的时候都要添加到请求头里面去
6.Jwt整体方案
Jwt是一个跨域的认证方案,在用户登录成功以后需要生成一个token,token包括3部分组成,header,payload,签名,返回到客户端,在客户端通过cookie或者storage写入到客户端本地,在以后每次请求的时候需要携带给服务端,让服务端去做一些验证,比如验证签名的token是否有效,验证token是否过期,在业务里边的话可能需要进行解密,拿到里面的数据,然后再进行业务处理。
二、Jwt实际操作
1.Token的生成
(1)安装jwt:yarn add jsonwebtoken -S
(2)在user.js页面中引入jwt:
const jwt = require('jsonwebtoken')
(3)后端服务器,登录接口生成token:
router.post('/login',async (ctx)=>{
try{
const {userName,userPwd} = ctx.request.body
const res = await User.findOne({
userName,
userPwd
},'userName userId userEmail state role deptId roleList')
const data = res._doc;
const token = jwt.sign({ data }, 'imooc', {expiresIn:'1h'});
if(res){
data.token = token;
ctx.body = util.success(data)
}
else{
ctx.body = util.fail("账号或密码不正确")
}
}catch(error){
ctx.body = util.fail(error.msg)
}
})
添加的代码:
const data = res._doc;//需要加密的数据
const token = jwt.sign({ data }, 'imooc', {expiresIn:'1h'});//密钥为imooc,有效时间为1小时,加密的数据时data
if(res) data.token = token;
一般返回的数据不能有用户密码,因此需要用到mongodb的语法来返回我们需要的数据。 返回数据库指定字段,有三种方式
- 'userId userName userEmail state role deptId roleList'
- {userId:1,_id:0}1表示返回,0表示不返回
- select('userId')
此处用第一种方式:
const res = await User.findOne({
userName,
userPwd
},'userName userId userEmail state role deptId roleList')
结果生成了token并返回了数据:
2.Token的校验
基本的登录验证过程: 前端用户登录后,后端服务器拿到用户信息生成token,并返回查找到的数据,返回拦截器拦截返回的信息,返回给前端需要的data,回到前端登录页面,验证通过后将数据保存到vuex和localstorage里面并跳转页面。再次请求时,请求拦截器拦截请求,并从vuex或storage里去获取token,添加到请求头header.Authorization里面,再去相应页面后端接口验证token是否合法或者过期
请求拦截器拦截:
//请求拦截
serve.interceptors.request.use((req)=>{
//TO-DO
const head = req.headers
// 在请求拦截里从localstorage去获取token
const {token} = storage.getItem('userInfo')
if(!head.Authorization) head.Authorization = 'Bearer ' + token;
return req
})
在count接口中验证token:
//下载并引入jwt
const jwt = require('jsonwebtoken')
router.get('/leave/count',(ctx)=>{
const token = ctx.request.headers.authorization.split(' ')[1];
const payload = jwt.verify(token, 'imooc');
ctx.body = payload;
})
验证通过的结果图:
exp:token过期时间
iat:token签发时间
若是token过期会有以下报错:
3.设置Token拦截
koa-jwt中间件可以自动验证token,利用中间件来拦截token可以验证token。实际上中间件相当于过滤,在接口请求之前先拦截验证token,但是在登录接口,我们需要跳转页面,因此要跳过login接口的拦截,可以用unless.
(1) 安装插件(中间件)yarn add koa-jwt -S
(2) 在app.js页面加载和启动中间件:
//加载中间件
const koajwt = require('koa-jwt')
//启用中间件,并过滤掉login接口,在登录页面放行
app.use(koajwt({secret:'imooc'}).unless({
path:[/^\/api\/users\/login/]
}))
//启用插件会自动去验证token
use后验证不通过会抛出401,然后自动去调用next()
app.use(async (ctx, next) => {
const start = new Date()
await next().catch((err)=>{
if(err.status == '401'){
ctx.status = 200;
ctx.body = util.fail('token认证失败',util.CODE.AUTH_ERROR)
}else{
throw err;
}
})
//util.CODE.AUTH_ERROR是500001状态码
因此在login页面可成功登录并跳转,没有登录就访问其他页面会提示 'token认证失败'