인증서버에서 유저에게 리소스 접근 권한을 허용하냐는 질문을 하고 승인했다면, 인증서버에서 클라이언트에게 액세스 토큰과 리프레쉬 토큰을 발급한다. (해당 토큰을 애플리케이션에 잘 보관해둬야 반복적으로 트위터를 통해 로그인할 필요가 없어진다.)
이제 트위터 Analizer는 리소스 소유자에게 /getTweets라는 api를 통해 리소스를 요청한다. 요청 시마다 액세스 토큰을 함께 보내줘야한다.
리소스 서버 즉 트위터는 액세스 토큰을 확인한다.
그럼 정보 읽기가 가능하다.
Authorizationcode grant flow
OUATH2에는 여러가지 인증유형이 있고 우리가 웹 어플리케이션을 만들 때 그러한 여러가지 인증 유형중 하나를 선택해야한다.
Authorization code: 엔드유저가 연관되어 있거나 두 개의 다른 어플리케이션이 서로 교류하려할 때 이 방법을 사용해야한다.
엔드유저가 리소스에 접근하고 싶다는 의사를 밝힌다.
클라이언트 어플리케이션은 유저의 권한이 필요하다는 정보를 보낸다.
유저는 인증 서버 로그인 페이지로 이동하게 된다.
로그인을 하면 인증서버는 액세스 토큰이 아닌 일시적인 Authorization code를 클라이언트 어플리케이션에 제공한다.
클라이언트 어플리케이션은 Authorization code를 인증 서버에 보내면서 유저의 인증 정보들도 함께 보낸다. 해당 정보들을 바탕으로 액세스 토큰을 발금해달라고 한다.
인증 서버는 정보들을 바탕으로 액세스 토큰을 클라이언트 어플리케이션에 반환해준다.
클라이언트 어플리케이션은 액세스 토큰을 리소스 서버에 보내면서 리소스요청을 한다.
액세스 토큰을 확인하고 리소스를 반환한다.
위의 2,3단계에서는 client_id, redirect_url, scope, state(csrf 토큰값), return_type등의 정보가 인증 서버에 보내진다.
인증서버는 Authorization code를 반환한다.
위의 5단계에서는 액세스 토큰을 발급받기 위해 인증서버에 Authorization code, client_id. client_secret, grant_type(Authorization_code의 유형이다를 명시), redirect_url의 정보가 인증 서버에 보내진다.
왜 두 번의 인증이 존재할까?
첫 번째 Authorization code 발급을 위한 인증은 유저가 인증서버에 유효한 유저임을 인증받는 과정이다.
두 번째 액세스 토큰 발급을 위한 인증은 해당 웹 어플리케이션이 Client로 등록된 웹이다 라는 것을 인증하기 위한 과정이다. 따라서 Client_secret이 두 번째 단계에서 전송된다.
위의 두 단계를 하나로 합친 implicit grant type이란 인증 타입이 존재하긴 하지만 보안성이 떨어지기 때문에 사용하지 않는다.
Authorization code 흐름 요약
엔드유저가 회원가입 등의 요청을 클라이언트 어플리케이션으로 보냄
클라이언트 어플리케이션에서 엔드유저를 인증서버로 보냄
같이 보내는 정보는 response type과 client id, redirect url, scope, state의 정보를 함께 보냄
유저는 인증서버로 이동한 후 자신의 인증 정보를 입력함
유저에게 클라이언트 어플리케이션이 당신의 사진에 접근하려 한다 허용하겠냐? 등의 메세지가 표시된다.
인증서버는 Authorization code와 함께 state에 담겨있던 csrf 토큰을 같이 클라이언트 어플리케이션으로 보낸다.
csrf 토큰에 오류가 없다면 무사히 수신된다.
클라이언트 어플리케이션은 post 유형으로 인증서버에 액세스 토큰을 요청한다.
같이 보내는 정보는 grant type, client id, client secret, redirect url, authorization code다.
인증서버에서 해당 정보들을 확인후 액세스 토큰을 반환한다.
같이 반환되는 정보에는 token 타입, 만료 시간, 액세스 토큰, scope, refresh 토큰이 있다.
Password grant / Resource Owner credentails grant flow
엔드유저가 자신의 실제 인증 정보를 클라이언트 어플리케이션에게 보낸다.
클라이언트 어플리케이션이 post 요청으로 유저의 인증정보와 client id, client secret 정보를 인증 서버에 넘긴다.
인증 서버는 액세스 토큰을 바로 클라이언트 어플리케이션에 반환해준다.
클라이언트 어플리케이션은 액세스 토큰을 리소스 서버에 보내면서 리소스를 요청한다.
액세스 토큰이 적절하다면 리소스를 반환한다.
단점: 엔드유저가 실제 인증 정보를 애플리케이션과 공유한다. (OUATH2를 이용할 이유가 없다.)
클라이언트 어플리케이션이 인증 서버에 보내는 정보: client 아이디 및 시크릿, scope, username&password, grant type
클라이언트 어플리케이션, 리소스 서버, 인증 서버가 모두 같은 단체에 속해있을 경우 위와 같은 인증방식을 사용한다.
Client credentials grant flow
엔드유저가 연결돼있지 않은 두 백엔드 어플리케이션에 관한 상황에서 사용한다.
클라이언트 어플리케이션은 A회사에 소속, 인증 서버와 리소스 서버는 B회사에 소속된 상황이라 생각해보자. 클라이언트 어플리케이션이 B회사의 리소스 서버에서 자료를 가져오고 싶다면 인증 서버에서 인증을 성공한 후에 가져올 수 있을것이다.
클라이언트 어플리케이션에서 유저 세부사항을 인증 서버로 보낸다.(엔드유저는 관련돼있지 않다.)
인증과 관련된 정보들을 인증서버에서 확인한 후 클라이언트 어플리케이션에 액세스 토큰을 반환한다.
클라이언트 어플리케이션에서 액세스 토큰과 함께 리소스 요청을 리소스 서버에 보낸다.
액세스 토큰이 적절하다면 리소스를 반환해준다.
클라이언트 어플리케이션에서 인증 서버에 보내야하는 정보
client id, client secret, scope, grant type
grant type에 client_credentials를 적어서 보내면 엔드유저가 관련되지 않다는 것을 인증 서버는 이해할것이다.
해당 방식에서 state에 csrf 토큰을 넣어서 보내지 않는 이유는 브라우저와 통신이 없기 때문이다.(세션, 쿠키가 없으므로 사용자 정보를 바탕으로 csrf 공격을 할 수 없기 때문)
refresh token grant flow
액세스 토큰의 기간이 만료됐을 때 리프레쉬 토큰을 다시 인증서버에 보낸다. 인증 서버는 리프레쉬 토큰을 확인한 후 새로운 액세스 토큰과 리프레쉬 토큰을 발급해준다.
refresh token grant flow는 엔드유저가 관련되지 않는다. 액세스 토큰의 기간이 만료됐다면, 자동으로 서버에서 인증 서버로 refresh 토큰을 보낼것이기 때문이다.
리소스 서버에 액세스 토큰을 보내며 자원을 요청함
리소스 서버에서 토큰 만료된 것을 보고 403 에러를 반환함
클라이언트 어플리케이션은 인증서버에 리프레쉬 토큰을 보내며 새 액세스 토큰을 요청함
리프레쉬 토큰이 적절하다면 인증서버는 새 액세스 토큰과 리프레쉬 토큰을 반환한다.
새 리프레쉬 토큰은 브라우저나 데이터베이스에 저장한다.
새 액세스 토큰을 활용하여 리소스 서버에 리소스를 요청한다.
클라이언트 어플리케이션에서 인증서버에 보내야하는 정보
client id, client secret, refresh token, scope, grant type
grnat type에는 refresh_token 이라고 적어서 보내야 인증 서버에서 액세스 토큰과 리프레쉬 토큰을 새로 발급해준다.
액세스 토큰이 적절한지 리소스 서버는 어떻게 확인할까?
인증 서버와 리소스 서버가 api 소통을 하는 것이다. 액세스 토큰이 들어오면 해당 토큰을 인증 서버에 보내며 유효성을 검사한다. (액세스 토큰이 들어올 때마다 인증 서버와 소통해야 하므로 효율이 약간 떨어진다.)
인증 서버와 리소스 서버가 동일한 데이터베이스를 공유하는 것이다. 인증서버에서 액세스 토큰을 발급해주면 데이터베이스에 활성화 표시를 해주고, 액세스 토큰이 리소스 서버에 들어오면 리소스 서버는 쿼리를 날려 활성화 상태인지 아닌지를 판단하면 된다.
어플리케이션이 시작할 때 리소스 서버는 인증 서버에게 공개 인증서를 발급받아둔다. 나중에 액세스 토큰이 들어올때 마다 리소스 서버는 공개 인증서를 통하여 액세스 토큰의 진위 여부를 판단한다. (현재 가장 많이 사용되는 방법이며 맨 처음 인증 서버와 리소스 서버의 연결이 된 이후 지속적인 소통이 필요 없기에 효율적이다.)
Oauth2는 유저의 정보가 어떤지 유저의 이메일은 무엇인지에 관한 정보는 파악할 수 없다.
Oauth2의 구조물을 그대로 사용하면서 유저의 세부 정보를 받고 싶었던 개발자들은 Ouath2 기반에서 사용 가능한 방법인 OpenID Connect를 개발했다.
OpenID Connect 프로토콜
인증 서버와 OICD로 연결을 시도하면 인증서버에선 액세스 토큰과 ID 토큰을 반환한다. (리프레쉬 토큰 제외)
여기서 ID 토큰이 유저의 세부 정보에 관한 권한을 관리한다.
우리가 반환값으로 ID 토큰을 받으려면 scope에 OpenID 라는 값을 보내야한다.
액세스 토큰과 ID 토큰의 조합을 통해 세부적인 유저 권한 관리가 가능해졌고, 그에따라 나타난 개념이 IAM 이다.
OIDC를 활용하면 scope에 profile, email, address 같은 값을 넣을 수 있고, jwt 토큰에 해당 정보들을 담아 교환할 수 있으며, /userinfo라는 공개 엔드 포인트를 통해 유저 정보를 쉽게 파악할 수도 있다.
인증서버에서 반환된 openid 토큰을 확인해보면 바디 부분에 base64로 우리가 scope에 담아 요청한 유저의 세부 정보가 인코딩 된 것을 확인할 수 있다.
Section 12.
Oauth2 구현
우선 spring-boot-starter-oauth2-client 다음과 같은 의존성을 추가해야 한다. 이를 통해 클라이언트 어플리케이션이라는 사실을 알려주는것이다.
그리고 config의 defalutSecurityFilterChain에서 http.authorizeRequests().anyRequest().authenticated().and().oauth2Login();
을 통해서 ouath2 방식의 로그인을 지원한다. 모든 url에 대하여 인증 서버로 이동 시켜준다.