① getMemberIdx() 메서드와 비슷한 원리로 Jwt를 파싱하여 만료시간에 대한 정보를 추출하고 있다.
② 만료시간에 대한 정보를 추출하는 getExpiration() 메서드의 반환타입은 Date이다.
③ Date를 Long타입으로 변환하기 위해 사용할 수 있는 메서드는 getTime() 메서드이다. 즉, getTime() 메서드를 통해 현재 날짜와 시간에 대한 정보를 Long형으로 변환한다.
④ System.currentTimeMills()는 현재 날짜와 시간에 대한 정보를 밀리초 단위로 반환하는 메서드로, 반환타입은 Long이다.
※ 날짜와 시간을 Long으로 바꾸는 원리
특정 날짜와 시간은 1970년 1월 1일부터 경과한 시간을 밀리초 단위로 환산하여 반환하면 현재의 시간을 Long형 정수로 나타낼 수 있다.
⑤ 토큰의 만료시간과 현재 시간 사이의 차이를 반환한다. 이미 만료된 상태라면 음수를 반환한다.
⑥ 메서드의 이름에서 착각하면 안되는 게 토큰의 만료시간을 반환하는 메서드가 아니라, 토큰의 만료까지 남은 시간을 반환하는 메서드이다.
Redis가 무엇인지 모른다면 아래의 설명을 보기 전에 반드시 아래의 링크를 읽어보기 바란다.
>> Redis 데이터베이스
① redisTemplate
② opsForValue()
③ set(accessToken, "logout", expiration, TimeUnit.MILLISECONDS)
이제 저번에 미처 설명하지 못한 checkBlackToken 메서드를 다시보자.
JWT가 만료되었을 때 발생하는 예외이다. 만약 요청 헤더에 포함된 JWT가 만료된 JWT일 경우, 이 예외에 해당하는 catch문이 실행된다. accessToken에 해당하는 멤버를 찾고, refreshAccessToken()에 그 멤버의 refreshToken 값을 입력해 새로운 accessToken을 재발급한다.
① refreshAccessToken()
※ ""과 null의 차이
""은 길이가 0인 문자열을 의미한다. 즉, 어떠한 문자도 포함하지 않는 문자열입니다. ""은 유효한 값으로 간주되며, 메모리에 실제로 할당되는 값이다.
반면, null은 아무런 값도 가지지 않음을 나타내는 특별한 상태이다. null은 값이 없음을 의미하며, 메모리상에서 참조가 없기 때문에 유효하지 않은 값으로 간주된다. 따라서, null에서 값을 가져올 경우 NPE가 발생하기 때문에 주의해야 한다.
② Exception Handling
위에서 예외가 발생할 경우, BaseException으로 예외가 처리되지 않는다.
그 이유는 예외처리를 담당하는 핸들러 메서드를 따로 정의해두었기 때문이다. 이 예외처리는 ExceptionController에 모아두었다.
ExpiredJwtException에 의해 throw 되는 모든 예외는 결국 위의 핸들러 메서드에 의해 일괄적으로 처리된다.
@ResponseStatus는 핸들러 메서드가 발생시킨 예외 또는 요청을 처리한 결과에 대한 HTTP 응답 상태 코드를 지정하는 데 사용된다. 위 예시에서는 BAD_REQUEST 즉, 400으로 상태 코드를 설정하고 있다.
e.printStackTrace()는 예외가 발생한 위치를 출력하는 메서드로, 주로 디버깅 목적으로 사용된다.
다시 말해 EXPIRED_USER_JWT, INVALID_JWT, FAILED_TO_REFRESH가 호출되지 않고, 그저 {"code" : "403", "msg" : "ExpiredJwtException"}이 Json 형식으로 사용자에게 반환될 것이다.
즉, 이 메서드는 Authorization 필드에 담긴 JWT에서 멤버의 ID를 추출하는 역할을 수행한다. Access Token이 만료된 경우, Refresh Token의 유효성을 평가해 Access Token을 재발급한 후 멤버의 ID를 반환한다.
지난 "update" 요청은 이메일 필드를 포함시켜 사용자 인증을 처리하였다. 그러나 이제는 JWT만 가지고 사용자 인증을 하고 있다. 닉네임 변경을 요청한 사람의 닉네임을 바꾸기 때문에, 다른 사람의 닉네임을 변경하는 것이 불가능해진다. 이제 사용자로부터 이메일을 입력받지 않아도 되기 때문에 사용자 편의성이 증진되고 코드가 간결해진다.
S3를 이용하는 자세한 로직에 대해서는 아래의 링크에서 확인할 수 있다.
>> 사진 업로드 API
프로필 변경에는 아래와 같이 4가지 경우가 있다. 이 4가지 모두에 대해 고려하여 분기문을 작성해야 한다.
즉, 기존 프로필이 없는 사용자가 이번에도 사진 없이 수정을 누른다면, 아무 처리도 안하면 되고, 이번에는 사진을 업로드한다면, 이를 S3에 업로드하고 Repository에 저장해야 한다.
또한, 기존 프로필이 있는 사용자가 이번에는 사진 없이 수정을 누른다면, 기존 사진을 S3에서 삭제하고 Repository에서 지워야 할 것이고, 다른 사진으로 업로드하면, 업로드 한 사진을 S3에 업로드하고 Repository에 저장해야 한다. (동일한 사진으로 업데이트하는 경우도 업데이트가 진행된다.)
이로써, Member Controller의 요청에 대한 설명을 모두 마쳤다. Member 이외에도 Board나 Comment 컨트롤러의 요청도 비슷하게 이루어졌다보니, 이 정도만 설명해도 모든 코드를 이해하는 데 부족함이 없을 거 같다. 설명은 생략하더라도 반드시 한번씩 읽어보기 바란다. 다음 포스팅에선 지금까지 구현한 API를 테스트해보고 결과를 확인해보도록 하자.