binchoo / spring-boot-210523

스프링 부트와 AWS로 혼자 구현하는 웹 서비스 정독하기
0 stars 0 forks source link

[요약] OAuth2.0 명세 공부하고 요약하세요 #18

Closed binchoo closed 3 years ago

binchoo commented 3 years ago

챕터: #12

[명세서] 75페이지 밖에 안 되니 다 읽어보셈.

[참고서] 잘 모르겠으면 이 친구도 참고 바람. image

[마감 기한] 6월 13일

binchoo commented 3 years ago

OAuth 2.0

소개

메이저 기업의 API를 사용해서 유저 데이터에 접근하는 권한을 얻으려면, 그들이 제공하는 전용 인증 플로우를 따라야 했다. 여러 서비스와 연계될 경우, 인증 플로우의 복잡함이 가중되었다. OAuth2.0 은 이러한 웹 기반의 인증 절차를 표준화한 것이다.

필요성

OAuth는 애플리케이션이 유저 데이터에 안전하게 접근하도록 한다. 유저는 3rd 파티 앱에서 로그인 아이디와 패스워드를 타이핑하지 않아도 된다. OAuth의 활용으로 구현할 수 있는 기능의 예는 아래와 같다.

  1. 유저의 소셜 그래프를 얻기: 페이스북, 트위터 친구 정보
  2. 유저의 앱 활동을 SNS 등에 공유하기
  3. 유저의 구글 독스나 드롭박스에 데이터를 저장하기

이렇게 개인 정보 자료에 접근하는 API를 쓰려면, 앱은 유저로부터 접근 권한을 부여 받아야 한다. API 권한 부여 과정을 표준 프로토콜화하면 러닝 커브를 낮추어 개발 UX도 향상시키며, 표준의 적용을 통해 API의 보안에 큰 기여를 한다.

패스워드의 위험성

로그인 아디 + 패스워드 조합은 인증 수단으로서 적합하지 않다.

  1. 유저가 순순히 당신의 앱에 아이디와 패스워드를 넘겨주기 어렵다.
  2. 유저를 피싱 사이트에 취약하게 만든다.
  3. 앱에 "필요한 데이터" 뿐만 아니라, "모든 데이터"에 접근하게 해 준다.
  4. 패스워드가 변경되면, 권한을 다시 받아야 한다.
  5. 유저가 앱 권한 부여를 취소할 수단이, 패스워드를 변경하는 것이다. 앱이 내 패스워드를 알고 있으니까..

용어 정리

Authentication, 인증

인증이란, 유저의 신분을 확인하는 것이다 자신이 A라고 주장하는 자가 진짜로 A인지 확인. 현실 세계의 인증은 주민등록증 사진과 내 얼굴의 유사성을 대조하는 과정과 같다.

웹에서의 인증은 어떨까? 전형적으로 유저에게 아이디+비밀번호를 요구하고 있다. 여기서 아이디는 ''자신이 A라고 주장''하는 것과 같으며, 비밀번호는 그 '주장대로 자기가 A임을 증명'하는 것이다.

Federated Authentication

보통 서비스들은 유저의 계정 정보를 보유한다만, 어떤 서비스들은 유저 검증을 위해 타 서비스에 의존하기도 한다.

Authorization, 인가

인가란, 어떤 유저가 특정 행동을 해도 되는 사람이라고 인정해 준 것이다. 절차는 보통 이렇게 이뤄진다.

경찰이 과속 위반 차량을 세우고, 운전자에게 운전 면혀증을 요구했다.
경찰은 운전자가 면허증의 사람과 동일함을 확인했다. (인증 절차)
경찰은 운전 면허의 진위 여부, 만료일 등을 확인했다.
이 자는 운전을 할 수 있는 권한이 있는 사람임이 확인되었다. (인가 절차)

Delegated Authorization, 위임 인가

남이 본인을 대신하여 특정 작업을 할 수 있도록 인가한다.

