인증

인증 관련

인증관련 기초개념을 기술한 페이지 입니다.


1. 인증 기초 개념

jwt, 세션, 토큰, 엑세스토큰, 리프레쉬토큰, http, ... 기초적인 용어정리 보다는 전체적인 메카니즘을 설명드립니다.

1-1. 인증(Authenntication).

인증은 쉽게 말하자면, 로그인 이다. 클라이언트가 자기자신이라고 주장하고 있는 사용자가 맞는지를 검증하는 과정이다. 예를 들어 로그인 화면에서 내가 유저 아이디를 USER1 로 입력하고 패스워드를 입력해 제출하면, 서버에서는 내가 진짜로 USER1 이라는 유저가 맞는지 확인한다.

1-2. 인가(Authorization)

인가는 인증 작업 이후에 행해지는 작업으로, 인증된 사용자에 대한 자원에 대한 접근 확인 절차를 의미한다.

여기에 일반 유저인 USER1과 USER2가 있다. 일반 유저인 USER1 은 글 작성, 조회, 수정, 삭제 등 일반적인 작업에 대한 권한이 부여되어 있다. 하지만 USER1 은 USER2가 작성한 글을 수정하거나 제거할 수는 없다. 타인의 리소스에 대해서는 인가되어 있지 않기 때문이다. 또한 USER1과 USER2 는 모두 관리자 페이지에 접속할 수 없다. 일반 유저는 관리자 페이지에 대해 인가되어 있지 않기 때문이다.

1-3. HTTP의 비상태성(Stateless)

HTTP는 비상태성이라는 특성을 갖는다. 서버는 클라이언트의 상태를 저장하지 않으며, 따라서 이전 요청과 다음 요청의 맥락이 이어지지 않는다. HTTP는 바로 직전에 발생한 통신을 기억하지 못한다. 따라서 HTTP 단독으로는 요청한 클라이언트가 이전에 이미 인증과정을 거쳤는지 알 방법이 없다.

그렇다고, 글을 조회하거나 작성할 때 마다 사용자에게 로그인을 하라고 요청할 수는 없는 노릇이다. 매 작업마다 로그인을 사용자에게 요청하는 것은 UX를 전혀 고려하지 않은 방식이다.

그렇다면, 사용자의 아이디와 패스워드를 브라우저에 그대로 저장해놓고, 매 요청마다 함께 그 정보를 보내는 방법은 어떨까? 이런 방식은 전송 데이터가 커져 비효율적일 뿐더러 클라이언트에 민감한 데이터가 그대로 저장되어 보안에도 굉장히 취약하다. 서버 입장에서도 매 작업마다 데이터베이스를 조회하고, 인증과정을 거치는 것이 비효율일 것 이다.

이런 HTTP 환경에서 서버는 어떤 방식으로 사용자를 인가할까? 웹 어플리케이션에서는 이 문제를 세션 또는 토큰을 사용하여 문제를 해결한다. 즉, 세션과 토큰은 인증 보다는 인가와 관련된 기술이라고 할 수 있다.

1-4. 세션 기반 인증

세션기반 인가는 사용자의 인증 정보가 서버의 세션 저장소에 저장되는 방식이다. 사용자가 로그인을 하면, 해당 인증 정보를 서버의 세션 저장소에 저장하고, 사용자에게는 저장된 세션 정보의 식별자인 Session ID를 발급한다. 발급된 Session ID는 브라우저에 쿠키 형태로 저장되지만, 실제 인증 정보는 서버에 저장되어 있다.

브라우저는 인증 절차를 마친 이후의 요청마다 HTTP Cookie 헤더에 Session ID 를 함께 서버로 전송한다. 서버는 요청을 전달받고, Session ID에 해당하는 세션 정보가 세션 저장소에 존재한다면 해당 사용자를 인증된 사용자로 판단한다.

1-5. 토큰 기반 인증

