# Amazon Cognito와 Amazon API Gateway를 사용한 인증/인가
#### Amazon API Gateway
Amazon API Gateway는 어떤 규모에서든 개발자가 API를 손쉽게 생성, 게시, 유지 관리, 모니터링 및 보안 유지할 수 있도록 하는 완전관리형 서비스입니다. API는 애플리케이션이 백엔드 서비스의 데이터, 비즈니스 로직 또는 기능에 액세스할 수 있는 "정문" 역할을 합니다. API Gateway를 사용하면 실시간 양방향 통신 애플리케이션이 가능하도록 하는 RESTful API 및 WebSocket API를 작성할 수 있습니다. API Gateway는 컨테이너식 서버리스 워크로드 및 웹 애플리케이션을 지원합니다.
API Gateway는 트래픽 관리, CORS 지원, 권한 부여 및 액세스 제어, 제한, 모니터링 및 API 버전 관리 등 최대 수십만 개의 동시 API 호출을 수신 및 처리하는 데 관계된 모든 작업을 처리합니다. API Gateway에는 최소 요금이나 시작 비용이 없습니다. 수신한 API 호출과 전송한 데이터 양에 대한 요금을 결제하며, API 게이트웨이 계층화 요금 모델을 사용하는 경우 API 사용량 증가에 따라 비용을 절감할 수 있습니다.
#### Amazon Cognito
Amazon Cognito는 웹 및 모바일 앱을 위한 자격 증명 플랫폼입니다. Amazon Cognito는 OAuth 2.0 액세스 토큰 및 AWS 보안 인증을 위한 사용자 디렉터리, 인증 서버, 인증 서비스입니다. Amazon Cognito를 사용하면 기본 제공 사용자 디렉터리, 엔터프라이즈 디렉터리, Google 및 Facebook 같은 소비자 ID 제공업체의 사용자를 인증하고 권한을 부여할 수 있습니다.
이 문서는 Amazon Cogntio와 Amazon API Gateway를 사용하여 인증/인가를 하는 방법을 설명합니다. 필요한 리소스의 생성은 CDK 또는 콘솔을 사용하여 생성합니다.
## 1.API Gateway의 메소드, 리소스 생성 및 Authorizers 생성
API Gateway는 예제를 사용합니다. API Gateway에서 REST API 생성을 클릭하고 예제 API를 클릭합니다. API Endpoint 유형은 Region이고 경고 실패를 체크하고 생성합니다. 아래와 같은 형태의 REST API가 만들어집니다.

Deploy API를 클릭하고 New Stage를 선택합니다. Stage name은 prod를 입력합니다. Stage에서 Invoke URL을 복사합니다. 크롬에 링크를 붙여넣고 엔터를입력하면 아래와 같은 화면이 나옵니다.

