Angular 中长时间不活动后出现刷新令牌问题

问题描述 投票:0回答:1

我正在开发一个 Angular 应用程序,该应用程序使用accessToken 和refreshToken 进行身份验证。我的目标是即使用户长时间不活动也能保持登录状态。这是预期行为与实际行为:

预期行为:当用户长时间不活动后返回时,应使用refreshToken来获取新的accessToken,而无需手动重新登录。 实际行为:refreshToken 仅在第一次有效。长时间不活动(例如几个小时或几天)后,它会自动停止刷新,迫使用户手动注销并重新登录。 我怀疑自动刷新逻辑存在问题,在一定时间后会停止正常运行。相关代码如下:

      interface CustomJwtPayload {
    exp: number; // Timestamp de l'expiration
    userId?: number; // ID utilisateur, optionnel
  }

  export class AuthService {

    private baseUrl = 'http://localhost:8080/api';
    private readonly ACCESS_TOKEN_EXPIRATION = 1000 * 60 * 15; // 15 minutes
    private readonly REFRESH_TOKEN_EXPIRATION = 1000 * 60 * 60 * 24 * 7; // 7 jours

    constructor(
      private http: HttpClient,
      @Inject(PLATFORM_ID) private platformId: Object,
      private ngZone: NgZone
    ) {
      this.setupAutoRefresh();
    }

    private getToken(): string | null {
      if (isPlatformBrowser(this.platformId)) {
        return localStorage.getItem('accessToken'); // Utilisez localStorage
      }
      return null;
    }

    private getRefreshToken(): string | null {
      return localStorage.getItem('refreshToken'); // Utilisez localStorage
    }

    private storeNewAccessToken(token: string) {
      if (isPlatformBrowser(this.platformId)) {
        localStorage.setItem('accessToken', token); // Utilisez localStorage
      }
    }

    public refreshToken(refreshToken: string): Observable<any> {
      return this.http.post(`${this.baseUrl}/refresh-token`, { refreshToken }, {
        headers: this.getHeaders()
      }).pipe(
        catchError(error => {
          console.error('Token refresh failed', error);
          return EMPTY; // Retourne un Observable vide pour éviter tout blocage
        })
      );
    }

    private setupAutoRefresh() {
      if (isPlatformBrowser(this.platformId)) {
        this.ngZone.runOutsideAngular(() => {
          setInterval(() => {
            if (this.isAccessTokenCloseToExpiration()) {
              const refreshToken = this.getRefreshToken(); // Récupère le token de rafraîchissement
              if (refreshToken) {
                this.refreshToken(refreshToken).subscribe(newAccessToken => {
                  this.storeNewAccessToken(newAccessToken.token); // Stocke le nouveau token d'accès
                });
              }
            }
          }, 13 * 60 * 1000); // Appelle toutes les 13 minutes
        });
      }
    }

    private isAccessTokenCloseToExpiration(): boolean {
      const decodedToken = this.decodeToken();
      if (!decodedToken) return false;
      const expirationTime = decodedToken.exp * 1000;
      const currentTime = new Date().getTime();
      return expirationTime - currentTime < 2 * 60 * 1000; // Moins de 2 minutes restantes
    }

    private decodeToken(): CustomJwtPayload | null {
      const token = this.getToken();
      if (!token) return null;
      try {
        return jwtDecode<CustomJwtPayload>(token);
      } catch (error) {
        console.error('Token decoding failed', error);
        return null;
      }
    }

SPR环启动

private final long ACCESS_TOKEN_EXPIRATION = 1000 * 60 * 15;
    private final long REFRESH_TOKEN_EXPIRATION = 1000 * 60 * 60 * 24 * 7;

    public String extractUsername(String token) {
  return extractClaim(token, Claims:: getSubject);
}

    public Date extractExpiration(String token) {
  return extractClaim(token, Claims:: getExpiration);
}

public < T > T extractClaim(String token, Function < Claims, T > claimsResolver) {
        final Claims claims = extractAllClaims(token);
  return claimsResolver.apply(claims);
}

    private Claims extractAllClaims(String token) {
  return Jwts.parserBuilder()
    .setSigningKey(SECRET_KEY)
    .build()
    .parseClaimsJws(token)
    .getBody();
}

    private Boolean isTokenExpired(String token) {
  return extractExpiration(token).before(new Date());
}

    // Génération d'un Token d'Accès
    public String generateAccessToken(String username, Long userId) {
  Map < String, Object > claims = new HashMap<>();
  claims.put("userId", userId); // Ajoutez l'ID utilisateur aux revendications
  return createToken(claims, username, ACCESS_TOKEN_EXPIRATION, SECRET_KEY);
}

    // Génération d'un Refresh Token
    public String generateRefreshToken(String username, Long userId) {
  Map < String, Object > claims = new HashMap<>();
  claims.put("userId", userId); // Ajoutez l'ID utilisateur aux revendications
  return createToken(claims, username, REFRESH_TOKEN_EXPIRATION, REFRESH_SECRET_KEY);
}

    // Extraction de l'ID utilisateur à partir du token
    public Long extractUserId(String token) {
        final Claims claims = extractAllClaims(token);
  return (Long) claims.get("userId");
}

    // Création d'un JWT avec expiration personnalisée et clé donnée
    private String createToken(Map < String, Object > claims, String subject, long expirationTime, Key key) {
  return Jwts.builder()
    .setClaims(claims)
    .setSubject(subject)
    .setIssuedAt(new Date(System.currentTimeMillis()))
    .setExpiration(new Date(System.currentTimeMillis() + expirationTime))
    .signWith(key) // Utilisez la clé appropriée
    .compact();
}

    // Validation du Token d'Accès
    public Boolean validateAccessToken(String token, String username) {
        final String extractedUsername = extractUsername(token);
  return (extractedUsername.equals(username) && !isTokenExpired(token));
}

    // Validation du Refresh Token
    public Boolean validateRefreshToken(String token) {
  try {
    Jwts.parserBuilder()
      .setSigningKey(REFRESH_SECRET_KEY)
      .build()
      .parseClaimsJws(token);
    return true;
  } catch (Exception e) {
    return false;
  }
}

    // Vérification si le Token d'Accès est expiré
    public Boolean isAccessTokenExpired(String token) {
  return isTokenExpired(token);
}

@覆盖 protected void doFilterInternal(HttpServletRequest请求,HttpServletResponse响应,FilterChain链) 抛出 ServletException、IOException {

final String authorizationHeader = request.getHeader("Authorization");
final String refreshTokenHeader = request.getHeader("Refresh-Token");

String jwt = null;
String username = null;

if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
    jwt = authorizationHeader.substring(7);
    try {
        username = jwtUtil.extractUsername(jwt);
    } catch (ExpiredJwtException e) {
        // Si le token d'accès est expiré, essayons de générer un nouveau token à partir du refresh token
        if (refreshTokenHeader != null && jwtUtil.validateRefreshToken(refreshTokenHeader)) {
            // Extraire le nom d'utilisateur à partir du refresh token
            String refreshTokenUsername = jwtUtil.extractUsername(refreshTokenHeader);
            String newAccessToken = jwtTokenRefreshService.refreshAccessToken(refreshTokenHeader, refreshTokenUsername);
            response.setHeader("New-Access-Token", newAccessToken); // Retourne le nouveau token d'accès dans les en-têtes de réponse
        } else {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Refresh token is invalid or not provided.");
            return;
        }
    } catch (Exception e) {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("JWT token parsing failed.");
        return;
    }

    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
        try {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtUtil.validateAccessToken(jwt, username)) {
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            } else {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("Invalid JWT token.");
                return;
            }
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Exception in setting authentication: " + e.getMessage());
            return;
        }
    }
}

chain.doFilter(request, response);

}

angular spring-boot web-hosting
1个回答
0
投票

写一个答案,因为我还不能发表评论......

通常在刷新访问令牌时,库会发出新的刷新令牌以及新的访问令牌,并使之前的刷新令牌失效。因此,您可能还需要替换存储的刷新令牌。

© www.soinside.com 2019 - 2024. All rights reserved.