5 minute read

인증 vs 인가

  • 인증은 누군가가 실제로 그 사람이 맞는지 확인하는 것을 말한다.(은행에서 신분증으로 신분확인 하는 것)
  • 인가는 인증된 사람이 그 사람이 원하는 일을 할 권한이 있는지 확인하는 것이다.(신분 확인된 사람이 출금을 할 권한이 있는지 확인 하는 것.)

OAuth2.0 프로토콜은 주로 두 가지 목적을 위해 사용된다.

  1. 연합된 신원 : 어떤 서비스가 다른 서비스에 신원 확인을 위임하는 것. 즉 다른 서비스의 계정을 이용한 인증을 허가하는 것.
  2. 권한 위임 : 어떤 서비스가 사용자 대신 사용자의 리소스에 접근할 수 있도록 권한을 부여받는 것.

OAuth2.0은 사실상 인증이 아닌 인가 프로토콜이기 때문에 독자적으로 연합된 신원을 제공할 수 없다. 다만 다른 프로토콜과 결합되는 방법으로 인증의 기능을 수행할 수 있다.

OAuth2.0 인증 과정

Oauth 2.0 클라이언트에게 필요한 준비과정

  1. 앱 등록
  2. 엑세스 토큰 얻기
  3. 엑세스 토큰 사용
  4. 엑세스 토큰 갱신

앱 등록시 서비스 제공자와 함께 설정되어야 하는 것들

  1. 클라이언트ID
  2. 클라이언트 시크릿
  3. 리다이렉션 엔드포인트 : 앱이 서비스 제공자로부터 컨트롤과 토큰 등의 정보를 제공받기위한 방법.
  4. 인가 엔드포인트 : 서비스 제공자가 사용자의 허가를 구하기 위한 엔드포인트. 정보 제공 동의 창 URL같은거.
  5. 토큰 엔드포인트 : code를 이용해 엑세스 토큰을 발급해 주는 엔드포인트. 여기다가 code를 주면 리다이렉션 엔드포인트로 엑세스 토큰을 반환해 준다.

신뢰 클라이언트와 비신뢰 클라이언트

신뢰 : 정보를 안전하게 저장하고 전송할 수 있는 능력. 신뢰 여부에 따라 클라이언트를 둘로 나눌 수 있고, 각 경우별로 OAuth2.0의 인가방식이 달라진다.

  1. 신뢰 클라이언트 : 정보를 안전하게 저장하고 전송할 수 있는 클라이언트 앱. 전형적인 클라이언트-서버-DB로 구성된 앱이 여기 해당 됨.
  2. 비신뢰 클라이언트 : 정보를 안전하게 저장하고 전송할 수 없는 것으로 보이는 클라이언트 앱. 브라우저 기반의 앱 등이 해당 됨.
  • 모바일은 두 경우 다에 해당 됨.

암시적 그랜트 vs 인가 코드 그랜트

OAuth2.0에서는 다양한 방식의 정보교환 방법을 제공한다. 대체로 아래 두 가지 방법이 사용된다.

  • 암시적 그랜트(implicit grant, 클라이언트 사이드 워크플로우) : 클라이언트가 비신뢰 클라이언트일 때. Auth code나 token의 저장을 신뢰할 수 없다.
  • 인가 코드 그랜트(authorization code grant, 서버 사이드 워크플로우) : 클라이언트가 신뢰 클라이언트일 때. 민감한 정보의 안전한 저장과 전송을 신뢰할 수 있다.

이 밖에도 리소스 소유자 비밀번호 자격증명 그랜트, 클라이언트 자격증명 그랜트가 있는데, 자주 사용하지 않으므로 패스.

