지난 포스팅에서 소셜로그인에 대한 로그아웃을 구현했다면, 이번엔 일반 로그인에 대한 로그아웃이다. 사실 로그아웃을 구현하는 방법은 여러 개가 잇을 수 있기 때문에 위의 코드는 참고용으로만 보기 바란다.
헤더의 Authorization 필드 값에 accessToken을 넣어주면, 이 값을 accessToken에 넣는다. 토큰이 없거나, 유효하지 않거나, 토큰에 해당하는 멤버가 없는 경우 INVALID_JWT 예외가 호출된다. 만약, accessToken이 만료된 경우에는 이미 로그아웃이 된 것이므로, 메시지로 사용자에게 로그아웃되었음을 알려주면 된다.
accessToken이 만료되지 않은 경우는 member의 refreshToken과 accessToken 값을 비워주면 된다. 대신 이런 식으로 로그아웃을 구현한다면 사용자 인증이 필요한 모든 경우에 대해 member.getAccessToken()과 member.getRefreshToken()으로 토큰을 가져오게끔 해야한다. 만약 헤더에 담긴 토큰을 그대로 사용할 경우 로그아웃을 했음에도 불구하고 요청이 처리되는 불상사가 발생할 수 있다.
그래서 아래의 구현한 update 요청에서도 member에 저장된 accessToken과 refreshToken이 없다면, token이 만료되지 않았더라도 요청이 처리되지 않도록 만들어 준 것을 볼 수 있다.
멤버의 닉네임을 변경하는 API이다. member의 소셜 로그인을 확인하는 부분이 추가되었다. 소셜 로그인인 경우와 아닌 경우를 구분하는 이유는 소셜 로그인을 통해 받은 JWT는 카카오에서 관리하는 id를 이용해 JWT를 만든 반면, 일반 로그인의 경우 DB에 저장된 순서로 결정된 id를 이용해 JWT를 만들고 있기 때문이다.
실제 카카오톡에서 준 나의 id는 2850417647이지만, 나의 DB에선 넣어준 순서이기 때문에 당연히 id가 달라 INVALID_JWT 예외가 발생할 것이다. 따라서 소셜로그인의 경우는 id 대신 email로 JWT의 유효성을 체크한다. 즉, JWT를 parsing하여 얻은 이메일과 입력받은 이메일이 동일한지 확인한다는 것이다.
먼저 create API를 통해 chrome이라는 멤버를 만들어주자. 그 후 로그인 API를 실행하면, 이제는 refreshToken까지 주어지는 것을 볼 수 있다. accessToken과 refreshToken을 복사해두자.
이제 닉네임 변경 API를 통해 닉네임을 hyunseop으로 변경해보자. Params에 이메일과 변경할 닉네임을 입력한다. 그 후 헤더의 Authorization 필드와 AuthorizationRef 필드에 각각 accessToken과 refreshToken을 넣는다.
닉네임이 hyunseop으로 변경된 것을 확인할 수 있다.
지난 포스팅에서 설명한 방식 그대로 Authorization의 각 필드를 설정한 후 Get New Access Token을 클릭한다. 로그인하고 Proceed를 눌러서 나온 창에서 Access Token과 Refresh Tokend을 복사해두자.
/oauth/kakao를 URI로 입력하고 Params의 accToken과 refToken 필드의 복사한 토큰 값을 넣어주면 된다.
DB에도 결과가 잘 나타난다. 소셜로그인을 한 덕분에 별도의 이메일이나 닉네임을 설정해준 적이 없음에도 저절로 입력된 것이 보일 것이다. 또한 패스워드 필드가 null이라는 사실도 추가로 확인하라.
이번에도 Params에 이메일과 변경할 닉네임을 넣어주었다. 이번에는 변현섭을 현섭이로 바꾸어보겠다. 또한 Headers의 Authorization 필드와 AuthorizationRef 필드에 각각 accessToken과 refreshToken을 넣어주자.
DB에도 결과가 반영되었다.
로그아웃 API의 경우 헤더의 Authorization 필드에 accessToken 값만 넣어주면 된다. 일반 로그인할 때 받았던 accessToken 값을 인자로 전달해주자.
chrome(hyunseop)에게 저장되어 있던 토큰이 모두 없어진 것을 확인할 수 있다.
비록 토큰이 만료된 것은 아니지만, 로그아웃을 하였기 때문에 이 상태에선 다시 토큰 값을 주더라도 API가 동작하지 않는다.
실제로 토큰이 만료돼서 동작하지 않는 것이 아니라 멤버에게 저장되어 있는 토큰을 사용해서 비교 로직을 수행하기 때문에 동작하지 않는 것이다. 이로써 로그아웃을 구현할 수 있다. 다만, 이것이 좋은 방법은 아니다. 구현은 단순하지만 실제 서비스에서 사용하기에는 무리가 있다. 당연히 실제 서비스의 경우 해당 토큰을 만료시키는 것으로 로그아웃을 구현한다.
소셜 로그인에 대한 로그아웃은 이미 카카오 API에서도 실습해보았기 때문에 별로 어렵지 않다. Params의 state 필드에 accessToken 값만 넣어주면 된다.
소셜 로그인 멤버에 대한 로그아웃은 카카오 서버에서 시켜주는 것이므로, 우리가 구현하지 않았다. DB를 확인해보면 어떨까?
로그아웃을 했음에도 토큰 값이 그대로 남아있다. 우리가 구현한 로그아웃 로직이야 대강 구현한 것이지만, 실제 서비스에서는 멤버에서 토큰을 지우는 것으로 로그아웃하지 않는다. 실제 토큰을 만료시키는 것으로 로그아웃을 구현한다. 테스트해보자.
동일한 토큰 값으로 다시 닉네임 변경을 요청했는데 로그아웃을 했기 때문에 동작하지 않는걸 확인할 수 있다. 하지만, 적어도 통일감을 위해서라도 멤버에서 토큰을 지워주는 로직을 추가하는 것이 좋아보인다.
이제 소셜 로그아웃에서도 토큰이 지워진다.
사실 여기서 구현한 코드는 최대한 간단히만 구현한 것이다. 제대로 구현하려면 refreshToken을 이용해서 accessToken을 재발급 받는 로직을 구현해주어야 하고, accessToken과 refreshToken을 대놓고 URL로 넘기는 대신 URL Encoding을 통해서 정보를 보호해주어야 한다. 그리고 로그아웃 시에도 토큰을 만료시켜 재사용이 불가하게 만들어야 한다.
이번 포스팅에서 다루기에는 시간도 많이 없고, 복잡한 내용이 많기 때문에 나중에 "따라하면서 배우는 JPA"에 차근차근 업로드하도록 하겠다. 그 때에는 카카오 로그인 뿐 아니라, 다른 소셜로그인도 구현해보기로 하고, 못 다한 페이징처리에 대해서도 다루어보겠다.