세션 기반 인증이 인증 정보를 서버에 저장하는 방식이라면, 토큰 기반 인증은 인증 정보를 클라이언트가 직접 들고 있는 방식이다. 이때 인증 정보가 토큰의 형태로 브라우저의 로컬 스토리지(혹은 쿠키)에 저장된다. 토큰의 종류에 따라 다르겠지만, 대표적인 토큰인 JWT의 경우 디지털 서명이 존재해 토큰의 내용이 위변조 되었는지 서버측에서 확인할 수 있다.

토큰 기반 인증에서는 사용자가 가지고 있는 토큰을 HTTP 의 Authorization 헤더에 실어 보낸다. 이 헤더를 수신한 서버는 토큰이 위변조 되었거나, 만료 시각이 지나지 않은지 확인한 이후 토큰에 담겨있는 사용자 인증 정보를 확인해 사용자를 인가한다.

1-6. 세션 인증 할껀지 vs 토큰 인증 할껀지 (추세는 토큰기반)

안전성과 보안문제 세션의 경우 모든 인증 정보를 서버에서 관리하기 때문에 보안 측면에서 조금 더 유리하다. 설령 세션 ID가 해커에게 탈취된다고 하더라도, 서버측에서 해당 세션을 무효 처리하면 된다.

하지만 토큰의 경우 그렇지 않다. 토큰은 서버가 트래킹하지 않고, 클라이언트가 모든 인증정보를 가지고 있다. 따라서 토큰이 한번 해커에게 탈취되면 해당 토큰이 만료되기 전까지는 속수무책으로 피해를 입을 수 밖에 없다.

또한 JWT 특성상 토큰에 실린 Payload가 별도로 암호화 되어있지 않으므로, 누구나 내용을 확인할 수 있다. 따라서 Payload에 민감한 데이터는 실을 수 없다. 즉, Payload 에 실을 수 있는 데이터가 제한된다.

하지만 세션과 같은 경우에는 모든 데이터가 서버에 저장되기 때문에 아무나 함부로 열람할 수 없으므로 저장할 수 있는 데이터에 제한이 없다.

확장성 그럼에도 불구하고 최근 모던 웹 어플리케이션이 토큰 기반 인증을 사용하는 이유가 바로 이 확장성이다.

일반적으로 웹 어플리케이션의 서버 확장 방식은 수평 확장을 사용한다. 즉, 한대가 아닌 여러대의 서버가 요청을 처리하게 된다. 이때 별도의 작업을 해주지 않는다면, 세션 기반 인증 방식은 세션 불일치 문제를 겪게 된다. 이를 해결하기 위해서 Sticky Session, Session Clustering, 세션 스토리지 외부 분리 등의 작업을 해주어야한다.

하지만, 토큰 기반 인증 방식의 경우 서버가 직접 인증 방식을 저장하지 않고, 클라이언트가 저장하는 방식을 취하기 때문에 이런 세션 불일치 문제로부터 자유롭다. 이런 특징으로 토큰 기반 인증 방식은 HTTP의 비상태성(Stateless)를 그대로 활용할 수 있고, 따라서 높은 확장성을 가질 수 있다.

서버의 부담 확장성과 어느정도 이어지는 내용이다. 세션 기반 인증 방식은 서비스가 세션 데이터를 직접 저장하고, 관리한다. 따라서 세션 데이터의 양이 많아지면 많아질수록 서버의 부담이 증가할 것 이다.

하지만 토근 기반 인증 방식은 서버가 인증 데이터를 가지고 있는 대신, 클라이언트가 인증 데이터를 직접 가지고 있다. 따라서 유저의 수가 얼마나 되던 서버의 부담이 증가하지 않는다. 따라서 서버의 부담 측면에서는 세션 기반 인증 방식보다는 토큰 기반 인증 방식이 조금 더 유리함을 알 수 있다.

2. Oauth 등장 배경

