Jwt认证方案

370 阅读6分钟

一、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的语法来返回我们需要的数据。 返回数据库指定字段,有三种方式

  1. 'userId userName userEmail state role deptId roleList'
  2. {userId:1,_id:0}1表示返回,0表示不返回
  3. 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认证失败'