源代码地址 jwt 介绍 什么是 jwt:jwt 全称是 jsonwebtoken,本质上是一个 token(秘钥),就像去电影院看电影,你得拿一张票,这张票有时间限制,区域限制,使用人数限制,token 就像一个电影票,必须满足合适的时间、区域、人数,才能“通过”。 jwt 依赖的库:kow-jwt(jsonwebtoken 的中间件),jsonwebtoken(用户生成 token ) jwt

源代码地址

jwt 介绍

  1. 什么是 jwt:jwt 全称是 jsonwebtoken,本质上是一个 token(秘钥),就像去电影院看电影,你得拿一张票,这张票有时间限制,区域限制,使用人数限制,token 就像一个电影票,必须满足合适的时间、区域、人数,才能“通过”。
  2. jwt 依赖的库:kow-jwt(jsonwebtoken 的中间件),jsonwebtoken(用户生成 token )
  3. jwt 的 token 可以存在客户端的或者服务端

应用场景

  1. 当请求接口时,需要先要判断用户是否合法,不合法 throw 异常(401)
  2. 指定接口不需要权限(比如静态文件),需要过滤指定接口

使用条件

  1. 服务端 token 检验与生成,客户端 token 存储
  • 使用 jsonwebtoken 生成 token
  • 使用 koa-kwt 加密 token,可设置过滤某些接口
  1. 客户端请求接口前,需要在 request 设置 Authorization:Bearer [token]字段
  • axios.interceptors.request 设置 Authorization:Bearer [token]字段
  1. 接口代理
  • 如果是 127.0.0.1 的 8080 端口访问服务端接口,就涉及代理的问题,需要在 webpack 文件夹中的 config 文件设置代理
  1. 客户端接口对 401 状态拦截,跳转指定页面,比如登录页
  • axios.interceptors.response 对状态进行捕获
  1. 客户端路由拦截,无效 token(退出登录后)无法再次访问要认证的接口
  • 使用 beforeEach 钩子进行路由拦截,拦截前需要在客户端路由 meta 中设置是否需要拦截
  1. 客户端设置 token
  • 定义几个 vue 页面的作用
    • getData.vue 请求被过滤的接口
    • getUserInfo.vue 只有有权限才能进入这个页面
    • login.vue 登录页面
    • logout.vue 退出登录页面

具体实现

服务端实现

server/jw.js

  • 具体步骤
  1. token 判断
async function tokenError(ctx, next) {
  try {
    const token = ctx.header.authorization
    if (token) {
      try {
        let payload = await verify(token.split(' ')[1], 'key1')
        ctx.user = {
          name: payload.name,
          id: payload.id
        }
      } catch (error) {
        console.log('token verify fail: ', error)
      }
    }
    await next()
  } catch (error) {
    if (error.status === 401) {
      ctx.status = 401
      ctx.body = {
        success: 1,
        message: '认证失败'
      }
    } else {
      ctx.status = 404
      ctx.body = {
        success: 1,
        message: '语法错误或路径错误'
      }
    }
  }
}
  1. 服务端设置接口
router.get('/login', async ctx => {
  const userToken = {
    name: 'Jen',
    id: '01'
  }
  const token = jwt.sign(userToken, 'key1', {
    expiresIn: '2h'
  })
  ctx.body = {
    success: 0,
    token: token,
    message: '登录成功'
  }
})
router.get('/logout', async ctx => {
  ctx.body = {
    success: 0,
    message: '退出成功'
  }
})
router.get('/getdata', async ctx => {
  ctx.body = {
    success: 0,
    message: 'getdata的get方式为忽略token判断的路由'
  }
})
router.get('/userInfo', async ctx => {
  let token = ctx.header.authorization
  ctx.body = {
    success: 0,
    message: token
  }
})
  1. 装载拦截和进行加密过滤
app.use(tokenError)
app.use(
  koaJwt({
    secret: 'key1'
  }).unless({
    path: [/\/getdata/, /\/logout/, /\/login/]
  })
)

添加 Authorization 和接口设置