REST API도 확인해봅니다. URL의 뒤에 /pets를 입력합니다.
```json=
[
{
"id": 1,
"type": "dog",
"price": 249.99
},
{
"id": 2,
"type": "cat",
"price": 124.99
},
{
"id": 3,
"type": "fish",
"price": 0.99
}
]
```
잘 동작하는 것을 확인했습니다. 이제 권한이 있는 사용자만 해당 API를 호출 할 수 있도록 변경하겠습니다.
## 2.Amazaon Cognito 생성
Cognito 콘솔로 이동합니다. Create User pool을 선택합니다. Cognito user pool sign-in options은 Email을 체크합니다.
#### User Pool 설정
나머지는 그대로 두고, MFA는 No MFA를 선택합니다.
여기도 다른건 건들지 말고 Required attributes에 profile, family_name, given_name을 클릭합니다. 그 외 다른 옵션을 가입 시 정보로 포함하고 싶다면 클릭합니다. 이 부분은 최초 생성시만 설정가능하구 user pool이 생성된 이후에는 변경이 불가능합니다. 기본적으로 제공 하는 것 외 추가정보를 받고싶다면 Custom attributes에 작성하면됩니다. custom attributes도 최초 생성시만 설정가능하고 이후는 추가 삭제만 가능합니다. ( Social Login을 사용하기 위해선 email과 profile이 필수입니다.)
Email provider는 send email with cognito를 선택합니다. 실제 프로덕션에서는 AMAZON SES를 통해 발송하는걸 권장합니다.
User pool name을 등록하고 Use the Cognito hosted UI를 체크합니다. built-in으로 제공하는 UI를 사용할 수 있습니다. Use a Cognito domain을 체크하고 Cognito domain에 원하는 주소를 입력합니다.
#### App client 구성
스크롤을 내려서 앱 클라이언트를 구성합니다. 앱 클라이언트는 인증되지 않은 API 작업을 호출할 수 있는 권한이 있는 사용자 풀의 단일 앱 플랫폼입니다. 사용자 풀에는 여러 앱 클라이언트가 있을 수 있습니다.
Public client를 생성합니다. app client name을 입력하고 client secret은 생성하지 않습니다.
Allowed callback URs의 URL에 https://localhost를 입력합니다.
다음을 선택하여 생성을 완료합니다.
Cognito Console의 user pools에 생성이 잘 되어있는지 확인합니다. 생성된 user pools의 이름을 클릭하고 App intergration 탭을 선택합니다. 처음에 같이 생성한 app client 이름를 클릭하고 hosted ui의 openID Connect scopes에 email, openId, Profile이 선택되어 있는지 확인합니다.(만약 안되어있다면 선택합니다.)
#### Cognito Resource 서버 등록
권한 부여 서버가 액세스 토큰에 추가할 범위를 선택할 수 있습니다. 범위는 리소스 서버 및 사용자 데이터에 대한 액세스 권한을 부여합니다.
리소스 서버는 OAuth 2.0 API 서버입니다. 리소스의 보안을 위해 이 서버는 API에서 요청된 메서드와 경로의 scope가 사용자 풀 액세스 토큰에 포함되어 있는지 확인합니다. 또한 토큰 서명, 토큰 만료 시간에 따른 유효성 및 토큰 클레임의 범위에 따라 액세스 수준을 기반으로 발급자를 확인합니다. user pool은 액세스 토큰 claim 범위에 표시합니다.

API의 petStore를 리소스 서버로 등록하고 적절한 scope가 있는 인증만 통과시킬 것입니다. Cognito의 User pools에 생성한 이름을 선택하고 App integration의 Resource servers탭의 Create resource server를 클릭합니다.
Resource server namerhk resource server identifier에 Pets를 입력합니다. Custom scopes는 아래와 같이 read, list, write를 추가합니다.

저장하고 완료합니다.
## 3.API gateway와 Cognito 연동
API의 petStore를 선택하고 Authorizers를 클릭합니다. Create an authorizer를 선택합니다.
- 사용자 풀을 사용하도록 새 권한 부여자를 구성하려면 다음을 수행합니다.
- 권한 부여자 이름에 이름을 입력합니다.
- 권한 부여자 유형으로 Cognito를 선택합니다.
- Cognito 사용자 풀의 경우 Amazon Cognito를 생성한 AWS 리전을 선택하고 사용 가능한 사용자 풀을 선택합니다.
- 토큰 소스에 헤더 이름으로 Authorization을 입력하여 사용자가 성공적으로 로그인할 때 Amazon Cognito에서 반환하는 자격 증명 또는 액세스 토큰을 전달합니다.
- (선택 사항) 토큰 검증 필드에 정규식을 입력하여 Amazon Cognito를 통해 요청에 대한 권한이 부여되기 전에 자격 증명 토큰의 aud(대상) 필드를 검증합니다. 액세스 토큰을 사용할 경우, 액세스 토큰에 aud 필드가 포함되지 않으므로 이 유효성 검사는 요청을 거부합니다.
- 권한 부여자 생성을 선택합니다.
COGNITO_USER_POOLS 권한 부여자를 생성한 후 필요할 경우, 사용자 풀에서 프로비저닝한 자격 증명 토큰을 제공하여 호출을 테스트할 수 있습니다. Amazon Cognito 자격 증명 SDK를 호출하여 사용자 로그인을 수행함으로써 이 자격 증명 토큰을 얻을 수 있습니다. InitiateAuth 작업을 사용해도 됩니다. 권한 부여 범위를 구성하지 않으면 API Gateway는 제공된 토큰을 자격 증명 토큰으로 취급합니다.
이전 절차에서는 새로 생성된 Amazon Cognito 사용자 풀을 사용하는 COGNITO_USER_POOLS 권한 부여자를 생성합니다. API 메서드에서 권한 부여자를 활성화하는 방법에 따라 통합된 사용자 풀에서 프로비저닝되는 자격 증명 토큰 또는 액세스 토큰을 사용할 수 있습니다.
이제 API:petStore에서 Resource를 선택합니다.Get /pets은 유효한 토큰이 있어야 접근가능하도록 변경하겠습니다. 리소스의 /pets Get을 클릭하고 Method request를 클릭합니다. edit를 선택합니다.

