다양한 애플리케이션 유형 보안

uchan·2024년 12월 5일
0

목표

  1. 보안을 수행하는 어플리케이션이 내부인지 외부인지 구분하기
  2. 다양한 어플리케이션 유형 보안을 알아보기
    • 웹 어플리케이션 보안
    • 네이티브 및 모바일 어플리케이션 보안
    • REST API 및 서비스 보안

내부 및 외부 어플리케이션 이해

내부 어플리케이션은 기업이 보유한 어플리케이션이다. 어플리케이션을 누가 개발하였는지 호스팅 방법은 어떻게 되었는지 중요하지 않다.내부 어플리케이션의 경우 해당 어플리케이션은 신뢰할 수 있고 Keycloak 에 어플리케이션을 등록한 관리자가 사용자를 대신해 접근 권한을 사전 승인할 수 있으므로 사용자를 인증할 때 사용자에게 어플리케이션에 대한 접근 권한을 부여하도록 요청할 필요가 없다. 즉, 사용자가 로그인을 한 후 어플리케이션이 특정 리소스에 권한을 요구할 때 "XX 권한을 부여하는데 동의하시겠습니다?" 와 같은 동의 페이지가 보여질 필요가 없다. 이미 관리자가 해당 어플리케이션 등록하면서 사용자의 권한을 사전 승인했기 때문이다.

반면 외부 어플리케이션은 반드시 접근 권한 동의페이지를 거쳐 권한을 받아야한다. 즉, 외부 어플리케이션은 기업이 보유하거나 관리하는 어플리케이션이 아니라 서드파티에서 관리되는 어플리케이션이다.

권한 동의 페이지는 Keycloak client 상세페이지에서 Consent Required 옵션을 통해 (비)활성화할 수 있다

웹 어플리케이션 보호

어플리케이션이 ID 제공자(네이버, 구글, Keycloak)를 통해 인증 처리를 할 경우 사용자를 신뢰할 수 있는 ID 제공자로 리다이렉트해야한다. 만약 ID 제공자쪽으로 리다이렉트하지 않고 직접 어플리케이션 단에서 사용자의 아이디 패스워드를 받아 대신 ID 제공자로 접근하여 인증을 처리할 경우 어플리케이션이 보안위협에 노출되면 사용자 인증 정보까지 탈취당하기 때문이다. 또한 어플리케이션의 로그인 화면 또는 iframe 등을 넣어 정보를 받아 ID 제공자로 넘겨주는 것도 위험하다. 따라서 어플리케이션이 ID 제공자를 통해 인증 처리를 할 경우 ID 제공자로 리다이렉트하여 인증 절차(ID, password 입력 후 로그인)을 한 뒤 인가코드를 발급받아 ID 제공자로부터 ID 토큰을 받아야한다. 이때 PKCE 확장 기능을 사용하면 인가 코드가 유출되어 남용되는 경우를 방지할 수 있다.

Keycloak 으로 웹 어플리케이션을 보호할 때 다음 사항들을 고려해야한다.

  • SSR 인지? CSR(SPA) 인지?
  • 어플리케이션이 REST API 를 사용하는지? 사용한다면 REST API 가 내부 어플리케이션이지 혹은 외부 어플리케이션이지 확인 필요

위 사항들을 통해 다음과 같이 4개의 아키텍처를 고려한다
server side: 웹 어플리케이션이 웹 서버 내부 또는 어플리케이션 서버 내에서 실행 중인 경우
SPA with dedicated REST API: 어플리케이션이 브라우저에서 실행되거나 동일 도메인에서 전용 REST API 만 호출하는 경우
SPA with intermediary API: 어플리케이션이 브라우저에서 실행되거나 어플리케이션과 동일한 도메인에서 호스팅되는 중계 API 를 통해 외부 REST API 를 호출하는 경우
SPA with external API: 어플리케이션이 브라우저에서 실행되거나 다른 도메인에서 호스팅되는 REST API 만 호출하는 경우

server side

Keycloak 을 사용해 서버 사이드 웹 어플리케이션을 보호하는 경우 Keycloak 에 보안 클라이언트를 등록해야 한다. 그리고 redirect uri 를 설정하여 오직 웹 어플리케이션 서버로만 리다이렉트 할 수 있도록 한다.

  1. 웹서버는 브라우저(클라이언트)에게 Keycloak 으로 리다이렉트한다.
  2. 브라우저는 Keycloak 로그인 페이지로 이동하여 로그인을 한다.
  3. Keycloak 웹서버에 인가코드를 전달한다.
  4. 웹서버는 인가코드를 사용해 Keycloak 에 토큰을 획득한다.
  5. 웹서버는 획득한 토큰을 이용해 인증된 HTTP 세션을 수립하고 브라우저의 요청에 쿠키가 담겨질 수 있도록 한다.

