共计 3404 个字符,预计需要花费 9 分钟才能阅读完成。
起因
http 是无状态协定,须要晓得是哪一个用户,因而须要一种验证机制。随着技术的倒退,分布式 web 利用的遍及,通过 session 治理用户登录状态老本越来越高,因而缓缓倒退成为 token 的形式做登录身份校验,而后通过 token 去取 redis 中的缓存的用户信息,随着之后 jwt 的呈现,校验形式更加简略便捷化,无需通过 redis 缓存,而是间接依据 token 取出保留的用户信息,以及对 token 可用性校验,单点登录更为简略。具体认证的实现过程如下:
什么是 jwt
JSON Web Token (JWT) 是一个凋谢规范 (RFC 7519),它定义了一种紧凑的、自蕴含的形式,用于作为 JSON 对象在各方之间平安地传输信息。该信息能够被验证和信赖,因为它是数字签名的。
jwt 的组成
JWT 是由三段信息形成的,将这三段信息文本用. 链接一起就形成了 Jwt 字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一局部咱们称它为头部(header), 第二局部咱们称其为载荷(payload, 相似于飞机上承载的物品 ),第三局部是签证(signature).
jwt 加密流程
1. 前端发动登录申请
login({commit}, userInfo) {const { username, password} = userInfo
return new Promise((resolve, reject) => {login({ username: username.trim(), password: password }).then(response => {commit('SET_TOKEN', response.token)
setToken(response.token)
resolve()}).catch(error => {reject(error)
})
})
}
2. 后端承受参数进行登录校验
this.app.jwt.sign({username: username}, this.app.config.jwt.web.secret, {expiresIn: expiresIn})
如上后端代码应用了 nodejs 的 egg 框架,应用的是 egg-jwt 插件进行加密和解密;其中代码中的 username 为用户账号(id),secret 则为加密的盐值(解密须要用到),expiresIn 则为生成的 token 的无效工夫。账号密码验证通过后,你能够自定你的加密内容(payload),其中示例代码退出了 username。当然你还能够退出你的 userInfo 等,并且将生成的 toekn 返回到前端(思考:很多编程老手喜爱在登录胜利之后间接返回了用户的信息,作者是不举荐这样做的;这里咱们只返回 token,前端拿到返回的 token 存储起来)
3. 登陆胜利后前端贮存 token
login({username: username.trim(), password: password }).then(response => {commit('SET_TOKEN', response.token)// 登录胜利之后存储 token,你能够存在 vuex 或者 local 中
setToken(response.token)
resolve()}).catch(error => {reject(error)
})
jwt 解密流程
1. 前端发动申请,axios 申请拦截器设置 headers
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['Authorization'] = getToken()}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
作者这里应用的是 axios 库配合拦截器,具体应用参考:https://www.jianshu.com/p/86122178002a
2. 服务端解析 headers 中的 token
const {Status, Code} = ctx.app.config
if (ctx.get('Authorization')) {
// 拿出 token
let token = ctx.get('Authorization');
try {
// 解密 token
ctx.app.jwt.verify(token, secret);
} catch (error) {if (error.name == 'TokenExpiredError') {
//token 过期啦
ctx.responseMsg(Status.FAIL.UNAUTHORIZED, { msg: Code.FAIL.TOKEN_EXPIRED.MSG});
return;
} else {ctx.responseMsg(Status.FAIL.UNAUTHORIZED, { msg: Code.FAIL.TOKEN_EXPIRED.MSG});
return;
}
}
await next();} else {
// 不非法的申请
ctx.responseMsg(Status.FAIL.UNAUTHORIZED, { msg: Code.FAIL.ILLEGAL_TOKEN.MSG});
return;
}
token:申请头中获取到的 token
secret:盐值,和加密的保持一致
其中对于 jwt 的解密工作应用了中间件,对于 egg 中间件参考:https://eggjs.org/zh-cn/basics/middleware.html
3. 服务端依据返回的约定 code 做出相应的响应
service.interceptors.response.use(
response => {
// if the custom code is not 20000, it is judged as an error.
const res = response.data;
if (res.code !== 20000) {
Message({
message: res.msg || 'Error',
type: 'error',
duration: 5 * 1000
})
} else {return res}
},
error => {//console.log(error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
if (error.response.status == 401) {
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {store.dispatch('user/resetToken').then(() => {location.reload()
})
})
} else if (error.response.status == 422) { }
return Promise.reject(error)
}
)
其中的 code 是依据和后端人员约定标准的,能够参考 restful 格调:https://zhuanlan.zhihu.com/p/66148320?utm_source=wechat_session&utm_medium=social&utm_oi=986957734650208256
如上即可实现基于 jwt 认证(第一次写,写得不好大佬们下手轻点)