우리의 서비스가 사용자를 대신하여 구글의 캘린더에 일정을 추가하거나, 페이스북, 트위터에 글을 남기는 기능을 만들 수 있을 것 이다. 이때, 가장 쉽게 이 기능을 구현하는 방법은 사용자로부터 구글, 페이스북, 트위터의 ID, Password 를 직접 제공받아 우리의 서비스에 저장하고 활용하는 방법이다.

하지만 이런 방법이 안전할까? 사용자들은 처음보는 우리 서비스를 신뢰하고 자신의 구글 계정 정보를 맡길 수 있을까? 사실 현재의 관점으로 바라보면 미친짓이나 다름없다.

심지어 일반적으로 사용자들은 많은 웹사이트에서 동일한 ID, Password 를 사용하기 때문에 이것이 유출된다면 우리의 사이트에서 피해가 발생하는 것으로만 끝나지 않을 것 이다.

이는 우리의 서비스와 구글, 페이스북, 트위터 등의 입장에서도 굉장한 부담이다. 우리의 입장에서는 사용자의 아주 민감한 정보를 직접 저장하고 관리해야한다는 부담이 생길 것 이다. 또한 구글, 페이스북, 트위터는 자신의 사용자 정보를 신뢰할 수 없는 제3자에게 맡긴다는 것이 매우 불만족스러울 것 이다.

또한 유저는 우리의 서비스로 부여한 권한을 취소하려면, 비밀번호를 변경하는 방법밖에 없다.

이런 문제를 해결하기 위해 OAuth 가 등장하기 이전에는 구글은 AuthSub, 야후는 BBAuth 등 각자 회사가 개발한 방법을 사용하였다. 하지만 이 방식은 표준화 되어있지 않기 때문에 구글과 연동하는 서비스를 만들기 위해서는 AuthSub, 야후와 연동하기 위해서는 BBAuth 에 맞춰 개별적으로 개발하고 유지보수 해야한다.

이를 위해 등장한 것이 바로 OAuth 이다. 최초 1.0 버전은 2006년 트위터와 Ma.gnolia 가 주도적으로 개발하였다. 이후 1.0버전이 개선된 1.0a 버전이 출시되었으나 모바일 어플리케이션 등에서 안전하게 사용될 수 없는 사례가 존재했다. 이런 사례를 보완하고 기존 버전보다 조금 더 단순화한 OAuth 2.0 버전이 2012년에 등장하게 되었다.

2-1. OAuth 란?

구글, 페이스북, 트위터와 같은 다양한 플랫폼의 특정한 사용자 데이터에 접근하기 위해 제3자 클라이언트(우리의 서비스)가 사용자의 접근 권한을 위임(Delegated Authorization)받을 수 있는 표준 프로토콜이다.

쉽게 말하자면, 우리의 서비스가 우리 서비스를 이용하는 유저의 타사 플랫폼 정보에 접근하기 위해서 권한을 타사 플랫폼으로부터 위임 받는 것 이다.

2-2. OAuth 2.0 주체

위에서 언급한 '사용자', '우리의 서비스', '타사 플랫폼(구글, 페이스북, 트위터)' 등을 부르는 용어가 존재한다. 이를 먼저 정리하고 넘어간다.

2-3. Resource Owner

리소스 소유자. 우리의 서비스를 이용하면서, 구글, 페이스북 등의 플랫폼에서 리소스를 소유하고 있는 사용자이다. 리소스라 하면 '구글 캘린더 정보', '페이스북 친구 목록', '네이버 블로그 포스트 작성' 등이 해당될 것이다.

2-4. Authorization & Resource Server

Authorization Server는 Resource Owner를 인증하고, Client에게 액세스 토큰을 발급해주는 서버이다. Resource Server는 구글, 페이스북, 트위터와 같이 리소스를 가지고 있는 서버를 말한다.

2-5. Client

