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

SpringBoot+React中雙token實現(xiàn)無感刷新

 更新時間:2025年10月20日 10:55:52   作者:悟能不能悟  
本文主要介紹了基于AccessToken和RefreshToken的雙Token無感刷新機制,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、方案說明

1. 核心流程

  1. ?用戶登錄?
    • 提交賬號密碼 → 服務(wù)端驗證 → 返回Access Token(前端存儲) + Refresh Token(HttpOnly Cookie)
  2. ?業(yè)務(wù)請求?
    • 請求頭攜帶Access Token → 服務(wù)端驗證有效性 → 有效則返回數(shù)據(jù)
  3. ?Token過期處理?
    • 若Access Token過期 → 前端攔截401錯誤 → 自動用Refresh Token請求新Token → 刷新后重試原請求
  4. ?Refresh Token失效?
    • 清除登錄態(tài) → 跳轉(zhuǎn)登錄頁

2. 安全設(shè)計

  • ?Access Token?
    • 存儲:前端內(nèi)存(如Vuex/Redux)或sessionStorage
    • 有效期:2小時
    • 傳輸:Authorization: Bearer <token>
  • ?Refresh Token?
    • 存儲:HttpOnly + Secure + SameSite=Strict Cookie
    • 有效期:7天
    • 刷新機制:單次使用后更新,舊Token立即失效

二、前端實現(xiàn)(React示例)

1. Axios封裝(src/utils/http.js)

import axios from 'axios';
 
const http = axios.create({
  baseURL: process.env.REACT_APP_API_URL,
});
 
// 請求攔截器:注入Access Token
http.interceptors.request.use(config => {
  const accessToken = sessionStorage.getItem('access_token');
  if (accessToken) {
    config.headers.Authorization = `Bearer ${accessToken}`;
  }
  return config;
});
 
// 響應(yīng)攔截器:處理Token過期
http.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;
    
    // 檢測401錯誤且未重試過
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        // 發(fā)起刷新Token請求
        const { accessToken } = await refreshToken();
        
        // 存儲新Token
        sessionStorage.setItem('access_token', accessToken);
        
        // 重試原請求
        originalRequest.headers.Authorization = `Bearer ${accessToken}`;
        return http(originalRequest);
      } catch (refreshError) {
        // 刷新失?。呵宄齌oken,跳轉(zhuǎn)登錄
        sessionStorage.removeItem('access_token');
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);
 
// 刷新Token函數(shù)
async function refreshToken() {
  const res = await axios.post(
    `${process.env.REACT_APP_API_URL}/auth/refresh`,
    {},
    { withCredentials: true } // 自動攜帶Cookie
  );
  return res.data;
}
 
export default http;

2. 登錄邏輯(src/pages/Login.js)

const LoginPage = () => {
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const res = await axios.post('/auth/login', {
        username: 'user',
        password: 'pass'
      }, { withCredentials: true });
      
      // 存儲Access Token
      sessionStorage.setItem('access_token', res.data.accessToken);
      
      // 跳轉(zhuǎn)主頁
      window.location.href = '/';
    } catch (err) {
      alert('登錄失敗');
    }
  };
 
  return (
    <form onSubmit={handleSubmit}>
      {/* 登錄表單 */}
    </form>
  );
};

三、后端實現(xiàn)(Spring Boot)

1. JWT工具類(JwtUtil.java)

@Component
public class JwtUtil {
    @Value("${jwt.secret}")
    private String secret;
 
    @Value("${jwt.access.expiration}")
    private Long accessExpiration;
 
    @Value("${jwt.refresh.expiration}")
    private Long refreshExpiration;
 
    // 生成Access Token
    public String generateAccessToken(UserDetails user) {
        return buildToken(user, accessExpiration);
    }
 
    // 生成Refresh Token
    public String generateRefreshToken(UserDetails user) {
        return buildToken(user, refreshExpiration);
    }
 
    private String buildToken(UserDetails user, Long expiration) {
        return Jwts.builder()
                .setSubject(user.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }
 
    // 驗證Token
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            throw new JwtException("Token驗證失敗");
        }
    }
 
    // 從Token中提取用戶名
    public String getUsernameFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }
}

2. 認證接口(AuthController.java)

