OAuth 2.0 원리

정현섭·2021년 4월 14일
0

본 글은 이고잉 님의 강의 를 보고 정리한 글입니다.

존재이유?

나의 서비스가 User의 권한으로 Google 등의 서비스를 이용하려 할 때 사용한다.

User의 Google ID, PW를 받아와서 처리하는 대신 안전하게 Access Token이라는 것을 발급하여 사용하는데 이 Access Token을 발급하는 것이 OAuth의 목적이다.

Access Token으로 Client(나의 서비스)는 Resource Owner(User)를 통하지 않고도 Resource Owner(User)의 권한으로 Resource Server(ex. google ) 이용할 수 있게 된다

용어 정리(3자)

OAuth에서는 3명이 존재한다.

  • Client : 나의 서비스
  • Resource Owner : 내 서비스의 사용자
  • Resource Server(+ Authorization Server) : Google처럼 내 서비스가 사용자의 권한으로 이용하려는 서비스

위 용어는 OAuth를 제공하는 시스템에서 통용되므로 밑에서는 이 용어를 통해 서술할 것 임.

특히 Client라는 용어가 헷갈리니 잘 생각하면서 읽어야 한다.

OAuth 과정

1. OAuth '등록' 절차

Client가 Resource Server를 이용하기 위해서는

사전에 승인을 받아와야 함. (이것을 Register(등록)이라 함)

등록 과정에서 보통 다음을 설정함

  • Client ID
  • Client Secret
  • Authorized redirect URIs

이 절차를 끝마치게 되면 Resource Server와 Client는 위 정보를 가지고 있게 됨.

2. OAuth '인증' 절차

OAuth의 인증 절차는

  1. Resource Owner의 승인
  2. Resurce Server의 승인

으로 이루어진다.

Resource Owner가 Client의 서비스를 이용하면서 Resource server 서비스를 이용해야 하는 것이 나타나면 Resource Owner는 해당 서비스(Resource Server)에 로그인 해야하는데

Client가 Resource Owner에게 제공하는 그 로그인 링크를 보게되면 다음과 같다.

http://resource.server/?client_id=1&scope=B,C&redirect_uri=http://my.server/api/

세 가지가 url parameter로 들어가 있는데

  • client_id
  • scope : Resource Owner가 허용하는 범위
  • redirect_uri : client의 uri

이다.

2-1) Resource Owner의 승인 과정

  1. Resource Owner가 해당 링크를 누르면 Resource server는 Resource Owner가 로그인이 된지 안 된지 확인 후 안 됐으면 로그인하라는 화면을 띄워줌.

  2. Resource Owner가 로그인에 성공하면 Resource Server는 owner가 클릭한 요청 링크의 파라메터에 client_id, redirect_url을 자신이 가지고 있는것과 같은지 확인함

  3. 이 후 Resource Owner에게 scope에 해당되는 권한을 client에게 제공해도 되는지 확인하는 메시지를 보냄.

  4. Resource Owner가 해당 메시지의 허용 버튼을 누르면 Resource Server는 해당 user_id가 scope A, B에 대해 승인하였음을 기록함.

    Owner의 승인 이후 server의 db 내용.

2-1 과정이 끝나면 Resource owner의 승인 여부를 client에게 전달하는resource server의 승인 과정으로 넘어감.

2-2) Resource Server의 승인 과정

Resource server의 승인과정은 아래의 두 단계로 나뉜다.

a) Resource owner의 승인여부를 client에게 전달

(이때 client는 Authorization code를 받는다.)

b) client가 Authorization code를 가지고 Resource server에게 승인 요청

(이때 Resource server가 승인을 하게되면 client에게 Access Token을 발급해준다.)

Resource Server는 Resource Owner의 승인에 대한 인증번호인 Authorization code를 Client로 보내기 위해서 다음과 같이 진행한다.

  • Resource Owner의 승인을 받은 Resource Server는 Authorization code를 생성하여 기록하고

  • Resource Owner에게 Location: https://client.com/callback?code=13 을 응답해서 Resource Owner로 하여금 redirect url을 통해 Client로 Authorization code를 보내게끔 한다.

    이 단계에서의 client가 받게되는 정보
  • Client는 이제 authorization code를 가지게 된다.

  • 이제 Client는 다음과 같은 정보를 포함하여 Resource Server에게 접속함으로써 Access Token을 요청한다.

    https://resource.server/token?
    	grant_type=authorization_code&
    	code=3&
    	redirection_uri=https://client.com/callback&
    	client_id=1&
    	client_secret=2

(이 때 client_secret이 사용된다.)

  • Resource Server는 Client의 파라메터 내용을 보고 승인하게 된다.

3. Access Token 발급

Resource Server가 해당 Client를 승인하게 되면 이제 Access Token을 생성하여 Client에게 전달한다.

이제부터는 Client가 Access Token을 가지고 Resource Server에 접속하게 되면

"이건 user id 1에 대해서 scope b, c의 권한을 행사 할 수 있는 사람(client)의 요청이구나"

라고 Resource Server가 인식할 수 있게 된다.

API

Client(내 서비스)는 이제 발급받은 Access Token을 가지고 Resource Server의 서비스를 이용해야 하는데

그것을 Resource Server가 제공하는 API를 통해서 할 수 있다.

Google API의 CalendarList

CalinderList를 받아 왔더니 응답된 데이터

API를 통해 Access Token없이 데이터를 요청했더니 Login Required라고 나온다.

즉, OAuth를 통해 Access Token을 발급받은 다음에 API를 써야한다는 것이다.

그럼 API에 발급받은 Access Token을 어떻게 포함시킬까?

