亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

雙Token無(wú)感刷新機(jī)制實(shí)現(xiàn)方式

 更新時(shí)間:2025年03月05日 09:08:56   作者:Hhzzy99  
本文介紹了如何在Vue.js前端和Java后端實(shí)現(xiàn)雙Token的無(wú)感刷新機(jī)制,后端通過(guò)Jwt過(guò)濾器解析Token,前端在請(qǐng)求時(shí)攜帶Token并保存,當(dāng)Token過(guò)期時(shí),前端會(huì)自動(dòng)刷新并重新發(fā)送請(qǐng)求,實(shí)現(xiàn)無(wú)感刷新

雙Token無(wú)感刷新機(jī)制實(shí)現(xiàn)

在現(xiàn)代 Web 應(yīng)用開(kāi)發(fā)中,前后端分離已經(jīng)成為一種趨勢(shì)。Vue.js 作為前端框架,Java作為后端語(yǔ)言的組合被廣泛應(yīng)用。在用戶(hù)認(rèn)證方面,JWT因?yàn)槠錈o(wú)狀態(tài)、易于擴(kuò)展等特點(diǎn)也備受青睞。

本文將詳細(xì)介紹如何在 Vue 前端和 Java后端實(shí)現(xiàn)雙 Token 的無(wú)感刷新機(jī)制。

后端依賴(lài)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <!-- 其他依賴(lài) -->
</dependencies>

安全配置

配置Jwt過(guò)濾器,以及認(rèn)證失敗過(guò)濾器。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    /**
     * 認(rèn)證失敗處理類(lèi)
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;
	/**
	* Jwt過(guò)濾器
	*/
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .cors().and()
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .headers().cacheControl().disable().and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers("/user/login", "/user/forgetPassword/**", "/user/sendUpdatePasswordEmailCode/**", "/user/register", "/swagger-ui.html", "/user/sendEmailLoginCode", "/user/verifyEmailLoginCode/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();

        return http.build();
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    // 使用BCryptPasswordEncoder作為security默認(rèn)的passwordEncoder
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Jwt過(guò)濾器 *

這個(gè)過(guò)濾器是實(shí)現(xiàn)token刷新機(jī)制的核心,每次前端的請(qǐng)求攜帶accessToken與refreshToken過(guò)來(lái),此過(guò)濾器拿到之后,先對(duì)accessToken進(jìn)行解析,如果解析失敗(過(guò)期),那么接下來(lái)會(huì)對(duì)refreshToken進(jìn)行解析,解析完成之后,如果沒(méi)有過(guò)期,就會(huì)生成新的accessToken與refreshToken返回給前端,并且設(shè)置一個(gè)新的請(qǐng)求頭Token-Refreshed,值可以隨便設(shè),前端能拿到就好。

package com.hblog.backend.config;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hblog.backend.entity.LoginUser;
import com.hblog.backend.entity.User;
import com.hblog.backend.exception.BusinessException;
import com.hblog.backend.exception.EnumException;
import com.hblog.backend.mapper.IUserMapper;
import com.hblog.backend.response.CommonResponse;
import com.hblog.backend.utli.*;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName: JwtAuthenticationFilter
 * @author: Hhzzy99
 * @date: 2024/3/17 16:09
 * description:繼承每個(gè)請(qǐng)求只會(huì)經(jīng)過(guò)一次的過(guò)濾器
 */
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    @Value("${token.expiration}")
    private Long expiration;

    @Autowired
    private IUserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // 獲取當(dāng)前請(qǐng)求路徑
        String requestPath = request.getRequestURI();

        // 排除不需要過(guò)濾的路徑
        if (requestPath.equals("/user/login") || requestPath.equals("/user/register")) {
            filterChain.doFilter(request, response);
            return;
        }

        // 獲取token
        String accessToken = request.getHeader("access_token");
        String refreshToken = request.getHeader("refresh_token");
        if ("null".equals(accessToken) || "".equals(accessToken) || "undefined".equals(accessToken) || null == accessToken) {
            // 放行
            filterChain.doFilter(request, response);
            return;
        }
        // 解析token
        String userId = "";
        boolean isRefresh = false;

        try {
            userId = JwtUtils.parseJWT(accessToken).getSubject();
        } catch (Exception e) {
            isRefresh = true;
            e.printStackTrace();
        }
        if (isRefresh) {
            try {
                userId = JwtUtils.parseJWT(refreshToken).getSubject();
                accessToken = JwtUtils.createJWT(userId);
                refreshToken = JwtUtils.createRefreshToken(userId);
                User loginUser = userMapper.getUserById(Long.valueOf(userId));
                Integer ttl = expiration.intValue() / 1000;
                log.warn("@@@@@@@@@@@@@@@@@@刷新token@@@@@@@@@@@@@@@@@@@@@");
                redisCache.setCacheObject("userInfo:" + userId, loginUser, ttl, TimeUnit.SECONDS);
                writeTokenResponse(response, accessToken, refreshToken);
                return;
            } catch (Exception e1) {
                throw new BusinessException(EnumException.THE_LOGIN_HAS_EXPIRED);
            }
        }
        // 從redis里面獲取用戶(hù)信息
        User loginUser = redisCache.getCacheObject("userInfo:" + userId);
        if (Objects.isNull(loginUser)) {
            throw new BusinessException(EnumException.THE_LOGIN_HAS_EXPIRED);
        }
        // 存入SecurityContextHolder
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        // 放行
        filterChain.doFilter(request, response);
    }

    private void writeTokenResponse(HttpServletResponse response, String accessToken, String refreshToken) throws IOException {
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("accessToken", accessToken);
        tokenMap.put("refreshToken", refreshToken);
        Map<String, String> headers = new HashMap<>();
        headers.put("Token-Refreshed", "true");
        CommonResponse<Map<String, String>> commonResponse = new CommonResponse<>(200, "Token refreshed successfully", tokenMap);
        WebUtil.renderString(response, headers, JSON.toJSONString(commonResponse));
    }
}