@RestController
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Autowired
    private RefreshTokenService refreshTokenService;
 
    // 登錄接口
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        UserDetails user = userDetailsService.loadUserByUsername(request.getUsername());
        
        // 密碼驗證
        if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
            throw new BadCredentialsException("密碼錯誤");
        }
 
        // 生成Token
        String accessToken = jwtUtil.generateAccessToken(user);
        String refreshToken = jwtUtil.generateRefreshToken(user);
 
        // 存儲Refresh Token
        refreshTokenService.saveRefreshToken(user.getUsername(), refreshToken);
 
        // 設(shè)置Refresh Token到Cookie
        ResponseCookie cookie = ResponseCookie.from("refreshToken", refreshToken)
                .httpOnly(true)
                .secure(true)
                .sameSite("Strict")
                .maxAge(jwtUtil.getRefreshExpiration() / 1000)
                .path("/auth/refresh")
                .build();
 
        return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, cookie.toString())
                .body(new AuthResponse(accessToken));
    }
 
    // 刷新Token接口
    @PostMapping("/refresh")
    public ResponseEntity<?> refreshToken(@CookieValue("refreshToken") String refreshToken) {
        // 驗證Refresh Token
        if (!jwtUtil.validateToken(refreshToken)) {
            throw new JwtException("無效Token");
        }
 
        String username = jwtUtil.getUsernameFromToken(refreshToken);
        
        // 檢查是否與存儲的Token一致
        if (!refreshTokenService.validateRefreshToken(username, refreshToken)) {
            throw new JwtException("Token已失效");
        }
 
        // 生成新Token
        UserDetails user = userDetailsService.loadUserByUsername(username);
        String newAccessToken = jwtUtil.generateAccessToken(user);
        String newRefreshToken = jwtUtil.generateRefreshToken(user);
 
        // 更新存儲的Refresh Token
        refreshTokenService.updateRefreshToken(username, newRefreshToken);
 
        // 返回新Token
        ResponseCookie cookie = ResponseCookie.from("refreshToken", newRefreshToken)
                .httpOnly(true)
                .secure(true)
                .sameSite("Strict")
                .maxAge(jwtUtil.getRefreshExpiration() / 1000)
                .path("/auth/refresh")
                .build();
 
        return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, cookie.toString())
                .body(new AuthResponse(newAccessToken));
    }
}

3. Refresh Token服務(wù)(RefreshTokenService.java)

@Service
public class RefreshTokenService {
    @Autowired
    private RefreshTokenRepository repository;
 
    public void saveRefreshToken(String username, String token) {
        RefreshToken refreshToken = new RefreshToken();
        refreshToken.setUsername(username);
        refreshToken.setToken(token);
        refreshToken.setExpiryDate(jwtUtil.getExpirationDateFromToken(token));
        repository.save(refreshToken);
    }
 
    public boolean validateRefreshToken(String username, String token) {
        return repository.findByUsernameAndToken(username, token)
                .map(t -> t.getExpiryDate().after(new Date()))
                .orElse(false);
    }
 
    public void updateRefreshToken(String username, String newToken) {
        repository.deleteByUsername(username);
        saveRefreshToken(username, newToken);
    }
}

四、安全配置(SecurityConfig.java)

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private JwtAuthenticationFilter jwtFilter;
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
                .antMatchers("/auth/?**?").permitAll()
                .anyRequest().authenticated()
            .and()
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    }
}
 
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    @Autowired
    private JwtUtil jwtUtil;
 
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            if (jwtUtil.validateToken(token)) {
                String username = jwtUtil.getUsernameFromToken(token);
                UsernamePasswordAuthenticationToken auth = 
                    new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        chain.doFilter(request, response);
    }
}

五、配置參數(shù)(application.yml)

jwt:
  secret: "your-256-bit-secret-key-here" # 通過環(huán)境變量注入
  access:
    expiration: 7200000 # 2小時(毫秒)
  refresh:
    expiration: 604800000 # 7天(毫秒)

六、數(shù)據(jù)庫表結(jié)構(gòu)(MySQL)

CREATE TABLE refresh_tokens (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(255) NOT NULL,
  token VARCHAR(512) NOT NULL,
  expiry_date DATETIME NOT NULL,
  UNIQUE KEY (username)
);

此方案完整實現(xiàn)了雙Token無感刷新機制,具備以下特點:

  1. 完整的前后端代碼示例,可直接集成到項目中
  2. 遵循安全最佳實踐(HttpOnly Cookie、短期Token)
  3. 支持并發(fā)請求處理和Token主動吊銷
  4. 清晰的模塊劃分,易于擴展維護

