인증은 사용자가 누구인지 확인한다. 인가는 사용자의 접근 권한을 확인한다.
(예) 공항에서 여권을 보여주어 나를 인증할 수 있고, 탑승 게이트에서는 내 탑승권을 보여서 내 권한을 입증해야 탑승을 인가 받을 수 있다.
OAuth는 인터넷 사용자들이 비밀 번호를 제공하지 않고, 다른 웹사이트 상의 자신의 정보에 대해 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다. (위키백과)
(예) 외부 어플리케이션은 사용자 인증을 위해 카카오톡, Google 등의 사용자 인증 방식을 사용한다. 이 때 해당 외부 애플리케이션은 OAuth를 바탕으로 카카오톡, Google의 특정 자원을 접근 및 사용할 수 있는 권한을 인가 받는다
GitHub 계정으로 웹 애플리케이션에 로그인하고, GitHub에서 제공하는 여러 API 기능을 외부 애플리케이션에서 사용하는 동작 과정에 대해 알아본다
클라이언트가 Resource Server를 이용하기 위해서는 자신의 서비스를 등록해서 사전 승인을 받아야 한다
GitHub의 경우에는 등록된 서비스에 다음과 같은 정보를 부여한다
외부 서비스를 통해 인증을 마치면 애플리케이션은 명시된 주소로 사용자를 리다이렉트 시킨다. 이 때 Query String으로 특별한 코드가 전달되는데, 애플리케이션은 해당 코드, Client ID, Client Secret을 Resource Server에 보내서 Resource Server의 자원을 사용할 수 있는 Access Token을 발급받는다.
GitHub 소셜 로그인을 위해서는 다음과 같은 파라미터들이 요구된다
GET https://github.com/login/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}?scope={scope}
Resource Owner가 Resource Server에 접속하여 로그인을 완료하면, Resource Server는 Query String으로 받은 파라미터들로 클라이언트를 검사한다
검증에 성공하면 Resource Server는 Resource Owner에게 명시한 scope에 대한 권한을 애플리케이션에 정말 부여할 것인지 질의한다. Resource Owner가 허용하면 Resource Server에게 Client의 접근을 승인하게 된다
Resource Owner가 승인한 경우, Resource Server는 클라이언트를 명시된 Redirect URL로 리다이렉트 시킨다. Resource Server는 클라이언트가 자신의 자원을 사용할 수 있는 Access Token을 발급하기 전에, 임시 암호인 Authorization Code를 함께 발급한다.
클라이언트는 Query String으로 이 코드를 받고, (주로) DTO에 Client ID, Client Secrety Key와 함께 담아 Resource Server에 전달한다. Resource Server는 유효한 요청인지 검증한 다음 Access Token을 발급한다.
클라이언트는 해당 토큰을 서버에 저장해두고, Resource Server의 자원을 사용하기 위한 API 호출시 해당 토큰을 헤더에 담아 보낸다.
클라이언트는 Access Token을 헤더에 담아 GitHub API를 호출한다.
Access Token에는 만료 기간이 있어서 만료된 토큰으로 API를 요청하면 401 Unauthorized 에러가 발생한다. Access Token이 만료될 때마다 서비스 이용자가 재로그인하는 것은 번거롭기 때문에 주로 Resource Server는 Access Token을 발급할 당시 Refresh Token을 함께 발급한다.
클라이언트는 두 토큰을 모두 저장한다. API를 호출할 때는 Access Token을 사용하고, Access Token이 만료되면 저장해둔 Refresh Token을 보내서 새로운 Access Token을 발급받는다.
HTTP는 각 요청에 대해 매번 연결을 생성하고 끊는 비연결성, 그리고 요청에 대한 상태를 저장하지 않는 무상태성이라는 특징을 가진다. 불필요한 자원 낭비를 줄일 수 있는 장점이다.
하지만 서버는 클라이언트를 식별하지 못한다는 단점도 존재한다. 이번 요청으로 로그인에 성공해도, 상태를 저장하지 않기 때문에 이 클라이언트가 로그인했음을 기억하지 않는다.
이러한 단점을 해결하기 위해 쿠키와 세션을 사용한다. 쿠키는 클라이언트가 어떤 웹사이트를 방문할 경우, 그 사이트의 서버에 의해 클라이언트의 브라우저에 저장되는 key-value 형태의 정보 파일을 말한다.
이 쿠키를 통해 로그인 상태를 유지시킬 수 있지만, 쿠키는 브라우저에 저장되어 보안이 약하기 때문에 유출 및 조작 당할 위험이 존재한다.
세션은 정보를 브라우저가 아닌 서버 측에 저장하기 때문에 보안이 더 좋다. 인증 정보는 서버에 저장하고, 클라이언트 식별자인 JSESSIONID
를 쿠키에 담아, 클라이언트는 요청을 보낼 때마다 JSESSIONID
쿠키를 보내면 서버가 유효성을 판단해 클라이언트를 식별한다.
그럼에도 해커가 JSESSIONID
쿠키를 탈취하면 클라이언트인 척 위장이 가능하고, 서버에 부하가 갈 수 있다는 단점이 존재한다.
JWT(JSON Web Token)은 인증에 필요한 정보를 암호화시킨 토큰을 의미한다. 쿠키/세션 방식과 유사하게 JWT 토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별한다.
JWT는 .을 구분자로 세 개의 문자열로 나뉜다.
alg
와 typ
으로 각각 암호화할 해싱 알고리즘과 토큰의 타입을 지정한다{
"alg": "HS256",
"typ": "JWT"
}
key-value 형식으로, 한 쌍의 정보를 Claim이라 부른다
{
"sub": "12345812",
"name": "Hong Gildong",
"iat": 2385123
}
HMACSHA256(
base64URLEncode(header) + "." + base64URLEncode(payload), {256-bit-secret}
)
헤더와 페이로드는 단순히 인코딩된 값이기 때문에 제3자가 복호화 또는 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화 할 수 없다. 따라서 Signature는 토큰의 위변조 여부를 확인하는데 사용된다.
Authorization
에 포함시켜 함께 전달한다Access Token과 함께 Refresh Token을 발급한다. Access Token을 탈취 당하면 기존 Access Token을 무효화 시킨 다음, 사용자는 다시 로그인해서 Access Token를 새로 발급 받는다.
하지만 서버는 Refresh Token을 별도의 저장소에 저장해야 하기 때문에 저장소가 필요없는 JWT의 장점을 완전히 누릴 수는 없다. 저장소에 저장한다는 건 처리하기 위해 I/O 작업을 수반한다는 의미기도 하다.
이 Refresh Token까지 탈취 당하면 안타깝다..
참고: