[JWT] JSON WEB TOKEN 적용하기 - 로그인, 로그아웃

leeng·2023년 4월 1일
0

JWT

목록 보기
2/2
post-thumbnail

1. 로그인

(이번 포스팅은 로그인, 로그아웃을 처음부터 끝까지 구현하는 것이 아니라 기존에 작성된 세션을 이용해 로그인 처리하던 것을 JWT 토큰 방식으로 변경하는 것입니다.)

카카오 로그인에 성공하여 회원정보까지 가져와 회원가입 or 기존 회원정보 가져오기에 성공한 이후 본격 로그인 처리가 시작된다. 일단 이전 포스팅에서 설명한 토큰 발급 메서드 createTokens() 메서드를 호출하여 액세스 토큰과 리프레쉬 토큰을 받아온다.

  Jwt jwt = tokenService.createTokens(member.getId(), member.getNickname(), kakaoAccessToken); // 세션 저장 대신 JWT 토큰 사용

        // 리프레쉬 토큰 httpOnly 쿠키 설정
        long maxAge = (jwt.getRefreshTokenExp().getTime() - System.currentTimeMillis()) / 1000;
        Cookie refreshTokenCookie = setCookie("refreshToken", jwt.refreshToken, true, false, (int) maxAge, "/");
        response.addCookie(refreshTokenCookie);

        CreateTokenResponse createTokenResponse = new CreateTokenResponse(jwt.accessToken, jwt.refreshToken, jwt.accessTokenExp, jwt.refreshTokenExp);

토큰을 받아온 후 클라이언트에서는 토큰을 어떻게 어디에 저장할지에 대해 찾아보기도 많이 찾아보고 고민도 많이 했더랬다. 처음에는 RefreshToken은 HttpOnly로 설정하여 쿠키에 저장하고 AccessToken은 메모리(변수)에 저장해서 사용하는 방법을 구현해보고 싶었는데 생각보다 복잡하고, 복잡도에 비해 과연 이게 좋은 구조인지 고민 끝에... 결국 쿠키에 저장하기로 했다.

RefreshToken은 HttpOnly로 설정하여 AccessToken이 만료된 경우 RefreshToken로 재발급해주기로 했다.
HttpOnly 설정은 클라이언트에서 불가능하기 때문에 서버에서 직접 설정해주어야한다.
참고로 토큰의 만료 시간은 Date 타입으로, 만료되는 시점을 의미하는 것이고 쿠키의 maxAge는 현재로부터 남은 초를 의미하는 것이기 때문에 추가 작업이 필요했다(long maxAge 세팅 부분).

쿠키 설정은 아래와 같은 setCookie 메소드를 만들어 설정해주었다.

    public static Cookie setCookie(String cookieName, String value, boolean isHttpOnly, boolean isSecure, int maxAge, String path) {
        Cookie cookie = new Cookie(cookieName, value);
        cookie.setHttpOnly(isHttpOnly);
        cookie.setMaxAge(maxAge);
        cookie.setPath(path);
        cookie.setSecure(isSecure); // HTTPS 프로토콜에서만 쿠키 전송 가능
        return cookie;
    }

리프레쉬 토큰 쿠키 추가까지 끝나면 사용자 정보와 함께 Jwt 토큰 정보를 리턴해준다. 그러면 클라이언트에서는 아래와 같이 액세스 토큰을 쿠키에 넣어주면 된다.

<script>
....
    axios
      .get("http://localhost:8080/api/login/token?code=" + code, {
        withCredentials: true, // cross domain에서 쿠키 받기 위해서  credentials 설정
      })
      .then((res) => {
        if (res.status == 200) {
          let refreshTokenExpDate = new Date(res.data.tokenInfo.refreshTokenExp);
          let accessTokenExpDate = new Date(res.data.tokenInfo.accessTokenExp);
          let propertyCookie = "; expires=" + refreshTokenExpDate.toGMTString() + "; path=/";
          let accessTokenProperty = "; expires=" + accessTokenExpDate.toGMTString() + "; path=/";

          //  쿠키 설정
          let nickname = res.data.member.nickname;
          document.cookie = "nickname=" + nickname + propertyCookie;
          accessToken = res.data.tokenInfo.accessToken;
          document.cookie = "accessToken=" + accessToken + accessTokenProperty;

           setTimeout(() => location.replace("/main"), 100);
        }
      })
      .catch((error) => {
  ....
  </script>

2. 로그아웃

JWT 토큰을 사용할 때의 로그아웃의 경우, 토큰을 블랙리스트에 추가하여 관리한다던지 하는 방법도 있지만 여기서는 간단하게 쿠키에서 토큰을 삭제하는 방법을 사용하였다.
리프레쉬 토큰은 HttpOnly 쿠키이기 때문에 클라이언트가 삭제할 수 없어서 서버에서 삭제해주고, 액세스 토큰이 저장된 쿠키는 클라이언트에서 삭제 처리하면 된다.

리프레쉬 토큰 쿠키를 삭제하는 방법은 요청 쿠키에서 "refreshToken"라는 이름을 가진 쿠키를 찾아 MaxAge를 0으로 설정한 후 응답에 추가해주면 된다.

그리고 기존에는 카카오로그인 액세스 토큰을 session에 저장해뒀었는데 이번에는 클라이언트에서 Authorization 헤더에 넣어서 보낸 액세스 토큰값을 이용하여 사용자 정보를 받아와서 카카오로그인 액세스 토큰 값을 가져왔다.

이렇게 가져온 토큰으로 카카오 로그아웃까지 해주고, 클라이언트로 204 noContent를 보내주었다.

    @GetMapping("/logout")
    private ResponseEntity<Void> logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Optional<Cookie> refreshTokenCookie = Arrays.stream(request.getCookies())
                .filter(cookie -> cookie.getName().equals("refreshToken")).findFirst();

        if (refreshTokenCookie.isPresent()) {
            refreshTokenCookie.get().setMaxAge(0);
            response.addCookie(refreshTokenCookie.get());
        } // refreshTokenCookie 삭제. HttpOnly여서 서버에서 삭제

        String accessToken = request.getHeader(JwtTokenConstants.HEADER_AUTHORIZATION).replace(JwtTokenConstants.TOKEN_PREFIX, "");

        Jwt jwt = tokenService.getAccessTokenInfo(accessToken);
        Long loggedoutId = kakaoLogout(jwt.getSocialAccessToken()); // 카카오 로그아웃

        if (loggedoutId == null) {
            throw new RuntimeException("logout failed");
        }

        return ResponseEntity.noContent().build();
    }

클라이언트에서의 로그아웃 로직은 아래와 같다.
액세스토큰 쿠키의 값으로 인증 헤더를 설정해주고, 204 코드를 받으면 클라이언트에서 쿠키를 삭제해준다.

그럼 다음 포스팅에서는 액세스 토큰 갱신하기와 필터를 통해 토큰을 검증하는 방법에 대해 알아보겠다.

profile
기술블로그보다는 기록블로그

0개의 댓글