最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

springboot+vue實現(xiàn)Token自動續(xù)期(雙Token方案)

 更新時間:2024年10月09日 10:54:38   作者:登山人在路上  
雙Token方案通過訪問令牌和刷新令牌提高用戶登錄安全性和體驗,訪問令牌有效期短,包含用戶信息,用于請求校驗,本文就來介紹一下springboot+vue實現(xiàn)Token自動續(xù)期(雙Token方案),感興趣的可以了解一下

本篇文章的代碼是在springboot+vue項目中使用jwt實現(xiàn)登錄認證的基礎上實現(xiàn)的Token自動續(xù)期的功能。

一、雙Token方案介紹

雙token解決方案是一種用于增強用戶登錄安全性和提升用戶體驗的認證機制。它主要涉及兩個令牌:訪問令牌(accessToken)和刷新令牌(refreshToken)。以下是對雙token解決方案的詳細介紹:

1. 令牌類型與功能

訪問令牌(accessToken):
有效期較短,通常設置為較短的時間,如兩小時或根據業(yè)務需求自定義(如10分鐘)。 儲存用戶信息權限等,包含用戶相關信息,如UserID、Username等。 用于前端與后端之間的通信認證,前端在每次請求時攜帶此令牌進行校驗。

刷新令牌(refreshToken):
有效期較長,可以設置為一星期、一個月或更長時間,具體根據業(yè)務需求自定義。 不儲存額外信息,只儲存用戶id,用于在accessToken過期后重新生成新的accessToken。 由于有效期長,因此降低了用戶需要頻繁登錄的頻率。

2.雙Token方案的優(yōu)點

增強安全性:通過短期有效的accessToken和長期有效的refreshToken的結合,即使accessToken泄露,攻擊者也只能在有限時間內進行模擬用戶行為,降低了安全風險。

提升用戶體驗:由于refreshToken的存在,用戶無需頻繁登錄,特別是在長時間操作或后臺服務場景下,提高了用戶體驗。

3.實現(xiàn)流程

登錄:用戶輸入用戶名和密碼進行登錄,后端驗證成功后生成accessToken和refreshToken,并發(fā)送給前端。

請求校驗:前端在每次請求時攜帶accessToken進行校驗,如果accessToken有效,則允許請求繼續(xù);如果無效但refreshToken有效,則使用refreshToken重新生成accessToken。

令牌刷新:當accessToken過期但refreshToken未過期時,前端可以使用refreshToken向后端請求新的accessToken,無需用戶重新登錄。

登出:用戶登出時,后端需要同時使accessToken和refreshToken失效,以確保用戶登出后的安全性。

二、具體實現(xiàn)

1.后端實現(xiàn)

1.1 jwt工具類

package com.etime.util;

import io.jsonwebtoken.*;

import java.util.Date;
import java.util.Map;
import java.util.UUID;

/**
 * @Date 2024/6/10 10:04
 * @Author liukang
 **/
public class JwtUtil {
//    private static long expire = 1000*60*5;// 單位是毫秒
    private static String secret = "secret";

    /**
     * 創(chuàng)建jwt
     * @author liukang
     * @date 10:36 2024/6/10
     * @param expire
     * @param map
     * @return java.lang.String
     **/
    public static String generateToken(long expire, Map map){
        // 床jwt構造器
        JwtBuilder jwtBuilder = Jwts.builder();
        // 生成jwt字符串
        String jwt = jwtBuilder
                //頭部
                .setHeaderParam("typ","JWT")
                .setHeaderParam("alg","HS256")
                // 載荷
                .setClaims(map) // 設置多個自定義數據  位置只能放在前面,如果放在后面,那前面的載荷會失效
                .setId(UUID.randomUUID().toString())// 唯一標識
                .setIssuer("liukang")// 簽發(fā)人
                .setIssuedAt(new Date())// 簽發(fā)時間
                .setSubject("jwtDemo")// 主題
                .setExpiration(new Date(System.currentTimeMillis()+expire))//過期時間
                // 自定義數據
//                .claim("uname","liukang")
                // 簽名
                .signWith(SignatureAlgorithm.HS256,secret)
                .compact();
        return jwt;
    }
    /**
     * 創(chuàng)建jwt
     * @author liukang
     * @date 10:36 2024/6/10
     * @param expire
     * @return java.lang.String
     **/
    public static String generateToken(long expire){
        // 床jwt構造器
        JwtBuilder jwtBuilder = Jwts.builder();
        // 生成jwt字符串
        String jwt = jwtBuilder
                //頭部
                .setHeaderParam("typ","JWT")
                .setHeaderParam("alg","HS256")
                // 載荷
                .setId(UUID.randomUUID().toString())// 唯一標識
                .setIssuer("liukang")// 簽發(fā)人
                .setIssuedAt(new Date())// 簽發(fā)時間
                .setSubject("jwtDemo")// 主題
                .setExpiration(new Date(System.currentTimeMillis()+expire))//過期時間
                // 自定義數據
//                .claim("uname","liukang")
                // 簽名
                .signWith(SignatureAlgorithm.HS256,secret)
                .compact();
        return jwt;
    }
    /**
     * 解析jwt
     * @author liukang
     * @date 10:36 2024/6/10
     * @param jwt
     * @return io.jsonwebtoken.Claims
     **/
    public static Claims parseToken(String jwt){
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secret).parseClaimsJws(jwt);
        Claims playload = claimsJws.getBody();
        return playload;

    }
}