前端的配置

前端對(duì)所有的axios請(qǐng)求進(jìn)行全局配置,先在每次請(qǐng)求的時(shí)候設(shè)置好請(qǐng)求頭accessToken與refreshToken,并且將每次請(qǐng)求都保存起來(lái),如果在請(qǐng)求時(shí)后端解析到accessToken失效,并且返回了新的accessToken與refreshToken,在請(qǐng)求頭拿到了后端設(shè)置好的Token-Refreshed,此時(shí)就可以重新將新的accessToken與refreshToken保存在瀏覽器本地,并且重新發(fā)送之前保存好的請(qǐng)求,就可以實(shí)現(xiàn)無(wú)感刷新。

request.js

import axios from 'axios'
import {ref} from "vue";

// create an axios instance
const service = axios.create({
    baseURL: '/api', // url = base url + request url
    timeout: 20000 // request timeout
})

const retryRequest = ref(null)

// request interceptor
service.interceptors.request.use(
    config => {
        // 加入頭信息配置
        if (localStorage.getItem("access_token") !== null && localStorage.getItem("access_token") !== undefined){
            config.headers['access_token'] = localStorage.getItem("access_token")
        }
        if (localStorage.getItem("refresh_token") !== null && localStorage.getItem("refresh_token") !== undefined){
            config.headers['refresh_token'] = localStorage.getItem("refresh_token")
        }
        retryRequest.value = config
        return config
    }
)

// response interceptor
service.interceptors.response.use(
    response => {
        if (response.headers['token-refreshed']) {
            console.log('Token刷新成功');
            // 如果有Token-Refreshed頭部,更新本地存儲(chǔ)中的Token
            localStorage.setItem('access_token', response.data.data.accessToken);
            localStorage.setItem('refresh_token', response.data.data.refreshToken);
            console.log("繼續(xù)")
            // 繼續(xù)發(fā)送原始請(qǐng)求
            return axios(retryRequest.value)
        }
        return response;
    },
    async error => {
        const originalRequest = error.config;

        // 如果是Token過(guò)期導(dǎo)致的401錯(cuò)誤,并且沒(méi)有retry標(biāo)記,嘗試刷新Token
        if (error.response.status === 401 && !originalRequest._retry) {
            originalRequest._retry = true;

            const refreshToken = localStorage.getItem('refresh_token');
            if (refreshToken) {
                try {
                    const response = await axios.post('/refresh-token', { refreshToken });
                    const { accessToken, refreshToken: newRefreshToken } = response.data.data;
                    localStorage.setItem('access_token', accessToken);
                    localStorage.setItem('refresh_token', newRefreshToken);

                    // 更新原始請(qǐng)求的Authorization頭部
                    originalRequest.headers['access_token'] = accessToken;
                    // 重新發(fā)送原始請(qǐng)求
                    return instance(originalRequest);
                } catch (refreshError) {
                    // 刷新Token失敗,跳轉(zhuǎn)到登錄頁(yè)或執(zhí)行其他處理
                    console.error('Token刷新失敗:', refreshError);
                    // 這里可以跳轉(zhuǎn)到登錄頁(yè)或者執(zhí)行其他處理
                }
            }
        }

        return Promise.reject(error);
    }
)