Authorization을 api gateway에 생성했던 authorization을 선택합니다. Authorization Scopes에 pets/list를 입력합니다. 설정을 완료한 후 Deploy API를 클릭하고 prod에 배포합니다.
Stage에서 Invoke URL의 /pets를 브라우저에 붙여넣습니다.
```
{"message":"Unauthorized"}
```
접근이 안되는 걸 볼 수 있습니다.
아무 설정도 안한 Invoke URL은 잘 접속되는걸 확인 할 수 있습니다.
## 4.Amazon Cognito의 Token 발급 및 로그인/회원가입
Postman은 API를 구축하고 사용하기 위한 API 플랫폼입니다. Postman은 API 수명 주기의 각 단계를 단순화하고 협업을 간소화하므로 더 나은 API를 더 빠르게 만들 수 있습니다.
PostMan을 사용하여 token을 발급하고 이를 사용하여 API의 동작을 테스트합니다.Cognito가 어떻게 동작하는지 알아보고 이를 나중에 실제 애플리케이션에 적용해봅니다.
Postman의 탭에서 Authorization을 선택하고 Type을 OAuth2.0을 선택합니다.
Cognito의 User pool에서 app intergration탭을 선택하면 Cognito Domain을 확인할 수 있습니다. 해당 링크를 복사하여 아래 붙여넣습니다.
Callback URL은 https://localhost 를 입력합니다. Cognito의 Hosting UI의 callback URL에도 https://localhost 가 있어야합니다.
그리고 사용할 앱 클라이언트를 선택한 후 클라이언트 ID를 아래 붙여넣습니다.
Scope에 openid와 profile을 입력합니다.
- Clitend ID : <<Cognito Client ID>>
- Callback URL : https://localhost
- Auth URL : <<CognitoDomain>>/oauth2/authorize
- Access Token URL : <<CognitoDomain>>/oauth2/token
- Scope : openid profile email pets/list pets/read pets/write

스크롤을 내려서 Get New Access Token을 클릭합니다. 로그인이 진행되고 토큰이 발급되는 것을 볼 수 있습니다. 진행을 클릭합니다. Access Token, Id Token, refresh_token을 확인할 수 있습니다. 그리고 토큰 관련 정보들도 같이 확인할 수 있습니다.
- id_token : ID 토큰은 애플리케이션에서 해당 콘텐츠를 쉽게 검사하고 해당 토큰이 예상 발급자로부터 제공되었는지, 다른 사람이 이를 변경하지 않았는지 확인할 수 있는 표준 형식인 JWT(JSON 웹 토큰)로 인코딩됩니다. id_token은 base64로 인코딩됩니다.
- access_token : Access 토큰은 클라이언트 애플리케이션이 사용자의 리소스에 액세스할 수 있도록 허용하는 아티팩트입니다. 사용자를 성공적으로 인증하고 동의를 얻은 후 인증 서버에서 발급됩니다. OAuth 2 컨텍스트에서 액세스 토큰을 사용하면 클라이언트 애플리케이션이 특정 리소스에 액세스하여 사용자를 대신하여 특정 작업을 수행할 수 있습니다. 사용자는 자신을 대신하여 리소스에 액세스하도록 클라이언트 애플리케이션을 위임합니다.
#### ID 토큰과 액세스 토큰의 차이점
액세스 토큰은 OAuth에서 정의되고, ID 토큰은 OpenID Connect에서 정의됩니다.
액세스 토큰은 OAuth 클라이언트가 API에 요청하는 데 사용됩니다. 액세스 토큰은 API에서 읽고 유효성을 검사하기 위한 것입니다. ID 토큰에는 사용자가 인증할 때 발생한 상황에 대한 정보가 포함되어 있으며 OAuth 클라이언트에서 읽을 수 있습니다. ID 토큰에는 이름이나 이메일 주소와 같은 사용자에 대한 정보가 포함될 수도 있지만 이는 ID 토큰의 요구 사항은 아닙니다.
ID 토큰과 액세스 토큰의 차이점은 다음과 같습니다.
- ID 토큰은 OAuth 클라이언트에서 읽도록 되어 있습니다. 액세스 토큰은 리소스 서버에서 읽혀집니다.
- ID 토큰은 JWT입니다. 액세스 토큰은 JWT일 수도 있지만 임의의 문자열일 수도 있습니다.
- ID 토큰은 API로 전송되어서는 안 됩니다. 클라이언트는 액세스 토큰을 절대 읽어서는 안 됩니다.
Use Token을 클릭합니다. Use token type의 access token을 선택합니다.
다시 Postman의 탭에 Get을 선택하고 Invoke URL에 /pets를 붙여서 보내봅니다.
```json=
[
{
"id": 1,
"type": "dog",
"price": 249.99
},
{
"id": 2,
"type": "cat",
"price": 124.99
},
{
"id": 3,
"type": "fish",
"price": 0.99
}
]
```
다시 잘 접속하는 것을 볼 수 있습니다.
## 5.Cognito OAuth2 Scope를 사용한 SpringBoot REST API