발렛 파킹 직원에게 키를 맡기어 주차를 대신 하도록 했다. 
그는 나를 대신하여 자동차를 운전해 주차를 할 수 있게 되었다.
그는 자동차를 운전하기 위해 키를 가지고 있구나! (위임 인가)

Roles

OAuth 프로토콜에서 식별하고 있는 주체들이다.

  1. 리소스 서버: 자원을 호스팅하고 있는 쪽이며, OAuth를 사용해 자원을 보호하고 있다. API를 사용하 리소스 서버에 접근하여 사진, 비디오, 캘린더 등 유저의 자료를 얻어갈 수 있다.
  2. 리소스 오너: 유저. 자원의 주인이시다. 리소스 오너는 3rd 파티 앱이 리소스에 접근할 수 있도록 인가할 수 있다.
  3. 클라이언트: 보호된 자원이 필요하여 API 요청을 보내는, 클라이언트 애플리케이션이다. 리소스 오너를 대신하여 보호된 자원에 접근한다.
  4. 인가 서버: 인가 서버는 리소스 오너의 수락 의사에 맞추어 클라이언트에게 Access Token 을 내려준다. 클라이언트는 오너의 자동차 키를 얻게 된 셈이다. API 제공자의 규모가 작다면 굳이 인가 서버와 리소스 서버의 도메인을 달리 하진 않을 것이다.
binchoo commented 3 years ago

암호화 서명의 제거

개발자 사용성을 위해 복잡한 암호화 서명을 스펙에서 제거했으나, 사용성과 보안성은 트레이드 오프 관계임을 명심하자.

서명이 있는 OAuth 2.0 인증 명세 요약

(관심 없는 내용이므로 생략)

클라이언트의 등록

애플리케이션은 인가 서버에 미리 등록되어 API 요청마다 식별 가능해야 한다. 업체들은 아래처럼 구현했다.

  1. 구글: API 콘솔에서 클라이언트를 등록해야 함.

    필요한 정보: 구글 계정, 제품 이름, 제품 로고(선택), 웹 앱의 경우 리다이렉션 URL

  2. 페북: 페북 개발자 사이트에서 클라이언트를 등록해야 함.

등록을 마치면 개발자는 클라이언트 크레덴셜을 지급받는다.

client_id

client_secret

Authorization Code 방식에서 Access Token을 내려받을 때나, Access Token을 새로고침할 때 필요함.

클라이언트 등록을 요구하는 이유

개발자는 등록을 통해 클라이언트 크레덴셜을 지급 받는데, 이 값으로 당신은 인가 서버에게 '인증'을 받게 되는 것이다.

등록 시 제공한 클라이언트 정보는, 고객들에게 전시되어 UX를 향상시키는 용도로도 활용되고 있다. (소셜 로그인 시, 해당 앱 이름과 로고를 전시하는 것)

클라이언트 프로필

OAuth 1.0은 API 대상 명세였기 때문에, 모바일 앱, 데스크톱 앱, 자바스크립트 앱, 브라우저 확장 프로그램 등에 대응하지 못 했다. OAuth 2.0 부터는 Client Profiles를 몇 가지 정의하였다.

  1. Server-side web application

    웹 서버에서 동작하는 OAuth 클라이언트. 리소스 오너(유저)가 웹 앱을 사용하면 서버 측은 적절한 API 콜을 서버 사이드 프로그래밍 언어로 호출한다. 리소스 오너는 클라이언트 크레덴셜에 대해 전혀 알고 있을 필요하 없다.

  2. Client-side web application

    웹 브라우저에서 동작하는 OAuth 클라이언트. 예를 들어 자바 스크립트 페이지, 브라우저 확장 프로그램 등이다. 클라이언트 크레덴셜이 리소스 오너(유저)에게 숨겨짐을 보장할 수 없으므로 몇몇 API 제공자들은 이 클라이언트 프로필을 지원해 주지 않는다.

  3. Native application

    Client-side web app과 비슷하게 클라이언트 크레덴셜이 유저에게 비밀로 유지된다는 보장이 없다. 다만 설치된 앱이기 때문에 브라우저 앱만큼 그 정보에 접근하기 쉽지는 않다.

