jwt: JSON Web トークン。これは、要求された ID 情報と ID 権限を確認するために通常使用される認証プロトコルです。これは 3 つの部分で構成されます: ヘッダー、ヘイロード、署名
header: つまり、このトークンを説明する基本情報であるヘッダー情報 json format
{ "alg": "HS256", // 表示签名的算法,默认是 HMAC SHA256(写成 HS256) "type": "JWT" // 表示Token的类型,JWT 令牌统一写为JWT }
payload: ペイロードJSON オブジェクトでもあり、転送する必要がある実際のデータを保存するために使用されます。パスワードなどの機密情報を保存することはお勧めできません。
{ "iss": "a.com", // 签发人 "exp": "1d", // expiration time 过期时间 "sub": "test", // 主题 "aud": "", // 受众 "nbf": "", // Not Before 生效时间 "iat": "", // Issued At 签发时间 "jti": "", // JWT ID 编号 // 可以定义私有字段 "name": "", "admin": "" }
署名は、データの改ざんを防ぐための最初の 2 つの部分の署名です。キーを指定する必要があります。このキーはサーバーのみが知っており、漏洩することはありません。ヘッダーで指定された署名アルゴリズムを使用して、式に従って署名を生成します。
署名を計算したら、ヘッダー、ペイロード、署名の 3 つの部分を 1 つの文字列に結合し、各部分を で区切ります。これにより、トークンが生成されます
# 2. ダブル トークンとは: データへのユーザー アクセス
#クライアントは、サーバーから送信されたリクエスト情報を受信し、2 回カプセル化された axios レスポンス インターセプターに accessToken 無効化情報があるかどうかを判断しますが、レスポンス データは返されません。無効な情報がある場合は、refreshToken を持参して新しい accessToken を要求します。
サーバーは、refreshToken が有効かどうかを検証します。有効な場合はトークンが再生成され、新しいトークンとプロンプト情報がクライアントに返され、無効な場合は無効な情報がクライアントに返されます。
クライアント応答インターセプターは、応答情報に有効なリフレッシュトークンがあるか無効であるかを判断します。無効です。現在のログインからログアウトします。有効です。新しいトークンを再保存し、最後のリクエストのデータのリクエストを続行します。
注意事項
サーバー側のホワイトリストでは、ログインが成功する前にトークンが要求されていないため、サーバーが要求を傍受すると、ログインできなくなります。ログインにトークン検証が必要ないようにホワイトリストをカスタマイズします。
3. サーバー コード
npm install koa-generator -g
koa2 server
cd サーバー jwt をインストールするプロジェクトを入力します。
npm i jsonwebtoken
サーバー側で直接 koa-cors を使用できるようにするため、クロスドメイン
npm i koa-cors
アプリケーションを紹介します。 app.js の cors
const cors=require('koa-cors') ... app.use(cors())
2. ダブル トークン
New utils/token.js
const jwt=require('jsonwebtoken') const secret='2023F_Ycb/wp_sd' // 密钥 /* expiresIn:5 过期时间,时间单位是秒 也可以这么写 expiresIn:1d 代表一天 1h 代表一小时 */ // 本次是为了测试,所以设置时间 短token5秒 长token15秒 const accessTokenTime=5 const refreshTokenTime=15 // 生成accessToken const setAccessToken=(payload={})=>{ // payload 携带用户信息 return jwt.sign(payload,secret,{expireIn:accessTokenTime}) } //生成refreshToken const setRefreshToken=(payload={})=>{ return jwt.sign(payload,secret,{expireIn:refreshTokenTime}) } module.exports={ secret, setAccessToken, setRefreshToken }
スキャフォールディングを使用して直接作成されたプロジェクトは既に app.js に含まれています ルーティング ミドルウェアを使用して、router/index.js
const router = require('koa-router')() const jwt = require('jsonwebtoken') const { getAccesstoken, getRefreshtoken, secret }=require('../utils/token') /*登录接口*/ router.get('/login',()=>{ let code,msg,data=null code=2000 msg='登录成功,获取到token' data={ accessToken:getAccessToken(), refreshToken:getReferToken() } ctx.body={ code, msg, data } }) /*用于测试的获取数据接口*/ router.get('/getTestData',(ctx)=>{ let code,msg,data=null code=2000 msg='获取数据成功' ctx.body={ code, msg, data } }) /*验证长token是否有效,刷新短token 这里要注意,在刷新短token的时候回也返回新的长token,延续长token, 这样活跃用户在持续操作过程中不会被迫退出登录。长时间无操作的非活 跃用户长token过期重新登录 */ router.get('/refresh',(ctx)=>{ let code,msg,data=null //获取请求头中携带的长token let r_tk=ctx.request.headers['pass'] //解析token 参数 token 密钥 回调函数返回信息 jwt.verify(r_tk,secret,(error)=>{ if(error){ code=4006, msg='长token无效,请重新登录' } else{ code=2000, msg='长token有效,返回新的token', data={ accessToken:getAccessToken(), refreshToken:getReferToken() } } }) })
utils/auth.js
const { secret } = require('./token') const jwt = require('jsonwebtoken') /*白名单,登录、刷新短token不受限制,也就不用token验证*/ const whiteList=['/login','/refresh'] const isWhiteList=(url,whiteList)=>{ return whiteList.find(item => item === url) ? true : false } /*中间件 验证短token是否有效 */ const cuth = async (ctx,next)=>{ let code, msg, data = null let url = ctx.path if(isWhiteList(url,whiteList)){ // 执行下一步 return await next() } else { // 获取请求头携带的短token const a_tk=ctx.request.headers['authorization'] if(!a_tk){ code=4003 msg='accessToken无效,无权限' ctx.body={ code, msg, data } } else{ // 解析token await jwt.verify(a_tk,secret.(error)=>{ if(error)=>{ code=4003 msg='accessToken无效,无权限' ctx.body={ code, msg, datta } } else { // token有效 return await next() } }) } } } module.exports=auth
const auth=requier(./utils/auth) ··· app.use(auth)
実際、単純な二重トークン検証を行うだけであれば、静的リソースの解析などの多くのミドルウェアは必要ありません。ただし、時間と利便性を節約するために、koa2 足場を直接使用しました。
最終ディレクトリ構造:
4. フロントエンド コード1. Vue3 Vite フレームワーク
npm init vite@latest client_side
npm i axios
2.使用する定数を定義します
config/constants.js
export const ACCESS_TOKEN = 'a_tk' // 短token字段 export const REFRESH_TOKEN = 'r_tk' // 短token字段 export const AUTH = 'Authorization' // header头部 携带短token export const PASS = 'pass' // header头部 携带长token
キーポイント: Promise を使用して、期限切れのトークンを含むリクエストを配列に保存し、保留状態に保ちます。つまり、resolve() を呼び出さないでください。新しいトークンを取得したら、再度リクエストしてください。 utils/refresh.js
export {REFRESH_TOKEN,PASS} from '../config/constants.js' import { getRefreshToken, removeRefreshToken, setAccessToken, setRefreshToken} from '../config/storage' let subsequent=[] let flag=false // 设置开关,保证一次只能请求一次短token,防止客户多此操作,多次请求 /*把过期请求添加在数组中*/ export const addRequest = (request) => { subscribes.push(request) } /*调用过期请求*/ export const retryRequest = () => { console.log('重新请求上次中断的数据'); subscribes.forEach(request => request()) subscribes = [] } /*短token过期,携带token去重新请求token*/ export const refreshToken=()=>{ if(!flag){ flag = true; let r_tk = getRefershToken() // 获取长token if(r_tk){ server.get('/refresh',Object.assign({},{ headers:{[PASS]=r_tk} })).then((res)=>{ //长token失效,退出登录 if(res.code===4006){ flag = false removeRefershToken(REFRESH_TOKEN) } else if(res.code===2000){ // 存储新的token setAccessToken(res.data.accessToken) setRefreshToken(res.data.refreshToken) flag = false // 重新请求数据 retryRequest() } }) } } }
utlis/server.js
import axios from "axios"; import * as storage from "../config/storage" import * as constants from '../config/constants' import { addRequest, refreshToken } from "./refresh"; const server = axios.create({ baseURL: 'http://localhost:3004', // 你的服务器 timeout: 1000 * 10, headers: { "Content-type": "application/json" } }) /*请求拦截器*/ server.interceptors.request.use(config => { // 获取短token,携带到请求头,服务端校验 let aToken = storage.getAccessToken(constants.ACCESS_TOKEN) config.headers[constants.AUTH] = aToken return config }) /*响应拦截器*/ server.interceptors.response.use( async response => { // 获取到配置和后端响应的数据 let { config, data } = response console.log('响应提示信息:', data.msg); return new Promise((resolve, reject) => { // 短token失效 if (data.code === 4003) { // 移除失效的短token storage.removeAccessToken(constants.ACCESS_TOKEN) // 把过期请求存储起来,用于请求到新的短token,再次请求,达到无感刷新 addRequest(() => resolve(server(config))) // 携带长token去请求新的token refreshToken() } else { // 有效返回相应的数据 resolve(data) } }) }, error => { return Promise.reject(error) } )
import * as constants from "./constants" // 存储短token export const setAccessToken = (token) => localStorage.setItem(constanst.ACCESS_TOKEN, token) // 存储长token export const setRefershToken = (token) => localStorage.setItem(constants.REFRESH_TOKEN, token) // 获取短token export const getAccessToken = () => localStorage.getItem(constants.ACCESS_TOKEN) // 获取长token export const getRefershToken = () => localStorage.getItem(constants.REFRESH_TOKEN) // 删除短token export const removeAccessToken = () => localStorage.removeItem(constants.ACCESS_TOKEN) // 删除长token export const removeRefershToken = () => localStorage.removeItem(constants.REFRESH_TOKEN)
6. インターフェイスのカプセル化
import server from "../utils/server"; /*登录*/ export const login = () => { return server({ url: '/login', method: 'get' }) } /*请求数据*/ export const getData = () => { return server({ url: '/getList', method: 'get' }) }
以上がVue3+Vite がデュアル トークンを使用して無意味なリフレッシュを実現する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。