[Hands-On] OAuth2.0/OIDC 표준 프로토콜을 이용한 인증 체계 구축 (Cognito IDP + Spring Security 연동 및 SSO 로그인)

들어가며

안녕하세요, 교보DTS 클라우드 기술팀 DevOps 파트 이민경입니다.
이 문서는 교보DTS 클라우드 빌링시스템인 OCKI에 OAuth2.0 인증 체계를 구축하고 OIDC를 연동하며 관련된 개념과 방법을 정리한 내용입니다.
OAuth 2.0과 OIDC는 로그인 인증 체계에서 사용할 수 있는 표준화된 프로토콜이며 이번 Hands On에서는 AWS Cognito IDP와 연동하기 위해 사용되었습니다. 로그인 인증 체계에 IDP를 연동하여 사용함으로써 얻을 수 있는 장점과 개념을 공부하고, Spring Security 를 이용하여 인증체계를 구축한 뒤 SSO 로그인을 적용하기 위한 실제 방법까지 Hands-On으로 알아봅시다.

Cognito 사용자풀을 이용한 OAuth2.0 인증 체계 구축

세션? 쿠키? JWT? OAuth2.0?

로그인 인증은 보안과 직결되는 문제이기도 하기 때문에 어떤 방식으로 사용자를 검증할 것인가하는 문제는 시스템 설계에서 매우 중요합니다. 지금까지 시스템 설계에서 사용되어온 로그인 인증 방식에는 여러가지가 있는데, JWT 토큰 방식이 사용되기 이전에는 세션과 쿠키를 사용한 인증 방식이 주로 사용되었습니다.

Cognito 사용자풀을 이용하여 OAuth2.0 프로토콜을 통해 인증 체계를 구축하게 되면 사용자 인증 정보 관리, Access Token 발급 등의 기능이 필요한 인증 서버를 직접 구축하는 비용을 아끼면서도 안전한 보안 체계를 구축할 수 있습니다. 또한, OIDC 연동을 통해 SNS 로그인 등의 기능도 손쉽게 추가할 수 있습니다.

1. 개념 이해하기

OAuth 2.0이란?

OAuth 2.0: A standard for authorization

OAuth 2.0(Open Authorization 2.0, OAuth2)은 인증을 위한 개방형 표준 프로토콜입니다. 이 프로토콜에서는 Third-Party 프로그램에게 리소스 소유자를 대신하여 리소스 서버에서 제공하는 자원에 대한 접근 권한을 위임하는 방식을 제공합니다.

OAuth2.0 프로토콜에서 주목해야할 점은 이 인증 방식이 ‘인증’이 아닌 ‘인가’에 대한 데이터라는 점입니다.

OAuth2.0의 Access Token 안에는 사용자가 누구인지에 대한 정보는 포함하고 있지 않습니다. 오직 해당 사용자가 어떤 권한을 갖고 있는지만을 포함함으로써, 제 3자가 데이터를 확인하더라도 이로 인해 보안상의 문제가 발생하지 않습니다.

Cognito OAuth2.0 인증 흐름

사용자가 Cognito 인증 서버에서 ID와 Password를 입력하면, Cognito는 Authorization Code와 함께 사용자를 Client 서버로 redirect 시켜줍니다. Authorization Code를 받은 Client 서버는 해당 코드를 다시 Cognito로 전송해서 Access Token을 발급받습니다. 이 Access Token 을 통해서 Client 는 API 서버와 통신할 수 있고, API 서버는 유효한 AccessToken 이 확인되지 않으면 요청을 거부합니다.

Access Token 을 서버에 저장하지 않아도 되나요?

Access Token 은 JWT(Json Web Token)형식으로 작성되어 RSA 공개 알고리으로 암호화되었으므로, 공개키만 있다면 누구든 해당 정보를 디코딩할 수 있도록 만들어져 있습니다. A는 A만이 사용할 수 있는 Private Key 를 이용해 Data를 암호화하고, B는 A가 제공한 공개키를 통해 암호화된 데이터를 복호화할 수 있습니다. 이 때, 복호화한 데이터를 읽을 수 있는 것은 자유롭지만 해당 데이터를 암호화할 수 있는 것은 Private Key 를 가지고 있는 A 뿐이기 때문에, B는 이 데이터를 A가 암호화했음을 신뢰할 수 있습니다.

RSA 방식 암호화 알고리즘

OIDC 란?

OpenID - Wikipedia

OIDC(Open ID Connect)란 OAuth2.0 체계 위에 ID 토큰 정보를 추가하여 사용자의 신원을 확인할 수 있도록 기능을 확장한 프로토콜을 말합니다.
앞서 설명한 OAuth 2.0 을 통해 어플리케이션에서 이용할 수 있는 권한 정보만으로 안전하게 IDP에 로그인하는데에는 성공했다 하더라도, SSO(Single Sign On) 로그인을 위해서는 결과적으로 해당 사용자가 누구인지 (Authentification)까지 알아야지만 또다른 IDP에 사용자 인증을 할 수 있게 됩니다.

예를들어, 구글에서 Cognito 1 을 사용한 인증 체계를 구축한 상태에서, Cognito 2, API Server2 등등을 사용중인 넷플릭스에 접속하고 싶다면 먼저 Cognito 1을 이용하여 인증을 진행하고 이 인증을 통해 Cognito 2에서 해당 사용자를 확인할 수 있습니다. 사용자 입장에서는 ‘구글 로그인’ 버튼 하나만 누르면 순식간에 인증 절차가 끝나게 되지만, 시스템 상에서는 이러한 인증 흐름이 진행됩니다.

