Spring security JWT - 4. RefreshToken으로 갱신하기
accessToken이 탈취되면 안되지만, 만약에 탈취가 되었을 경우, Server는 stateless하기 때문에 사용자를 강제로 종료 시킬 수 없다.
거기에 대한 방안으로 accessToken의 유효시간을 짧게 가져가, 탈취된 토큰으로 짧은시간동안만 활동 할 수 있게 미리 제약을 걸어놓는다.
유효시간이 지나 만료된 accessToken은 더이상 사용 할 수 없어, refreshToken을 사용해 재발급 받을 수 있다.
refreshToken은 accessToken보다 긴 유효시간을 가지고 있다.
refreshToken까지 탈취 당하면 진짜 위험해져서 다른 방안이 추가되어야 한다. (탈취는 서버잘못이 아닌 클라이언트 잘못.. 잘 간수해야지)
우선 이번에는 refreshToken을 통해 재발급 받는 로직을 작성한다.
클라이언트 입장에서 플로우
- 평소대로 로그인을 한다.
- 기존에는 token 데이터 하나만 쿠키에 담겨 넘어왔으나, 이젠 accessToken, refreshToken 두개가 전달 된다.
- accessToken이 만료되면 재발급 받는 API로 refreshToken을 전달하여 accessToken을 전달 받는다.
서버의 동작
- 클라이언트로부터 최초 userId, password로 로그인요청이 오면 accessToken, refreshToken을 만들고 DB에 저장한다.
- 보통 redis에 저장하고 hash생명 시간을 refreshToken의 유효시간으로 설정하여 refreshToken이 만료되었을 때 알아서 삭제 되도록 한다. (redis설치하기 귀찮아서 여기서는 mysql에 저장하고, 유효시간을 검증하도록 함)
- 클라이언트로부터 재발급 요청이 오면 해당 데이터를 조회하고 검증한 뒤, refreshToken으로 accessToken을 새로 만든 뒤, DB에 저장 후 클라이언트로 리턴한다.
이전에 작성했던 코드에서 많이 변경된다..
1. 코드 작성
- 전에는 expiration-second 한개로 값을 설정했지만 refreshToken로 만료시간을 설정해야해서 두개로 나눴다.
- access-token-expiration-second는 원래 300으로 5분의 유효시간을 갖도록 했으나, 테스트를 위해 1로 변경하였다.
- tokenProvider로 만든 accessToken과 refreshToken을 저장 할 엔티티
- refreshToken이 같은지, 유효시간이 지나지 않은지 검증할 수 있다.
- 기존엔 userEntity를 전달받고 값을 넣도록 하였으나, refreshToken으로도 accessToken을 만들어야 하기 때문에 userId와 role을 받는 것으로 수정하였다.
- accessToken의 데이터는 기존과 똑같고, refreshToken에서는 userId값만 넣고 만들었다.
- 만료시간도 위에 yml에 설정한 값을 가져와서 넣었다.
- 재발급 요청데이터를 파라메터로 받아 accessToken을 재발급 해주는 메소드
- request에서 accessToken으로 값을 조회 후 인증 작업을 통해 알맞은 요청인지 확인한다.
- 확인 후 refreshToken을 검증해서 userId 값을 얻고, 그 값과 role 값으로 accessToken을 새로 만들고, DB데이터를 업데이트 한다.
- role에 USER가 고정값으로 박혀있는데, 사용자 로그인 controller와 관리자 로그인 controller를 나누고 각각 controller에서 요청 할 때 role을 넣어 요청하던지 reIssue 메소드를 분리하여 각각 만들어서 사용하면 된다.
- 토큰 재발급시 해당 검증을 타지 않아야해서 if로 위와 같이 재발급 API일 때는 따로 검증없이 넘어 갈 수 있도록 수정 하였다.
- 로그인 성공 후 accessToken과 refreshToken을 전달받으면 db에 저장하도록 수정
- cookie에 token 값 하나만 넣었었는데, 이제는 access_token과 refresh_token을 넣도록 수정
- 재 인증시 access_token 값만 cookie에 설정하여 리턴하도록 수정
2.코드 실행
- postman으로 로그인 요청을하면 전과 다르게 setCookie로 access_token, refresh_token 두 값이 전달 된다.
- accessToken 값 : eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyOlVTRVIiLCJpc3MiOiJ5ZW8tY2hpIiwiaWF0IjoxNzAwNDg4NDA4LCJleHAiOjE3MDA0ODg0MDl9.0MoBqZy4hnEI346ZFB0niM1j2iQEz5cRR-pzGFhBP_cftEsoApMlYvuX74HMwlQC-6-L69QmmaK5W8WwBC8ibw
- accessToken의 유효시간을 1초로 설정해서, 바로 get요청을 보냈지만 console에 ExpiredJwtException이 발생했다.
- 해당 ExceptionAdvice 설정을 해서 클라이언트로 에러코드를 내려주는게 맞다. (다음에)
- reIssue API로 토큰 재발급 요청을 하면 accessToken 값이 변경되어 나온다.
- DB데이터도 수정됨.
3. 보안
위와 같이 accessToken의 유효시간을 짧게 가져가면서 refreshToken으로 재발급 받도록 설정하면, accessToken만 탈취 당했을 때 어느정도 보호를 할 수 있다.
히지만 refreshToken까지 탈취를 당했으면, refreshToken이 만료될때까지 해당 요청은 계속 유효하다.
그래서 추가적인 보안 작업이 필요하다. (다음 글에..)