로그인 방식
용어 정리
인증과 인가
- 시스템의 자원을 적절하고 유효한 사용자에게 전달하고 공개하는 방법
인증 (Authentication)
- 인증은 쉽게 말하자면, 로그인이다.
- 클라이언트가 자기자신이라고 주장하고 있는 사용자가 맞는지를 검증하는 과정이다.
- 예: 클라이언트에서 보낸 유저 아이디와 서버에 등록돼있는 유저 아이디를 확인한다.
인가 (Authorization)
- 인가는 인증 작업 이후에 행해지는 작업으로, 인증된 사용자에 대한 자원에 대한 접근 확인 절차를 의미한다.
여기에 일반 유저인 USER1과 USER2가 있다. 일반 유저인 USER1 은 글 작성, 조회, 수정, 삭제 등 일반적인 작업에 대한 권한이 부여되어 있다. 하지만 USER1 은 USER2가 작성한 글을 수정하거나 제거할 수는 없다. 타인의 리소스에 대해서는 인가되어 있지 않기 때문이다. 또한 USER1과 USER2 는 모두 관리자 페이지에 접속할 수 없다. 일반 유저는 관리자 페이지에 대해 인가되어 있지 않기 때문이다.
HTTP의 비상태성(Stateless)
- 서버는 클라이언트의 상태를 저장하지 않으며, 따라서 이전 요청과 다음 요청의 맥락이 이어지지 않는다.
- HTTP는 바로 직전에 발생한 통신을 기억하지 못한다. 따라서 HTTP 단독으로는 요청한 클라이언트가 이전에 이미 인증과정을 거쳤는지 알 방법이 없다.
- 매 요청마다 로그인을 할 수는 없기 때문에 웹에서는 세션 또는 토큰을 사용해서 해결한다. 즉, 세션과 토큰은 인가와 관련된 기술이다.
쿠키(Cookie)
- 쿠키는 HTTP 요청과 응답에 함께 실려 전송된다.
- 쿠키는 클라이언트 즉, 브라우저에 저장된다.
- 웹 서버가 클라이언트로 보내는 응답의 헤더 중
Set-Cookie
라는 헤더에 키와 값을 함께 실어 보내면 그 응답을 받은 브라우저는 해당 쿠키를 저장하고, 그 다음 요청부터 자동으로 쿠키를 헤더에 넣어 송신한다.
쿠키의 문제점
- 쿠키는 한번 생성되면 매 요청마다 헤더에 실려 서버로 전송된다. 만약 쿠키에 저장된 정보가 많다면, 매번 요청마다 큰 오버헤드가 발생할 것이다.
- 이런 이유로 브라우저마다 다르겠지만 일반적으로 쿠키의 데이터는 4kb로 제한이 되어있다. 또한 인터넷 익스플로러를 제외하고는 사이트당 쿠키의 갯수도 20개로 제한이 되어있다고 한다.
- 또한 쿠키는 만료시각을 명시할 때 파일로 저장되어 브라우저가 종료되더라도 휘발하지 않기 때문에 보안상의 이슈가 발생할 수 있다.
세션
- 세션은 쿠키와 다르게 정보를 서버측에 저장하는 방식이다. 세션을 사용하면 위에서 언급한 쿠키의 문제점의 다수를 해결할 수 있다.
- 세션이란 브라우저로 웹서버에 접속한 시점부터 브라우저를 종료하여 연결을 끝내는 시점까지의 일련의 요청을 하나의 상태로 간주하고, 그 상태를 일정하게 유지하는 기술이다.
세션 생성 과정
- 맨 처음, 사용자가 HTTP 요청의 Body에 인증 정보 (유저이름이나 패스워드 같은 것들)을 실어 서버로 보낸다.
- 서버에서는 해당 인증정보가 유효하면 사용자와 데이터를 식별하는 Session ID를 생성한다.
- 생성된 Session ID는 응답의
Set-Cookie
헤더에 생성된 세션 아이디를 실어 보내진다. - 클라이언트는 해당 세션 아이디를 쿠키에 저장하고, 매 요청마다 세션 아이디를
Cookie
헤더에 실어 전송한다. 서버는 전달받은 세션 아이디를 통해 해당 요청의 송신자가 누구인지 식별할 수 있다.
세션 아이디에 대한 쿠키의 Key는 세션을 관리하는 주체에 따라 다르다. 즉, 무조건 쿠키에 SESSIONID 라는 이름으로 저장되는 것은 아니다. 예를들어 톰캣은 JSESSIONID 라는 이름으로 세션 아이디를 저장하고, node.js 는 connect.sid 라는 이름으로 저장한다고 한다.
세션 정보 저장 장소
- 일반적으로 생성된 세션 데이터는 서버의 메모리에 저장된다.
- 하지만 로드 밸런싱등의 이유로 서버를 수평 확장(Scale Out) 하는 경우가 많을 것이다.
- 수평 확장은 수직 확장(Scale Up, 고성능 장비로 교체) 서버를 여러 대 두기만 하면 되므로 경제적 부담이 적고 확장에 유연하다.
- 이런 경우 최초 세션이 생성된 서버와 그 이후의 요청을 받은 서버가 다른 경우 세션이 불일치하는 문제가 발생한다.
- 이런 문제를 해결하기 위해 Sticky Session(유저의 요청이 무조건 세션을 생성한 서버로 향하도록 한다.), Session Clustering(여러 웹서버가 모두 동일한 세션 정보를 가지고 있다.) 방식이 있다.
- 하지만, 세션 정보를 관리하는 서버를 아예 별개로 두는 Session Storage 방식이 가장 많이 쓰인다고 한다.
- 이를 위해 MySQL 같은 일반적인 RDBMS를 사용할수도 있겠지만, Key-Value로 저장되는 세션 특성상 Redis나 memcached 와 같은 Key-Value 쌍으로 저장되는 인메모리 스토어를 사용하는 것이 일반적이다.
세션기반 인증
- 세션기반 인가는 사용자의 인증 정보가 서버의 세션 저장소에 저장되는 방식이다. 사용자가 로그인을 하면, 해당 인증 정보를 서버의 세션 저장소에 저장하고, 사용자에게는 저장된 세션 정보의 식별자인 Session ID를 발급한다.
- 발급된 Session ID는 브라우저에 쿠키 형태로 저장되지만, 실제 인증 정보는 서버에 저장되어 있다.
- 브라우저는 인증 절차를 마친 이후의 요청마다 HTTP Cookie 헤더에 Session ID 를 함께 서버로 전송한다.
- 서버는 요청을 전달받고, Session ID에 해당하는 세션 정보가 세션 저장소에 존재한다면 해당 사용자를 인증된 사용자로 판단한다.
JWT
JWT란?
- Json Web Token 의 줄임말이다. RFC 7519 에 명세되어 있는 국제 표준으로써, 통신 양자간의 정보를 JSON 형식을 사용하여 안전하게 전송하기 위한 방법이다.
- JWT는 정보가 토큰 자체에 포함된 (Self-Contained) 클레임 (Claim) 기반 토큰이다.
- JWT는 인증 (Authentication) 과 권한부여(Authorization) 에 사용되는 것이 가장 일반적이다.
- 인증 절차를 거쳐 서버에서 JWT 를 발급해주면, 클라이언트는 이를 잘 보관하고 있다가 API 등을 사용할 때에 서버에 JWT를 함께 제출하며 서버로부터 행위에 대해 인가 받을 수 있다.
- JWT 는 해시 혹은 비대칭키 방식을 사용하여 서명 (Signature) 하기 때문에 무결성을 검증할 수 있다는 특징이 있다.
- 또한 토큰 자신이 정보를 직접 포함하고 있는 특징 덕분에, 통신 양자간 정보를 안전하게 전송할 때에도 사용된다.
- 또한 JWT 는 URL에 대해 안전한 (URL-Safe) 문자열로 구성되어 있어 어떤 경로로든 전송할 수 있다.
서버기반 인증과 토큰기반 인증
서버기반 인증
- 서버기반 인증에서는 사용자가 성공적으로 로그인한 이후 서버에서 사용자에 대한 세션(Session) 을 생성한다.
- 또한 이와 동시에 사용자의 브라우저에는 세션 ID 를 저장하는 쿠키가 생성된다.
- 서버는 이 세션 ID 를 통해 사용자를 식별하고, 사용자에 대한 정보를 저장, 관리한다.
토큰기반 인증
- 토큰기반 인증방식은 유저의 정보를 서버에 저장하지 않는다. 유저가 성공적으로 로그인하면, 서버는 클라이언트로 토큰 (가장 일반적으로 JWT 가 사용됨) 을 발급한다.
- 클라이언트는 토큰을 받아 저장하고, 서버에 요청을 할 때 HTTP header 에 실어 함께 전송한다.
- 서버는 이를 검증 (Verification) 하고, 유저를 인가한다. 이와 같이 서버는 ‘발급’ 과 ‘검증’ 두가지 역할만 할뿐 직접 정보를 갖고 있지 않다. 유저 상태의 저장 책임이 서버에서 클라이언트로 이동된 것이다.
- 수평 확장의 환경에서 여러대의 서버 컴퓨터가 모두 유저에 대한 정보를 기억하고 있을 필요가 없다는 뜻이다.
하지만, JWT 와 같이 토큰 자체에 정보가 저장되는 형태의 토큰은 세션과 달리 클라이언트에 유저의 정보가 저장되므로 노출되기 매우 쉽다. 따라서 민감한 정보를 담아서는 절대 안된다. 또한 토큰의 사이즈는 세션 ID 에 비해 굉장히 비대하다. 토큰기반 인증을 사용하면 이런 토큰을 사용하여 통신하면서 발생하는 오버헤드를 감안해야한다는 단점이 존재한다.
예전에는 의미없는 랜덤 문자열등을 생성해서 토큰 기반 인증을 구현하였는데, 이 토큰에는 만료시각 등의 정보를 담을 수 없어, 따로 만료시킬 수단이 존재하지 않는다. JWT 같은 경우 데이터를 직접 갖고 있는 클레임 (Claim) 기반 토큰이므로 토큰의 만료를 구현할 수 있게 되었다.
JWT 의 구조
- 토큰은 헤더 (Header), 페이로드 (Payload), 서명 (Signature) 세 부분으로 구성되어 있다. 각 구성요소는 점 (
.
) 으로 분리된다. 따라서 JWT 는헤더.페이로드.서명
의 형태를 갖는다.
각각의 구성요소는 JSON 형태로 표현된다. 다만, JSON 의 경우 개행을 포함할 수 있어, 이를 한줄로 나타내기 위해 최종적으로는 각 구성요소를 Base64 로 인코딩한다.
헤더 (Header)
- 헤더에는 일반적으로 토큰의 유형과 암호화 알고리즘 두가지 정보를 아래와 같이 JSON 의 형태로 담고있다.
{
"alg": "HS256",
"typ": "JWT"
}
- 토큰을 사용하는 측에서 토큰의 유형이 JWT 임을 확신할 수 있다면,
typ
필드는 생략되어도 괜찮다. alg
에 넣어둔 암호화 알고리즘은 주로 HMAC SHA256 또는 RSA 가 사용된다. 이는 후술할 서명 (Signature) 에서 사용된다.
페이로드 (Payload)
- 페이로드는 사용자의 정보 혹은 데이터 속성 등을 나타내는 클레임(Claim) 이라는 정보 단위로 구성된다.
- 클레임도 3가지로 구분할 수 있는데 각각 등록된 클레임 (Registered Claim), 공개 클레임 (Public Claim), 비공개 클레임 (Private Claim) 으로 구성되어 있다.
등록된 클레임 (Registered Claim)
- JWT 사양에 이미 정의된 클레임이다.
- 아래의 7개의 등록된 클레임이 정의되어 있다. 모든 클레임은 선택적이다. 토큰 사이즈를 작게 유지하기 위해 이름이 3글자로 축약되어 있는 것을 확인할 수 있다.
iss
: Issuer. 토큰 발급자를 나타낸다.sub
: Subject. 토큰 제목을 나타낸다.aud
: Audience. 토큰 대상자를 나타낸다.exp
: Expiration Time. 토큰 만료 시각을 나타낸다. Numeric Date 형식으로 나타낸다.nbf
: Not Before. 토큰의 활성 시각을 나타낸다. 쉽게 말해, 이 시각 전에는 토큰이 유효하지 않다는 의미이다. Numeric Date 형식으로 나타낸다.iat
: Issued At. 토큰이 발급된 시각을 나타낸다. Numeric Date 형식으로 나타낸다. 이 값으로 토큰이 발급된지 얼마나 오래됐는지 확인할 수 있다.jti
JWT ID. JWT 의 식별자를 나타낸다.
공개 클레임 (Public Claim)
- 공개 클레임은 JWT 를 사용하는 사람들에 의해 정의되는 클레임으로, 충돌 방지를 위해 URI 형태로 이름을 짓거나, IANA JSON Web Token Claims Registry 라는 곳에 직접 클레임을 등록해야한다.
- 사실 단순히 서버와 클라이언트 사이에서 사용자를 인증하는 용도로 사용한다면 크게 신경쓰지 않아도 좋다.
- 서버-클라이언트 사이의 단순 통신을 넘어 제 3자도 JWT 토큰을 사용할 때 충돌이 일어나지 않도록 합의된 클레임이라고 생각하면 된다.
{
"email": "sample@domain.com",
"profile": "http://domain.com/image.png",
"http://domain.com/xxx/yyy/is_admin": true
}
- 위 처럼 등록된 공개 클레임인
email
,profile
등을 사용할수도 있고,http://domain.com/xxx/yyy/is_admin
처럼 URI 형태로도 사용할 수 있다.
비공개 클레임 (Private Claim)
서버와 클라이언트 사이에서만 협의된 클레임으로, 공개 클레임과 충돌이 일어나지 않게 사용하면 된다.
{
"user_id": "123456790",
"user_age": 25
}
서명 (Signature)
- 특정 암호화 알고리즘을 사용하여, Base64 인코딩된 헤더와 Base64 인코딩된 페이로드 그리고 비밀키를 이용하여 암호화한다. 서명을 통해 서버는 헤더 혹은 페이로드가 누군가에 의해 변조되었는지 그 무결성을 검증하고 보장할 수 있다.
- HMAC SHA256 을 사용한 서명 생성을 아래와 같은 수도코드 (pseudo-code) 로 나타낼 수 있다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Reference
'Develop > Spring' 카테고리의 다른 글
Spring | Spring MVC 패턴 - HTTP 요청을 받고 응답하기까지의 전 과정(2) (1) | 2024.09.05 |
---|---|
Spring | Spring MVC 패턴 - MVC와 서블릿 (1) (0) | 2024.09.05 |
Spring | Spring AOP의 동작원리와 JDK Dynamic Proxy vs CGLIB Proxy 비교 및 Spring AOP와 AspectJ 비교 (0) | 2024.09.05 |
Spring, BE | Apache Tomcat 바로 알기 + 웹서버(Nginx와 Apache 비교)를 두는 이유 (2) | 2024.08.13 |
고민 | 새로고침 마다 데이터가 중첩 저장된다..? (0) | 2023.08.17 |