2. Hands-On

코그니토 User Pool 생성하기

Cognito > User pools > Create user pool

Step1 – 로그인 환경 구성

Step2 – 보안 요구 사항 구성

Step3 – 가입 환경 구성

Step4 – 메시지 전송 구성

Step5 – 앱 통합

Step6 – 검토 및 생성

가장 아래쪽의 ‘사용자 풀 생성’ 클릭

클라이언트에서 Cognito 로그인 페이지 접속하기

Cognito에서는 User Pool 을 생성하면 해당 사용자 풀에 대한 호스팅 UI 도 함께 제공합니다. 제공되는 UI를 통해서 로그인&인증 절차를 통과하고 Authorization Code와 함께 원하는 URI로 리다이렉트할 수 있습니다.

로고가 들어간 호스팅 UI / CSS 구성도 커스터마이징 가능

허용된 콜백 URL에 Authorization Code를 넘겨받을 어플리케이션 URL을 입력해두고, 같은 URL을 redirect_uri= 뒷부분에 입력하면,
호스팅 UI에서 로그인에 성공한 다음 해당 URL에서 Authorization Code를 쿼리 파라미터로 받을 수 있습니다.

Web Client 에서 Access Token 발급받기

javascript-react 프로젝트에서 작성한 코드

위와 같은 getAccessToken 함수를 통해서 grant_type, client_id, code(Authorization Code), redirect_uri 를 body로 전달하면
Response 값으로 Accesss Token 정보를 얻을 수 있습니다.

Access Token을 얻었다면, API를 호출할 때 Header 값으로 “Authorizaion” 정보를 넣어줍니다.

API Request 에 Access Token 정보가 포함되었다면, 다음은 API 서버에서 해당 요청이 유효한지 확인할 차례입니다.

Spring Boot Spring Security 연동하기

먼저, API 서버에서 필요한 라이브러리를 추가합니다. build.gradle에 아래와 같이 implementation을 추가하면 됩니다.

그리고 나면 SecurityConfig.java 파일을 생성하여 SecurityConfig 클래스를 정의합니다.
이 때, SecurityConfig 내부에서 TokenProvider라는 클래스를 참조하게 되는데 이 클래스 역시 새로 정의해야합니다.

Spring Boot API 서버에서 JWT 토큰이 유효한지 확인하기 (TokenProvider)

코그니토에서 발행한 Access Token 의 공개키는 아래와 같이 Region 과 userPoolId정보를 이용해 URI를 구성해서 확인할 수 있습니다.
이 uri를 JwtDecoder에서 불러와 활용합니다.

SecurityFilterChain 작성 (SecurityConfig)

SecurityFilterChain 에서는 http 호출 시 api 코드를 호출하기 전에 해당 요청에 적절한 권한이 있는지 확인합니다.
“.oauth2ResourceServer”로 들어오는 oauth2 프로토콜을 확인하여, oauth2 프로토콜의 jwt 토큰을 미리 정의해놓은 tokenProvider의 accessTokenDecoder를 통해 복호화합니다.

복호화에 성공하면 해당 토큰이 유효한지까지 확인한 후, 유효할 경우에만 리소스 접근 권한을 허용합니다.

OIDC 자격 증명 공급자 추가

내부 시스템에 OAuth 인증 쳬계 구성을 마쳤다면, 드디어 SSO 로그인을 위해 OIDC 를 연동할 차례입니다. Cognito 사용자 풀 정보에서 [로그인 경험] 탭의 [연동 자격 증명 공급자 로그인] 항목에서 [자격 증명 공급자 추가] 버튼을 클릭합니다. 그리고 연동 로그인 옵션으로 OIDC 를 선택합니다.
* 이 과정은 소셜 로그인을 통해 로그인하게 될 RP (Relying Party) 측 Cognito에서 진행해야합니다.

등록할 OIDC 공급자가 동일한 Cognito IDP인 경우, 호스팅 엔드포인트의 /.well-known/openid-configuration 요청을 통해 필요한 정보들을 자동으로 가져올 수 있습니다. OIDC 엔드 포인트 검색에서 [발급자 URL을 통해 자동 채우기]를 이용하여, 발급자 URL에 엔드포인트 주소를 입력합니다. 이때, 443 혹은 80포트만이 URL에 사용될 수 있으므로 반드시 HTTPS 를 사용해야합니다.

마무리하며…

이렇게 OAuth2.0 프로토콜을 이용한 SSO Cognito 인증 Flow를 Spring Boot의 SpringSecurity와 함께 구현해 보았습니다.
Spring Security의 FilterChain을 이용하면, API호출마다 번거롭게 반복해서 jwt 토큰을 검사하는 코드를 구현하지 않아도 매 API에 해당 Filter를 적용할 수 있습니다. 또한, ‘authorizeRequests’를 이용해서 Request가 요청된 주소를 검증하여 원하지 않는 클라이언트로부터의 접근을 차단할 수도 있습니다.
Cognito는 위와 같이 카카오 로그인, 페이스북 로그인 등의 SNS 로그인도 쉽게 연결할 수 있도록 지원하며 이 과정에서 인증정보를 Resource 서버나 API 서버를 거치지 않으면서도 안전하게 전달할 수 있습니다.

이상으로 OAuth2.0/OIDC 표준 프로토콜을 이용한 인증 체계 구축에 대한 포스팅을 마치겠습니다. 긴 글 읽어주셔서 감사합니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다