Jak mogę odświeżyć tokeny w zabezpieczeniach wiosennych
Ta linia:
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
Zgłasza taki błąd, gdy mój token jwt wygaśnie:
JWT wygasł 2020-05-13T07: 50: 39Z. Aktualny czas: 2020-05-16T21: 29: 41Z.
Mówiąc dokładniej, to ta funkcja zgłasza wyjątek „ExpiredJwtException”:

Jak mam zająć się tymi wyjątkami? Czy powinienem je złapać i odesłać do klienta komunikat o błędzie i zmusić go do ponownego zalogowania?
Jak mogę zaimplementować funkcję odświeżania tokenów? Używam Springa i mysql w backendzie i vuejs w interfejsie.
Generuję początkowy token w ten sposób:
@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;
}
A potem w klasie 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();
}
Aby uzyskać więcej informacji, oto moja funkcja doFilterInternal, która filtruje każde żądanie:
@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);
}
}
Odpowiedzi
Istnieją 2 główne sposoby radzenia sobie z takimi sytuacjami:
Zarządzaj dostępem i odświeżaj tokeny
W tym przypadku przepływ jest następujący:
Logowanie użytkownika do aplikacji (w tym
username
ipassword
)Twoja aplikacja backendu zwraca wszystkie wymagane dane uwierzytelniające i:
2.1 Dostęp do tokena JWT z wygasłym czasem, zwykle „niskim” (15, 30 minut itd.).
2.2 Odśwież token JWT z upływem czasu dłuższym niż czas dostępu.
Od teraz Twoja aplikacja frontendowa będzie używać
access token
wAuthorization
nagłówku każdego żądania.
Po powrocie zaplecza 401
aplikacja frontendowa spróbuje użyć refresh token
(używając określonego punktu końcowego), aby uzyskać nowe poświadczenia, bez zmuszania użytkownika do ponownego logowania.
Przepływ tokenu odświeżania (to jest tylko przykład, zwykle wysyłany jest tylko token odświeżania)
Jeśli nie ma problemu, użytkownik będzie mógł dalej korzystać z aplikacji. Jeśli backend zwróci nowy 401
frontend => powinien przekierować do strony logowania.
Zarządzaj tylko jednym tokenem Jwt
W tym przypadku przepływ jest podobny do poprzedniego i możesz utworzyć własny punkt końcowy, aby radzić sobie z takimi sytuacjami: /auth/token/extend
(na przykład), włączając wygasły Jwt jako parametr żądania.
Teraz zarządzasz:
- Jak długo wygasły token Jwt będzie „ważny”, aby go przedłużyć?
Nowy punkt końcowy będzie zachowywał się podobnie jak odświeżanie w poprzedniej sekcji, to znaczy zwróci nowy token Jwt lub coś podobnego 401
, z punktu widzenia frontendu przepływ będzie taki sam.
Jedna ważna rzecz , niezależnie od podejścia, które chcesz zastosować, „nowy punkt końcowy” powinien zostać wykluczony z wymaganych punktów końcowych uwierzytelnianych przez Spring, ponieważ będziesz samodzielnie zarządzać zabezpieczeniami:
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()
..
}
}
Możesz wywołać interfejs API w celu uzyskania tokenu odświeżania, jak poniżej
POST https://yourdomain.com/oauth/token
Header
"Authorization": "Bearer [base64encode(clientId:clientSecret)]"
Parameters
"grant_type": "refresh_token"
"refresh_token": "[yourRefreshToken]"
Należy zauważyć, że plik
- base64encode to metoda szyfrowania autoryzacji klienta. Możesz używać online pod adresemhttps://www.base64encode.org/
- refresh_token jest wartość String grant_type
- yourRefreshToken to token odświeżania otrzymany z tokenem dostępu JWT
Wynik można postrzegać jako
{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJK.iLCJpYXQiO.Dww7TC9xu_2s",
"expires_in":20,
"refresh_token":"7fd15938c823cf58e78019bea2af142f9449696a"
}
Powodzenia.