API에 Access Token 포함시키기

Google API를 기준으로 보자면 두 가지 방식으로 Access Token을 포함시킬 수 있다.

  • Query Parameter에 포함시키기 (비권장)

예시 url은 다음과 같다.

https://www.googleapis.com/calendar/v3/users/me/calendarList?
access_token=<access_token>
  • Authorization: Bearer 라는 HTTP Header에 포함시키기 (표준)

curl을 이용하여 요청을 보낸다고 친다면 다음과 같다.

curl -H "Authorization: Bearer <access_token>" https://www.googleapis.com/calendar/v3/users/me/calendarList

보통 Query Parameter 방식은 시스템에서 제공하지 않을 수도 있으며

제공한다고 하더라도 HTTP Header(Authorization: Bear) 방식을 더 권장한다.

Refresh Token

Access Token을 발급받아서 사용하다가 만료되면 어떻게 할까?

Access Token이 만료될 때 마다 다시 발급받기 위해서

"User에게 로그인을 시키고 ~~"의 과정을 반복하기는 힘들 것이다.

이 때는 Refresh Token을 이용하여 Access Token을 간편하게 가져온다.

OAuth2.0(RFC6749)의 전체 과정

위 그림의 (G), (H)를 보면 Refresh Token만으로 Acess Token을 가져오고 있다.

사실 이때까지의 설명에서는 설명의 편의를 위해 Authorization Server와 Resource Server를 구분하지 않았다. 그냥 둘 다 Resource Server라고 부름.

그런데 사용하는 Resouce Server마다 refresh token을 제공하는 정책이 다르기 때문에

제공하는 OAuth에 대한 도큐먼트를 읽어봐야 한다.

(Google의 경우 : https://developers.google.com/identity/protocols/oauth2/policies)

구글 API for server side web app 의 refresh token 발급받는 법.

위는 구글의 예시이지만 OAuth2.0은 표준화된 방식이기 때문에 다른 서비스에서도 거의 비슷하다.

마치면서

OAuth의 원리를 살펴보았다.

OAuth를 통해 그냥 server, client의 1 대 1 상황에서 제 3의 서비스를 이용하려고 할 때

권한 인증이 어떻게 이루어지는지 알았다.

OAuth에 대한 원리이해 없이 그냥 기능을 쓸 수 있게 해주는 라이브러리를 사용해도 되지만

OAuth의 원리를 모르고 사용한다면 라이브러리 자체가 어렵게 느껴질 것이고

OAuth의 원리를 알고 사용한다면 라이브러리가 얼마나 우리의 수고로움을 덜어주고 있는지 깨달을 것이다.

우리 어플리케이션이 다른 어플리케이션이나 플랫폼과 상호작용하는 것은 피할 수 없는 숙명이기 때문에 OAuth의 원리를 공부하는 것은 언젠간 분명 해야할 일이었다.

추가 (php 구글 로그인 코드)

<?php
// Enable error reporting
error_reporting(E_ALL);
ini_set('display_errors', 1);

//include google api files
$client = new Google_Client();
$client->setApplicationName('php-web-login');
$client->setAuthConfig($_SERVER['DOCUMENT_ROOT'] . '/private/client_secret.json');
$client->addScope(Google_Service_Oauth2::USERINFO_PROFILE);
$client->addScope(Google_Service_Oauth2::USERINFO_EMAIL);

$google_oauthV2 = new Google_Service_Oauth2($client);

// LOGOUT
if (isset($_GET['logout'])) 
{
	unset($_SESSION['user_id']);
	$client->revokeToken();
	header('Location: /main'); //redirect user back to page
	return;
}



// GOOGLE CALLBACK
if (isset($_GET['code']))
{
	
	$client->authenticate($_GET['code']);
	$token = $client->getAccessToken();

	$client->setAccessToken($token);
	
	$user = $google_oauthV2->userinfo->get();

	$googleId = $user['id'];
	$googleEmail = filter_var($user['email'], FILTER_SANITIZE_EMAIL);
	$googleFirstname = filter_var($user['givenName'], FILTER_SANITIZE_SPECIAL_CHARS);
	$googleLastname = filter_var($user['family_name'], FILTER_SANITIZE_SPECIAL_CHARS);
	$googlePicture = filter_var($user['picture'], FILTER_VALIDATE_URL);

	// die(var_dump($user));
	// db에 user data 없으면 넣기.

    $sql = 'SELECT * FROM user WHERE email="'. $googleEmail. '"';
	
	$count = App::get('database')->rowCount($sql);

	// 해당 user의 data가 db에 없을 때
	if($count === 0) {
		$user = [
			'email' => $googleEmail,
			'firstname' => $googleFirstname,
			'lastname' => $googleLastname,
			'picture_url' => $googlePicture
		];

		App::get('database')->insert('user', $user);
	}
	
	//바뀐 데이터가 없는지 확인(TODO) + user id 가져오기
	$sql = ''
	. 'SELECT id FROM user WHERE email = "' . $googleEmail  . '"';

	$user_in_db = App::get('database')->selectOne('user', 'email = "' . $googleEmail . '"');
	//App::get('database')->query($sql);

	$_SESSION['user_id'] = $user_in_db->id;

	header('Location: /main'); //redirect user back to page
	return;
}


// PAGE RELOAD (이미 로그인 되어있을 때)
if (isset($_SESSION['user_id'])) 
{
	header('Location: /main'); //redirect user back to page  
	return;
}


// GOOGLE LOG-IN PAGE
$authUrl = $client->createAuthUrl();
header('Location: ' . filter_var($authUrl, FILTER_SANITIZE_URL));

?>

0개의 댓글