Come posso aggiornare i token nella sicurezza di primavera
Questa riga:
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
Genera un errore come questo quando il mio token jwt scade:
JWT è scaduto il 2020-05-13T07:50:39Z. Ora corrente: 2020-05-16T21:29:41Z.
Più specificamente, è questa funzione che genera l'eccezione "ExpiredJwtException":

Come faccio a gestire queste eccezioni? Devo rilevarli e inviare al client un messaggio di errore e costringerli a riaccedere?
Come posso implementare una funzione di token di aggiornamento? Sto usando Spring e mysql nel backend e vuejs nel front-end.
Genero il token iniziale in questo modo:
@Override
public JSONObject login(AuthenticationRequest authreq) {
JSONObject json = new JSONObject();
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authreq.getUsername(), authreq.getPassword()));
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority())
.collect(Collectors.toList());
if (userDetails != null) {
final String jwt = jwtTokenUtil.generateToken(userDetails);
JwtResponse jwtres = new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(),
userDetails.getEmail(), roles, jwtTokenUtil.extractExpiration(jwt).toString());
return json.put("jwtresponse", jwtres);
}
} catch (BadCredentialsException ex) {
json.put("status", "badcredentials");
} catch (LockedException ex) {
json.put("status", "LockedException");
} catch (DisabledException ex) {
json.put("status", "DisabledException");
}
return json;
}
E poi nella classe JwtUtil:
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRESIN))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
Per maggiori informazioni, ecco la mia funzione doFilterInternal che filtra ogni richiesta:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException, ExpiredJwtException, MalformedJwtException {
try {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
boolean correct = jwtUtil.validateToken(jwt, userDetails);
if (correct) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
} catch (ExpiredJwtException ex) {
resolver.resolveException(request, response, null, ex);
}
}
Risposte
Esistono 2 approcci principali per affrontare tali situazioni:
Gestisci gli accessi e aggiorna i token
In questo caso il flusso è il seguente:
Accesso utente all'applicazione (inclusi
username
epassword
)L'applicazione back-end restituisce tutte le informazioni sulle credenziali richieste e:
2.1 Accedere al token JWT con un tempo scaduto solitamente "basso" (15, 30 minuti, ecc.).
2.2 Token JWT di aggiornamento con un tempo scaduto superiore a quello di accesso.
D'ora in poi, la tua applicazione frontend utilizzerà
access token
nell'intestazioneAuthorization
per ogni richiesta.
Quando il backend restituisce 401
, l'applicazione frontend tenterà di utilizzare refresh token
(utilizzando un endpoint specifico) per ottenere nuove credenziali, senza costringere l'utente ad accedere nuovamente.
Flusso token di aggiornamento (questo è solo un esempio, di solito viene inviato solo il token di aggiornamento)
Se non ci sono problemi, l'utente potrà continuare a utilizzare l'applicazione. Se il backend restituisce un nuovo 401
=> il frontend dovrebbe reindirizzare alla pagina di accesso.
Gestisci un solo token Jwt
In questo caso il flusso è simile al precedente ed è possibile creare il proprio endpoint per far fronte a tali situazioni: /auth/token/extend
(ad esempio), includendo il Jwt scaduto come parametro della richiesta.
Ora tocca a te gestire:
- Per quanto tempo un token Jwt scaduto sarà "valido" per estenderlo?
Il nuovo endpoint avrà un comportamento simile al refresh di quello della sezione precedente, cioè restituirà un nuovo token Jwt o 401
giù di lì, dal punto di vista del frontend il flusso sarà lo stesso.
Una cosa importante , indipendentemente dall'approccio che vuoi seguire, il "nuovo endpoint" dovrebbe essere escluso dagli endpoint autenticati Spring richiesti, perché gestirai la sicurezza da solo:
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
..
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
..
.authorizeRequests()
// List of services do not require authentication
.antMatchers(Rest Operator, "MyEndpointToRefreshOrExtendToken").permitAll()
// Any other request must be authenticated
.anyRequest().authenticated()
..
}
}
Puoi chiamare l'API per ottenere il token di aggiornamento come di seguito
POST https://yourdomain.com/oauth/token
Header
"Authorization": "Bearer [base64encode(clientId:clientSecret)]"
Parameters
"grant_type": "refresh_token"
"refresh_token": "[yourRefreshToken]"
Si prega di notare che, il
- base64encode è il metodo per crittografare l'autorizzazione del client. Puoi usare online suhttps://www.base64encode.org/
- refresh_token è il valore String di grant_type
- yourRefreshToken è il token di aggiornamento ricevuto con il token di accesso JWT
Il risultato può essere visto come
{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJK.iLCJpYXQiO.Dww7TC9xu_2s",
"expires_in":20,
"refresh_token":"7fd15938c823cf58e78019bea2af142f9449696a"
}
Buona fortuna.