jwt
代码-分支/jwt- JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
- 场景
- 身份认证在这种场景下,一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。
- 信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的
结构
- JWT包含了使用.分隔的三部分
- Header 头部
- 在header中通常包含了两部分:token类型和采用的加密算法。
- { "alg": "HS256", "typ": "JWT"}
- Payload 负载
- 负载就是存放有效信息的地方。这个名字像是指货车上承载的货物,这些有效信息包含三个部分
- 标准中注册的声明
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间,这是一个秒数
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- 公共的声明
- 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密
- 私有的声明
- 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
- 负载例子
- { "sub": "1234567890", "name": "zfpx", "admin": true}
- 上述的负载需要经过Base64Url编码后作为JWT结构的第二部分
- Signature 签名
- 创建签名需要使用编码后的header和payload以及一个秘钥
- 使用header中指定签名算法进行签名
- 例如如果希望使用HMAC SHA256算法,那么签名应该使用下列方式创建
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)- 签名用于验证消息的发送者以及消息是没有经过篡改的
- 完整的JWT 完整的JWT格式的输出是以. 分隔的三段Base64编码
- 密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。
前端
- vue&&aioxs
- /src/api/AjaxRequest.js
import axios from 'axios'
class AjaxRequest {
constructor () {
this.baseURL = process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : '/'
// this.baseURL = 'http://localhost:3000'
this.timeout = 2000
}
request (config) { // 设置请求的方法
const instance = axios.create({
baseURL: this.baseURL,
timeout: this.timeout
})
// 设置拦截
instance.interceptors.request.use(config => {
config.headers.Authorization = `${localStorage.getItem('token')}`
return config
}, (err) => {
Promise.reject(err)
})
// 设置响应拦截
instance.interceptors.response.use(res => res.data, err => Promise.reject(err))
return instance(config)
}
}
export default new AjaxRequest()
- /src/api/index.js
import axios from './AjaxRequest'
export const getTest = () => axios.request({ method: 'get', url: '/test' })
export const login = (username) => axios.request({ method: 'POST', url: '/login', data: { username } })
export const validate = () => axios.request({ method: 'get', url: '/validate' })
export default axios
- views/home.vue
<template>
<div class="home">
首页111111111
</div>
</template>
- views/login.vue
<template>
<div class="home">
login
<input v-model="user">
<button @click='btn'>登录</button>
</div>
</template>
<script>
export default {
name: 'login',
data () {
return {
user: ''
}
},
methods: {
btn () {
this.$store.dispatch('login', this.user).then(() => {
this.$router.push('/profile')
})
}
}
}
</script>
- views/profile.vue
<template>
<div class="home">
profile
</div>
</template>
<script>
export default {
name: 'profile'
}
</script>
- App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/login">Login</router-link> |
<router-link to="/profile">Profile</router-link>
</div>
<router-view></router-view>
</div>
</template>
- main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false
Vue.use(ElementUI)
// 登录验证
router.beforeEach(async (to, from, next) => {
if (to.path === '/') {
next()
}
const flag = await store.dispatch('validate')
// 首次没有登陆
if (flag) {
if (to.path === '/login') {
next('/')
} else {
next()
}
} else {
let flag = to.matched.some(item => item.meta.needLogin)
console.log('========', flag)
if (flag) {
next('/login')
} else {
next()
}
}
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
- router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Login from './views/login.vue'
import Profile from './views/profile.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [{
path: '/',
name: 'home',
component: Home
},
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/profile',
name: 'profile',
component: Profile,
meta: { needLogin: true }
},
{
path: '/parent',
name: 'parent',
component: Parent
},
{
path: '/children',
name: 'children',
component: Children
}
]
})
- store.js
import Vue from 'vue'
import Vuex from 'vuex'
import Axios, { login, validate } from './api/index'
import { authRoutes } from './router'
Vue.use(Vuex)
const getTreeList = (menuList) => {
let menu = []// 用来渲染菜单的
let routeMap = {}
let auths = []
menuList.forEach(m => {
auths.push(m.auth)
m.children = []
routeMap[m.id] = m
if (m.pid == -1) { // 是根节点
menu.push(m)
} else {
// 找父级 将值传递进去
if (routeMap[m.pid]) {
routeMap[m.pid].children.push(m)
}
}
})
return { auths, menu }
}
const formatList = (authRoutes, auths) => {
return authRoutes.filter(route => {
if (auths.includes(route.name)) {
if (route.children) {
route.children = formatList(route.children, auths)
}
return true
}
})
}
// hasPermission 权限相关的
export default new Vuex.Store({
state: {
username: '',
hasPermission: false,
menuList: []
},
mutations: {
setUserName (state, username) {
state.username = username
},
setMenuList (state, menu) {
state.menuList = menu
},
setPermission (state) {
state.hasPermission = true
}
},
actions: {
// 发起请求,请求后端数据
async getNewRoute ({ commit, dispatch }) {
// 获取权限
let { menuList } = await Axios.request('/roleAuth')
// 需要把后端的数据扁平化
let { auths, menu } = getTreeList(menuList)
commit('setMenuList', menu)
let needRoutes = formatList(authRoutes, auths)
commit('setPermission')
return needRoutes
// console.log('list', authRoutes, auths)
},
async login ({ commit }, username) {
const r = await login(username)
if (r.code === 1) {
return Promise.reject(r)
}
localStorage.setItem('token', r.token)
commit('setUserName', r.username)
},
async validate ({ commit }) {
const r = await validate()
if (r.code === 1) {
return false
}
commit('setUserName', r.username)
localStorage.setItem('token', r.token)
return true
}
}
})
后端
- sever.js
let express = require('express')
let app = express()
let bodyParser = require('body-parser')
let jwt = require('./jwt')
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:8082')
res.header('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT')
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization')
if (req.method.toLowerCase() === 'options') {
return res.end()
}
next()
})
app.use(bodyParser.json())
let secret = 'zfjg'
app.get('/test', (req, res) => {
let obj = { test: 'test' }
obj = JSON.stringify(obj)
res.end(obj)
})
app.post('/login', (req, res) => {
console.log('/login')
let { username } = req.body
if (username === 'admin') { // 如果访问的是admin 种植cookie
res.json({
code: 0,
username: 'admin',
token: jwt.sign({ username: 'admin', exp: Date.now() + 1000 * 5 }, secret)
})
} else {
res.json({
code: 1,
data: '用户名不存在'
})
}
})
app.get('/validate', (req, res) => {
let token = req.headers.authorization
console.log('token', token)
try {
let rs = jwt.verify(token, secret)
console.log('rs', rs)
res.json({
username: rs.username,
code: 0, // 延长tokne的过期时间
token: jwt.sign({ username: 'admin', exp: Date.now() + 1000 * 5 }, secret, {
// expiresIn: 20
})
})
} catch (error) {
return res.json({
code: 1,
data: 'token失效了'
})
}
})
app.listen(3000)
- jwt.js
const crypto = require('crypto')
// 编码
function encode (payload, key) {
let header = { type: 'JWT', alg: 'sha256' }// 声明类型和算法
var segments = []// 声明一个数组
segments.push(base64urlEncode(JSON.stringify(header)))// 对header进行base64
segments.push(base64urlEncode(JSON.stringify(payload)))// 对负载进行base64
segments.push(sign(segments.join('.'), key))// 加入签名
return segments.join('.')
}
// 加密
function sign (input, key) {
return crypto.createHmac('sha256', key).update(input).digest('base64')
}
// 解码
function decode (token, key) {
if (!token) {
throw new Error('verify failed')
} else {
var segments = token.split('.')
var headerSeg = segments[0]
var payloadSeg = segments[1]
var signatureSeg = segments[2]
var header = JSON.parse(base64urlDecode(headerSeg))
var payload = JSON.parse(base64urlDecode(payloadSeg))
// 验证签名算法
if (signatureSeg != sign([headerSeg, payloadSeg].join('.'), key)) {
throw new Error('verify failed')
}
// 过期时间verify
if (payload.exp && Date.now() > payload.exp) {
throw new Error('Token expired')
}
return payload
}
}
function base64urlEncode (str) {
return Buffer.from(str).toString('base64')
}
function base64urlDecode (str) {
return Buffer.from(str, 'base64').toString()
}
module.exports = {
sign: encode,
verify: decode
}