Skip to main content

One post tagged with "token"

View All Tags

· 7 min read
Jeffrey

一、Token

  • 什么是 Token? Token 指访问资源的凭据,是一种身份认证的方式,它是解决跨域认证的最流行的一种方式。

  • 为什么用 Token? 以前较为流行的是通过 session 去做身份认证,session 是通过服务器中保存会话数据来做身份认证,这种方式会导致在高并发中服务器压力过大的情况,还有就是,如果是服务器集群,那么就需要这些服务器 session 共享。

Token 不在服务器中保存会话数据,而是保存在客户端。每次请求的 headers 中存入 Token,在服务器中判断 Token 的有效性,是否可以访问资源。

  • 传统 Token 和 JWT 的区别

    • 传统 Token 用户发起登录请求,登录成功之后返回 Token,并且存于数据库,用户访问资源的时候需要携带 Token,服务端获取 Token 之后和数据库中的对比。
    • JWT 用户发起登录请求,登录成功之后返回 Token,但是不存于数据库,用户访问资源的时候需要携带 Token,服务端获取 Token 之后去校验 Token 的合法性。

二、JWT 实现过程

  • JWT 分为三个部分 header、payload、verify signature

  • header

内部包含有签名算法、Token 类型,然后通过 base64url 算法转成字符串

//明文例子:
{
"alg":"HS256",
"typ":"JWT"
}
  • payload 内部包含 JWT 标准数据和自定义数据,然后通过 base64url 算法转成字符串 JWT 标准数据常见的有:
iss:提供方。
sub:主题,一般是用户ID。
exp:过期时间。
iat:创建时间。
jti:token的唯一标识。

可选择性使用以上标准数据

//明文例子:
{
"id": 3,
"name": "Bmongo",
"age": 18,
"iat": 1588139323,
"exp": 1588139333
}

注意:由于 JWT 是默认不加密的,所以在这边不要存敏感信息

  • verify signature 这部分是对前两部分的签名,防止数据的篡改 secret 是服务器端保存的密钥,只有服务器端知道,再使用 header 中所指定的签名算法对上面的俩部分进行签名,按照以下公式生成签名
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)

复制代码 算出签名之后,把三部分通过.分隔开返回给用户就行了

  • 客户端请求

  • 每次客户端的请求都需要带上这个 token,一般是把 token 写入到请求的 headers 中

三、Node.js 中使用

Node.js 中使用 JWT

1.开始使用

通过 npm 包 jsonwebtoken 来完成 token 的生成和验证

npm install --save jsonwebtoken

2.生成、验证 Token

auth/token.js
const jwt = require('jsonwebtoken')
//撒盐,加密时候混淆
const secret = '113Bmongojsdalkfnxcvmas'

//生成token
//info也就是payload是需要存入token的信息
function createToken(info) {
let token = jwt.sign(info, secret, {
//Token有效时间 单位s
expiresIn: 60 * 60 * 10,
})
return token
}

//验证Token
function verifyToken(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, secret, (error, result) => {
if (error) {
reject(error)
} else {
resolve(result)
}
})
})
}
const whiteList = ['/api/login']

function verifyTokenMiddle(req, res, next) {
if (!whiteList.includes(req.url)) {
// 发起请求是headers.authorization 添加token
verifyToken(req.headers.authorization)
.then((res) => {
next()
})
.catch((e) => {
res.status(401).send('invalid token')
})
} else {
next()
}
}

module.exports = {
verifyTokenMiddle,
createToken,
}

3.使用

app.js
var createError = require('http-errors')var express = require('express')var path = require('path')var cookieParser = require('cookie-parser')var logger = require('morgan')const { verifyTokenMiddle } = require('./auth/token')const expressSession = require('express-session')const redis = require('redis')const { redisConfig } = require('./config/config')// 创建Redis连接配置const redisClient = redis.createClient(redisConfig)const RedisStore = require('connect-redis')(expressSession)redisClient.on('connect', function () {  console.log('Redis client connected')})redisClient.on('error', function (e) {  console.error(e)})var app = express()app.use(express.static(path.join(__dirname, '/public')))//设置跨域访问 -- 开始 --app.all('*', function (req, res, next) {  res.header('Access-Control-Allow-Origin', '*') //的允许所有域名的端口请求(跨域解决)  res.header('Access-Control-Allow-Headers', 'Content-Type')  res.header('Access-Control-Allow-Methods', '*')  res.header('Content-Type', 'application/json;charset=utf-8')  if (req.method === 'OPTIONS') {    res.end()  } else {    next()  }})app.use(cookieParser())app.use(  expressSession({    store: new RedisStore({ client: redisClient }),    name: 'session_id', // 默认connect.sid    secret: 'yupi996', // 设置签名秘钥  内容可以任意填写    resave: true, // 强制保存,如果session没有被修改也要重新保存,默认true(推荐false)    saveUninitialized: true, // 如果原先没有session那么就设置,否则不设置(推荐true)    rolling: true, // 每次请求更新有效时长    cookie: {      domain: '.mianshiya.com',      // 全局设置cookie,就是访问随便api就会设置cookie,也可以在登录的路由下单独设置      maxAge: 1000 * 60 * 60 * 24 * 15, // 15 天后过期      httpOnly: true, // 是否允许客户端修改cookie,(默认true 不能被修改)    },  }))// app.use(logger('dev'));app.use(express.json())app.use(express.urlencoded({ extended: false }))// 添加验证token中间件app.use(verifyTokenMiddle)// 注册路由setRoutes()app.use(function (req, res, next) {  next(createError(404))})// error handlerapp.use(function (err, req, res, next) {  console.log(2)  // set locals, only providing error in development  res.locals.message = err.message  res.locals.error = req.app.get('env') === 'development' ? err : {}  // render the error page  res.status(err.status || 500)  res.send('error')})/** *  注册路由 */function setRoutes() {  const API = '/api/'  const fileDir = 'routes'  const noIncludePath = ['index']  const getPrefix = (path) => path.split('.')[0]  require('fs')    .readdirSync(path.join(__dirname, fileDir))    .filter((path) => noIncludePath.some((noPath) => !path.startsWith(noPath)))    .forEach((api) => {      app.use(        API + getPrefix(api),        require(path.join(__dirname, fileDir + '/') + getPrefix(api))      )    })}module.exports = app
routers/login
var express = require('express')
var router = express.Router()
const { createToken } = require('../auth/token')
router.get('/', function (req, res) {
var user = {
name: 'zs',
ps: 123,
}
let token = createToken(user)
res.send(token)
})
module.exports = router

总结

tip
  • 登录成功之后 服务端根据用户信息生成 token
  • 非登录接口发起请求是需设置 headers.authorization = token
  • 添加token验证中间件,除登录接口 其他接口全部验证 token
  • 验证成功通过,不成功提示错误 :::