前端双token无感刷新图文详解

以下是对前端双token无感刷新的图文详解:

图片[1]_前端双token无感刷新图文详解_知途无界

一、概念介绍

  1. Token
    • Token是一种用户标识,表示用户身份,类似于身份证件。它是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么服务端会返回Token给前端。前端可以在每次请求时带上Token,以证明自己的合法地位。
  2. 双Token
    • 双Token机制涉及两种类型的Token:Access Token(访问令牌)和Refresh Token(刷新令牌)。
    • Access Token:用户直接用于访问受保护资源的凭证,有效期较短,通常几分钟到几小时。一旦过期,用户需要重新认证以获取新的Access Token。即使Access Token被泄露,由于其有效期短,攻击者利用它进行不当操作的时间窗口有限。
    • Refresh Token:用于在Access Token过期后或过期前的一个定时间内重新获取新的Access Token。有效期通常较长,甚至可以说是永久的,但出于安全考虑,一般会设置过期时间或使用次数限制。Refresh Token通常不会直接发送给客户端,而是保存在服务器端或经过加密后存储在客户端本地(如localStorage或sessionStorage)。

二、双Token无感刷新详解

实现原理

  • 双Token无感刷新的核心在于自动在后台处理Access Token的过期和刷新过程,而无需用户重新登录或感知到这一过程。
  • 用户通过用户名(账号)、密码或其他认证方式向认证服务器请求授权。
  • 认证成功后,服务器返回Access Token和Refresh Token给前端。
  • 前端在访问受保护资源时,将Access Token放入请求头中发送给后端。
  • 如果Access Token有效,后端正常处理请求并返回结果。
  • 如果Access Token过期,后端会返回一个错误响应(如HTTP 401 Unauthorized)。
  • 前端在接收到错误响应后,自动在后台使用Refresh Token向认证服务器请求新的Access Token。
  • 认证服务器验证Refresh Token的有效性后,返回一个新的Access Token和(可选的)新的Refresh Token。
  • 前端更新本地存储的Access Token和Refresh Token,并重新发起之前的请求。

实现步骤

  • 前端实现
    • 设置全局Axios拦截器,用于在请求和响应阶段拦截和处理Token。
    • 在请求拦截器中,从本地存储中获取Access Token,并将其添加到请求头中。
    • 在响应拦截器中,检查后端返回的响应码。如果响应码表示Access Token过期,则自动使用Refresh Token请求新的Access Token。
    • 更新本地存储中的Access Token和Refresh Token,并重新发起因Access Token过期而失败的请求。
  • 后端实现
    • 在认证成功后,生成并返回Access Token和Refresh Token给前端。
    • 在验证Access Token时,如果检测到其已过期,则返回错误响应,并提示前端使用Refresh Token进行刷新。
    • 在验证Refresh Token时,如果其有效,则返回新的Access Token和(可选的)新的Refresh Token给前端。

示例代码

  • 前端Axios拦截器设置示例(伪代码):

    import axios from 'axios';  
    import { getToken, setToken, getRefreshToken, setRefreshToken } from './tokenUtils';  
    import router from '@/router';  
    
    const service = axios.create({  
      baseURL: 'http://your-api-base-url',  
      timeout: 5000,  
    });  
    
    service.interceptors.request.use(  
      config => {  
        const accessToken = getToken();  
        if (accessToken) {  
          config.headers['Authorization'] = `Bearer ${accessToken}`;  
        }  
        return config;  
      },  
      error => {  
        // 处理请求错误  
        return Promise.reject(error);  
      }  
    );  
    
    service.interceptors.response.use(  
      response => {  
        return response;  
      },  
      error => {  
        const { response, config } = error;  
        if (response && response.status === 401 && response.data.code === 'TOKEN_EXPIRED') {  
          // 调用刷新token的函数  
          return refreshToken(config).then(accessToken => {  
            // 更新token并重新发起请求  
            config.headers['Authorization'] = `Bearer ${accessToken}`;  
            return service(config);  
          }).catch(err => {  
            // token刷新失败,清除tokens并跳转到登录页面  
            // 你可以在这里进行页面跳转等操作  
            router.push('/login');  
            return Promise.reject(err);  
          });  
        }  
        return Promise.reject(error);  
      }  
    );  
    
    function refreshToken(config) {  
      const refreshToken = getRefreshToken();  
      if (!refreshToken) {  
        return Promise.reject('No refresh token');  
      }  
      return axios.post('/api/auth/refresh-token', { refreshToken }).then(response => {  
        setToken(response.data.accessToken);  
        setRefreshToken(response.data.refreshToken);  
        return response.data.accessToken;  
      }).catch(err => {  
        return Promise.reject(err);  
      });  
    }  
    
    export default service;

    • 后端Token生成与验证逻辑示例(伪代码,以Java为例):

      // 生成Token的工具类  
      public class TokenUtils {  
          // 使用JWT或其他方式生成Token  
          public static String generateAccessToken(String userId) {  
              // 生成Access Token的逻辑  
          }  
      
          public static String generateRefreshToken(String userId) {  
              // 生成Refresh Token的逻辑  
          }  
      }  
      
      // 认证服务器接口  
      @RestController  
      @RequestMapping("/api/auth")  
      public class AuthController {  
            
          @PostMapping("/login")  
          public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {  
              // 验证用户名和密码  
              // ...  
                
              String userId = // 获取用户ID的逻辑  
              String accessToken = TokenUtils.generateAccessToken(userId);  
              String refreshToken = TokenUtils.generateRefreshToken(userId);  
                
              // 返回Access Token和Refresh Token给前端  
              return ResponseEntity.ok(new LoginResponse(accessToken, refreshToken));  
          }  
            
          @PostMapping("/refresh-token")  
          public ResponseEntity<?> refreshToken(@RequestBody RefreshTokenRequest refreshTokenRequest) {  
              String refreshToken = refreshTokenRequest.getRefreshToken();  
                
              // 验证Refresh Token的有效性  
              // ...  
                
              String newAccessToken = TokenUtils.generateAccessToken(userId); // 重新生成新的Access Token  
              // 可选:生成新的Refresh Token  
              // String newRefreshToken = TokenUtils.generateRefreshToken(userId);  
                
              // 返回新的Access Token(和可选的新的Refresh Token)给前端  
              return ResponseEntity.ok(new RefreshTokenResponse(newAccessToken /*, newRefreshToken*/));  
          }  
      }

      三、注意事项

      1. 安全性
        • 确保Refresh Token的安全存储和传输,避免其被泄露。
        • 设置合理的Token过期时间,以平衡安全性和用户体验。
      2. 性能
        • 在使用双Token机制时,需要注意对后端接口的调用频率,避免对服务器造成过大的压力。
        • 可以通过缓存等技术手段来优化性能。
      3. 用户体验
        • 在实现双Token无感刷新时,要确保用户在使用过程中不会感知到Token的刷新过程,以提高用户体验。
        • 在Token刷新失败时,要提供友好的错误提示和引导用户重新登录。

      通过以上介绍和示例代码,相信读者已经对前端双Token无感刷新有了深入的了解。在实际项目中,可以根据具体需求进行灵活的应用和调整。

      © 版权声明
      THE END
      喜欢就点个赞,支持一下吧!
      点赞62 分享
      评论 抢沙发
      头像
      欢迎您留下评论!
      提交
      头像

      昵称

      取消
      昵称表情代码图片

        暂无评论内容