Access Token

Access Token만 보유하면, 보호된 리소스에 접근할 수 있다. 암호화 키 등 필요하지 않음.

OAuth의 내용이 바로 이것이다: OAuth 액세스 토큰을 얻어 어플리케이션이 유저를 대신해 API 요청을 던질 수 있도록 만드는 것.

Access Token + API 호출

액세스 토큰을 얻고 나면, API 요청에 어떤 방식으로든 함께 실어 나르면 된다. 추천되는 방식은 HTTP Authorization 헤더에 토큰을 담는 방법이다.

GET /tasks/v1/lists/@default/tasks HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer ya29.AHES6ZSzX

이것 말고도 액세스 토큰을 나르는 방법이 추가로 명세에 소개되어 있다. 하지만 API 제공자들이 이 내용을 모두 구현해야 하는 것은 아니라서..

인가 절차

클라이언트는 프로필 별로 정의된 프로토콜을 통해, 자료에 접근할 수 있는 인가를 받게 된다. OAuth2.0은 주요 Grant Types(인가 부여 방식) 4가지를 정의하였고, 추가적인 Grant Types을 도입하기 위한 확장 매커니즘도 다루고 있다.

  1. Authorization Code

    서버 사이드 웹 앱에 적합한 방식이다. 리소스 오너가 자료 접근을 허용하고 리다이렉션을 통해 웹 앱으로 되돌아간다. 이 때 Authorization Code가 리다이렉션 쿼리 파라미터에 담긴다. 이 인가 코드를 인가 서버에 전해주면 Access Token을 얻을 수 있다. 웹 앱은 인가 서버에게 "인증" 받아야 하므로 cliend_idclient_secret도 필요하겠져?

  2. Implicit grant for brower-based client-side applications

    Grant Types 중 가장 간략한 절차이다. 클라이언트 사이드 웹 앱에 적합하다. 리소스 오너가 앱에게 접근을 허용하면, 액세스 토큰이 URL#해시 조각으로써 발행이 된다. 앱은 JS 스크립트로 #해시 조각을 떼어내어 액세스 토큰으로 쓰면 된다. Authorization Code가 필요하지 않은 방식이지만, 토큰 리프레시가 불가능하다.

  3. Resource owner password-based grant

    리소스 오너의 ID + 패스워드를 전달하고, Access Token을 받는다. 신뢰 수준이 높은 클라이언트에게만 써야 하는 방식이다. 가령 API 제공자가 만든 모바일 앱에서라면 이 방식을 써도 될 것이다. 유저 패스워드가 클라이언트에 노출되지만, 클라이언트는 이걸 저장해선 안 된다. 이 상황이 보장된다면 첫 인가 이후, 유저는 비밀번호 변경 없이 인가를 취소할 수 있다. 액세스 토큰은 '일부' 자료에만 권한을 갖게 되므로, 전통적인 ID+패스워드 인가 방식보다는 보안성이 높다.

  4. Client credentials

    클라이언트 크레덴셜로, '클라이언트가 소유하는' 리소스에 대해서 액세스 토큰을 얻는다. 이 Grant Type은 앱이 스토리지/데이터베이스 서비스 API를 사용하고 있을 때 적절하다. 특정 유저를 대신하여 어떤 작업을 하는 게 아니고, 자기 자신을 위한 것임!

binchoo commented 3 years ago

Server-side 웹 앱 OAuth 인증 절차

개요

보안 측면

Step1. Authorization Code 얻기