Resource Server의 자원을 이용하고자 하는 서비스. 보통 우리가 개발하려는 서비스가 될 것이다.

2-6. 어플리케이션 등록

OAuth 2.0 서비스를 이용하기전에 선행되어야 하는 작업이 있다. Client를 Resource Server 에 등록해야하는 작업이다. 이때, Redirect URI를 등록해야한다. Redirect URI는 사용자가 OAuth 2.0 서비스에서 인증을 마치고 (예를 들어 구글 로그인 페이지에서 로그인을 마쳤을 때) 사용자를 리디렉션시킬 위치이다.

Redirect URI OAuth 2.0 서비스는 인증이 성공한 사용자를 사전에 등록된 Redirect URI로만 리디렉션 시킨다. 승인되지 않은 URI로 리디렉션 될 경우, 추후 설명할 Authorization Code를 중간에 탈취당할 위험성이 있기 때문이다. 일부 OAuth 2.0 서비스는 여러 Redirect URI를 등록할 수 있다.

Redirect URI는 기본적으로 보안을 위해 https만 허용된다. 단, 루프백(localhost)은 예외적으로 http가 허용된다.

Client ID, Client Secret 등록과정을 마치면, Client ID와 Client Secret를 얻을 수 있다. 발급된 Client ID와 Client Secret은 액세스 토큰을 획득하는데 사용된다. 이때, Client ID는 공개되어도 상관없지만, Client Secret은 절대 유출되어서는 안된다. 심각한 보안사고로 이어질 수 있다.

2-7. Oauth 2.0의 동작 메커니즘

1 ~ 2. 로그인 요청

Resource Owner가 우리 서비스의 '구글로 로그인하기' 등의 버튼을 클릭해 로그인을 요청한다. Client는 OAuth 프로세스를 시작하기 위해 사용자의 브라우저를 Authorization Server로 보내야한다.

클라이언트는 이때 Authorization Server가 제공하는 Authorization URL에 response_type , client_id , redirect_uri , scope 등의 매개변수를 쿼리 스트링으로 포함하여 보낸다.

예를 들어 어떤 OAuth 2.0 서비스의 Authorization URL이 https://authorization-server.com/auth 라면, 결과적으로 Client는 아래와 같은 URL을 빌드할 것 이다.

https://authorization-server.com/auth?response_type=code
&client_id=29352735982374239857 &redirect_uri=https://example-app.com/callback
&scope=create+delete

이때 Authorization Server에게 보낼 매개변수는 아래와 같은 것들이 있다.

response_type : 반드시 code 로 값을 설정해야한다. (참고) 인증이 성공할 경우 클라이언트는 후술할 Authorization Code를 받을 수 있다. client_id : 애플리케이션을 생성했을 때 발급받은 Client ID redirect_uri : 애플리케이션을 생성할 때 등록한 Redirect URI scope : 클라이언트가 부여받은 리소스 접근 권한. 아래에서 더 자세히 설명하겠다.

5 ~ 6 Authorization Code 발급, Redirect URI로 리디렉트

인증이 성공되었다면, Authorization Server 는 제공된 Redirect URI로 사용자를 리디렉션시킬 것 이다. 이때, Redirect URI에 Authorization Code를 포함하여 사용자를 리디렉션 시킨다. 구글의 경우 코드를 쿼리 스트링에 포함한다.

이때, Authorization Code란 Client가 Access Token을 획득하기 위해 사용하는 임시 코드이다. 이 코드는 수명이 매우 짧다. (일반적으로 1~10분)

7 ~ 8 Authorization Code와 Access Token 교환

Client는 Authorization Server에 Authorization Code를 전달하고, Access Token을 응답받는다. Client는 발급받은 Resource Owner의 Access Token을 저장하고, 이후 Resource Server에서 Resource Owner의 리소스에 접근하기 위해 Access Token을 사용한다.

