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…