export default service

通過(guò)以上步驟,我們就可以實(shí)現(xiàn)雙 Token 無(wú)感刷新機(jī)制。該機(jī)制通過(guò)短期有效的訪問(wèn) Token 和長(zhǎng)期有效的刷新 Token 相結(jié)合,在 Token 過(guò)期時(shí)自動(dòng)刷新。

本示例僅展示了基礎(chǔ)的實(shí)現(xiàn)方式,實(shí)際生產(chǎn)環(huán)境中還需要考慮更多安全性和健壯性的問(wèn)題。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java使用SFTP上傳文件到服務(wù)器的簡(jiǎn)單使用

    Java使用SFTP上傳文件到服務(wù)器的簡(jiǎn)單使用

    這篇文章主要介紹了Java使用SFTP上傳文件到服務(wù)器的簡(jiǎn)單使用,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-02-02
  • Java實(shí)現(xiàn)多線程下載和斷點(diǎn)續(xù)傳

    Java實(shí)現(xiàn)多線程下載和斷點(diǎn)續(xù)傳

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)多線程下載和斷點(diǎn)續(xù)傳,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • 詳解如何為SpringBoot Web應(yīng)用的日志方便追蹤

    詳解如何為SpringBoot Web應(yīng)用的日志方便追蹤

    在Web應(yīng)用程序領(lǐng)域,有效的請(qǐng)求監(jiān)控和可追溯性對(duì)于維護(hù)系統(tǒng)完整性和診斷問(wèn)題至關(guān)重要,SpringBoot是一種用于構(gòu)建Java應(yīng)用程序的流行框架,在本文中,我們探討了在SpringBoot中向日志添加唯一ID的重要性,需要的朋友可以參考下
    2023-11-11
  • Java實(shí)力彈彈球?qū)崿F(xiàn)代碼

    Java實(shí)力彈彈球?qū)崿F(xiàn)代碼

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)力彈彈球?qū)崿F(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • Java實(shí)現(xiàn)各種文件類(lèi)型轉(zhuǎn)換方式(收藏)

    Java實(shí)現(xiàn)各種文件類(lèi)型轉(zhuǎn)換方式(收藏)

    這篇文章主要介紹了Java?各種文件類(lèi)型轉(zhuǎn)換的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03
  • Mybatis插件之自動(dòng)生成不使用默認(rèn)的駝峰式操作

    Mybatis插件之自動(dòng)生成不使用默認(rèn)的駝峰式操作

    這篇文章主要介紹了Mybatis插件之自動(dòng)生成不使用默認(rèn)的駝峰式操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-11-11
  • SpringCloud Ribbon與OpenFeign詳解如何實(shí)現(xiàn)服務(wù)調(diào)用

    SpringCloud Ribbon與OpenFeign詳解如何實(shí)現(xiàn)服務(wù)調(diào)用

    這篇文章主要介紹了SpringCloud Ribbon與OpenFeign實(shí)現(xiàn)服務(wù)調(diào)用的過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-09-09
  • Java中遞歸、循環(huán)的優(yōu)劣分析

    Java中遞歸、循環(huán)的優(yōu)劣分析

    這篇文章主要給大家介紹了關(guān)于Java中遞歸、循環(huán)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • springboot時(shí)間格式化的五種方法總結(jié)(解決后端傳給前端的時(shí)間顯示不一致)

    springboot時(shí)間格式化的五種方法總結(jié)(解決后端傳給前端的時(shí)間顯示不一致)

    這篇文章主要給大家介紹了關(guān)于springboot時(shí)間格式化的五種方法,文中介紹的方法解決了后端傳給前端的時(shí)間顯示不一致,文中通過(guò)圖文以及代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • Java運(yùn)算符從見(jiàn)過(guò)到掌握上

    Java運(yùn)算符從見(jiàn)過(guò)到掌握上

    計(jì)算機(jī)的最基本用途之一就是執(zhí)行數(shù)學(xué)運(yùn)算,作為一門(mén)計(jì)算機(jī)語(yǔ)言,Java也提供了一套豐富的運(yùn)算符來(lái)操縱變量,本篇對(duì)大家的學(xué)習(xí)或工作具有一定的價(jià)值,需要的朋友可以參考下
    2021-09-09

最新評(píng)論