1.2 響應工具類

代碼如下(示例):

package com.etime.util;

import com.etime.vo.ResponseModel;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @Date 2024/6/10 10:00
 * @Author liukang
 **/
public class ResponseUtil {
    public static void write(ResponseModel rm, HttpServletResponse response) throws IOException {
        // 構造響應頭
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("utf-8");
        // 解決跨域問題 設置跨域頭
        response.setHeader("Access-Control-Allow-Origin","*");
        // 輸出流
        PrintWriter out = response.getWriter();
        // 輸出
        out.write(new ObjectMapper().writeValueAsString(rm));
        // 關閉流
        out.close();
    }
}

1.3 實體類

登錄用戶實體類

package com.etime.entity;

import lombok.Data;

/**
 * @Date 2024/6/10 10:39
 * @Author liukang
 **/
@Data
public class User {
    private String username;
    private String password;
}

響應vo類

package com.etime.vo;

import lombok.Data;

import java.util.Objects;

/**
 * @Date 2024/6/10 10:37
 * @Author liukang
 **/
@Data
public class ResponseModel {
    private Integer code;
    private String msg;
    private Object token;

    public ResponseModel(Integer code, String msg, Object token) {
        this.code = code;
        this.msg = msg;
        this.token = token;
    }
}

1.4 過濾器

package com.etime.filter;

import com.etime.util.JwtUtil;
import com.etime.util.ResponseUtil;
import com.etime.vo.ResponseModel;
import com.sun.deploy.net.HttpResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description jwt過濾器
 * @Date 2024/6/10 9:46
 * @Author liukang
 **/
@WebFilter(urlPatterns = "/*") // 過濾所有路徑
public class JwtFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 得到兩個對象
        HttpServletRequest request =  (HttpServletRequest) servletRequest;
        HttpServletResponse response =  (HttpServletResponse) servletResponse;
        //直接放行
        if(HttpMethod.OPTIONS.toString().equals(request.getMethod())){
            filterChain.doFilter(request,response);
            return;
        }
        String requestURI = request.getRequestURI(); // 不含主機和端口號
        if(requestURI.contains("/login")){
            filterChain.doFilter(request,response);
            return;
        }
        // 得到請求頭的信息(accessToken)
        String token = request.getHeader("accessToken");
        if(!StringUtils.hasText(token)){
            //響應前端錯誤的消息提示
            ResponseModel responseModel = new ResponseModel(500,"failure","令牌缺失!");
            ResponseUtil.write(responseModel,response);
            return;
        }
        // 解析Token信息
        try {
            JwtUtil.parseToken(token);
        }catch (Exception e){
            //響應前端錯誤的消息提示
            ResponseModel responseModel = new ResponseModel(401,"failure","令牌過期!");
            ResponseUtil.write(responseModel,response);
            return;
        }
        filterChain.doFilter(request,response);


    }
}

1.5 controller

登錄Controller

package com.etime.controller;

import com.etime.entity.User;
import com.etime.util.JwtUtil;
import com.etime.vo.ResponseModel;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * @Date 2024/6/10 10:38
 * @Author liukang
 **/
@RestController
@CrossOrigin
public class LoginController {
    @PostMapping("/login")
    public ResponseModel login(@RequestBody User user){
        Integer code = 200;
        String msg = "success";
        String accessToken = null;
        String refreshToken = null;
        Map tokenMap = new HashMap();
        if(user.getUsername().equals("admin")&&user.getPassword().equals("123")){
            // 生成jwt
            accessToken = JwtUtil.generateToken(1000*10);// 設置有效期為10s
            refreshToken = JwtUtil.generateToken(1000*30);// 設置有效期為30s
            tokenMap.put("accessToken",accessToken);
            tokenMap.put("refreshToken",refreshToken);
        }else {
            code = 500;
            msg = "failure";
        }
        return new ResponseModel(code,msg,tokenMap);
    }


}

測試請求Controller

package com.etime.controller;

import com.etime.vo.ResponseModel;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Date 2024/6/10 12:51
 * @Author liukang
 **/
