슬기로운슬기

AccessToken과 RefreshToken을 사용한 인증 단계

인증(로그인) 단계

  • 사용자가 아이디와 비밀번호로 로그인을 하면, 서버는 ATK와 RTK를 발급함
  • ATK : 사용자의 세션을 식별하는데 사용
  • RTK : ATK가 만료되었을 때 새로운 ATK를 발급받을 수 있게 해줌 

AccessToekn 만료 시

  • ATK가 만료되면, 서버에서는 클라이언트에게 에러를 반환하거나, 클라이언트에서 새로운 ATK를 요청하도록 유도해야함
  • 클라이언트는 RTK와 함께 새로운 ATK를 요청함

RefreshToken을 사용한 Access Token 재발급

  • 클라이언트는 RTK과 함께 서버에 ATK 재발급을 요청
  • 서버는 클라이언트의 요청을 검증, 유효한 경우 새로운 ATK를 발급해줌 

이러한 순서로 진행될 예정이다. RefreshToken 검증을 위해 Redis를 사용하였다. 

 

UserController (로그인, 엑세스 토큰 재발급 API)

@Operation(summary = "로그인")
@PostMapping("/login")
public ResponseEntity<TokenResponseDto> login(@RequestBody LoginRequestDto requestDto) {
  TokenResponseDto result = userService.login(requestDto);
  return ResponseEntity.status(HttpStatus.OK).body(result);
}

@Operation(summary = "엑세스 토큰 재발급")
@GetMapping("/reissue")
public TokenResponseDto reissue(
    @AuthenticationPrincipal UserDetailsImpl userDetails, HttpServletRequest request) {
  TokenResponseDto result = userService.reissue(userDetails.getUser(), request);
  return result;
}

 

TokenResponseDto

@Getter
@AllArgsConstructor
public class TokenResponseDto {
  private final String atk;
  private final String rtk;
}
  • ResponseBody에 AccessToken과 RefreshToken을 넣어줄 것 이다.
  • 프론트랑 회의를 하고 이렇게 진행하게 되었다..  

UserService

[로그인 메서드]

  @Override
  public TokenResponseDto login(LoginRequestDto dto) {
    String username = dto.getUsername();
    String password = dto.getPassword();

	// User 찾기
    User user = findUser(username);

    // password 확인
    passwordCheck(password, user.getPassword());

    // JWT 생성 
    String atk = jwtUtil.createToken(username, user.getRole());
    String rtk = jwtUtil.createRefreshToken(username, user.getRole());

    // RefreshToken Redis 저장
    redisUtil.saveRefreshToken(user.getUsername(), rtk);

    return new TokenResponseDto(atk, rtk);
  }
  • 사용자의 로그인을 처리하는 메서드 
  • 로그인 시 엑세스토큰, 리프레시토큰 생성  
  • 리프레시토큰은 Redis에 저장 -> 나중에 엑세스 토큰 재발급 할 때 필요 ! (key: username, value: RefreshToken)

[엑세스 토큰 재발급 메서드]

  @Override
  public TokenResponseDto reissue(User user, HttpServletRequest request) {
    String newAtk = jwtUtil.createToken(user.getUsername(), user.getRole());
    return new TokenResponseDto(newAtk, null);
  }
  • 액세스 토큰을 재발급하는 메서드
  • 새로운 엑세스 토큰 생성
  • Request : HttpServletRequest로 RefreshToken을 받을 예정
  • Response : RefreshToken은 사용하지 않기 때문에 null로 설정 
  • RefreshToken에 대한 확인은 Jwt 검증 필터에서 할 예정 

 

JwtUtil

[AccessToken 생성 메서드]

  // access token 토큰 생성
  public String createToken(String username, UserRoleEnum role) {
    Date date = new Date();

    return BEARER_PREFIX +
        Jwts.builder()
            .setSubject(username) // 사용자 식별자값(ID)
            .claim(AUTHORIZATION_KEY, role)  //사용자 권한
            .setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
            .setIssuedAt(date) // 발급일
            .signWith(key, signatureAlgorithm) // 암호화 알고리즘
            .compact();
  }
  • 만료시간은 1시간으로 설정 

 

[RefreshToken 생성 메서드]

  // refresh token 생성
  public String createRefreshToken(String username, UserRoleEnum role) {
    Date now = new Date();

    return BEARER_PREFIX + Jwts.builder()
        .setSubject(username) // 사용자 식별자값(ID)
        .claim(AUTHORIZATION_KEY, role)  //사용자 권한
        .setExpiration(new Date(now.getTime() + REFRESH_TOKEN_TIME))  //만료시간
        .signWith(key, signatureAlgorithm)
        .compact();
  }
  • 만료시간 1주일 설정

[RefreshToken 확인 메서드]

  // refreshToken 확인
  public boolean checkRefreshToken(String username, String token) {
    String storedToken = redisUtil.getRefreshToken(username);

    return storedToken !=null && storedToken.substring(7).equals(token);
  }
  • Redis에 저장한 RefreshToken과 Header의 request와 같은지 확인

 

JwtAuthorizationFilter-Jwt 검증 필터

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    String token = jwtUtil.resolveToken(request);

    if(token != null) {
      if(!jwtUtil.validateToken(token)){
        log.error("Token Error");
        return;
      }
      if(request.getRequestURI().equals("/home/users/reissue")) {
        String username = jwtUtil.getUsernameFromToken(token);
        if(!jwtUtil.checkRefreshToken(username, token)) {
          log.error("Refresh token check failed");
          return;
        }
      }
      Claims info = jwtUtil.getUserInfoFromToken(token);
      setAuthentication(info.getSubject());
    }
    try {
      filterChain.doFilter(request, response);
    } catch (FileUploadException e) {
      log.error(e.getMessage());
    }
  }
  • 여기서 추가적인 처리를 해준 부분은 request.getRequestURI().equals("/home/users/reissue") 이다.
  • 요청 URI가 "/home/users/reissue"인 경우에 리프레시 토큰의 유효성을 검사하는 메서드를 실행한다. 
반응형
profile

슬기로운슬기

@스를기

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!