一、添加 token
1.原理
1. 登陆时,客户端发送用户名密码
2. 服务端验证用户名密码是否正确,校验通过就会生成一个有时效的token串,发送给客户端
3. 客户端储存token,一般都会存储在localStorage或者cookie里面
4. 客户端每次请求时都带有token,可以将其放在请求头里,每次请求都携带token
5. 服务端验证token,所有需要校验身份的接口都会被校验token,若token解析后的数据包含用户身份信息,则身份验证通过,返回数据
2.利用 nodejs + jwt 生成token 验证token
安装 jsonwebtoken
npm install jsonwebtoken --save
nodejs 服务端
新建 jwt.js 生成token和验证token方法
// 引入模块依赖
const fs = require('fs')
const path = require('path')
const jwt = require('jsonwebtoken')
// 创建 token 类
class Jwt {
constructor(data) {
this.data = data
}
//生成token
generateToken() {
let data = this.data
let created = Math.floor(Date.now() / 1000)
let PRIVATE_KEY = fs.readFileSync(path.join(__dirname, './pem/rsa_private_key.pem')) //私钥 可以自己生成
let token = jwt.sign(
{
data,
exp: created + 60 * 60 * 24 * 3
},
PRIVATE_KEY,
{ algorithm: 'RS256' }
)
return token
}
// 校验token
verifyToken() {
let token = this.data
let PUBILC_KEY = fs.readFileSync(path.join(__dirname, './pem/rsa_public_key.pem')) //公钥 可以自己生成
let res
try {
let result = jwt.verify(token, PUBILC_KEY, { algorithms: ['RS256'] }) || {}
let { exp = 0 } = result,
current = Math.floor(Date.now() / 1000)
if (current <= exp) {
res = result.data || {}
}
} catch (e) {
res = 'err'
}
return res
}
}
module.exports = Jwt
rsa_public_key和private_key生成
OpenSSL生成私钥和公钥和使用非对称加密颁发验证token
登录时 生成token 并返回给客户端
const JwtUtil = require('./jwt') // 引入jwt token工具
// 登录
const login = async (request, response) => {
const q1 = `SELECT * FROM userinfo WHERE name = $1`
const q2 = `SELECT * FROM userinfo WHERE name = $1 AND password = $2`
try {
let values = [request.body.username, request.body.password]
let res1 = await pool.query(q1, [values[0]])
let res2 = await pool.query(q2, values)
if (res1.rowCount !== 0) {
if (res2.rowCount !== 0 && res2.rows[0].password === values[1]) {
// 登陆成功,添加token验证
let user = { id: res2.rows[0].id, name: res2.rows[0].name }
// 将用户传入并生成token
let jwt = new JwtUtil(user)
let token = jwt.generateToken()
response.status(200).json({
status: true,
message: '登录成功!',
token: token,
results: { id: res2.rows[0].id, username: res2.rows[0].name }
})
} else {
response.status(400).json({ status: false, message: '账号密码错误!' })
}
} else {
response.status(404).json({ status: false, message: '账号不存在!' })
}
} catch (err) {
response.status(500).json({ status: false, message: '账号密码错误!' })
}
}
入口文件index.js中 需要验证身份的接口进行请求时 要验证token
const JwtUtil = require('./jwt')
app.use(function (req, res, next) {
// 登陆和注册请求不验证,其他的多有请求需要进行token校验
if (req.url != '/login' && req.url != '/register') {
let token = req.headers.authorization
// let token = req.headers.authorization.replace('Bearer ', '') // postman token测试
// console.log('token:', token)
let jwt = new JwtUtil(token)
let result = jwt.verifyToken()
// 如果考验通过就next,否则就返回登陆信息不正确
if (result == 'err') {
res.status(403).send({ status: false, message: '登录已过期,请重新登录' })
} else {
next()
}
} else {
next()
}
})
客户端
store中的index.js (其中isLogin可不看)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
// 设置属性
state: {
isLogin: false,
token: ''
},
// 获取属性的状态
getters: {
//获取登录状态
isLogin: state => state.isLogin
},
// 设置属性状态
mutations: {
//保存登录状态
userStatus(state, flag) {
state.isLogin = flag
},
set_token(state, ltoken) {
// localStorage.setItem('token', ltoken)
state.token = ltoken
},
del_token(state) {
// localStorage.removeItem('token')
state.token = ''
}
},
// 应用mutations
actions: {
userLogin(context, flag) {
context.commit('userStatus', flag)
},
set_token(context, token) {
context.commit('set_token', token)
},
del_token(context) {
context.commit('del_token')
}
},
modules: {}
})
export default store
Login.vue中 登录 存放token
submit() {
this.$refs.loginForm.validate(valid => {
if (valid) {
const params = {
username: this.loginForm.username,
password: this.loginForm.password
}
login(params)
.then(res => {
console.log(res)
if (res.status) {
//使用vuex对全局token进行状态管理
// this.$store.dispatch('set_token', res.token)
localStorage.setItem('token', res.token)
//设置Vuex登录标志为true,默认userLogin为false
this.$store.dispatch('userLogin', true)
//Vuex在用户刷新的时候userLogin会回到默认值false,所以我们需要用到HTML5储存
//我们设置一个名为Flag,值为isLogin的字段,作用是如果Flag有值且为isLogin的时候,证明用户已经登录了。
localStorage.setItem('Flag', 'isLogin')
this.$message.success(res.message)
const { id, username } = res.results
localStorage.setItem('UserInfo', JSON.stringify({ id: id, username: username }))
this.$router.push({ path: '/home' })
} else {
this.$message.error(res.message)
}
})
} else {
return false
}
})
},
示例连接:
vue & nodejs jwt 的基于token身份验证
nodejs 基于token的身份验证
JWT_nodeJs_express_vue 实例
二、封装 axios
安装axios
npm install --save axios
在src下 创建文件夹request
存放http.js
和api.js
http.js (只封装的通用的get和post,可以根据自己的需求进行封装,修改headers 也可添加到参数中)
import axios from 'axios'
import router from '../router/index'
import store from '../store/index'
import { Message } from 'element-ui'
// 消息提示函数
const tip = msg => {
Message({
message: msg,
type: 'error',
duration: 2000,
showClose: true
})
}
// 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
const toLogin = () => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
}
// 环境的切换
if (process.env.NODE_ENV == 'development') {
axios.defaults.baseURL = 'http://10.52.184.226:3000'
} else if (process.env.NODE_ENV == 'debug') {
axios.defaults.baseURL = ''
} else if (process.env.NODE_ENV == 'production') {
axios.defaults.baseURL = ''
}
axios.defaults.timeout = 1000 * 60 * 3 // 请求超时时间
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8' // post请求头
// 请求拦截器
axios.interceptors.request.use(
config => {
// 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
// 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
const token = localStorage.getItem('token')
store.dispatch('set_token', token)
if (token) {
config.headers.Authorization = token
}
return config
},
error => {
return Promise.error(error)
}
)
// 响应拦截器
axios.interceptors.response.use(
response => {
if (response.status === 200) {
return Promise.resolve(response)
} else {
return Promise.reject(response)
}
},
// 服务器状态码不是200的情况
error => {
if (error.response.status) {
switch (error.response.status) {
// 401: 未登录
// 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401:
toLogin()
break
// 403 token过期
// 清除本地token和清空vuex中token对象 并 跳转登录页面
case 403:
store.dispatch('del_token')
localStorage.removeItem('token')
setTimeout(() => {
toLogin()
}, 1000)
break
// 其他错误,直接抛出错误提示
default:
tip(error.response.data.message)
}
return Promise.reject(error.response)
}
}
)
/**
* get方法,对应get请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
export function get(url, params) {
return new Promise((resolve, reject) => {
axios
.get(url, {
params: params
})
.then(res => {
resolve(res.data)
})
.catch(err => {
reject(err.data)
})
})
}
/**
* post方法,对应post请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
export function post(url, params) {
return new Promise((resolve, reject) => {
axios
.post(url, params)
.then(res => {
resolve(res.data)
})
.catch(err => {
reject(err.data)
})
})
}
api.js
import { get, post } from './http'
export const login = p => post('/login', p)
export const register = p => post('/register', p)
export const getUserInfo = p => post('/getUserInfo', p)
export const cbmProperty = () => get('/data/cbmProperty')
封装接口调用示例——getUserInfo
:
import { getUserInfo } from '@/request/api'
getUserInfo() {
const params = { id: this.userInfo.id }
getUserInfo(params)
.then(res => {
if (res.status) {
this.userInfo.username = res.username
}
})
}