@CrossOrigin
@RestController
public class TestController {
    @PostMapping("/test")
    public ResponseModel test() {
        return new ResponseModel(200,"success","測試請求接口成功!");
    }

}

刷新Token的Controller

package com.etime.controller;

import com.etime.util.JwtUtil;
import com.etime.vo.ResponseModel;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * @Date 2024/6/10 15:48
 * @Author liukang
 **/
@CrossOrigin
@RestController
public class NewTokenController {
    @GetMapping("/newToken")
    public ResponseModel newToken(){
        String accessToken = JwtUtil.generateToken(1000*10);
        String refreshToken = JwtUtil.generateToken(1000*30);
        Map tokenMap = new HashMap();
        tokenMap.put("accessToken",accessToken);
        tokenMap.put("refreshToken",refreshToken);
        return new ResponseModel(200,"success",tokenMap);
    }
}

1.6 啟動類

package com.etime;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

/**
 * @Author liukang
 * @Date 2022/7/4 11:32
 */
@SpringBootApplication
@ServletComponentScan(basePackages = "com.etime.filter")// 這個包下激活WebFilter這個注解
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

2、前端實現(xiàn)

2.1 登錄頁面

<template>
    <div class="hello">
      <form>
        用戶名:<input v-model="username"/>
        

        密碼<input v-model="password" />
        

        <button @click="login">登錄</button>
      </form>

    </div>
  </template>

  <script>
  export default {
    data () {
      return {
        username:'',
        password:'',

      }
    },

    methods:{

        login(){
          this.axios.post('http://localhost:8088/login',{

            username:this.username,
            password:this.password,

          }).then(response => {
              console.log(response.data);
              if(response.data.code==200){
                sessionStorage.setItem("accessToken",response.data.token.accessToken)
                sessionStorage.setItem("refreshToken",response.data.token.refreshToken)
                this.$router.push({ path: 'index'});
              }
            }).catch(error => {
              console.error(error);
            });

        }
    },
  }
  </script>

  <style scoped>

  </style>

2.2 index頁面

<template>
    <div>
        <button @click="test">請求受保護的接口</button>

    </div>
  </template>

  <script>
import intercepterConfig from './js/config'

  export default {
    data () {
      return {
      }
    },
    methods:{
      test(){
        const accessToken = sessionStorage.getItem('accessToken')
        let token = null
        if(accessToken){
          token = accessToken
        }
        // console.log(token)
        this.axios.post('http://localhost:8088/test',{},/*{headers:{accessToken:'token'}}*/
        ).then(response => {
          // if(response.data.code==200){
            console.log(response.data);
          // }
        }).catch(error => {
          console.error(error);
        });
      },
    },
  }
  </script>


  <style scoped>

  </style>

2.3 請求攔截器和響應攔截器

import axios from "axios";
//axios請求攔截器
axios.interceptors.request.use(
  config=>{// 正確的請求攔截器
    let token = null;
    let url = config.url
    // url.indexOf('/newToken')==-1  如果是刷新Token的請求 不用在攔截器里面加accessToken 這個請求已經在請求頭中設置accessToken,加了會覆蓋
    if(sessionStorage.getItem('accessToken')!=null && url.indexOf('/newToken')==-1){
      token = sessionStorage.getItem('accessToken')
      config.headers['accessToken'] = token
    }

    // 加入頭信息的配置
    return config // 這句沒寫請求會發(fā)不出去
  },
  error=>{ // 出現(xiàn)異常的請求攔截器
    return Promise.reject(error)
  })
// axios響應攔截器
axios.interceptors.response.use(
  async res => {
    // 判斷 401狀態(tài)碼 自動續(xù)期
    if (res.data.code == 401 &&!res.config.isRefresh) {//!res.config.isRefresh  不是刷新Token的請求才攔截 是則不攔截
      // 1.自動續(xù)期
      const res2 = await getNewToken()
      if(res2.data.code == 200){
        console.log('自動續(xù)期成功'+new Date().toLocaleString())
        // 2.更新sessionStorage里面的Token   沒有這一步會死循環(huán)
        sessionStorage.setItem('accessToken',res2.data.token.accessToken)
        sessionStorage.setItem('refreshToken',res2.data.token.refreshToken)
        //3.重新發(fā)送請求
        res = await axios.request(res.config)// res.config 代表請求的所有參數(這里是上一次請求的所有參數),包括url和攜帶的所有數據

      }

    }
    return res     // 將重新請求的響應作為響應返回
  },
  error=>{
    return Promise.reject(error)
  })

