들어가기 전에
만약 어떤 서비스를 처음 이용한다고 하면 편리한 이용을 위해 개인정보가 담긴 계정이 필요합니다.
하지만 계정을 만들기에는 많은 시간들이 소요됩니다.
서비스 이용에 있어 회원가입은 필수지만 가입에 걸리는 시간 때문에 회원, 서비스 운영자 모두 손해를 보게 됩니다.
그래서 일부 유명 기업에서는 각자 개발한 방법대로 인증방식을 표준화한 방식이 있었지만 여러 애플리케이션을 통합하여 사용하지 못한다는 단점이 있었습니다.
따라서 2010년에 IETF에서 인증방식을 표준화한 OAuth 1.0 공식 표준안이 RFC 5849로 발표되었습니다.
OAuth란?
OAuth 2.0 인증 프레임워크는 타사 애플리케이션이 HTTP 서비스에 제한적으로 접근할 수 있도록 지원합니다.
사용자와 HTTP 서비스 간의 상호작용을 조정하거나 타사 응용 프로그램이 접근 권한을 얻도록 허용하여 사용자를 대신합니다.
이렇게 OAuth가 등장하고 난 뒤 안전하게 유명기업에서는 사용자의 정보를 전달해줄 수 있게 되었고
사용자는 안전하고 빠르게 회원가입을 했으며, 서비스는 회원가입이라는 지루한 과정을 생략할 수 있게 됐습니다.
OAuth 용어 정리
OAuth에 대해 본격적으로 알아보기 전에 OAuth 4가지에 대해 짚고 넘어가겠습니다.
- Resource Owner : 서비스를 이용하려는 유저
- Resource Server : 보호된 리소스들을 호스팅 하는 서버
- Client : Resource Owner 대신에 리소스 권한 부여를 통해 보호된 리소스를 요청하는 응용 프로그램(혹은 응용 프로그램을 실행시키는 서버, 데스크 탑 등등..)
- Authorization Server : 성공적으로 인증과 허가가 완료된 Client에게 Access Token을 발행하는 서버
OAuth의 Work Flow
해당 그림은 RFC OAuth 문서에서 발췌한 흐름도입니다.
1. 먼저 Client가 Resource Owner에게 허가 요청을 보냅니다. 이때 Resource Owner에게 직접 수행하거나 Authorization Server가 중개자로 간접적으로 수행될 수 있습니다.
2. 요청으로부터 리소스 소유자의 권한을 나타내는 인증 정보인 권한 허가(Authorzation Grant)를 받습니다.
Authorzation Grant는 Access Token을 얻기 위한 인증 정보로 Authorzation Code, Implicit, Resource Owner Password Credentials, Client Credentials의 4가지 타입이 있습니다.
이 중에서 이번에 사용할 Authorzation Code는 Resource Owner에게 직접 요청하는 대신 Authorization Server로 돌려 Authorzation Code를 얻고 다시 Client에게 보냅니다.
이렇게 얻은 Authorzation Code는 추후에 Access Token을 얻을 때 Resource Owner을 통해 얻지 않고 Client가 자체적으로 얻을 수 있으므로 보안 이점을 제공합니다.
3. Client가 권한 허가를 바탕으로 Access Token을 요청합니다.
Access Token은 보호된 리소스에 접근하기 위한 인증 정보이며 Client에게 발행한 허가를 나타낸 문자열입니다.
또한 추상화를 제공하여 Resource Server가 이해하는 단일 토큰에 대한 서로 다른 권한 부여 구조를 대체합니다. 이러한 추상화를 통해 짧은 기간 동안 유효한 Access Token을 발급할 수 있을 뿐만 아니라 Resource Server가 광범위한 인증 체계를 이해할 필요가 없습니다.
4. Authorization Server는 Client를 증명하고 권한 허가를 입증합니다. 만약 타당하다면 Access Token을 발행합니다.
5. Client는 증명된 Access Token과 함께 Resource Server로부터 보호된 자원(Protected Resource)을 요청합니다.
6. Resource Server는 Access Token을 확인하고 만약 타당하다면 보호된 자원을 반환합니다.
+) Refresh Token
짧은 유효 기간을 가진 Access Token이 만료되면 새로운 Access Token을 받아야합니다.
Refresh Token은 Authorization Server에 의해 선택적으로 Client에게 발급되고 타당하지 않거나 만료된 Access Token을 재발급해줍니다.
Access Token과는 다르게 Authorization Server에서만 사용됩니다.
(B)에서 Authorization Server이 Refresh Token 역시 함께 발급해줍니다.
이후 (F)에서 Access Token이 만료됐다면 Refresh Token을 Authorization Server에게 전송합니다.
만약 Refresh Token이 타당하다면 새로운 Access Token과 Refresh Token(optional)을 다시 반환합니다.
직접 구현해보기
사실 위 설명만으로 OAuth가 어떻게 동작하는지에 대해 명확한 이해를 하기란 쉽지 않아 보입니다. 따라서 직접 구현해보면서 실제로 어떻게 동작하는지 확인해보겠습니다.
예제 코드는 깃헙 주소에 있습니다. (.env 설정은 별도로 해주셔야 합니다. )
OAuth를 제공하는 기업들은 기업 개발자 홈페이지에 들어가면 관련 API를 확인할 수 있습니다.
이번 예시는 카카오를 통해 구현해보겠습니다.
먼저 카카오 개발자 홈페이지로 접속합니다.
이후 내 애플리케이션으로 들어가 새로운 애플리케이션을 생성해줍니다.
생성해준 애플리케이션에 접속해서 요약정보 탭에 들어가 REST API 키를 확인해줍니다.
카카오 로그인 탭에 들어가 카카오 로그인을 활성화해줍니다.
이후 동의 항목 탭에 들어가 아래 사진과 같이 설정해줍니다.
그럼 이제 코드상으로 직접 구현해보도록 하겠습니다.
대략적으로 설계를 해보면 내가 카카오로 로그인 하기 버튼을 누른다면 아래와 같은 프로세스로 진행되어야 합니다.
그렇다면 로그인 버튼을 누르면 위의 "서비스 이용 동의" 혹은 "카카오톡으로 로그인 하기" 화면이 나와야 합니다.
그러기 위해서는 요청을 받으면 상단의 URL로 이동할 수 있도록 Redirect를 해줘야 합니다.
해당 링크를 참고하여 API를 확인해줍니다. https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-code
카카오 API 문서에서 제공하는 대로 아래와 같이 작성해줍니다.
router.get("/authorize", (req, res) => {
res.redirect(
`https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${process.env.REST_API_KEY}&redirect_uri=${process.env.REDIRECT_URI}`
);
});
다만 여기서 REDIRECT_URL를 설정해줘야 하는데 REDIRECT_URL은 내 애플리케이션 - 카카오 로그인 하단에 Redirect URI에 Redirect 할 URL를 추가해줍니다. (저는 로컬에서 테스트하기 때문에 http://localhost:3000/login로 설정했습니다.)
즉, 서비스 이용 동의까지 완료했다면 내가 설정한 REDIRECT_URL로 이동하게 됩니다.
해당 REDIRECT_URL로 이동하게 되면 URL Query로 Authorization Code가 오게 됩니다.
이제 Authorization Code를 받았으니 Access Token을 요청할 차례입니다.
http://localhost:3000/login로 redirect를 요청했으니 그에 맞는 응답문을 작성해보도록 하겠습니다.
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-token 의 문서를 읽어보면
POST 형식으로 https://kauth.kakao.com/oauth/token로 요청하도록 되어 있습니다.
따라서 문서 그대로 요청을 해줍니다.
router.get("/login", async (req, res) => {
const { code } = req.query;
const bodyData = {
grant_type: "authorization_code",
client_id: process.env.REST_API_KEY,
redirect_uri: process.env.REDIRECT_URI,
client_secret: process.env.SECRET,
code,
};
const queryStringBody = Object.keys(bodyData)
.map((k) => encodeURIComponent(k) + "=" + encodeURI(bodyData[k]))
.join("&");
const options = {
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
},
};
const { data } = await axios.post(
"https://kauth.kakao.com/oauth/token",
queryStringBody,
options
);
console.log("token_type: ", data.token_type);
console.log("access_token: ", data.access_token);
console.log("expires_in: ", data.expires_in);
console.log("refresh_token: ", data.refresh_token);
console.log("refresh_token_expires_in: ", data.refresh_token_expires_in);
...
});
이후 콘솔에 찍힌 결과를 확인하면 다음과 같습니다
이제 Access Token까지 얻었으니 Protected Resource를 얻을 차례입니다. 동의 항목 설정 당시 닉네임과 프로필 사진이 들어가 있으니 2개의 정보를 가져오도록 하겠습니다.
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info 문서를 참고하면
GET 메서드에 kapi.kakao.com/v2/user/me로 요청하라고 되어있습니다.
로그인 버튼을 누르고 바로 Protected Resource가 나오도록 만들고 싶으니 login 라우트 함수에 이어서 작성하도록 하겠습니다.
router.get("/login", async (req, res) => {
...
const result = await axios.get("https://kapi.kakao.com/v2/user/me", {
headers: {
Authorization: `Bearer ${data.access_token}`,
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
},
});
const { properties } = result.data;
res.render("result", {
nickname: properties.nickname,
profile_image: properties.profile_image,
});
});
해당 요청을 통해 이제 Protected Resource를 얻게 되었습니다.
'CS 지식' 카테고리의 다른 글
웹이란 무엇인가요? (0) | 2021.12.11 |
---|---|
[HTTP] HTTP란? (0) | 2021.07.16 |