src/http2/http.js

  • 具体步骤
  1. Authorization
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.timeout = 20000
// 手动设置Authorization头
axios.interceptors.request.use(
  config => {
    const token = localStorage.getItem('HAS_SET_TOKEN')
    if (token) {
      // Bearer是JWT的认证头部信息
      config.headers.common['Authorization'] = 'Bearer ' + token
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)
// 添加一个注射器
axios.interceptors.response.use(
  response => {
    return response
  },
  error => {
    return Promise.reject(error)
  }
)
  1. 定义接口
// 这是登录接口
const loginApi = '/api/login'
// 这是获取用户信息接口
const getUserInfoApi = '/api/userInfo'
// 这是过滤后的接口
const getDataApi = '/api/getdata'
// 这是首页
const loginOutApi = '/api/logout'

接口代理

config/index.js

module.exports = {
  dev: {
    '/api': {
      target: 'http://127.0.0.1:1111',
      changeOrange: true,
      pathRewrite: {
        '^/api': ''
      }
    }
  }
}

状态拦截

main.js

axios.interceptors.response.use(
  response => {
    return response
  },
  error => {
    if (error.response.status === 401) {
      router.push('/login')
    } else {
    }
    return Promise.catch(error)
  }
)

客户端路由拦截

src/router/index.js

  • 具体步骤
  1. 路由设置
routes: [
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path: '/logout',
    name: 'logout',
    component: Logout,
    meta: {
      requireAuth: true
    }
  },
  {
    path: '/userInfo',
    name: 'GetUserInfo',
    component: GetUserInfo,
    meta: {
      requireAuth: true
    }
  },
  {
    path: '/getdata',
    name: 'GetData',
    component: GetData,
    meta: {
      requireAuth: false
    }
  }
]

可以看出/login 是登录页面,所以不需要判断是否需要重定向,getdata 是过滤的接口,所以也不用判断是否需要重定向

  1. 路由拦截
// to即将要进入的路由
// from导航要离开的路由
// next next()是进行管道中的下一个钩子,如果全部钩子执行完了,则导航的状态就是 confirmed ;next("/login")路由跳转
router.beforeEach((to, from, next) => {
  if (to.meta.requireAuth) {
    const token = localStorage.getItem('HAS_SET_TOKEN')
    if (token && token !== 'null') {
      next()
    } else {
      next('/login')
    }
  } else {
    next()
  }
})

客户端设置 token

  • 设置 token

src/components/login.vue

http.login().then(res => {
  if (res.success == 0) {
    this.msg = '登录成功'
    localStorage.setItem('HAS_SET_TOKEN', res.token)
  }
})
  • 删除 token

src/components/logout.vue

if (res.success == 0) {
  this.msg = res.message
  localStorage.removeItem('HAS_SET_TOKEN')
}

总结

  1. 既然 token 是存在 localStorage 中,那为什么不直接存一个 localStorage 进行用户校验,问题的关键点是,这个 localStorage 的 value 是由服务端分配的,秘钥的校验和生成都在服务端完成,localStorage 存储是为了解决 session 的跨域问题,当然 jwt 产生的 token 也可以存在服务端
  2. jwt 可以实现单点登录,但是无法控制单点登录,比如一个账户在 A 设备登录后也能在 B 设备登录,两者不冲突
  3. jwt 因为是存在 request 的 Authorization 字段中,那非法用户就可以先获取到这个值,然后模拟登录,存在安全问题
  4. jwt 解决了跨域的问题,session+cookie 存在跨域问题,比如一个项目是分布式部署(a.com,b.com) 需要做多机共享 session 机制,并且 cookie 也存在安全问题
  5. jwt 生成的 token 是无法手动删除的,只能等在过期或者改动秘钥(就是那个 key1)才能失效,要在客户端进行两个操作:1、每次登录前判断 localStorage 是否存在,如果存在就证明了已经设置了 Authorization 了;2、退出登录,删除 localStorage,localStorage 不存在了,所以前端路由会 next('/login'),只会跳转到登录页面

暂无评论