전용 REST API 가 포함된 SPA 보호

server side 와 거의 유사하다. SPA 에서 웹서버로 로그인 요청을 하면 웹서버는 사용자에게 Keycloak 으로 리다이렉트해주고 이후 단계는 server side 와 동일하다.

웹서버로 로그인 요청하는 1번을 제외하고 나머지 2~7번은 server side 와 동일하다.

중개 REST API 를 사용하는 SPA 보호

SPA 에서 외부 REST API 를 호출하는 가장 안전한 방법은 SPA 와 동일한 도메인에서 호스팅되는 중개 API 를 사용하는 것이다.

1~7. 앞서 전용 REST API 가 포함된 SPA 보호와 동일하다.
8. 외부 REST API 서버로 접근 토큰이 포함된 요청한다.
9. 외부 REST API 는 접근 토큰을 확인하고 웹서버로 응답한다.
10. 웹서버는 SPA 에 HTTP 세션 쿠키가 포함된 응답한다.

웹서버는 해당 요청을 외부 REST API 로 프록시해주는 역할이며, 중개 API 를 사용하면 보안 클라이언트를 활용할 수 있고 브라우저에서 직접 토큰에 접근할 수 없기 때문에 토큰 유출될 가능성이 적다.

외부 REST API 를 사용하는 SPA 보호

Keycloak 을 통해 SPA 를 보호하는 가장 간단한 방법은 Keycloak 에 등록된 공용 클라이언트를 사용하는 방법이다. 이는 SPA 에서 직접 토큰을 발급받기 때문에 토큰 노출 위험도가 높아 민감한 어플리케이션(금융)에서는 권장하지 않는다.

  1. SPA(브라우저) 는 Keycloak 으로 리다이렉트된다.
  2. Keycloak 에서 SPA 로 인가 코드를 반환한다.
  3. SPA는 Keycloak 으로부터 토큰을 발급받는다.
  4. SPA 는 REST API 로 접근 토큰이 포함된 요청을 보낸다.
  5. REST API는 SPA로 CORS 헤더가 포함된 응답을 보낸다.

여기서 3번 째 SPA(브라우저) 에서 직접 인가코드와 토큰을 교환하는데, 이는 브라우저에서 클라이언트의 자격증명을 보호할 수 없기 때문에 Keycloak 에 등록된 공용 클라이언트를 사용한다. 그리고 5번 째 REST API 가 CORS 헤더가 담긴 응답을 보내면서 다른 도메인을 가진 응답을 브라우저가 받을 수 있도록 한다. 하지만 이는 브라우저에서 접근 토큰을 직접 접근하기 때문에 토큰 노출 위험도가 높다. 따라서 이를 사용하려면 PCKE 사용 및 리프레쉬 토큰 수명을 짧게하고 갱신하여 이전 리프레쉬 토큰을 사용 못하게 하는 등 적절히 보안 수준을 높여야된다.

네이티브 및 모바일 어플리케이션 보호

웹 어플리케이션과 동일하게 어플리케이션 자체에서 로그인 페이지를 구현해 사용자 이름과 패스워드를 수집한 다음 OAuth 2.0 리소스 소유자 패스워드 자격증명 부여(Credential grant)를 활용해 토큰을 획득하고자 할 수 있다. 그러나 이는 권장하지 않는 방법이다. 되도록 웹 어플리케이션에서 말했던 리다이렉트를 통해 인증절차를 밟는게 가장 안전하다. 즉, 모바일 어플리케이션에서도 브라우저를 사용하는 것이 가장 안전하다. 이를 관련해 어플리케이션 유형에 따라 다음 세 가지 옵션을 사용할 수 있다.

  • 임베디드 웹 뷰 사용
  • 외부 사용자 에이전트 사용 (기본 브라우저)
  • 안드로이드 및 iOS 에서 지원하는 어플리케이션이 필요 없는 인앱 브라우저 탭 사용

