쿠키와 세션을 통한 인증
쿠키는 인증이 필요한 요청을 할 때마다 쿠키를 던져 요청하는 동작 구조를 가진다.
쿠키를 통한 인증 시 다음과 같은 단점들이 존재한다.
쿠키의 단점
1. 민감 정보들을 노출당하기 쉽고 조작당하기도 쉽다.
2. 웹 브라우저마다 지원 형태가 다르기 때문에 공유가 불가능하다.
3. 쿠키의 사이즈 제한(4KB)이 있어 충분한 데이터를 담을 수 없다.
4. 서버에 매번 쿠키 값을 넘겨 인증해야하며, 조작된 쿠키가 넘어오더라도 방지할 수 없다.
세션은 개인 민감 정보를 노출하는 쿠키의 단점을 막을 수 있다.
세션은 인증 정보 자체를 특정 세션에 저장하고, 쿠키에 담아 클라이언트가 쿠키를 요청할 때 마다 세션에 저장되어 있는 정보와 동일한 지 인증한다.
아래의 코드는 개발자 첫 입문 프로젝트 시 사용했던 스프링 로그인 관련 코드이다.
로그인 시 인증 객체를 통해 세션 ID를 만들어 user_email, nickname세션에 저장하고, 세션 ID를 쿠키에 담아 반환할 것이다.
@RequestMapping("signin_check")
public ModelAndView signin_check(@RequestParam String email, String password, UserDto userDto,
HttpSession session, ModelAndView mv) {
String str = userService.login(userDto);
if(str != null) {
session.setAttribute("user_email", userDto.getEmail());
session.setAttribute("nickname", str);
session.setMaxInactiveInterval(60*30);
mv.setViewName("redirect:/");
}else {
mv.setViewName("user/signin");
}
return mv;
}
//로그아웃
@RequestMapping("signout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/";
}
세션에 식별할 수 있는 인증 값을 넣어두고, 비교할 수 있기 때문에 보완이 쿠키에 비해서 좋다. 만약 보안 문제가 발생할 경우, 서버의 세션을 지워버리면 된다. 하지만 반대로 이야기하면, 세션의 장애가 발생할 경우, 인증 전반에 문제가 생겨 사용자의 인증이 불가능한 상황을 야기시킨다.
추가적으로 세션의 단점을 정리해보자면 다음과 같다.
세션의 단점
1. 위에서 언급했듯이, 세션 자체에 문제가 발생하면, 인증 체계가 무너진다.
2. stateful하기 때문에 http의 stateless에 위배된다.
> (scale out 시 걸림돌이 될 수 있다. 세션 ID를 또 다른 서버에 저장해야함)
3. 인증 요청 시 매번 세션 저장소를 조회해야한다.
4. 세션 저장소를 사용하는 비용. 사용자가 증가할수록 메모리 사용량도 증가.
JWT
JWT(Json Web Token)은 인터넷 표준 인증 방식으로, Json 객체에 인증이 필요한 정보들을 담은 후 비밀키로 서명한 토큰이다. 말 그대로 인증에 필요한 정보들을 Token에 담아 암호화시켜 사용하는 토큰이다.
인증 구조 자체는 쿠키와 크게 다르지 않지만, 서명된 토큰이라는 점이 주요 차이점이다. public/private key를 쌍으로 사용하여 토큰에 서명할 경우, 서명된 토큰은 개인 키를 보유한 서버가 이 서명된 토큰이 정상적인 토큰인지 인증할 수 있다.
JWT의 동작 과정
동작 과정은 위와 같다.
사용자가 서버에 로그인 요청을 보내면, 서버는 secret key를 사용해 암호화한 JWT 토큰을 발급하여 내보낸다.
이렇게 받은 토큰을 클라이언트의 로컬에 저장해두고, 인증이 필요한 요청 시 토큰을 헤더에 실어서 보내 토큰 검증 과정을 거친다.
JWT의 구조
JWT는 Header, Payload, Signature의 세 구조로 되어있다.
Header
Header는 토큰의 타입, 서명 생성에 어떤 알고리즘이 사용되었는지를 저장한다.
Payload
Payload에는 Claim을 Key-Value 형태로 저장한다.
여기서 Claim이란 사용자 혹은 토큰에 대한 프로퍼티이며, 토큰에서 사용할 정보의 조각을 의미한다.
여기에는, 커스텀하여 값들을 넣을 수 있지만, 민감한 정보를 payload에 담지 않는 것이 중요하다. header와 payload는 json이 디코딩되어 있을 뿐, 특별한 암호화가 걸려있지 않기 때문에 누구나 값을 알 수 있기 때문이다.
JWT 표준 Claim
1. iss(issuer) - 토큰 발급자
2. sub(subject) - 토큰 제목(사용자에 대한 식별 값)
3. aud(audience) - 토큰 대상자
4. exp(expiration time) - 토큰 만료 시간
5. nbf(not before) - 토큰 활성 날짜
6. iat(issued at) - 토큰 발급 시간
7. jti(jwt id) - 토큰 식별자 (issuer가 여러 명일 때를 위한 구분 값)
Signature
헤더와 페이로드의 문자열을 합친 후, 헤더에서 선언한 알고리즘과 key를 통해 암호화한 값이다.
위에서 언급했듯, Header와 Payload는 단순히 Base64로 인코딩되어있어 누구나 쉽게 복호화할 수 있지만, Signature는 암호화 시 사용된 key가 없으면 복호화 할 수 없다. 이를 통해 JWT는 보안상 안전하다는 특성을 가질 수 있게 된 것이다.
정리
장점
1. 로컬에 저장하기 때문에 서버 스펙에 영향을 받지 않고, 네트워크 부하가 적다 (http 헤더를 통한 전송)
2. 공통 스펙으로 사용 가능한 확장성이 뛰어난 토큰
3. 여러 플랫폼 및 기기에서 동작 가능하며 서로 다른 도메인에서도 통신이 가능
4. 별도의 세션 저장소를 강제하지 않아 stateless하다.
단점
1. 토큰 만료 처리를 구현해야한다.
2. 토큰의 크기를 신경써야한다. (트래픽에 영향)
JWT Token을 활용해서, 개발하는 서비스의 성격에 맞게 보안과 사용자의 편의를 고려하여 조율하면서 좋은 인증 정책을 결정할 수 있도록 해야겠다.
참조
2023.04 ~ 백엔드 개발자의 기록
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!