https://developers.google.com/identity/protocols/oauth2/web-server?csw=1#python_1

  1. API Provider에 당신의 앱을 등록

    client_id, client_secret을 획득한다.

  2. 유저에게 당신 앱이 필요로 하는 권한을 설명하고, 인가 서버로 이동시킵니다.

    https://accounts.google.com/o/oauth2/auth

    몇 가지 쿼리 파라미터가 필요할 수 있으며, API Provider 공식 문서마다 요구하는 것이 다를 수 있다.

    • client_id
    • redirect_uri: 유저가 권한 부여를 승낙하고서 다시 이동하게 될 페이지. 보통 클라이언트 등록 과정에서 함께 입력하는 경우가 많다.
    • scope: 클라이언트가 필요로 하는 유저 데이터. 보통 공백 문자로 구분되는 문자이며, 페이스북은 콤마로 구분한다.
    • response_type=code: 응답 값으로 Authorization Code를 기대함을 알려줌. 즉, 해당 인증 절차가 Server-side Web Application Flow임을 알려줌.
    • state: CSRF 공격을 방지를 위해 사용되는 유니크한 값.

오류 응답 플로우

인가 서버로 리다이렉션 할 때 유효하지 않은 파라미터들을 넘겼다면, 인가 서버는 오류 응답을 파라미터에 담아 클라이언트 리다이렉션 페이지로 돌려보낸다.

redirect_uri + error [error_description] [error_uri] 형태로!

정상 응답 플로우

유저는 권한을 승낙하여 Authorization Token과 함께 클라이언트 리다이렉션 페이지로 돌아간다.

이 URL은 redirect_uri + code [state] 형태이다

Step2. Access Token 얻기

클라이언트는 이제 Access Token을 얻어야 한다. POST 메서드로 인가 서버에게 요청을 보내야 한다. 필요한 파라미터는 다음과 같다.

한 편, 이 요청은 client_idclient_secret을 사용하여 인증되어야 한다. 클라이언트 크레덴셜을 요청에 싣는 방식은 크게 2가지이다.

  1. HTTP Authorization 헤더에 넣기

    https://en.wikipedia.org/wiki/Basic_access_authentication 방식을 사용해야 한다.

    Authorization: Basic
    MDAwMDAwMDA0NzU1REU0MzpVRWhrTDRzTmVOOFlhbG50UHhnUjhaTWtpVU1nWWlJNg==
    // username(client_id)와 password(client_secret)을 콜론으로 접합하고
    // Base64로 인코딩한 값
  2. 그냥 HTTP POST 요청의 파라미터로 전달하기

    많은 API 제공자가 흔하게 지원하고 있는 방식이다.

정리하자면, 이런 파라미터들을 요청에 추가적으로 삽입할 수 있다.

정상 응답 플로우

JSON 응답이 내려진다.

{
  "access_token" : "ya29.AHES6ZSzX",
  "token_type" : "Bearer",
  "expires_in" : 3600,
  "refresh_token" : "1/iQI98wWFfJNFWIzs5EDDrSiYewe3dFqt5vIV-9ibT9k"
}

액세스 토큰은 유저를 포함하여 다른 누구에게도 알려지면 안 된다.

Access Token과 Refresh Token 두 개가 있는 이유?

액세스 토큰은 수명주기가 짧고, 리프레시 토큰은 수명주기가 길다. 굳이 토큰 2개를 쓰는 이유는 뭘까? 보안상의 이득.

액세스 토큰 타입은 Bearer 토큰이다. 즉 이 토큰으로 API 호출시에 서명이 필요하지 않다. 그러므로 악의적 사용자가 클라이언트에 내려진 액세스 토큰을 훔쳤을 때, 자유롭게 유저 데이터에 액세스하게 된다. 이 영향을 줄이기 위해 액세스 토큰에게 유효 시간을 걸어 둔다.

한 편 API 제공자가 액세스 토큰 검증을 요렇게 구현할 수도 있다: API들이 독립적으로 & 데이터 베이스 룩업 없이 & 암호학적 액세스 토큰을 검증함. 이런 상황에서 유저가 권한 부여를 취소하면, 그걸 반영해 줄 수 방도가 없다. (DB가 없잖아)

그러므로 액세스 토큰의 유효 시간을 짧게 걸어둬야 한다.

Step3. API 호출하기

Authorization 헤더에 Bearer 타입으로 Access Token을 전달하면 된다.

Authorization: Bearer
ya29.AHES6ZS_2G4-VuL041L0GpFJqH0wGfGSR