到此這篇關(guān)于SpringBoot+React中雙token實現(xiàn)無感刷新的文章就介紹到這了,更多相關(guān)雙token無感刷新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JAVA 靜態(tài)代理模式詳解及實例應(yīng)用

    JAVA 靜態(tài)代理模式詳解及實例應(yīng)用

    這篇文章主要介紹了JAVA 靜態(tài)代理模式詳解及實例應(yīng)用的相關(guān)資料,這里舉例說明java 靜態(tài)代理模式該如何使用,幫助大家學(xué)習(xí)參考,需要的朋友可以參考下
    2016-11-11
  • Java按周對事件進行分組的實現(xiàn)示例

    Java按周對事件進行分組的實現(xiàn)示例

    本兒主要介紹了Java按周對事件進行分組,java.time包提供了處理日期和時間的功能,包括獲取某個日期屬于一年中的第幾周,具有一定的參考價值,感興趣的可以了解一下
    2025-08-08
  • 簡單了解spring bean作用域?qū)傩詓ingleton和prototype的區(qū)別

    簡單了解spring bean作用域?qū)傩詓ingleton和prototype的區(qū)別

    這篇文章主要介紹了簡單了解spring bean作用域?qū)傩詓ingleton和prototype的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12
  • Spring MVC中自帶的跨域問題解決方法

    Spring MVC中自帶的跨域問題解決方法

    最近做一個微信小項目遇到一個跨域問題,就是我的前端和后端是放在不同的服務(wù)器上的,然后使用opst請求的時候報錯,所以通過查找相關(guān)的資料終于解決了,下面這篇文章主要給大家介紹了關(guān)于Spring MVC中自帶的跨域問題解決方法的相關(guān)資料,需要的朋友可以參考下。
    2017-09-09
  • SpringBoot 2.6.x整合springfox 3.0報錯問題及解決方案

    SpringBoot 2.6.x整合springfox 3.0報錯問題及解決方案

    這篇文章主要介紹了SpringBoot 2.6.x整合springfox 3.0報錯問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Java設(shè)計模式之單例模式實例詳解【懶漢式與餓漢式】

    Java設(shè)計模式之單例模式實例詳解【懶漢式與餓漢式】

    這篇文章主要介紹了Java設(shè)計模式之單例模式,簡單說明了單例模式的原理并結(jié)合具體實例形式分析了單例模式中懶漢式與餓漢式的具體實現(xiàn)與使用技巧,需要的朋友可以參考下
    2017-09-09
  • java后臺如何接收get請求傳過來的數(shù)組

    java后臺如何接收get請求傳過來的數(shù)組

    這篇文章主要介紹了java后臺如何接收get請求傳過來的數(shù)組問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • springboot+nacos+gateway實現(xiàn)灰度發(fā)布的實例詳解

    springboot+nacos+gateway實現(xiàn)灰度發(fā)布的實例詳解

    灰度發(fā)布是一種在軟件部署過程中用于平滑過渡的技術(shù),通過引入灰度發(fā)布SDK和配置網(wǎng)關(guān)策略實現(xiàn),本文就來介紹一下,感興趣的可以了解一下
    2022-03-03
  • Java 中的 DataInputStream 介紹_動力節(jié)點Java學(xué)院整理

    Java 中的 DataInputStream 介紹_動力節(jié)點Java學(xué)院整理

    DataInputStream 是數(shù)據(jù)輸入流。它繼承于FilterInputStream。接下來通過本文給大家介紹Java 中的 DataInputStream的相關(guān)知識,需要的朋友參考下吧
    2017-05-05
  • 使用Java打印PDF文件的自定義設(shè)置指南

    使用Java打印PDF文件的自定義設(shè)置指南

    在 Java 中處理 PDF 文件時,除了能夠讀取、修改和生成 PDF 文件外,打印 PDF 文件同樣是一個常見需求,本文將介紹如何使用 Java 打印 PDF 文件,重點講解如何自定義打印機、頁面范圍、雙面打印、紙張大小等常見設(shè)置,需要的朋友可以參考下
    2026-02-02

最新評論

启东市| 大宁县| 马关县| 安乡县| 沾益县| 凭祥市| 鹿泉市| 广南县| 确山县| 乌拉特前旗| 桑日县| 兴化市| 临清市| 长宁区| 甘洛县| 施甸县| 锡林浩特市| 东乌珠穆沁旗| 墨脱县| 凤阳县| 邵东县| 吐鲁番市| 年辖:市辖区| 建始县| 青神县| 百色市| 随州市| 惠安县| 土默特左旗| 射阳县| 莒南县| 柳州市| 平江县| 门头沟区| 托里县| 郑州市| 永州市| 灌阳县| 松江区| 尼木县| 尤溪县|