Spring güvenliğinde jetonları nasıl yenileyebilirim

Aug 15 2020

Bu hat:

Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();

Jwt belirtecimin süresi dolduğunda şöyle bir hata veriyor:

JWT'nin süresi 2020-05-13T07: 50: 39Z'de doldu. Güncel saat: 2020-05-16T21: 29: 41Z.

Daha spesifik olarak, "ExpiredJwtException" istisnasını atan bu işlevdir:

Bu istisnalarla nasıl başa çıkacağım? Onları yakalayıp müşteriye bir hata mesajı göndermeli ve yeniden oturum açmaya zorlamalı mıyım?

Bir yenileme jetonu özelliğini nasıl uygulayabilirim? Arka uçta Spring ve mysql ve ön uçta vuejs kullanıyorum.

İlk belirteci şu şekilde oluşturuyorum:

   @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;
        }

Ve sonra JwtUtil sınıfında:

   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();
        }

Daha fazla bilgi için, işte her isteği filtreleyen doFilterInternal işlevim:

   @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);
        }
    } 

Yanıtlar

3 doctore Aug 16 2020 at 17:38

Bu tür durumlarla başa çıkmak için 2 ana yaklaşım vardır:


Erişimi yönetin ve jetonları yenileyin

Bu durumda akış aşağıdaki gibidir:

  1. Uygulamaya kullanıcı girişleri ( usernameve dahil password)

  2. Arka uç uygulamanız gerekli tüm kimlik bilgilerini döndürür ve:

    2.1 JWT belirtecine , genellikle "düşük" (15, 30 dakika, vb.) Süresi dolmuş bir süre ile erişin .

    2.2 JWT belirtecini , erişim süresinden daha uzun süre dolan bir süre ile yenileyin .

  3. Şu andan itibaren, ön uç uygulamanız her istek access tokeniçin Authorizationbaşlıkta kullanacak .

Arka uç döndüğünde 401, ön uç uygulaması refresh token, kullanıcıyı yeniden oturum açmaya zorlamadan yeni kimlik bilgileri almak için kullanmayı (belirli bir uç noktayı kullanarak) deneyecektir .

Simge akışını yenileme (Bu yalnızca bir örnektir, genellikle yalnızca yenileme belirteci gönderilir)

Herhangi bir sorun yoksa, kullanıcı uygulamayı kullanmaya devam edebilecektir. Arka uç yeni bir 401=> ön uç döndürürse , oturum açma sayfasına yönlendirmelidir.


Yalnızca bir Jwt belirtecini yönetin

Bu durumda, akış öncekine benzerdir ve bu tür durumlarla başa çıkmak için kendi uç noktanızı oluşturabilirsiniz: /auth/token/extend(örneğin), isteğin parametresi olarak süresi dolan Jwt dahil.

Şimdi yönetmek size kalmış:

  • Süresi dolan bir Jwt token'ı uzatmak için ne kadar süre "geçerli" olacaktır?

Yeni uç nokta, önceki bölümdekine benzer bir yenileme davranışına sahip olacak, yani yeni bir Jwt belirteci döndürecek 401, ön uç açısından akış aynı olacak.


İzlemek istediğiniz yaklaşımdan bağımsız olarak önemli bir nokta, güvenliği kendiniz yöneteceğiniz için "yeni uç nokta" nın gerekli Spring kimlik doğrulamalı uç noktalarından çıkarılması gerekir:

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()
      ..
   }
}
2 QuocTruong Aug 16 2020 at 18:18

Yenileme jetonunu almak için API'yi aşağıdaki gibi çağırabilirsiniz

POST https://yourdomain.com/oauth/token 

Header
  "Authorization": "Bearer [base64encode(clientId:clientSecret)]" 

Parameters
  "grant_type": "refresh_token"
  "refresh_token": "[yourRefreshToken]"

Lütfen dikkat edin,

  • base64encode , istemci yetkilendirmesini şifreleme yöntemidir. Online olarak şu adresten kullanabilirsiniz:https://www.base64encode.org/
  • refresh_token String değeridir grant_type
  • yourRefreshToken , JWT erişim belirteci ile alınan yenileme belirtecidir

Sonuç şu şekilde görülebilir:

{
    "token_type":"bearer",
    "access_token":"eyJ0eXAiOiJK.iLCJpYXQiO.Dww7TC9xu_2s",
    "expires_in":20,
    "refresh_token":"7fd15938c823cf58e78019bea2af142f9449696a"
}

İyi şanslar.