Oauth 2.0 & OpenID Connect 알아보기
회사에서
KeyCloak
의 OpenId를 이용해 로그인 및 권한관리를 구현하려고 한다.따라서 각 기술에 대해 공부해보았다.
OAuth는 무엇인가?
OAuth(Open Authorization)
은 위임 권한부여를 위한 표준 프로토콜이다.
OAuth는 어플리케이션이 사용자의 패스워드 없이 사용자의 데이터에 접근 가능하도록 허가해준다.
OAuth 2.0 용어 정리
다음 용어들을 알아야 OAuth 2.0을 이해할 수 있다.
- 리소스 소유자(Resource Owner): 클라이언트 어플리케이션이 접근하길 원하는 데이터를 가지고 있는 사용자이다.
- 클라이언트(Client): 사용자의 데이터에 접근하고 싶어하는 어플리케이션이다.
- 권한부여 서버(Authorization Server): 사용자로부터 권한을 부여받음으로써 클라이언트가 사용자의 데이터에 접근할 권한을 부여해주는 권한부여 서버이다.
- 리소스 서버(Resource Server): 클라이언트가 접근하길 원하는 데이터를 갖고 있는 시스템이다. 때때로 권한부여 서버와 리소스 서버가 같은 경우가 있다.
- 액세스 토큰(Access Token): 리소스 서버에서 사용자에 의해 부여된 데이터에 접근하기 위해서 클라이언트가 사용할 수 있는 유일한 키가 바로 이 엑세스 토큰이다.
아래 그림은 OAuth 2.0의 추상적인 흐름이다.
권한 키/인가는 타입 코드 또는 토큰이 될 수 있다. 다른 권한 부여들에 대해서는 일단 나중에 배워보고, 먼저 권한 부여 플로우를 자세히 알아보자.
- 사용자가 권한 부여 플로우를 시작한다. 주로
구글로 로그인하기
,페이스북으로 로그인하기
와 같은 버튼을 눌러서 시작한다. - 클라이언트는 유저를 권한 부여 서버로 리다이렉트 시킨다. 리다이렉트가 되는 동안 클라이언트는 클라이언트 ID 그리고 리다이렉트 URI와 같은 정보를 전달한다.
- 권한 부여 서버가 사용자 인증을 처리하고 동의를 구하는 화면을 표출한 뒤에 유저로부터 클라이언트에게 허가가 떨어진다. 만일 사용자가
구글로 로그인하기
버튼을 눌렀다면, 사용자는 반드시 구글에게 로그인 자격을 제공해야 한다. 이를테면account.google.com
과 같은 사이트에서 로그인하는 것을 말한다. (클라이언트는 제공하지 않아도 된다.) - 사용자가 허가를 내줬다면, 권한부여 서버는 권한 부여 키(코드 혹은 토큰)와 함께 사용자를 클라이언트로 돌려보내준다.
- 클라이언트는 리소스 서버에게 권한 부여 키를 이용하여 사용자의 데이터에 대한 응답을 요청한다.
- 리소스 서버는 권한부여 키의 유효성 검사를 한 뛰에 요청된 데이터를 클라이언트에게 보내준다.
위에 설명한 내용이 어떻게 사용자가 서드파티 어플리케이션에게 직접 패스워드를 주지 않으면서 사용자의 데이터에 접근할 수 있게 하는지에 대한 방법이다. 이 시점에서, 아마 다음과 같은 질문이 있을 수 있다.
- 어떻게 리소스 서버에서 특정한 데이터에만 접근이 가능하도록 제한할까?
- 만일 클라이언트가 데이터를 읽기만 바라고 수정하지는 않길 바란다면?
이러한 질문들은 OAuth 용어에서 ‘스코프’라는 개념을 공부하면 이해 가능하다.
OAuth의 스코프
OAuth 2.0에서 스코프는 어플리케이션이 사용자 데이터에 접근하는 것을 제한하기 위해 사용된다. 사용자에 의해 특정 스코프로 제한된 권한 인가권을 발행함으로써 데이터 접근을 제한한다.
클라이언트가 권한 인가를 위해 권한 부여 서버로 요청을 보낼 때, 클라이언트는 스코프도 함께 보낸다. 권한 부여 서버는 동의를 구하는 화면을 만들고 사용자로부터 허가를 받기 위해 스코프의 리스트를 사용한다. 만일 사용자가 동의 화면에서 동의했다면, 그 권한 부여 서버는 유저에 의해 부여된 스코프에 제한된 토큰 또는 권한 부여 코드를 발행한다.
예를들어 만일 클라이언트에게 나의 구글 연락처의 리스트를 볼 수 있게 하기 위해 권한을 부여했다면 권한 부여 서버로부터 발행되어 클라이언트로 전해진 토큰은 나의 연락처를 삭제하거나 나의 캘린더 이벤트를 볼 수는 없다. 왜냐하면 오직 구글 연락처를 읽도록 스코프가 설정되어 있기 때문이다.
OAuth 2.0을 위한 셋업
OAuth 플로우에 대해 배워보기 전에, 몇가지 OAuth 설정에 대해 미리 알아두면 좋다. 권한 부여를 위한 요청이 초기화되었을 때, 클라이언트가 몇가지 설정 데이터를 권한부여 서버에게 쿼리 파라미터로 보낸다. 기본적인 쿼리 파라미터는 다음과 같다.
response_type
: 우리가 권한부여 서버로부터 받길 원하는 응답의 타입이다.scope
: 클라이언트가 접근하길 원하는 리스트의 스코프이다. 이 리스트는 동의를 구하는 화면을 만들 때 권한부여 서버에 의해 사용된다.client_id
: 클라이언트에 OAuth 세팅을 할 때, 권한부여 서버에 의해 제공된다. 이 ID는 권한 부여 서버가 OAuth 플로우를 시행하려는 클라이언트가 누구인지 알아내기 위해 사용된다.redirect_url
: 권한부여 서버에게 OAuth 플로우가 끝나면 어디로 보내줄지에 대해 알려주는 역할이다.client_secret
: 권한부여 서버에 의해 제공된다. OAuth 플로우에서 이 파라미터는 필수 옵션은 아니다. 권한부여 코드 플로우에서 이client_secret
의 중요성에 대해 알아볼 것이다.
다른 OAuth 플로우들 이해하기
가장 흔하게 쓰이는 두가지 OAuth 2.0 플로우는 2가지가 있다.
- 서버를 베이스로한 어플리케이션을 위한 권한부여 코드 플로우
- 순수한 자바스크립트 Single Page Application(SPA)을 위한 암묵적인 플로우
이 글에서는 서버를 베이스로 한 권한부여 코드 플로우만 설명한다. 쉬운 설명을 위해 OAuth 플로우를 설명하면서, 구글을 OAuth 서비스 제공자로 가정하면서 설명하겠다.
권한부여 코드 플로우
권한부여 코드 플로우 또는 권한부여 코드 부여는 보안성을 높이기 위해서 이상적인 OAuth 플로우로 여겨진다. 왜냐하면 OAuth 2.0 매커니즘을 구현하기 위해서 프론트엔드 채널도 이용하고, 백엔드 채널도 이용하기 때문이다.
잘보면 권한코드를 부여하고 그 권한 코드를 엑세스 토큰으로 변경하는 부분이 추가되었다.
클라이언트는 code라고 세팅된 response_type
과 함께 사용자를 권한부여 서버로 리다이렉팅함으로써 권한 부여 시퀀스를 시작한다. 이게 의미하는 바는 권한부여 서버에게 권한부여 코드로 응답하라는 것을 말한다. 이 플로우에 대한 URI는 다음과 같이 생겼다.
1
2
3
4
5
https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
client_id=your_client_id&
scope=profile%20contacts&
redirect_uri=https%3A//oauth2.example.com/code
위 요청에서, 클라이언트는 요청의 scope
파라미터에 인자를 보냄으로써, 사용자의 공개 프로필과 연락처에 접근할 수 있는 사용자의 권한을 요구한다. 이 요청의 결과는 액세스 토큰으로 변환 가능한 권한 부여 코드이다. 권한부여 코드는 다음과 같이 생겼다.
1
4/W7q7P51a-iMsCeLvIaQc6bYrgtp9
왜 코드를 토큰으로 바꾸는 것일까?
액세스 토큰은 리소스 서버에 존재하는 데이터에 접근하기 위한 유일한 것이다. 하지만 어플리케이션 코드는 아니다. 그래서 왜 액세스 토큰이 필요할 때, 클라이언트가 response_type
을 code로 세팅하는 것일까?
이유는 OAuth 플로우의 보안성을 높게 만들기 위해서이다.
문제: 액세스 토큰은 다른 사람이 접근하면 안되는 정보의 비밀스러운 부분이다. 만일 클라이언트가 액세스 토큰을 직접적으로 요청하고 브라우저에 갖고 있게 되면 탈취당할 수 있다. 왜냐하면 브라우저는 완전히 보안이 되어있진 않으니까. 다른 사람이 페이지 소스 또는 잠재적으로 개발자 도구로 액세스 토큰을 얻어갈 수 있다.
해결방법: 브라우저에서 액세스 토큰 노출을 방지하기 위해서, 클라이언트의 프론트엔드 채널은 권한 부여 서버로부터 어플리케이션 코드를 얻는다. 그리고 어플리케이션 코드를 백엔드 채널로 보낸다. 어플리케이션 코드를 액세스 토큰으로 바꾸기 위해서, client_secret이 필요하다. client_secret은 클라이언트의 백엔드 채널에 의해서만 알려지게 되며, 프론트엔드 채널은 client_secret에 관여하지 않는다.
백엔드 채널은 POST 요청을 권한부여 서버에 어플리케이션 코드와 어플리케이션 코드를 동봉해서 보낸다. 요청은 다음과 같이 보내진다.
1
2
3
4
5
6
7
8
POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded
code=4/W7q7P51a-iMsCeLvIaQc6bYrgtp9&
client_id=your_client_id&
client_secret==your_client_secret_only_known_by_server&
redirect_url=https%3A//oauth2.example.com/code
권한 부여 서버는 client_secret
과 application code
의 유효성을 검사하고 액세스 토큰을 보내준다. 백엔드 채널은 액세스 토큰을 갖고 있고 이 토큰을 리소스 서버에서 정보를 갖고올 때 사용한다. 이러한 방법으로 브라우저는 액세스 토큰에 접근하지 않는 방식으로 구현할 수 있다.
인증(Authentication) vs 권한 부여(Authorization)
우리가 알고있듯, OAuth는 위임된 권한부여 문제를 해결해준다. 하지만 이 방법은 사용자 인증을 위한 표준 방법을 제공해주진 않는다. 우리는 다음과 같이 말할 수 있다.
- OAuth 2.0은 권한 부여를 위한 것이다.
- OpenID Connect는 인증을 위한 것이다.
아마 ‘권한 부여’와 ‘인증’이 헷갈릴 수 있는데, 차이점은 다음과 같다.
- 인증(Authentication)은 내가 소통하는 주체가 어떤 것인지 확신하는 것이다.
- 권한 부여(Authorization)는 소통하는 주체가 리소스에 접근할 수 있는지 아닌지에 대해 확인하는 프로세스이다.
authentication(인증)은 당신이 누구인지에 대한 것이고, authorization(권한 부여)은 당신이 어떤 권한을 가졌는지에 대한 것이다.
OpenID Connect란
OpenID Connect는 OAuth 2.0 프로토콜의 최상위 레이어와 동일한 레이어다. OpenID Connect는 OAuth 2.0을 확장하여 인증 방식을 표준화한다.
OAuth는 유저 인증을 곧바로 제공하지 않지만 권한 부여를 위한 엑세스 토큰을 제공한다. OpenID Connect는 권한부여 서버에 의해 작동하는 인증 시스템을 기반으로 클라이언트가 사용자를 판단할 수 있게 해준다. 권한부여 서버에 유저 로그인과 동의를 요청할 때, openid라는 스코프를 정의하면 OpenID Connect 사용이 가능하다. openid는 OpenID가 필요되는 권한부여 서버에 필수적인 스코프이다.
OpenID Connect 인증을 위한 URI 요청은 다음과 같이 만들어진다.
1
2
3
4
5
https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
client_id=your_client_id&
scope=openid%20contacts&
redirect_uri=https%3A//oauth2.example.com/code
이 요청의 결과는 액세스 토큰과 ID 토큰으로 바꿀 수 있는 어플리케이션 코드이다. OAuth 플로우가 암묵적 플로우면, 서버는 액세스 토큰과 ID 토큰을 바로 줄 것이다.
ID 토큰은 JWT 또는 JSON 웹 토큰이다. JWT는 header, payload, signature 3가지 부분이 담겨있는 인코드된 토큰이다. ID 토큰을 얻은 이후에, 클라이언트는 payload 부분에 인코드된 사용자 정보를 얻을지 결정할 수 있다.
1
2
3
4
5
6
7
{
"iss": "https://accounts.google.com",
"sub": "10965150351106250715113082368",
"email": "johndoe@example.com",
"iat": 1516239022,
"exp": 1516242922
}
Claims
ID 토큰이 갖고 있는 payload는 claims
라 알려진 어떤 필드들을 포함한다. 기본적인 claims
는 다음과 같다.
iss
: 토큰 발행자sub
: 사용자를 구분하기 위한 유니크한 구분자email
: 사용자의 이메일iat
: 토큰이 발행되는 시간을 Unix time으로 표기한 것exp
: 토큰이 만료되는 시간을 Unix time으로 표기한 것
하지만, claims는 이러한 영역에 제한된 것이 아니다. 권한부여 서버에서 어떻게 claims
를 인코드할 것인지에 달린 것이다. 클라이언트는 이러한 정보를 이용하여 사용자를 인증할 수 있다.
만일 클라이언트가 더 많은 사용자 정보를 원한다면, 클라이언트는 표준 OpenID Connect 스코프에 더 많은 것들을 기재하여 권한 부여 서버에게 ID Token의 payload에 필요한 것들을 더 추가하라고 할 수 있다. 이러한 스코프는 email, address, phone, profile 등이다.