jwt简介
JSON Web Token(JWT)是非常流行的跨域身份验证解决方案。
jwt构成
HEADER: ALGORITHM & TOKEN TYPE
生成jwt token
安装 jsonwebtoken
npm i -S jsonwebtoken
使用
const jwt = require('jsonwebtoken')
const { PRIVATE_KEY, JWT_EXPIRED } = require('../utils/constant')
login(username, password).then(user => {
if (!user || user.length === 0) {
new Result('登录失败').fail(res)
} else {
const token = jwt.sign(
{ username },
PRIVATE_KEY,
{ expiresIn: JWT_EXPIRED }
)
new Result({ token }, '登录成功').success(res)
}
})
这里需要定义 jwt 的私钥和过期时间,过期时间不宜过短,也不宜过长,课程里设置为 1 小时,实际业务中可根据场景来判断,通常建议不超过 24 小时,保密性要求高的业务可以设置为 1-2 小时:
module.exports = {
// ...
PRIVATE_KEY: 'admin_imooc_node_test_youbaobao_xyz',
JWT_EXPIRED: 60 * 60, // token失效时间
}
jwt认证
安装 express-jwt
npm i -S express-jwt
创建 /router/jwt.js
const expressJwt = require('express-jwt');
const { PRIVATE_KEY } = require('../utils/constant');
const jwtAuth = expressJwt({
secret: PRIVATE_KEY,
credentialsRequired: true // 设置为false就不进行校验了,游客也可以访问
}).unless({
path: [
'/',
'/user/login'
], // 设置 jwt 认证白名单
});
module.exports = jwtAuth;
在 /router/index.js 中使用中间件
const jwtAuth = require('./jwt')
// 注册路由
const router = express.Router()
// 对所有路由进行 jwt 认证
router.use(jwtAuth)
在 /utils/contants.js 中添加:
module.exports = {
// ...
CODE_TOKEN_EXPIRED: -2
}
修改 /model/Result.js:
expired(res) {
this.code = CODE_TOKEN_EXPIRED
this.json(res)
}
修改自定义异常:
router.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
new Result(null, 'token失效', {
error: err.status,
errorMsg: err.name
}).expired(res.status(err.status))
} else {
const msg = (err && err.message) || '系统错误'
const statusCode = (err.output && err.output.statusCode) || 500;
const errorMsg = (err.output && err.output.payload && err.output.payload.error) || err.message
new Result(null, msg, {
error: statusCode,
errorMsg
}).fail(res.status(statusCode))
}
})
前端传入 JWT Token
后端添加路由的 jwt 认证后,再次请求 /user/info 将抛出 401 错误,这是由于前端未传递合理的 Token 导致,下面我们就修改 /utils/request.js,使得前端请求时可以传递 Token:
service.interceptors.request.use(
config => {
// 如果存在 token 则附带在 http header 中
if (store.getters.token) {
config.headers['Authorization'] = `Bearer ${getToken()}`
}
return config
},
error => {
return Promise.reject(error)
}
)
前端去掉 /user/info 请求时传入的 token,因为我们已经从 token 中传入,修改 src/api/user.js:
export function getInfo() {
return request({
url: '/user/info',
method: 'get'
})
}
用户查询 /user/info API
在 /db/index.js 中添加:
function queryOne(sql) {
return new Promise((resolve, reject) => {
querySql(sql)
.then(results => {
if (results && results.length > 0) {
resolve(results[0])
} else {
resolve(null)
}
})
.catch(error => {
reject(error)
})
})
}
在 /services/user.js 中添加:
function findUser(username) {
const sql = `select * from admin_user where username='${username}'`
return queryOne(sql)
}
此时有个问题,前端仅在 Http Header 中传入了 Token,如果通过 Token 获取 username 呢?这里就需要通过对 JWT Token 进行解析了,在 /utils/index.js 中添加 decode 方法:
const jwt = require('jsonwebtoken')
const { PRIVATE_KEY } = require('./constant')
function decode(req) {
const authorization = req.get('Authorization')
let token = ''
if (authorization.indexOf('Bearer') >= 0) {
token = authorization.replace('Bearer ', '')
} else {
token = authorization
}
return jwt.verify(token, PRIVATE_KEY)
}
修改 /router/user.js:
router.get('/info', function(req, res) {
const decoded = decode(req)
if (decoded && decoded.username) {
findUser(decoded.username).then(user => {
if (user) {
user.roles = [user.role]
new Result(user, '获取用户信息成功').success(res)
} else {
new Result('获取用户信息失败').fail(res)
}
})
} else {
new Result('用户信息解析失败').fail(res)
}
})
此时在前端重新登录,登录终于成功了!
关于 RefreshToken
如果你的场景需要授权给第三方 app,那么通常我们需要再增加一个 RefreshToken 的 API,该 API 的用途是根据现有的 Token 获取用户名,然后生成一个新的 Token,这样做的目的是为了防止 Token 失效后退出登录,所以 app 一般会在打开时刷新一次 Token,该 API 的实现方法比较简单,所需的技术之前都已经介绍过,大家可以参考之前的文档进行实现
网友评论