babpool 8 . JWT Filter 만들기
이전에는 시큐리티에서 사용하는 필터중 로그인을 담당하는 필터 UsernamePasswordAuthenticationFilter 를 새롭게 구성하여 내 입맛대로 만들어보는 과정을 거쳤다. 그래서 로그인이 되는 과정을 보았고 토큰을 받은 사용자는 이제 서버에게 무언가 요청을 할때마다 토큰을 검증하여 우리가 허락한 사람이 맞는지 확인하는 과정을 거치게 될것이다.
1. 검증을 하는 과정
사용자는 DB에 접촉하여 데이터를 가져와야 하는 경우에 서버에 요청을 할텐데 그때마다 AccessToken 이라는 것을 가져와야 하게 만들어주었다. 이것은 프론트 쪽의 상황이기 때문에 따로 서술하진 않겠다. 그럼 우리는 토큰을 보고 바로 오케이 통과 하는것이 아니라 토큰을 뜯어보고 정말로 내가 준것인지 확인하는 과정을 거쳐야 한다. 그럼 우선 전체적인 코드를 한번 보자
조금 길어서 글자가 너무 작으므로 하나씩 뜯어서 보자
2. 전체적인 필터의 내용
2 - 1. 인증을 거치지 않는 요청 분리
우선 처음으로 List 형태로 URL 을 묶어준것을 확인해볼 필요가 있다. 이곳은 우리가 모든 요청에 인증이 필요한것은 아니다. 그렇기 때문에 인증과 인가가 필요하지 않은 페이지를 설정해서 Filter 를 통과해줘야 할 필요가 있었다. 그래서 이렇게 만들어 주어서 해당 URL 을 가진 경우 doFilter 를 통해 그냥 지나치게 만들어 준것이다. 이로써 인증이 필요하지 않은 요청을 구분해 주었다.
2 - 2. RefreshToken의 존재하지 않을경우
앞서 JWTService 에 만들어 두었던 jwtService.extractRefreshToken 을 통해 리프레쉬 토큰이 있는지 없는지 확인한다. 왜냐하면 리프레쉬 토큰을 보내준다는 이유는 AccessToken의 만료를 뜻하기 때문이다.
이렇게 null로써 없을 경우 checkAccessTokenAndAuthentication 으로 인증을 시작한다.
이런식으로 인증을 하는데 오류가 발생하면 예외처리를 해주기 위해서 try catch 문을 작성하였다.
그 안에서 다중 if 문을 작성하면서 상세한 예외 설정을 해두었다.
1. 요청에서 AccessToken 을 Optional 객체로 만들어 빼내주고 여기서 부터 바로 없으면 예외처리를 선언해주었다.
2. 해당 토큰의 유효성을 검사한다. 이것또한 유효성이 검증되지 않으면 예외처리를 진행하도록 만들었다. 유효성을 검증하는 메소드는 6번의 글을 보면 알수 있다.
3. 그리고 extractId를 통해 토큰에 들어있는 memberId 이름을 가지고 있는 것을 추출하여 확인하고 또 예외처리를 진행한다
4. 만약 안에 memberId 가 있다면 DB에서 memberID 를 통해 해당 유저의 모든 내용을 가져와서 확인을 진행한다. 우리 프로젝트에서 memberId 는 기본키로써 중복값이 존재할수 없기때문에 할수 있는 설정이다.
5. 이제 모든 것을 찾고 우리에게 멤버 존재도 확인이 된다면 해당 유저의 Context를 설정해서 인증받은 유저임을 확인하고 해당 유저의 Context를 전역에서 관리할수 있게 해준다.
보이다시피 해당 멤버의 패스워드가 없다면 자동으로 난수를 지정해서 만들어주고 그것을 암호화를 진행하고 이제 이 사용자의 정보를 User 객체로 만들어주어서 UsernamePasswordAuthenticationToken 객체로 만들어서 Context를 넣어주는 것이다.
6. 이렇게 인증이 완료되고 사용자는 이제 원하는 요청을 진행하게 될것이다. 물론 모든 요청에 이런식으로 진행하게 될것이다. 이번의 요청이 완료되고 응답이 나간후 또 다시 요청이 들어오면 같은 유저라고 할지라도 또 다시 Context 를 만들것이다.
2 - 3. RefreshToken의 존재 할 경우
이제는 AccessToken이 없다면 발생하는 예외처리로 인해 프론트에게 전달하게 되고 그 특정한 예외처리는 RefreshToken을 Header에 실어서 보내게 된다. 그럼 만약 헤더에 RefreshToken이 존재한다면 어떻게 진행이 될까?
이렇게 null 이 아닌경우 즉, 헤더에 RefreshToken 이 포함되어 있는 경우에는 우리가 AccessToken을 새로 발급해줄 것이다. 그렇다면 어떻게 진행되는 것일까?
우리는 초기에 RefreshToken을 DB에 저장하기로 했다. 때문에 RefreshToken을 저장한 데이터와 사용자가 보낸 RefreshToken을 확인해서 인증을 진행하게 된다.
만약 해당 유저의 refreshToken이 우리가 가진 RefreshToken과 일치한다면 reIssuedRefreshToken을 통해 새로운 RefreshToken을 DB에 저장하고 사용자에게도 그 새로운 RefreshToken을 전달할 것이다.
그리고 AccessToken또한 memberId를 넣어서 새로 만들어서 sendAccessAndRefreshToken을 통해 사용자에게 보낸다. 이렇게 응답을 마치면 새롭게 받은 RefreshToken 과 AccessToken을 새롭게 저장하고 원래 하려했던 요청을 다시 진행한다.
번외 . Context 에 담긴 사용자의 principal을 확인
이렇게 Util 형식으로 다른곳에서는 그냥 호출을 하는것만으로 검증을 진행하도록 할수있게 해주는 내용이다.
만약 해당 메소드를 부른다면 Context 안에 있는 memberId 를 꺼내어 Mapper 에 사용하는 ID를 확인할 수 있다.
JWT 의 AccessToken 과 RefreshToken 의 사용 방법
이렇게 인증 Filter 를 진행하게 되는데 Filter 에서 진행한 인증을 간단하게 다시 적어보면
우리는 JWT 토큰을 통해 인증과 인가를 진행하고 처음에 전달한 AccessToken과 RefreshToken중 RefreshToken은 우리가 저장한다. 그 이유는 AccessToken을 새롭게 만들어줄수 있는 아무 내용이 없는 간단한 토큰이 되는것이다. 그 후에 사용자가 인증이 필요한 요청을 하게 되는 경우 AccessToken을 전달하고 Filter는 헤더에서 요청 토큰을 확인해서 AccessToken만 들어있다면 위에 설명한 인증을 진행하게 되고 만약 RefreshToken이 존재한다면 AccessToken을 새롭게 발급한다.
서버가 Refreshtoken을 요청하는것은 AccessToken이 만료되었다는 예외처리를 보내어 프론트에게 알리는것으로 시작된다. 그러면 프론트는 RefreshToken을 담아서 새로운 토큰을 달라는 요청을 보내고 필터가 그 내용을 확인후에 AccessToken을 새로 발급해서 보내는데 그때 RefreshToken또한 새롭게 발급해서 전달한다 물론 우리의 DB 내용에도 RefreshToken을 새롭게 저장한다.