Spring 보안에서 토큰을 어떻게 새로 고칠 수 있습니까?

Aug 15 2020

이 줄 :

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

내 jwt 토큰이 만료되면 다음과 같은 오류가 발생합니다.

JWT는 2020-05-13T07 : 50 : 39Z에 만료되었습니다. 현재 시간 : 2020-05-16T21 : 29 : 41Z.

보다 구체적으로 "ExpiredJwtException"예외를 발생시키는 것은 다음 함수입니다.

이러한 예외를 처리하려면 어떻게해야합니까? 그들을 잡아서 클라이언트에게 오류 메시지를 보내고 강제로 다시 로그인해야합니까?

새로 고침 토큰 기능을 구현하려면 어떻게해야합니까? 백엔드에서 Spring과 mysql을 사용하고 프론트 엔드에서 vuejs를 사용하고 있습니다.

다음과 같이 초기 토큰을 생성합니다.

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

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

자세한 내용은 모든 요청을 필터링하는 doFilterInternal 함수입니다.

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

답변

3 doctore Aug 16 2020 at 17:38

이러한 상황을 처리하기위한 두 가지 주요 접근 방식이 있습니다.


액세스 관리 및 토큰 새로 고침

이 경우 흐름은 다음과 같습니다.

  1. 애플리케이션에 사용자 로그인 ( username및 포함 password)

  2. 백엔드 애플리케이션은 필요한 자격 증명 정보를 반환하고 다음을 수행합니다.

    2.1 만료 시간이 일반적으로 "낮음"(15 분, 30 분 등)으로 JWT 토큰에 액세스 합니다.

    2.2 만료 시간이 액세스 시간보다 긴 JWT 토큰새로 고칩니다 .

  3. 이제부터 프런트 엔드 애플리케이션은 모든 요청 access token에 대해 Authorization헤더 에서 사용 합니다 .

백엔드가를 반환 401하면 프런트 엔드 애플리케이션은 refresh token사용자가 다시 로그인하도록 강요하지 않고 특정 엔드 포인트 를 사용 하여 새 자격 증명을 얻으 려고 시도 합니다.

새로 고침 토큰 흐름 (이것은 예일 뿐이며 일반적으로 새로 고침 토큰 만 전송 됨)

문제가 없으면 사용자는 응용 프로그램을 계속 사용할 수 있습니다. 백엔드가 새로운 401=> 프런트 엔드를 반환하면 로그인 페이지로 리디렉션되어야합니다.


하나의 Jwt 토큰 만 관리

이 경우 흐름은 이전 흐름과 유사하며 이러한 상황을 처리하기 위해 고유 한 엔드 포인트를 생성 할 수 있습니다 /auth/token/extend(예 : 만료 된 Jwt를 요청의 매개 변수로 포함).

이제 다음을 관리 할 수 ​​있습니다.

  • 만료 된 Jwt 토큰이 연장 할 수있는 "유효"한 시간은 얼마입니까?

새 끝점은 이전 섹션에서 새로 고침과 유사한 동작을 갖습니다. 즉, 새 Jwt 토큰을 반환 할 것 401입니다. 프런트 엔드의 관점에서 보면 흐름은 동일합니다.


따르려는 접근 방식과 관계없이 한 가지 중요한 것은 보안을 직접 관리하기 때문에 필요한 Spring 인증 엔드 포인트에서 "새 엔드 포인트"를 제외해야한다는 것입니다.

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

아래와 같이 새로 고침 토큰을 얻기 위해 API를 호출 할 수 있습니다.

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

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

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

주의하시기 바랍니다,

  • base64encode 는 클라이언트 인증을 암호화하는 방법입니다. 온라인에서 사용할 수 있습니다.https://www.base64encode.org/
  • refresh_token도는 의 문자열 값입니다 grant_type
  • yourRefreshTokenJWT 액세스 토큰으로 받은 새로 고침 토큰입니다.

결과는 다음과 같이 볼 수 있습니다.

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

행운을 빕니다.