предисловие
Обновление токена — необходимая часть внешней безопасности. В этой статье рассказывается, как обновлять токены, не ощущая весь процесс от серверной до внешней части. Код страницы был реализован и пройден лично, пожалуйста, ешьте его с уверенностью. Если вам нужен исходный код, пожалуйста, оставьте сообщение в области комментариев. Слова кодировать не просто, лайк и поддержка! ! !
Во-первых, реализация идеи
Реализовано с помощью длинных и коротких токенов: короткие токены используются для запроса данных приложения, а длинные токены используются для получения новых коротких токенов (длина относится к сроку действия).
2. Бэкенд-дизайн
- В бэкенде есть два поля, которые хранят длинные и короткие токены соответственно и каждый раз обновляют их.
- Если срок действия короткого токена истек, вернуть код возврата: 104, если срок действия длинного токена, вернуть код возврата: 108, если запрос выполнен успешно, вернуть код возврата: 0;
- Пропуск в заголовке запроса используется для получения длинного токена клиента, а авторизация в заголовке запроса используется для получения короткого токена клиента.
1. Построить службу узла
Создайте новую папку, откройте ее с помощью vscode, запустите:
npm init -y
установить коа
npm i koa -s
Новый index.js
const Koa = require('koa')
const app = new Koa();
app.use(async(ctx,next)=>{
ctx.body = "这是一个应用中间件";
await next()
})
app.listen(4000,() => {
console.log('server is listening on port 4000')
})
установить нодмон
npm i nodemon -g
настроить package.json
"dev":"nodemon index.js",
Выполнить: npm run dev Доступ: 127.0.0.1:4000, вы можете видеть, что на странице показано, что это промежуточное программное обеспечение приложения.
2. Используйте промежуточное ПО для маршрутизации
Установить
npm i koa-router -S
Новые маршруты/index.js
const router = require("koa-router")();
let accessToken = "init_s_token"; //短token
let refreshToken = "init_l_token"; //长token
/* 5s刷新一次短token */
setInterval(() => {
accessToken = "s_tk" + Math.random();
}, 5000);
/* 一小时刷新一次长token */
setInterval(() => {
refreshToken = "l_tk" + Math.random();
}, 600000);
/* 登录接口获取长短token */
router.get("/login", async (ctx) => {
ctx.body = {
returncode: 0,
accessToken,
refreshToken,
};
});
/* 获取短token */
router.get("/refresh", async (ctx) => {
//接收的请求头字段都是小写的
let { pass } = ctx.headers;
if (pass !== refreshToken) {
ctx.body = {
returncode: 108,
info: "长token过期,重新登录",
};
} else {
ctx.body = {
returncode: 0,
accessToken,
};
}
});
/* 获取应用数据1 */
router.get("/getData", async (ctx) => {
let { authorization } = ctx.headers;
if (authorization !== accessToken) {
ctx.body = {
returncode: 104,
info: "token过期",
};
} else {
ctx.body = {
code: 200,
returncode: 0,
data: { id: Math.random() },
};
}
});
/* 获取应用数据2 */
router.get("/getData2", async (ctx) => {
let { authorization } = ctx.headers;
if (authorization !== accessToken) {
ctx.body = {
returncode: 104,
info: "token过期",
};
} else {
ctx.body = {
code: 200,
returncode: 0,
data: { id: Math.random() },
};
}
});
module.exports = router;
Изменить index.js
//删除
app.use(async(ctx,next)=>{
ctx.body = "这是一个应用中间件";
await next()
})
//新增
const index = require('./routes/index')
app.use(index.routes(),index.allowedMethods())
3. Междоменная обработка
Установить
npm i koa2-cors
использовать:
const cors = require('koa2-cors');
app.use(cors());
окончательный файл index.js
const Koa = require('koa')
const app = new Koa();
const index = require('./routes/index')
const cors = require('koa2-cors');
app.use(cors());
app.use(index.routes(),index.allowedMethods())
app.listen(4000,() => {
console.log('server is listening on port 4000')
})
Структура каталога:
Перезапустите npm run dev и сервер готов
3. Интерфейсный дизайн
1. Определите используемые константы
Новый конфиг/constant.js
/* localStorage存储字段 */
export const ACCESS_TOKEN = "s_tk"; //短token
export const REFRESH_TOKEN = "l_tk"; //长token、
/* HTTP请求头字段 */
export const AUTH = "Authorization"; //存放短token
export const PASS = "PASS"; //存放长token
Новый конфиг/returnCodeMap.js
// 在其它客户端被登录
export const CODE_LOGGED_OTHER = 106;
// 重新登陆
export const CODE_RELOGIN = 108;
// token过期
export const CODE_TOKEN_EXPIRED = 104;
//接口请求成功
export const CODE_SUCCESS = 0;
2. Услуги по упаковке
Ключевой момент: хранить запрос с просроченным токеном в массиве с помощью промиса, держать промис в состоянии ожидания (т.е. не вызывать резолв) и перезапрашивать по одному при получении нового короткого токена
Если цепочка обещаний не поддерживается, она будет рассматриваться как новый запрос, и содержимое страницы не будет обновляться.
установить аксиомы
npm i axios -S
Новый сервис/index.js
import axios from "axios";
import { refreshAccessToken, addSubscriber } from "./refresh";
import { clearAuthAndRedirect } from "./clear";
import {
CODE_LOGGED_OTHER,
CODE_RELOGIN,
CODE_TOKEN_EXPIRED,
CODE_SUCCESS,
} from "../config/returnCodeMap";
import { ACCESS_TOKEN, AUTH } from "../config/constant";
const service = axios.create({
baseURL: "//127.0.0.1:4000",
timeout: 30000,
});
service.interceptors.request.use(
(config) => {
let { headers } = config;
const s_tk = localStorage.getItem(ACCESS_TOKEN);
s_tk &&
Object.assign(headers, {
[AUTH]: s_tk,
});
return config;
},
(error) => {
return Promise.reject(error);
}
);
service.interceptors.response.use(
(response) => {
let { config, data } = response;
//retry:第一次请求过期,接口调用refreshAccessToken,第二次重新请求,还是过期则reject出去
let { retry } = config;
/* 延续Promise链 */
return new Promise((resolve, reject) => {
if (data["returncode"] !== CODE_SUCCESS) {
if ([CODE_LOGGED_OTHER, CODE_RELOGIN].includes(data.returncode)) {
clearAuthAndRedirect();
} else if (data["returncode"] === CODE_TOKEN_EXPIRED && !retry) {
config.retry = true;
addSubscriber(() => resolve(service(config)));
refreshAccessToken();
} else {
return reject(data);
}
} else {
resolve(data);
}
});
},
(error) => {
return Promise.reject(error);
}
);
export default service;
Новый сервис/refresh.js
import service from "./index";
import { ACCESS_TOKEN, REFRESH_TOKEN, PASS } from "../config/constant";
import { clearAuthAndRedirect } from "./clear";
let subscribers = [];
let pending = false; //同时请求多个过期链接,保证只请求一次获取短token
export const addSubscriber = (request) => {
subscribers.push(request);
};
export const retryRequest = () => {
subscribers.forEach((request) => request());
subscribers = [];
};
export const refreshAccessToken = async () => {
if (!pending) {
try {
pending = true;
const l_tk = localStorage.getItem(REFRESH_TOKEN);
if (l_tk) {
/* 重新获取短token */
const { accessToken } = await service.get(
"/refresh",
Object.assign({}, { headers: { [PASS]: l_tk } })
);
localStorage.setItem(ACCESS_TOKEN, accessToken);
retryRequest();
}
return;
} catch (e) {
clearAuthAndRedirect();
return;
} finally {
pending = false;
}
}
};
Новый сервис/clear.js
import {ACCESS_TOKEN} from '../config/constant'
/* 清除长短token,并定位到登录页(在项目中使用路由跳转) */
export const clearAuthAndRedirect = () =>{
localStorage.removeItem(ACCESS_TOKEN)
window.location.href = '/login'
}
3. Используйте
React используется здесь для достижения того же эффекта, что и при использовании vue.
//App.js
import { useState } from "react";import service from "./service/index.js";import { ACCESS_TOKEN, REFRESH_TOKEN } from "./config/constant";const App = () => { const [data1, setData1] = useState(); const [data2, setData2] = useState(); const getData = () => { service.get("/getData").then((res) => { setData1(res.data.id); }); service.get("/getData2").then((res) => { setData2(res.data.id); }); }; const getToken = () => { service.get("/login").then((res) => { //存储长token localStorage.setItem(REFRESH_TOKEN, res.refreshToken); //存储短token localStorage.setItem(ACCESS_TOKEN, res.accessToken); }); }; return ( <div> {data1}--{data2} <button onClick={getData}>按钮</button> <button onClick={getToken}>登录</button> </div> );};export default App;
Запустите проект, чтобы увидеть эффект
Сначала нажмите кнопку входа в систему, чтобы получить длинные и короткие токены, а затем нажмите кнопку через 5 секунд, чтобы получить данные приложения (вышеупомянутая серверная часть устанавливает срок действия короткого токена 5s).
Данные страницы могут быть обновлены, а интерфейс обновления вызывается только один раз.