Access Token은 유출되어서는 안된다. 따라서 제 3자가 가로채지 못하도록 HTTPS 연결을 통해서만 사용될 수 있다.

Authorization Code와 Access Token 교환은 token 엔드포인트에서 이루어진다. 아래는 token 엔드포인트에서 Access Token을 발급받기 위한 HTTP 요청의 예시이다. 이 요청은 application/x-www-form-urlencoded 의 형식에 맞춰 전달해야한다.

POST /oauth/token HTTP/1.1 Host: authorization-server.com
grant_type=authorization_code &code=xxxxxxxxxxx
&redirect_uri=https://example-app.com/redirect &client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx

필수로 전달해야하는 매개변수를 살펴보자.

grant_type : 항상 authorization_code 로 설정되어야 한다. (참고) code : 발급받은 Authorization Code redirect_uri : Redirect URI client_id : Client ID client_secret : RFC 표준상 필수는 아니지만, Client Secret이 발급된 경우 포함하여 요청해야한다.

9. 로그인 성공

위 과정을 성공적으로 마치면 Client는 Resource Owner에게 로그인이 성공하였음을 알린다.

10 ~ 13. Access Token으로 리소스 접근

이후 Resource Owner가 Resource Server의 리소스가 필요한 기능을 Client에 요청한다. Client는 위 과정에서 발급받고 저장해둔 Resource Owner의 Access Token을 사용하여 제한된 리소스에 접근하고, Resource Owner에게 자사의 서비스를 제공한다.

OAuth 2.0의 스코프

OAuth 2.0은 스코프라는 개념을 통해서 유저 리소스에 대한 클라이언트의 접근 범위를 제한할 수 있다. 스코프는 여러개가 될 수 있으며, 대소문자를 구문하는 문자열을 공백으로 구분하여 표현된다. 이때 문자열은 OAuth 2.0 인증 서버에 의해 정의된다.

예를 들어 우리의 서비스가 사용자의 구글 연락처를 받아오고 싶다면, OAuth 2.0 스코프에 연락처 스코프 문자열을 포함하여 OAuth 2.0 제공자에게 전달하면 된다. 그렇다면 사용자는 아래의 사진과 같이 스코프에 명시된 권한을 요청하는 화면을 만날 수 있을 것 이다.

이 과정을 거쳐 발급된 액세스 토큰은 부여된 스코프에 해당하는 권한을 제한적으로 획득할 수 있다.

Authorization Code가 왜 필요한 것 인가?

그런데 조금 이상하다. Authorization Code를 발급하지 않고, 곧바로 Client에게 Access Token을 발급해줘도 되지 않을까? 왜 굳이 Access Token을 획득하는 과정에 Authorization Code 발급 과정이 필요할까?

Redirect URI를 통해 Authorization Code를 발급하는 과정이 생략된다면, Authorization Server가 Access Token을 Client에 전달하기 위해 Redirect URI를 통해야 한다. 이때, Redirect URI를 통해 데이터를 전달할 방법은 URL 자체에 데이터를 실어 전달하는 방법밖에 존재하지 않는다. 브라우저를 통해 데이터가 곧바로 노출되는 것 이다.

하지만, Access Token은 민감한 데이터이다. 이렇게 쉽게 노출되어서는 안된다. 이런 보안 사고를 방지 Authorization Code를 사용하는 것 이다.

Redirect URI를 프론트엔드 주소로 설정하여, Authorization Code를 프론트엔드로 전달한다. 그리고 이 Authorization Code는 프론트엔드에서 백엔드로 전달된다. 코드를 전달받은 백엔드는 비로소 Authorization Server의 token 엔드포인트로 요청하여 Access Token을 발급한다.

이런 과정을 거치면 Access Token이 항상 우리의 어플리케이션과 OAuth 서비스의 백채널을 통해서만 전송되기 때문에 공격자가 Access Token을 가로챌 수 없게된다. (참고)

Previous
Anti-pattern