Je travaille sur une application Angular qui utilise l'authentification avec accessToken et refreshToken. Mon objectif est de garder l'utilisateur connecté même après une longue période d'inactivité. Voici le comportement attendu par rapport au comportement réel*:

Comportement attendu*: lorsque l'utilisateur revient après une longue période d'inactivité, le refreshToken doit être utilisé pour obtenir un nouveau accessToken sans nécessiter de reconnexion manuelle.
Comportement réel*: le refreshToken ne fonctionne que la première fois. Après une longue période d'inactivité (par exemple, plusieurs heures ou jours), il cesse de s'actualiser automatiquement, forçant l'utilisateur à se déconnecter manuellement et à se reconnecter.
Je soupçonne qu'il y a un problème avec la logique d'actualisation automatique, qui cesse de fonctionner correctement après une certaine durée. Voici le code correspondant*:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
 
 
 
          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;
              }
            }
 
 
          SPRing boot
 
 
            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);
          }
 
 
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                  throws 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);
          }