임베디드 웹 뷰를 사용할 경우 어플리케이션 내에 로그인 페이지를 배치할 수 있다. 그러나 해당 옵션은 자격증명 탈취와 관련된 취약점이 있기 때문에 권장하지는 않는다. 또한 어플리케이션 간의 공유 쿠키가 없기 때문에 SSO 를 사용하지 않는다
어플리케이션의 로그인 페이지를 표시하면서 시스템 브라우저를 활성화하기 때문에 인앱 브라우저 탭을 사용하는 것은 적절한 방법이다. 하지만 악의적인 어플리케이션이 인앱 브라우저 탬과 유사한 어플리케이션 로그인 페이지를 렌더링하여 자격증명을 탈취할 수도 있다.
따라서 어플리케이션에서 인앱 브라우저를 사용하기보다 외부 브라우저(기본 브라우저 등)를 사용해 ID provider 에 로그인하고 인가 코드를 받아 어플리케이션에 반환하는게 안전하다.
인가 코드를 어플리케이션에 반환하는 경우, OAuth 2.0 에서 정의한 특수 리다이렉트 URI 를 사용하는 4개의 서로 다른 접근 방법이 존재한다.

  • Claimed HTTPS scheme
    - ex: https://example.com/callback
    • HTTPS를 사용
  • Custom URI scheme
    - ex: myapp://callback
    • 애플리케이션 고유의 URI 스키마를 사용
  • Loopback interface
    - ex: http://127.0.0.1:port/callback
    • 어플리케이션이 루프백 인터페이스에서 임시 웹서버를 띄운 후 리다이렉트를 받도록 함
  • A special redirect URI
    - ex: urn:ietf:wg:oauth:2.0:oob
    • 따로 리다이렉트를 사용하지 않고, 사용자가 직접 인가 코드를 복사해 어플리케이션에 붙여넣기함

HTTPS 구조를 사용할 수 있는 경우 HTTPS 방식을 사용하고, 그렇지 못한 경우(CLI 등) 루프백 인터페이스 활용이 권장된다.

간단하게 시스템 브라우저를 사용해 인가코드를 가져오는 방법을 살펴보자.

참고 코드: https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/tree/main/ch6

추가로 해당 코드에서 아래 부분을 지워야 정상적으로 동작한다.

// 삭제 해야될 코드
var dns = require('node:dns');
dns.setDefaultResultOrder('ipv4first');

코드 수정까지 완료했다면 Keycloak 에 클라이언트를 다음과 같이 추가한다.

client ID: cli
Access Type: public
Standard Flow Enabled: ON
valid Redirect URI: http://127.0.0.1/callback

이제 npm install && npm start 를 실행하면 Keycloak 로그인 화면이 뜨고, 로그인 수행완료 뒤 인가 코드를 받아오는 것을 확인할 수 있다.

만약 그래픽 인터페이스를 지원하지 않는다면(터미널에서 사용할 경우) Device Code 승인 유형을 활용하면 된다.

REST API 및 서비스 보호

REST API 를 호출할 때 헤더에 Keycloak 으로부터 받은 접근 토큰을 담아 요청한다.

Authorization: bearer eyJh...

만약 마이크로 서비스로 아키텍쳐가 구성되었다면 최상단 서비스에서 접근 토큰을 받아 이후 다른 서비스들 호출할 때 접근 토큰을 담아 인증 컨텍스트를 전파하여 사용할 수도 있다. 즉, 최상단 서비스와 그 외 다른 서비스 모두 동일한 인증 컨텍스트를 사용하여 처리할 수 있다.

Keycloak 은 service account 를 지원하기 때문에 서비스가 Client Credentials Grant 유형을 사용해 자체적으로 접근 토큰을 획득할 수 있다. 즉, 아래 실습을 통해 확인해보자.

일단 클라이언트를 다음과 같이 생성한다

client ID: service
client protocol: openid-connect
access type: confidential
Client authentication: ON
Standart Flow Enabled: OFF
Implicit Flow Enabled: OFF
Direct Access Grant Enabled: OFF
Service Accounts Enabled: ON // Client Credentials Grant 활성화

이후 터미널에 다음과 같이 실행하여 결과를 확인한다.

export SECRET=<Client Credential Secret>

KEYCLOAK_ACCESS_TOKEN=$(curl -X POST \
  'http://localhost:8080/realms/myrealm/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d "client_id=service" \
  -d "client_secret=${SECRET}" \ 
  -d 'grant_type=client_credentials' | jq -r '.access_token')
  
echo $KEYCLOAK_ACCESS_TOKEN              
eyJhbGciO...

0개의 댓글