암시적 그랜트 플로우

  1. 사용자가 제 3의 서비스 제공자의 리소스가 필요한 기능을 앱에 요청(유저 -> 앱)(ex. 카톡 친구목록을 이용한 앱의 어떤 기능)

  2. 인가 요청(앱 -> 서비스 제공자) : URL-encode 포맷이어야 함. 서비스 제공자의 인가 엔드포인트로 엑세스 토큰을 얻기 위한 GET요청을 보냄. 사용자는 사용자 동의 폼을 보게 됨.
    • response_type : token으로 세팅
    • client_id : 클라이언트 ID
    • redirect_url : 리다이렉션 엔드포인트
    • scope : 엑세스 토큰 사용 범위
    • state : 클라이언트가 사용하는 형식이 정해지지 않은 값. CSRF공격을 차단하기 위해 사용하는 것이 권장된다. 여기서 어떤 상태값을 세팅하면 응답에서도 같은 상태값을 반환 받음으로써 정당한 요청에 의한 정당한 응답임을 확인할 수 있다.
  3. 엑세스 토큰 응답(서비스 제공자 -> 앱) : 사용자가 승인한다면 서비스 제공자는 앱의 리다이렉션 엔드포인트로 엑세스 토큰을 보낼 것이다. 역시 URL-encode포맷이다. 리턴 값들은 URL 프래그먼트에 포함되어 전달된다.
    • access_token : 엑세스 토큰. 보호된 리소스에 접근하기 위한 열쇠와 같으며 엑세스 토큰은 일반적으로 유효기간이 있다. 접근 범위와 권한 등이 함축된 문자열이다.
    • token_type : 일반적으로 bearer. bearer토큰은 이 토큰 하나로 인가 여부를 판단하는 토큰이라는 의미.
    • expires_in : 유효기간(초 단위)
    • scope
    • state

인가 코드 그랜트 플로우

  1. 사용자가 제 3의 서비스 제공자의 리소스가 필요한 기능을 앱에 요청(유저 -> 앱)

  2. 인가 요청(앱 -> 서비스 제공자) : 서비스 제공자의 인가 엔드포인트로 Auth code를 얻기 위한 GET요청을 보냄. 사용자는 사용자 동의 폼을 보게 됨.
    • response_type : code로 세팅
    • client_id
    • redirect_url
    • scope
    • state
  3. code 응답(서비스 제공자 -> 앱) : 사용자가 승인한다면 서비스 제공자는 앱의 리다이렉션 엔드포인트로 엑세스 토큰과 교환할 수 있는 code를 보낼 것이다. 이 코드는 단 한번만 엑세스 토큰으로 교환할 수 있는 소모품이다. 리턴 값들은 URL 쿼리 파라메터 대신 URL 프래그먼트에 포함되어 전달된다. code에는 만료시간이 있으며 OAuth2.0 스펙에서는 최대 10분으로 권장한다.
    • code : 엑세스 토큰과 단 한번 교환할 수 있는 인증 코드.
    • state
  4. 엑세스 토큰 요청(앱 -> 서비스 제공자) : code를 서비스 제공자의 토큰 엔드포인트로 보내 엑세스토큰을 POST 요청한다. 또한 이 과정에서는 클라이언트가 서비스 제공자에게 자신의 자격증명을 알리는 내용을 함께 전달해야 하는데 일반적으로 요청의 Authorization Header에 clientId와 clientSecret을 전달함으로써 수행되지만 이 과정은 서비스 제공자에 따라 Header가 아닌 파라메터로 전달하는 등 차이가 있을 수 있다.
    • grand_type : 엑세스 토큰으로 교환하고자 한다는 것을 나타내기 위해 authorization_code
    • code : 아까 발급 받은 Auth code
    • redirect_uri
    • client_id
  5. 엑세스 토큰 응답(서비스 제공자 -> 앱) : 토큰 요청에 성공하면 다음 내용을 응답으로 받을 수 있다.
    • access_token
    • token_type : 일반적으로 bearer
    • expires_in
    • refresh_token : 엑세스 토큰을 갱신하기 위한 토큰, 제공하지 않는 경우도 있음. 엑세스 토큰에 비해 상대적으로 유효기간이 길다.
    • scope

엑세스 토큰의 사용

이렇게 얻게 된 엑세스 토큰을 사용함으로써 타 서비스 제공자가 제공하는 보호된 리소스에 접근을 인가받게 된다. 원하는 요청에 함께 담아 보내면 되는데, 서비스 제공자마다 가이드가 다를 수 있다. 엑세스 토큰을 요청에 담는 보편적인 방법에는 3가지가 있다.

  1. 요청의 Authorization 헤더에 담아 전달(GET)
  2. 인코딩된 폼 파라메터로 전달(POST)
  3. URL 쿼리 파라메터로 전달(GET, 보안 위험있음) : 테스트 용도로 적당하며 curl 명령어로 손쉽게 테스트 가능.

