除了cookie,你还可以用jwt(json web token)!

·  阅读 3637

1. 认识jwt(json web token)

  • jwt是为了在网络应用环境传递声明而执行的一种基于json的开放标准。
  • jwt被用来在身份提供者和服务提供者间传递被认证的用户身份信息,简单来说,就是用来验证身份的手段,例如登录校验,像我们之前用的cookie。
  • jwt可以使用HMAC算法或者是RSA的公私秘钥对来进行签名,来保证信息的可靠性。

2. 应用场景

在例如身份验证场景中,用户一旦登录,接下来的每个请求都会包含jwt,用来验证身份信息。由于通信双方使用jwt对数据进行编码,它的信息是经过签名的,所以可以确保信息的安全性。

3. jwt对比cookie

cookie缺点

  • 客户端发请求给服务器,服务器种植cookie后,每次请求都会带上cookie,浪费带宽
  • cookie不能跨服务器访问,不支持跨域
  • 服务器要对登录的用户对象进行存储,浪费服务器内存

jwt优点

  • jwt是不基于状态的,不需要每次请求都带上token,节约流量
  • 服务器不需要占用内存,信息相对于可靠些
  • 可以跨服务端,可以共用

4. jwt结构

  • Header头部:{typ:'jwt',alg:'HS256'} alg:当前用的什么算法加密的;使用Base64Url编码组成了JWT结构的第一部分
  • PlyLoad负载:存放有效信息的地方
  • Signature签名:创建签名需要使用编码后的header和payload以及一个秘钥;例如如果希望使用HMAC SHA256算法,那么签名应该使用下列方式创建 HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

完整的jwt格式的输出是以 . 分隔的三段Base64编码 密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。

5. 举个栗子

express+vue+mongoose 后端app.js,包括注册,登录,获取订单接口

let express = require('express')
let bodyParser = require('body-parser')//中间件
let jwt = require('jwt-simple')//jwt库
//数据库
let User = require('./model/user')
//监听函数
let app = express()
let {secret} = require('./config')
//中间件一定是函数,处理发回来的json类型,还有text,urlencoded(a=b&c=d)
app.use(bodyParser.json())

//防止跨域 request请求 response响应
app.use(function(req, res, next){
    res.setHeader('Access-Control-Allow-Origin','*');//简单点,接收所有
    res.setHeader('Access-Control-Allow-Headers','Content-type,Authorization');
    res.setHeader('Access-Control-Allow-Methods','GET,POST,DELETE,PUT,OPTIONS');
    if(req.method === 'OPTIONS') {
        res.end()
    }else {
        next()
    }
})

//注册
app.post('/reg', async function(req, res, next){
    let user = req.body;
    try {
        user = await User.create(user) //在数据库中插入数据
        res.json({
            code: 0,
            data: {
                user: {
                    id: user._id,
                    username: user.username
                }
            }
        })
    } catch (error) {
        res.json({
            code: 1,
            data: '注册失败'
        })
    }  
})
//登录
app.post('/login', async function(req,res,next){
    let user = req.body;
    user = await User.findOne(user)//数据库中查找
    if(user) {
        let token = jwt.encode({//编码
            id: user._id,
            username: user.username
        },secret);
        res.json({//返回信息
            code: 0,
            data: { token }
        })
    }else {
        res.json({
            code: 1,
            data: '用户不存在'
        })
    }
}) 
// 用户校验 中间件
let auth = function(req, res, next){
    //post模拟时 添加Headers Authorization: Bearer token的值
    let authorization = req.headers['authorization']
    if(authorization) {
        let token = authorization.split(' ')[1];
        try {
            //看token是否合法,解码,如果串改过token就解不出来,进入异常页面
            let user = jwt.decode(token, secret);
            req.user = user;//后面就可以拿到user,中间件用法 
            next();//下一步
        } catch (error) {
            console.log(error)
            res.status(401).send('Not Allowed')
        }
    } else {
        res.status(401).send('Not Allowed');
    }
}
//发送请求,看看能不验证成功auth,如果可以拿到返回数据
app.get('/order', auth, function(req,res,next){
    res.json({
        code: 0,
        data: {
            user: req.user
        }
    })
})
app.listen(3000)
复制代码

数据库页面

// 操作数据库
let mongoose = require('mongoose');
let {DB_URL} = require('../config');

mongoose.connect(DB_URL,{useNewUrlParser:true})

/**
  * 连接成功
  */
 mongoose.connection.on('connected', function () {    
    console.log('Mongoose connection open to ' + DB_URL);  
});    

/**
 * 连接异常
 */
mongoose.connection.on('error',function (err) {    
    console.log('Mongoose connection error: ' + err);  
});

//创建Schema数据模型
let UsrSchema = new mongoose.Schema({
    username: String,
    password: String
});
module.exports = mongoose.model('User', UsrSchema);
复制代码

axios简单封装

import axios from 'axios'
import router from '../src/router'
axios.defaults.baseURL = 'http://localhost:3000'
//axios 拦截器对拿到的数据进行拦截
axios.interceptors.response.use(function(res){
    if(res.data.code !== 0) {
        return Promise.reject(res.data.data)
    }
    return res.data;
},res=>{
    if(res.response.status === 401){ // 没权限跳到登录页
        router.history.push('/login');
    }
    return Promise.reject('Not Allowed');
});

//对发送的请求统一加上token,来验证是否是本人登录
axios.interceptors.request.use(function(config){
    let token = localStorage.getItem('token')
    if(token) {
        config.headers.Authorization = `Bearer ${token}`
    }
    return config;
})

export default axios
复制代码

config.js

module.exports = {
    'DB_URL': 'mongodb://localhost:27017/jwt',
    'secret': 'jeffywin'//秘钥 加盐
}
复制代码

前台界面vue-cli脚手架,没什么说的,登录界面

<template>
  <div class="main">
    <div class="item">
      <div style="width:100px">登录页</div> 
      <input type='text' v-model='user.username'/>
    </div>
    <div class="item">
      <div style="width:100px">密码</div> 
      <input type='text' v-model='user.password'/>
    </div>
    <button @click="login">提交</button>  
  </div>
</template>

<script>
import axios from '../../utils/axios'
export default {
  data() {
      return {
        user: {
          username: '',
          password: ''
        }
      }
  },
  methods: {
    login() {
      axios.post('/login',this.user).then(res => {
        localStorage.setItem('token', res.data.token)//登录后存储token
        this.$router.push('/order')
      })
    }
  }
}
</script>

<style scoped lang="scss">
.main {
  margin: 0 auto;
  width: 300px;

  .item {
    display: flex;
    margin-bottom: 10px;
  }
  }
</style>

复制代码

order界面

<template>
  <div class="order">
    <h1>This is an order page</h1>
    {{username}}//如果登录成功,跳转order界面,拿到登录的用户
  </div>
</template>

<script>
import axios from '../../utils/axios'
  export default {
    data() {
      return {
        username: ''
      }
    },
    mounted() {
      axios.get('/order').then(res => {
        this.username = res.data.user.username
      })
    },
  }
</script>

复制代码

源码在本人github github.com/jeffywin/jw…