😃 JWT와 세션, 현재까지의 학습 결과는 세션이 훨씬 매력적이네요!
최근 인증과 인가를 학습하며 JWT와 세션 방식을 대조하며 학습하고 있습니다.
현재까지 학습한 결과는 다음과 같습니다.
세션 방식이 압도적으로 우선 선택되어야 하며,
일부 토큰 방식이 더 적합한 케이스가 있을 가능성을 배제할 수는 없으나
현재까지는 그러한 케이스를 발견하지 못했다.
이렇게 생각하게 된 이유는, 세션의 단점으로 지적되고 있던 부분들이
이미 해소되었다고 생각하기 때문입니다.
전통적으로 꼽는 세션 방식의 가장 큰 단점이 서버 부하, 확장에 막혀있음 이라고 생각하는데요,
이러한 단점들은 이미 해소되었다고 느껴져서 세션의 강력한 장점인 서버의 로그인 유지 통제 용이성을 가져가는 게 나은 것 같습니다.
💾 중립적인 공간에 세션을 저장하기 위한 Spring Session
확장성에 대해서는 세션 아이디를 서버 내부 메모리에 저장하는 것이 아닌,
중립적인 외부 공간을 사용하는 것으로 대응이 가능한데요,
이와 관련해 Spring Session이라는 기능이 이미 존재함을 최근 알았습니다.
Redis, RDBMS, Hazelcast 등 원하는 데이터베이스를 지정하기만 하면,
추상화된 SessionRepository가 제공되어 세션을 쉽게 다룰 수 있다는 점이 아주 매력적입니다.
이렇게 중립적인 공간을 세션 저장소로 사용한다면,
유지해야할 세션이 증가해도 서버에 부담은 증가하지 않게 됩니다.
즉, 최초 제기했던 문제들은 다 해소된 거지요.
그러나 중립적인 세션 저장소에 대한 부하가 커지면 어떻게 하느냐.
거기까지는... 아직 모르겠네요 ^^;;;ㅋㅋㅋ
다른 주제들을 좀 더 공부한 뒤에 이 부분은 다시 찾아봐야할 것 같습니다.
🪙 토큰에는 식별자와 권한을 담자!
토큰에 어떤 정보를 담아야할지에 대해 고민이 많았습니다.
세션은 탈취당해도 그 안에 개인정보가 담겨있진 않지는 않지만,
토큰은 탈취당할 경우 payload에 담긴 정보가 즉시 유출되는 것이기 때문입니다.
그렇기에 민감한 정보는 담을 수 없고, 꼭 사용자를 식별할 수 있는 최소한의 정보만 담아야 합니다.
그렇다면 토큰에는 식별자만 담아야할까요?
공식 사이트에서 토큰이 유용한 시나리오 중 하나로 인가를 소개하고 있습니다.
Authorization:
This is the most common scenario for using JWT.
Once the user is logged in, each subsequent request will include the JWT,
allowing the user to access routes, services, and resources that are permitted with that token.
Single Sign On is a feature that widely uses JWT nowadays,
because of its small overhead and its ability to be easily used across different domains.
이 외에도 여러 소스를 통해 학습한 결과, 토큰은 인가를 위해 사용되기 위해 만들어진 것이다 결론지었습니다.
그렇다면 사용자를 식별할 수 있는 정보와 함께 사용자의 권한들을 토큰에 담아야 할 것입니다.
🧐 토큰에 사용자의 권한을 담아도 될까? 중간에 권한이 수정된다면?
공부한 내용에 따르자면 아래와 같은 payload가 JWT의 본래 목적에 가장 부합한다고 생각했습니다.
{
"sub": "1234567890",
"email": "ztzy1907@gmail.com",
"role": ["user", "admin"],
"iat": 1516239022,
"exp": 1516277777
}
사용자를 식별할 수 있는 정보, 어떤 권한을 가지고 있는지에 대한 정보, 발행일과 만료일을 담는 겁니다.
그러나 이런 구성을 실제 상황에 대입했을 때 예상되는 치명적인 문제가 있었는데,
바로 실시간으로 권한이 확장되거나 축소될 때, 토큰이 이를 반영할 수 없다는 점이었습니다.
물론 굳이 하려면 방법이 있을 수도 있습니다.
인터셉터에서 권한의 확장, 축소가 일어난 식별자와 일치하는지 확인 후
일치할 경우 요청 처리와 함께 새로 발급된 토큰을 함께 내려주는 것입니다.
그러나 이런 구성은 억지로 끼워맞춘 느낌이고 프론트와 백엔드 모두 코드가 난잡해질 것입니다.
물론 로그아웃 했다가 로그인 하면 간단히 해결되는 문제이긴 하지만,
인가를 위해 사용되는 토큰이 권한 목록의 동기화가 안된다면.. 이라는 생각에 인지적 모순이 발생했습니다.
그래서, 실제 토큰 방식을 사용하는 서비스들에선 어떻게 토큰을 사용할지 보고싶어졌습니다.
🕵🏻♂️ 프롤로그로 살펴본 토큰의 사용 예시
우아한테크코스의 학습로그 사이트, Prolog입니다.
로그인 과정은 다음과 같습니다.
1. Github OAuth를 이용해 code 받기
2. code를 프롤로그의 /login/token에 POST로 보내기
3. Response Body에 담긴 accessToken 받기
4. accessToken을 LocalStorage에 저장하기
로그인 성공 결과로 발급받은 토큰은 LocalStorage 에 저장되고,
이후 같은 서버로 요청을 보낼 때마다 Request Header에 담깁니다.
Authorization: Bearer {토큰} 의 형태로 담겨 전송됩니다.
그리고 이 토큰은 당연하게도, base64로 디코딩하면, payload에 어떤 정보가 담겼는지 확인할 수 있습니다.
sub는 식별자로 생각되네요, iat과 exp로 발행시간, 만료시간이 확인되고, 권한에 해당하는 role : CREW가 확인되네요.
앞서 토큰에 권한 정보를 담으면 권한이 확장, 축소되었을 때 동기화하기 어렵다는 점을 고민했었는데요,
프롤로그 예시를 보며 생각지 못했던 지점을 알게 됐습니다.
권한이 거의 바뀌지 않는 경우가 있다는 것이죠.
가령 제가 우아한테크코스 크루에서 코치가 된다면, "role" : ["CREW", "COACH"] 가 될 수도 있겠습니다.
그러나 이런 경우가 실시간 동기화가 요구될만큼 빈번하게 일어나지는 않을 것입니다.
혹시나 제가 4~5년 후 우아한테크코스에 코치로 합류하게 된다면, 그때는 권한을 확장받을 수도 있습니다.
그러나 그 정도 시간이 흐른 후, 그 정도의 큰 이벤트라면 굳이 실시간 동기화까지 고려하진 않아도 되겠다 싶습니다.
이렇게, 권한에 대한 실시간 동기화까지는 고려하지 않아도 될 정도로
권한의 확장, 축소가 빈번하게 일어나지 않을 경우에는 토큰에 권한 정보를 담아서
인터셉터를 통한 빠른 접근제어가 가능하겠다는 생각이 듭니다.
🙌 한 사이트 내에서 때론 관리자이면서 때론 사용자여야한다면? - 찜꽁 사례
우아한테코크스 3기 크루분들께서 만드신 찜꽁은 나만의 공간을 만들고
쉽게 예약해서 사용할 수 있게 하기 위해 만들어진 서비스입니다.
저를 비롯한 4기 크루들, 그리고 테크 살롱에 찾아주시는 많은 분들이 너무 잘 이용하고 있습니다.
찜꽁에서는 JWT에 식별자만 담기로 했고, 그 식별값을 email로 잡았습니다.
@LoginEmail 애너테이션이 확인되면,
ArgumentResolver에서는 전달된 토큰의 payload를 꺼내 LoginEmailDto로 만들어 컨트롤러에 전달합니다.
마찬가지로 토큰은 LocalStorage에 저장되어 사용됩니다.
찜꽁은 회원 가입 시 나만의 공간을 만들 수 있습니다.
여기에서는 제가 관리자 권한을 갖고 공간을 모두 제 취향대로 꾸밀 수 있습니다.
그러나 선릉 캠퍼스 공간에서는 제가 관리자가 아닙니다.
즉 한 사이트 내에서도 관리자이기도 해야 하고, 사용자이기도 해야 합니다.
만약 굳이 하고 싶다면 토큰에 관리자인 곳의 공간 식별자들을 배열로 담을 수도 있을 것입니다.
그러나 관리하는 공간이 많아지면 토큰이 커지고 비효율을 초래하게 됩니다.
그래서 찜꽁에서는 아마 권한 정보를 토큰에 담지 않은 것 같습니다.
결론적으로 빈번하게 권한이 확장, 축소되거나
관리자이거나 사용자이거나 하는 경우에는 토큰에 권한 정보를 담기가 곤란합니다.
그러나 한 번 정해진 권한 정보가 거의 바뀔 일이 없는 곳에선
토큰에 권한 정보를 담아서 이를 유용하게 사용할 수 있겠습니다.
✍️ REMEMBER_SESSION - LMS 사례
마지막으로 LMS의 인증 방식을 소개해볼까 합니다.
일단 세션 방식입니다.
쿠키에 JSESSIONID와 NEXTSTEP_REMEMBER_ME 가 보입니다.
주목할만한 점은, JSESSIONID는 세션 쿠키이고
NEXTSTEP_REMEMBER_ME는 영속 쿠키라는 점입니다.
세션 쿠키는 브라우저를 종료하면 만료됩니다.
영속 쿠키는 정해진 만료시간 까지는 브라우저를 종료해도 유지됩니다.
그래서, LMS에서 로그인 이후 JSESSIONID를 삭제하고 페이지를 새로고침할 경우,
로그인은 그대로 유지되고 이전과 다른 값을 가진 JSESSIONID가 쿠키에 저장됩니다.
NEXTSTEP_REMEMBER_ME를 삭제하고 새로고침해도 로그인은 유지됩니다.
세션 쿠키인 JSESSIONID가 아직 유효하기 때문입니다.
그러나 이때 브라우저를 종료했다가 다시 접속하면 로그인이 해제됩니다.
마치 리프레시 토큰 처럼 유효기간 1달짜리 리프레시 세션을 둔 것처럼 읽혀지는데요,
이렇게 두 가지 세션을 사용하는 이유와 장점에 대해서는 추후 더 공부해봐야겠네요!
요약
로그인 성공 결과로 토큰을 응답할 땐 대부분 Response Body에 담아 내려주는 것 같네요!
토큰은 Cookie가 아닌 LocalStorage에 저장하는 것이 일반적인 것 같습니다. 왜 그러한지, 무엇이 다른지는 추후 과제입니다.
토큰에는 민감하지 않은 최소한의 식별자를 저장해야 합니다.
권한 정보가 쉽게 바뀌지 않는다면, 토큰에 권한 정보를 담아 이를 유용하게 사용할 수 있습니다.
'우아한테크코스 4기' 카테고리의 다른 글
JJWT 훑어보기 (0) | 2022.06.06 |
---|---|
🪪 출입증 (8) | 2022.06.04 |
스프링 통합 테스트에 사용되는 도구와 설정들(Sql, Transactional, JdbcTestUtils) (6) | 2022.05.24 |
find vs get (네이밍 컨벤션과 JPA에서의 내부 동작 차이) (2) | 2022.05.24 |
🆙 테스트 어려운 부분 끌어올리기 (feat. Spring 체스 게임방) (2) | 2022.05.15 |