본문 바로가기

끄적끄적

[끄적끄적] OAuth proxy 서버 인증 flow 설계과정

API서버에서 가입과정을 OAuth 애플리케이션에 전가하고, 인증과정으로 토큰 기반 인증을 채택했을때의 인증 flow 설계 과정을 적은 글입니다.
이 글이 유사한 상황에서 인증 과정을 설계하는 사람들에게 영감이 되어주면 좋겠습니다.
실제 인증 API는 https://api.gitofolio.com/restdocs 인증목차에서 확인할 수 있습니다.

OAuth proxy 서버 인증 flow 설계

목차

- 인증 기술 선택 

- 클라이언트측의 인증 과정과 redirect를 적용했을 경우의 문제점

- redirect포함 인증 flow 설계와 인증과정 탈취에 안전하게 설계하기


인증 기술 선택

토이프로젝트를 진행하면서 API서버에 인증과정을 구현해야했다. 

인증 기술로는 2가지를 고려했는데,

1. 세션 - 쿠키 기반 인증

2. 토큰 기반 인증

API서버의 특성상 자신과 통신하는 클라이언트가 브라우저가 아닌 서버일 가능성이 많다. 따라서, 세션 - 쿠키 기반 인증 기술을 선택하기에는 문제가 있어 보였다.

 

왜 API 서버에서는 세션 - 쿠키 기반 인증 기술을 선택하는데 무리가 있을까?

1. 서버는 클라이언트의 요청을 받을경우 세션에 1:1 연결되는 COOKIE-ID를 생성해서 클라이언트에게 전달해준다. 클라이언트는 다음 요청마다 세션에 연결된 COOKIE-ID를 전송하고, 서버는 이 쿠키에 연결된 세션을 찾는 메커니즘 이다. 즉, 세션을 이용해 로그인을 유지하기 위해선, 쿠키가 필요하다.

그런데, 위 문단에서 클라이언트의 의미는 '브라우저'다. 쿠키의 저장은 기본적으로 브라우저에 종속되어있다. 따라서 클라이언트가 '서버'일 경우, 서버 <-> 서버 통신일 경우 쿠키 - 세션의 이용가능성이 불분명하다.

(클라이언트측 서버에서 api서버측에서 전송된 쿠키값을 뽑아 세션에 저장하고 요청을 보낼때마다 세션에 저장된 쿠키값을 담아 전송한다면 가능할까? 이 부분은 좀 더 알아봐야할듯 싶다)

2. api서버를 이용하는 클라이언트측 서버(이하 클라이언트)의 경우 한번의 인증으로 인증 필요 API의 지속적인 접근권한을 얻는다. 이 권한은 짧게는 일주일 에서 길게는 무한정 지속될 수 있다.
이러한 상황에서 세션 - 쿠키 기반인증을 사용할 경우, 서버측은 자신에게 인증된 서버의 세션을 메모리에 무한정 올려두고 있어야한다. 이는 메모리 누수로 이어질 가능성이 매우 높기 때문에 좋지못한 방법이다.

반면에, 토큰 기반 인증 기술의 경우 위와 같은 문제가 발생하지 않는다.

1. 토큰 기반 인증에서 토큰에 인증정보를 담아 전송하면, 메모리를 절약할 수 있다. (JWT)

2. 접근권한을 갖고있는 토큰을 생성해서 클라이언트 서버에 전송하면, 클라이언트 서버는 1회 인증으로 권한 필요 api에 무한정 접근할 수 있다.

 

위 와 같은 이유로 토큰 기반 인증을 선택했다.

 

구체적으로 사용한 토큰은 2가지 이다.

1. 클라이언트가 브라우저 일때, JWT를 사용하고, 인증과정의 무결성을 보장하기 위해, 자체적으로 만든 Personal access token을 사용한다.

2. 클라이언트가 서버 일때, Personal access token을 이용한다.


클라이언트측의 인증 과정 리다이렉트 요청 과 설계

인증 혹은 가입에 OAuth 애플리케이션을 사용할때, flow는 전형적으로 아래 그림과 같이 나온다.

빨간색은 refresh토큰 관련으로 선택사항이다.

위 과정을 요약하자면,

1. 클라이언트는 OAuth애플리케이션에 자신을 인증한다.

2. 서버와 OAuth 서버는 클라이언트의 인증정보를 갖고 핸드셰이킹(2-5번)을 통해 유저의 타당성을 검증한다.

3. 서버는 토큰을 발급하고 클라이언트에게 전송한다.

4. 클라이언트는 다음요청부터 이 토큰을 Authorization헤더에 포함시켜서 전송한다.

(토큰 포함 위치는 어디든 상관은 없다. 클라이언트와 서버가 서로 정한 규약을 따르면 된다.)

 

위 그림은 전형적인 OAuth 인증패턴으로 이해하기 어렵지 않다. 하지만 위 과정 7번에 리다이렉트 기법을 적용해서, 클라이언트가 원하는 url로 인증과정을 진행할수있게하려면 상당한 수고가 필요하다.

 

우선, 7번 과정에 리다이렉트를 적용할경우 Authorization헤더에 토큰을 담아 줄 수 없다. 

