无感刷新token(从后端到前端整个流程)

前言

token刷新是前端安全中必要的一部分,本文从后端到前端整个流程介绍如何实现无感刷新token。页面代码亲自实现并跑通,请放心食用。如需源码,请在评论区留言。码字不易,点赞支持!!!

一、实现思路

通过长短token实现:短token用来请求应用数据,长token用于获取新的短token(长短指的是过期时间)

二、后端设计

  • 后端存有两个字段,分别保存长短token,并且每一段时间更新他们
  • 短token过期,返回 returncode:104;长token过期,返回 returncode: 108;请求成功返回returncode: 0;
  • 请求头中pass用来接收客户端长token,请求头中authorization用来接收客户端短token

1. 搭建node服务

创建一个新文件夹,通过vscode打开,运行:

npm init -y 

安装koa

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')
}) 

安装nodemon

npm i nodemon -g 

配置package.json

"dev":"nodemon index.js", 

运行:npm run dev 访问:127.0.0.1:4000,可以看到页面显示这是一个应用中间件

2. 使用路由中间件

安装

npm i koa-router -S 

新建routes/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,这时服务端已准备好

三、前端设计

1. 定义使用到的常量

新建 config/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 

新建 config/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. 封装服务

关键点:将token过期的请求,借助Promise将请求存进数组中,让这个Promise一直处于pending状态(即不调用resolve),当获取到新的短token时,再逐个重新请求

如果不保持promise链,就会当成一个新的请求,页面内容不会更新

安装axios

npm i axios -S 

新建 service/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; 

新建 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;
    }
  }
}; 

新建 service/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; 

运行项目,查看效果

先点击登录按钮获取长短token,过5秒再去点击按钮,获取应用数据(上面后端设置了短token 5s过期)

页面数据能更新,并且refresh接口只调用一次

essToken); }); }; return (

{data1}–{data2} 按钮 登录
);};export default App;



运行项目,查看效果

先点击登录按钮获取长短token,过5秒再去点击按钮,获取应用数据(上面后端设置了短token 5s过期)

页面数据能更新,并且refresh接口只调用一次

# 学习资料分享

当然,**只给予计划不给予学习资料的行为无异于耍流氓**,###                   如果你对网络安全入门感兴趣,那么你点击这里**👉**[CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享](https://mp.weixin.qq.com/s/kmoGmxbG8GrCsPzWTPIQyw)

**如果你对网络安全感兴趣,学习资源免费分享,保证100%免费!!!(嘿客入门教程)**

**👉网安(嘿客)全套学习视频👈**

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

### 

### ![img](https://img-blog.csdnimg.cn/img_convert/d1c617b78ee48eda7601e5b803e69276.png)

### **👉网安(嘿客红蓝对抗)所有方向的学习路线****👈**

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

### ![img](https://img-blog.csdnimg.cn/img_convert/de55dfd737dae0cf88e416d0454b17a8.png)

###  学习资料工具包

压箱底的好资料,全面地介绍网络安全的基础理论,包括逆向、八层网络防御、汇编语言、白帽子web安全、密码学、网络安全协议等,将基础理论和主流工具的应用实践紧密结合,有利于读者理解各种主流工具背后的实现机制。

![在这里插入图片描述](https://img-blog.csdnimg.cn/9609a53465cf4253b492a5185896fa71.png)

**面试题资料**

独家渠道收集京东、360、天融信等公司测试题!进大厂指日可待!
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f5f267c281c543fb9cc9af53b9003a37.png)

### **👉****嘿客必备开发工具****👈**

工欲善其事必先利其器。学习**嘿**客常用的开发软件都在这里了,给大家节省了很多时间。

### 这份完整版的网络安全(**嘿**客)全套学习资料已经上传至CSDN官方,朋友们如果需要点击下方链接**也可扫描下方微信二v码获取网络工程师全套资料**【保证100%免费】



### ![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/16c400294b6fda8f01400f24f1f12b0c.png)

###               如果你对网络安全入门感兴趣,那么你点击这里**👉**[CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享](https://mp.weixin.qq.com/s/kmoGmxbG8GrCsPzWTPIQyw)
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这道题涉及到前端中的认证和授权问题,以及如何在前端中处理 token 刷新的问题。 认证和授权是指用户在访问系统资源时需要进行身份验证和权限验证。前端通常会在用户登录后返回一个 token,然后在每次请求时将 token 带上,服务端会通过 token 来判断用户是否有权限访问资源。 在使用 token 进行认证和授权时,由于 token 有一定的有效期限制,因此需要在 token 过期前进行刷新。在前端中可以通过定时器来定时检查 token 的有效期,当 token 即将过期时,发送一个刷新 token 的请求,获取新的 token,然后将新的 token 存储在本地,同时更新请求头中的 token。 以下是一个示例代码: ```javascript // 定义定时器,每隔一段时间检查 token 是否即将过期 let timer = setInterval(() => { let token = localStorage.getItem('token') let expiredTime = localStorage.getItem('expiredTime') if (new Date().getTime() > expiredTime - 60000) { // token 即将过期 refreshToken(token) } }, 1000) // 刷新 token 的函数 function refreshToken(token) { // 发送请求获取新的 token axios.post('/refreshToken', {token: token}) .then(res => { // 更新本地存储的 token 和过期时间 localStorage.setItem('token', res.data.token) localStorage.setItem('expiredTime', new Date().getTime() + res.data.expiresIn * 1000) // 更新请求头中的 token axios.defaults.headers.common['Authorization'] = 'Bearer ' + res.data.token }) } ``` 需要注意的是,当用户退出登录时,需要及时清空本地存储的 token 和过期时间,并停止定时器。 ```javascript function logout() { localStorage.removeItem('token') localStorage.removeItem('expiredTime') clearInterval(timer) } ``` 总之,前端刷新 token 的主要思路就是定时检查 token 的有效期,当即将过期时发送一个刷新 token 的请求,获取新的 token,然后更新本地存储的 token 和过期时间,并更新请求头中的 token

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值