function getNewToken(){
  let url = "http://localhost:8088/newToken"
  let token = null
  if(sessionStorage.getItem('refreshToken')!=null){
    token = sessionStorage.getItem('refreshToken')
  }
  return  axios.get(url,{headers:{accessToken:token},isRefresh:true})
  // 注意這里參數是accessToken:token  因為后端過濾器里面獲取的是accessToken,所以要寫這個,不然過濾器通不過過濾器
}

效果展示

1.登錄頁面

在這里插入圖片描述

2.輸入用戶名和密碼點擊【登錄】

在這里插入圖片描述

3.點擊【請求受保護的資源】按鈕

在這里插入圖片描述

3.等待10秒,accessToken過期,但refreshToken未過期時,點擊【請求受保護的資源】按鈕

在這里插入圖片描述

4.等待30秒后,refreshToken和accessToken都過期,再次點擊【請求受保護的資源】按鈕

在這里插入圖片描述

到此這篇關于springboot+vue實現(xiàn)Token自動續(xù)期(雙Token方案)的文章就介紹到這了,更多相關springboot Token自動續(xù)期內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家! 

相關文章

  • JDK8的lambda方式List轉Map的操作方法

    JDK8的lambda方式List轉Map的操作方法

    account是一個返回本身的lambda表達式,其實還可以使用Function接口中的一個默認方法代替,使整個方法更簡潔優(yōu)雅,這篇文章主要介紹了JDK8的lambda方式List轉Map,需要的朋友可以參考下
    2022-07-07
  • java中?${}?和?#{}?有什么區(qū)別

    java中?${}?和?#{}?有什么區(qū)別

    本文主要介紹了java中${}和#{}有什么區(qū)別,${}和#{}都是MyBatis中用來替換參數的,它們都可以將用戶傳遞過來的參數,替換到MyBatis最終生成的SQL中,但它們區(qū)別卻是很大的,感興趣的小伙伴可以一起來學習下面詳細內容
    2022-08-08
  • 基于Java實現(xiàn)音頻播放功能

    基于Java實現(xiàn)音頻播放功能

    音頻播放是現(xiàn)代應用程序中常見的一項功能,無論是在桌面應用、游戲開發(fā)、媒體播放器、還是在廣告和提示音效中,下面我們就來看看如何使用Java實現(xiàn)簡單的音頻播放功能吧
    2025-03-03
  • 基于String變量的兩種創(chuàng)建方式(詳解)

    基于String變量的兩種創(chuàng)建方式(詳解)

    下面小編就為大家?guī)硪黄赟tring變量的兩種創(chuàng)建方式(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • Java?windows環(huán)境構建圖文教程

    Java?windows環(huán)境構建圖文教程

    這篇文章主要為大家介紹了Java?windows環(huán)境構建圖文教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>
    2023-12-12
  • MyBatis學習教程(八)-Mybatis3.x與Spring4.x整合圖文詳解

    MyBatis學習教程(八)-Mybatis3.x與Spring4.x整合圖文詳解

    這篇文章主要介紹了MyBatis學習教程(八)-Mybatis3.x與Spring4.x整合圖文詳解的相關資料,需要的朋友可以參考下
    2016-05-05
  • Java調用python代碼的五種方式總結

    Java調用python代碼的五種方式總結

    這篇文章主要給大家介紹了關于Java調用python代碼的五種方式,在Java中調用Python函數的方法有很多種,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2023-09-09
  • JAVA線程池原理實例詳解

    JAVA線程池原理實例詳解

    這篇文章主要介紹了JAVA線程池原理,結合實例形式詳細分析了java線程池概念、原理、創(chuàng)建、使用方法及相關注意事項,需要的朋友可以參考下
    2019-03-03
  • java獲取當前日期和時間的二種方法分享

    java獲取當前日期和時間的二種方法分享

    這篇文章主要介紹了java獲取當前日期和時間的二種方法,需要的朋友可以參考下
    2014-03-03
  • Java語法關于泛型與類型擦除的分析

    Java語法關于泛型與類型擦除的分析

    泛型沒有其看起來那么深不可測,它并不神秘與神奇,泛型是Java 中一個很小巧的概念,但同時也是一個很容易讓人迷惑的知識點,它讓人迷惑的地方在于它的許多表現(xiàn)有點違反直覺
    2021-09-09

最新評論

淄博市| 庐江县| 广丰县| 甘泉县| 清丰县| 桃源县| 钦州市| 全南县| 湟源县| 房产| 涟水县| 桃源县| 娄烦县| 博白县| 阜宁县| 美姑县| 利津县| 津市市| 民丰县| 榆树市| 安阳市| 双流县| 轮台县| 晋州市| 平乡县| 祁门县| 天全县| 吴忠市| 南京市| 突泉县| 吉安县| 集贤县| 万载县| 长泰县| 嘉定区| 泰和县| 托克托县| 永丰县| 灵武市| 永福县| 厦门市|