Token鉴权原理

  1. 客户端使用用户名和密码请求登录
  2. 服务端收到请求,去验证用户名与密码
  3. 验证成功后,服务端会签发一个令牌(token),再把这个token发送给客户端
  4. 客户端收到token以后可以把它存储起来,如放在localStorage或Cookie中
  5. 客户端每次向服务端请求资源的时候都需要带着服务端签发的Token
  6. 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就像客户端返回请求的数据

Koa实现Token鉴权

通过jsonwebtoken加签,koa-jwt验签
token本质上就是一种签名
服务端

const koa = require('koa')
const router = require('koa-router')()
const jwt = require('jsonwebtoken')
const jweAuth = require('koa-jwt')
//秘钥
const secret = "it's a secret"
const cors = require('koa2-cors')
const bodyParser = require('koa-bodyparser')
const static = require('koa-static')
const app = new koa()
app.keys = ['some secret']
app.use(static(__dirname+'/'))
app.use(bodyParser())

router.post("/login-token", async ctx => {
    const {body} =ctx.request
    const userinfo = body.username
    ctx.body = {
        message:"登录成功",
        user:userinfo,
        //生成token,返回给客户端
        token:jwt.sign({
            data:userinfo,
            //设置过期时间,单位为秒
            exp:Math.floor(Date.now()/1000)+60*60
        },secret)
    }
    /*
    {
        "message":"登录成功",
        "user":"test",
        "token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoidGVzdCIsImV4cCI6MTYwNDgzNTQzNywiaWF0IjoxNjA0ODMxODM3fQ.dHOVcVGt_QemICoiyEdZJR2MSHuoVXD-U_KDCGD0g-w"}
    */
})

router.get(
    "/getUser-token",
    //验签
    jweAuth({secret}),
    async ctx => {
        //ctx.state可以获取到加签时的数据
        ctx.body = {
            message:"获取数据成功",
            userinfo:ctx.state.user.data
        }
        /**
         * {"message":"获取数据成功","userinfo":"test"}
         */
    }
)

app.use(router.routes())
app.listen(3000)

客户端

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  </head>

  <body>
    <div id="app">
      <div>
        <input v-model="username" />
        <input v-model="password" />
      </div>
      <div>
        <button v-on:click="login">Login</button>
        <button v-on:click="logout">Logout</button>
        <button v-on:click="getUser">GetUser</button>
      </div>
      <div>
        <button @click="logs=[]">Clear Log</button>
      </div>
      <!-- 日志 -->
      <ul>
        <li v-for="(log,idx) in logs" :key="idx">
          {{ log }}
        </li>
      </ul>
    </div>
    <script>
      axios.interceptors.request.use(
        config => {
          const token = window.localStorage.getItem("token");
          if (token) {
            // 判断是否存在token,如果存在的话,则每个http header都加上token
            // Bearer是JWT的认证头部信息
            config.headers.common["Authorization"] = "Bearer " + token;
          }
          return config;
        },
        err => {
          return Promise.reject(err);
        }
      );

      axios.interceptors.response.use(
        response => {
          app.logs.push(JSON.stringify(response.data));
          return response;
        },
        err => {
          app.logs.push(JSON.stringify(response.data));
          return Promise.reject(err);
        }
      );
      var app = new Vue({
        el: "#app",
        data: {
          username: "test",
          password: "test",
          logs: []
        },
        methods: {
          async login() {
            const res = await axios.post("/login-token", {
              username: this.username,
              password: this.password
            });
            localStorage.setItem("token", res.data.token);
          },
          async logout() {
            localStorage.removeItem("token");
          },
          async getUser() {
            await axios.get("/getUser-token");
          }
        }
      });
    </script>
  </body>
</html>

JWT(JSON WEB TOKEN)原理解析

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoidGVzdCIsImV4cCI6MTYwNDgzNTQzNywiaWF0IjoxNjA0ODMxODM3fQ.dHOVcVGt_QemICoiyEdZJR2MSHuoVXD-U_KDCGD0g-w

生成的token包含三部分,分别用base64解码可得
{“alg”:“HS256”,“typ”:“JWT”}
{“data”:“test”,“exp”:1604835437,“iat”:1604831837}
由上可知,Bearer Token包含三个组成部分:令牌头、payload、哈希
签名
默认使用base64对payload编码,使用hs256算法对令牌头、payload和秘钥进行签名生成哈希
验签
默认使用hs256算法对令牌中得数据签名并将结果和令牌中的哈希比对

HMAC SHA256 HMAC(Hash Message Authentication Code,散列消息鉴别码,基于密钥的Hash算法的认证协议。消息鉴别码实现鉴别的原理是,用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。使用一个密钥生成一个固定大小的小数据块,即MAC,并将其加入到消息中,然后传输。接收方利用与发送方共享的密钥进行鉴别认证等。

BASE64按照RFC2045的定义,Base64被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。(The Base64 Content-Transfer-Encoding isdesigned to represent arbitrary sequences of octets in a form that need not be humanlyreadable.)常见于邮件、http加密,截取http信息,你就会发现登录操作的用户名、密码字段通过BASE64编码的

Beare作为一种认证类型(基于OAuth 2.0),使用"Bearer"关键词进行定义

session/cookie和token两种鉴权方式对比

  1. session要求服务端存储信息,并且根据id能够检索,而token不需要(因为信息就在token中,这样实现了服务端无状态化)。在大规模系统中,对每个请求都检索会话信息可能是一个复杂和耗时的过程。但另外一方面服务端要通过token来解析用户身份也需要定义好相应的协议(比如JWT)。
  2. session一般通过cookie来交互,而token方式更加灵活,可以是cookie,也可以是header,也可以放在请求的内容中。不使用cookie可以带来跨域上的便利性。
  3. token的生成方式更加多样化,可以由第三方模块来提供。
  4. token若被盗用,服务端无法感知,cookie信息存储在用户自己电脑中,被盗用风险略小。