간단한 테스트를 위해 필요한 의존성을 3가지 입니다.
```java=
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("org.springframework.boot:spring-boot-starter-security")
```
Security를 주입하면 모든 엔드포인트가 자동으로 보안됩니다.
OAuth2 Scope를 사용하여 메소드을 제한하기 위해 JWT, resource 서버를 사용합니다.
Spring Security에서 사용 예제입니다.
- Application.yml
```yml=
spring:
security:
oauth2:
resourceserver:
jwt:
issuerUri: https://cognito-idp.{AWS-RESION-ID}.amazonaws.com/{Cognito UserPool ID}
```
- Controller Sample
```java=
@PostMapping("/user")
@PreAuthorize("hasAuthority('SCOPE_user/write')") //preauthorize for scope user/write
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
```
- Main enable method security
```java=
@EnableMethodSecurity // all endpoint security token enable
@SpringBootApplication
public class RestServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RestServiceApplication.class, args);
}
}
```
# Cognito를 사용한 멀티-테넌트 애플리케이션 모범 사례
Amazon Cognito 사용자 풀을 사용하면 테넌트 수와 예상 볼륨이 관련 Amazon Cognito 서비스 할당량과 일치하는 소규모 다중 테넌트 애플리케이션을 보호할 수 있습니다.
:::warning
Amazon Cognito 할당량은 AWS 계정 및 리전별로 적용됩니다. 이러한 할당량은 애플리케이션의 모든 테넌트에서 공유됩니다. Amazon Cognito 서비스 할당량을 검토하고 할당량이 애플리케이션의 예상 볼륨과 예상 테넌트 수를 충족하는지 확인하세요.
:::
### 사용자 풀(User-pool) 기반
이 설계를 사용하면 애플리케이션의 각 테넌트에 대한 사용자 풀을 생성할 수 있습니다. 이 접근 방식은 각 테넌트에 대한 최대 격리를 제공합니다. 각 테넌트에 대해 서로 다른 구성을 구현할 수 있습니다. 사용자 풀을 통한 테넌트 격리는 사용자-테넌트 매핑의 유연성을 제공합니다. 동일한 사용자에 대해 여러 프로필을 만들 수 있습니다. 그러나 각 사용자는 액세스할 수 있는 각 테넌트에 대해 개별적으로 등록해야 합니다. 이 접근 방식을 사용하면 각 테넌트에 대해 호스팅된 UI를 독립적으로 설정하고 사용자를 애플리케이션의 테넌트별 인스턴스로 리디렉션할 수 있습니다. 또한 이 접근 방식을 사용하여 API 게이트웨이와 같은 백엔드 서비스와 통합할 수도 있습니다.
다음 시나리오에서 사용자 풀 기반 다중 테넌시를 사용할 수 있습니다.
- 애플리케이션에는 테넌트마다 다른 구성이 있습니다. 예를 들어 데이터 상주 요구 사항, 암호 정책 및 MFA 구성은 테넌트마다 다를 수 있습니다.
- 애플리케이션에 복잡한 사용자-테넌트 역할 매핑이 있습니다. 예를 들어 어떤 사용자는 테넌트 A는 "학생"이 될 수 있고 동시에 테넌트 B에서는 "교사"가 될 수도 있습니다.
- 애플리케이션은 기본 Amazon Cognito 호스팅 UI를 로컬 사용자의 기본 인증 방법으로 사용합니다. 로컬 사용자는 외부 IdP를 통한 페더레이션 없이 사용자 풀 디렉터리에만 존재합니다.
- 애플리케이션에는 각 테넌트가 사용을 위해 애플리케이션 인프라의 전체 인스턴스를 가져오는 사일로 다중 테넌트 애플리케이션이 있습니다.
:::success
이 접근 방식을 사용하기 위한 개발 및 운영 노력이 높습니다. Amazon Cognito API 작업 및 자동화 도구를 사용하는 애플리케이션에 테넌트 온보딩 및 관리 구성 요소를 구축해야 합니다. 이러한 구성 요소는 각 테넌트에 필요한 리소스를 만드는 데 필요합니다. 또한 테넌트 일치 사용자 인터페이스를 구현해야 합니다. 또한 사용자가 해당 테넌트의 사용자 풀에 가입하고 로그인할 수 있도록 애플리케이션에 논리를 추가해야 합니다.
:::
### 애플리케이션 클라이언트(App-client) 기반
애플리케이션 클라이언트 기반 다중 테넌시를 사용하면 사용자 프로필을 다시 만들 필요 없이 동일한 사용자를 여러 테넌트에 매핑할 수 있습니다. 각 테넌트에 대해 애플리케이션 클라이언트를 생성하고 테넌트 외부 IdP를 이 애플리케이션 클라이언트가 사용할 수 있는 유일한 ID 공급자로 만들 수 있습니다. 자세한 내용은 사용자 풀 앱 클라이언트 구성을 참조하세요.
호스팅 UI는 이미 인증된 사용자를 인식할 수 있도록 브라우저에 세션 쿠키를 설정합니다. 여러 앱 클라이언트가 있는 사용자 풀의 로컬 사용자를 인증하면 해당 세션 쿠키가 동일한 사용자 풀의 모든 앱 클라이언트에 대해 해당 사용자를 인증합니다. 로컬 사용자는 외부 IdP를 통한 페더레이션 없이 사용자 풀 디렉터리에만 존재합니다. 세션 쿠키는 1시간 동안 유효합니다. 세션 쿠키 기간은 변경할 수 없습니다.
다음 시나리오에서 앱 클라이언트 기반 다중 테넌트를 사용할 수 있습니다.
- 애플리케이션은 모든 테넌트에서 동일한 구성을 갖습니다. 예를 들어 데이터 상주 및 암호 정책은 모든 테넌트에서 동일합니다.
- 애플리케이션에는 사용자와 테넌트 간의 일대다 매핑이 있습니다. 예를 들어 단일 사용자는 동일한 프로필을 사용하여 여러 테넌트에 액세스할 수 있습니다.
- 테넌트가 항상 외부 IdP를 사용하여 애플리케이션에 로그인하는 페더레이션 전용 다중 테넌트 애플리케이션이 있습니다.
- B2B 다중 테넌트 애플리케이션이 있고 테넌트 백엔드 서비스는 클라이언트 자격 증명 부여를 사용하여 서비스에 액세스합니다. 이 경우 각 테넌트에 대한 애플리케이션 클라이언트를 생성하고 머신 간 인증을 위해 클라이언트 ID 및 시크릿을 테넌트 백엔드 서비스와 공유할 수 있습니다.
:::success
이 접근 방식을 사용하려면 개발 노력이 많이 듭니다. 사용자를 해당 테넌트의 애플리케이션 클라이언트와 일치시키려면 테넌트 일치 로직과 사용자 인터페이스를 구현해야 합니다.
:::
### 코그니토 그룹(Cognito Group) 기반
그룹 기반 다중 테넌시를 사용하면 Amazon Cognito 사용자 풀 그룹을 테넌트와 연결할 수 있습니다. 이렇게 하면 RBAC(역할 기반 액세스 제어)를 통해 추가 기능을 사용할 수 있습니다. 자세한 내용은 [역할 기반 액세스 제어](https://docs.aws.amazon.com/cognito/latest/developerguide/role-based-access-control.html)를 참조하세요.
### 사용자 속성(Custom-attribute) 기반
사용자 정의 속성 기반 멀티 테넌시를 사용하면 사용자 프로필에 테넌트_id와 같은 테넌트 식별 데이터를 사용자 정의 속성으로 저장할 수 있습니다. 그런 다음 애플리케이션 및 백엔드 서비스에서 모든 다중 테넌트 논리를 처리합니다. 이 접근 방식을 사용하면 모든 사용자에게 통합된 가입 및 로그인 환경을 사용할 수 있습니다. 애플리케이션에서 사용자의 테넌트를 식별하려면 이 사용자 정의 속성을 확인하면 됩니다.
# OWASP TOP 10 API Security Risks
2023년 OWASP에서 발표한 API 보안 위험 상위 10개입니다. 아래를 고려하여 API를 개발하시는 것을 권장드립니다.
- API1:2023 — 손상된 오브젝트 수준의 권한 확인: API는 개체 식별자를 처리하는 엔드포인트를 노출하는 경향이 있어 개체 수준 액세스 제어 문제의 광범위한 공격 표면을 만듭니다. 사용자의 ID를 사용하여 데이터 소스에 액세스하는 모든 기능에서는 개체 수준 권한 부여 확인을 고려해야 합니다.
- API2:2023 — 취약한 인증: 인증 메커니즘은 잘못 구현되는 경우가 많아 공격자가 인증 토큰을 손상시키거나 구현 결함을 악용하여 일시적으로 또는 영구적으로 다른 사용자의 신원을 가장할 수 있습니다. 클라이언트/사용자를 식별하는 시스템의 기능이 손상되면 전반적인 API 보안이 손상됩니다.
- API3:2023 — 손상된 오브젝트 프로퍼티 수준의 권한 확인: BOPLA는 API 엔드포인트가 최소 권한 원칙을 무시하고 해당 기능에 필요한 것보다 많은 데이터 속성을 불필요하게 노출하는 보안 결함입니다.
- API4:2023 — 무제한 리소스 사용 이는 API 리소스 고갈이라고도 하는 취약점의 한 종류로, API가 주어진 시간 내에 제공하는 요청 수나 데이터 양에 제한을 두지 않는 취약점입니다.
- API5:2023 — 손상된 기능 수준의 권한 확인: API 엔드포인트에 대한 접속 제어 모델이 잘못 구축된 경우 BFLA가 발생할 수 있습니다.
- API6:2023 — 민감한 비즈니스 플로우에 대한 무제한 접속: 이러한 리스크는 API가 충분한 접속 제어 없이 비즈니스 로직 같은 중요한 작업을 노출할 때 발생합니다.
- API7:2023 — 서버 측 요청 위조: SSRF를 사용하면 공격자는 서버 측 애플리케이션이 공격자가 선택한 임의의 도메인에 HTTPS 요청을 하도록 유도할 수 있습니다.
- API8:2023 — 잘못된 보안 설정: 이 리스크는 보안 제어를 부적절하게 설정해 시스템을 공격에 취약하게 만드는 것을 말합니다.
- API9:2023 — 부적절한 인벤토리 관리: 이 리스크는 API를 관리하는 모든 기업이 직면한 과제입니다. API 보안 솔루션은 알려진 API를 보호할 수 있지만 중단된 API, 레거시 API, 오래된 API 등 알 수 없는 API에는 패치가 적용되지 않아 공격에 취약할 수 있습니다.
- API10:2023 — 안전하지 않은 API 사용: 이 리스크는 적절한 보안 조치를 취하지 않고 써드파티 API를 사용할 때 발생할 수 있는 리스크를 의미합니다.