왜 Authorization헤더에 토큰을 담아줄 수 없을까? 
*아래에서 언급하는 리다이렉트는 모두 301(서버사이드)리다이렉트다.

토큰 기반 인증을 사용하기위해, 클라이언트는 서버가 응답한 토큰을 '어딘가에 저장' 해야한다. 하지만, 리다이렉트 요청을 받을 경우 클라이언트는 토큰을 읽지 못한다.

이는 리다이렉트 우선순위와 관련이 있는데, 리다이렉트는 크게 3가지 방식이 있다.
1. HTTP 리다이렉트
2. HTML 리다이렉트
3. Javascript 리다이렉트

위 리다이렉트 방식의 우선순위는 1 -> 2 -> 3번 순서다.
api서버의 경우, 리다이렉트 전략으로 HTTP 리다이렉트를 사용하게되는데, HTTP 리다이렉트가 '어떠한 페이지도 다운되기 전'에 실행되는것을 감안하였을때, 클라이언트가 Authorization 헤더를 읽을틈도 없이 redirect url로 재요청을 보내고 클라이언트가 최종적으로 받는 응답의 Authorization헤더에는 어떠한 정보도 들어있지 않게된다.
비약하자면, 토큰을 저장하는 자바스크립트 (혹은 어떤것)가 실행되기전에, 리다이렉트가 일어나 클라이언트가 첫번째 요청의 Authorization헤더를 읽지 못한다는 것 이다.

Redirect 포함 설계와 인증과정 탈취 방지 기술

리다이렉트 기능을 넣기위해 flow를 조금 변형해보자. 

변형은 아래와 같이 진행될 것 이다.

 

1. 위 그림의 7번과정에서 Authorization에 토큰을 넣어 직접 전달하는것이 아닌, redirect url에 쿼리스트링으로 임시토큰을 전달해줄것 이다. 클라이언트는 임시토큰을 지정된 api에 보내며 실제 토큰을 발급받는다.

 

2. 임시토큰은 쿼리스트링에 노출되므로 보안에 취약하다. 임시토큰과 실제 인증토큰이 1:1 매칭되는것을 방지하기 위해, personal access token을 사용한다.

personal access token?

프로젝트에서 서버 to 서버인증과정에 사용되는 토큰으로 아래와 같은 구조다.
(웹 스펙은 아니고 자체적으로 만든 토큰임)
{
    key : asdfasdfsdafasdfaasdsdf
    value : asdfagfdgawerqwefsdagdfgdafgdfsgsd
}
(token은 key와 value의 쌍으로 이루어져있다.)

personal access token은 frontend server에 저장되는 토큰으로, 서버 to 서버 통신에서 클라이언트측 서버가 자신을 인증할때 사용하는 토큰이다. 발급시점은 일반적으로 브라우저 to 서버 통신에서 브라우저가 인증을 요청하는 시점보다 한참 앞에있다.
*github의 personal access token을 생각하면 이해하기 편하다.

Personal access token의 동작 원리는 아래와 같다.

Personal access token은 첫번째 인증을 요청한 클라이언트와 마지막으로 임시토큰을 요청하는 클라이언트가 같은 클라이언트인지 보장하기 위한 토큰으로 클라이언트는 첫번째 요청에 personal access token-key를 보낸다. 마지막으로 임시토큰을 통해 실제 인증 토큰을 받아오는 과정에서는 personal access token-key와 value, 임시토큰을 함께 보내며, 서버는 첫번째 요청에 보낸 personal access token-key와 실제 토큰을 요청할때 보낸 personal access token-value가 매칭되지 않는다면, 올바른 요청이라고 생각하지 않고 요청을 거절한다. 

즉, 임시토큰이 탈취되더라도 실제 토큰을 얻어오기위해서, 언제 발급받았는지도 모르는 personal access token의 value를 알아야한다.

 

세로 점선은 순서대로 1.클라이언트(브라우저) 2.클라이언트(프론트-서버) 3.서버(api서버) 4.서버(OAuth 서버) 다.

(전체 인증과정은 게시글 가장 아래에 첨부했다.)

보기에는 복잡해보여도 이해하면 어렵지 않다. 위 1번 과 2번 변형이유를 상기하며 flow를 순서대로 따라가보자...


전체 인증과정


위 인증과정의 실제 구현은 레포지토리에서 볼수있다.

 

레포지토리 : https://github.com/gitofolio/gitofolio

 

GitHub - gitofolio/gitofolio: 💎 Github, Notion, Blog... 를 자신의 이력 카드로 꾸미세요

💎 Github, Notion, Blog... 를 자신의 이력 카드로 꾸미세요. Contribute to gitofolio/gitofolio development by creating an account on GitHub.

github.com

 

API문서 : https://api.gitofolio.com/restdocs

 

Gitofolio RESTdocs

user에 관련된 API 목차 입니다. 유저 수정과 저장은 OAuth인증과정에서 자동으로 진행되며, 임의로 수정, 저장 할 수 없습니다. 이 목차에는 유저 가져오기, 유저 삭제하기, 현재 로그인 되어있는

api.gitofolio.com

 

홈페이지 : https://gitofolio.com 

 

Gitofolio

💎 Decorate Github, Notion, Blog etc...

gitofolio.com