엑세스 토큰은 유효기간이 있으므로 반드시 만료된다. 따라서 이 경우에 엑세스 토큰을 재발급받을 필요가 있는데 이 때 필요한 것이 리프레시 토큰이다. 이 리프레시 토큰은 신뢰 클라이언트 플로우에서만 제공되며, 서비스 제공자가 지원해야만 한다.

  • 리프레시 토큰을 이용한 엑세스 토큰 재 발급 플로우
  1. 엑세스 토큰 요청(앱 -> 서비스 제공자) : 엑세스 토큰을 처음 발급받을 때와 같이 Authorization header에 클라이언트의 자격증명을 기입하여 POST요청한다.
    • grand_type : refresh_token
    • refresh_token
    • scope : 범위를 새로 설정할 수는 있으나 기존 엑세스 토큰의 범위보다 넓게 지정할 수는 없다. 만약 범위가 같다면 생략할 수 있는 필드이다.
  2. 엑세스 토큰 응답(서비스 제공자 -> 앱) : 토큰 요청에 성공하면 다음 내용을 응답으로 받을 수 있다.
    • access_token
    • token_type
    • expires_in
    • refresh_token : 엑세스 토큰 갱신이 성공하면 새로운 리프레시 토큰이 발급된다.
    • scope : 사용 범위

리프레시 토큰도 유효기간이 있으니(며칠에서 몇 주쯤) 언젠가는 만료된다. 게다가 리프레시 토큰 플로우를 지원하지 않는경우도 있다. 이러한 경우에는 인가 프로세스를 처음부터 다시 시작해야 한다.

보안을 위한 고려사항

  1. 엔드포인트, 리다이렉트 URL이 https프로토콜을 사용하도록 한다.
  2. 최소한의 scope를 요청한다.
  3. 비신뢰 클라이언트 플로우를 사용할 때는 읽기전용 권한만을 요청하여 공격 피해를 최소화 한다.
  4. 자격증명과 토큰은 사용자가 알 수 없도록 한다.
  5. 가능하면 신뢰 클라이언트 플로우만을 사용한다.
  6. 가능하면 리프레시 토큰을 사용하여 사용자 정보전송을 최소화 한다.
  7. 내장 브라우저 대신 네이티브 브라우저를 활용한다.(기존 세션 사용, 보안 플러그인 사용 등의 장점이 있음)
  8. 리다이렉션 엔드포인트에서 서드파티 스크립트를 사용하지 말 것.
  9. 클라이언트 자격증명을 수시로 바꾼다.
  10. 알기힘든 state파라메터를 사용한다.
  11. 리다이렉션 URI는 조작할 수 없도록 와일드카드를 사용하지 않고 절대경로를 사용한다.

모바일 앱에서의 OAuth2.0

  • 모바일앱은 신뢰 클라이언트인가? : 모바일 앱을 크게 웹앱과 네이티브앱으로 분류해 보자. 여기서 웹앱은 모바일 플랫폼에 맞게 설계된 일반적인 웹앱과 다름없으므로 기존의 신뢰vs비신뢰 클라이언트 구분법이 그대로 적용된다. 또한 네이티브 앱의 경우도 백엔드 서버가 있다고, 중요한 정보를 여기서 처리하여 사용자나 공격자가 모바일 환경에서 이 과정을 알 수 없다면 역시 신뢰 클라이언트라 할 수 있다. 그러나 백엔드 서버가 없는 네이티브 앱의 경우는 사용자의 민감한 정보를 기기내부에 저장해야만 하는데, 아무리 강력한 보안API(android keystore API, IOS data protection)를 사용한다고 하더라도, 최악의 상황에서는 공격자가 이를 이용할 수 있는 가능성이 있기 때문에 완전한 신뢰 클라이언트라 하기 어렵다. 따라서 가급적 백엔드 서버를 활용하는 편이 바람직하다.

  • 하이브리드 아키텍처 : 대부분의 서비스 제공자들은 하나의 클라이언트가 암시적 그랜트, 인가 코드 그랜트 두 방식을 모두 사용할 수 있도록 지원한다. 모바일앱이 백엔드 서버를 사용할 수 있는 경우 이를 활용할 수 있는데, 크게 민감하지 않고 읽기전용으로 사용할 자원은 모바일앱에서 암시적 그랜트 플로우로, 더 민감하고 중요한 자원은 백엔드 서버에서 인가 코드 플로우를 사용해 접근하도록 개선할 수 있다.

  • 모바일 환경에서 인가 엔드포인트는 서비스 제공자의 웹 페이지가 아닌 앱을 실행시켜 사용자의 인가를 얻을 수도 있다.

다양한 형태의 토큰

OAuth2.0 스펙에서는 토큰의 형태를 문자열이라고 느슨하게 규정 했는데 대표적으로 아래 두 형태의 토큰이 많이 사용된다.

  1. JWT(Json Web Token)
  2. SAML assertion

Categories:

Updated: