Skip to content

Latest commit

 

History

History
395 lines (282 loc) · 19.4 KB

File metadata and controls

395 lines (282 loc) · 19.4 KB
source https://www.keycloak.org/securing-apps/policy-enforcer
keycloak_version 26.6.2
translated 2026-06-03
category securing-apps

Keycloak 정책 실행자(Policy Enforcer)

원문 제목(EN): Keycloak policy enforcer 원문: https://www.keycloak.org/securing-apps/policy-enforcer

Java 애플리케이션에서 Keycloak 정책 실행자 사용하기

정책 실행 지점(Policy Enforcement Point, PEP)은 하나의 설계 패턴으로, 다양한 방식으로 구현할 수 있습니다. Keycloak은 다양한 플랫폼, 환경, 프로그래밍 언어를 위한 PEP 구현에 필요한 모든 수단을 제공합니다. Keycloak Authorization Services는 RESTful API를 제공하며, OAuth2 인가 기능을 활용하여 중앙화된 인가 서버를 통한 세분화된 인가(fine-grained authorization)를 지원합니다.

PEP 개요

PEP는 보호된 리소스와 연결된 정책을 평가하여 Keycloak 서버가 내린 액세스 결정을 실행하는 역할을 합니다. 이는 애플리케이션 내에서 필터 또는 인터셉터로 동작하여, 보호된 리소스에 대한 특정 요청이 해당 결정에 의해 부여된 권한을 기반으로 충족될 수 있는지 여부를 확인합니다.

Keycloak은 Keycloak Policy Enforcer를 Java 애플리케이션에 활성화하기 위한 내장 지원을 제공하며, JakartaEE 호환 프레임워크 및 웹 컨테이너 보호를 위한 내장 지원도 포함합니다. Maven을 사용하는 경우, 프로젝트에 다음 의존성을 추가해야 합니다:

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-policy-enforcer</artifactId>
    <version>26.0.9</version>
</dependency>

정책 실행자를 활성화하면 애플리케이션으로 전송되는 모든 요청이 인터셉트되며, 보호된 리소스에 대한 액세스는 Keycloak이 요청하는 ID에 부여한 권한에 따라 허용됩니다.

정책 실행은 애플리케이션의 경로와 Keycloak 관리 콘솔을 사용하여 리소스 서버에 대해 생성한 리소스와 밀접하게 연결됩니다. 기본적으로 리소스 서버를 생성하면 Keycloak은 리소스 서버에 대한 기본 구성을 생성하여 정책 실행을 빠르게 활성화할 수 있도록 합니다.

구성(Configuration)

정책 실행자 구성은 JSON 형식을 사용하며, 리소스 서버에서 사용 가능한 리소스를 기반으로 보호된 경로를 자동으로 해석하려는 경우 대부분 아무것도 설정할 필요가 없습니다.

보호되는 리소스를 수동으로 정의하려면 다음과 같이 조금 더 상세한 형식을 사용할 수 있습니다:

{
  "enforcement-mode" : "ENFORCING",
  "paths": [
    {
      "path" : "/users/*",
      "methods" : [
        {
          "method": "GET",
          "scopes" : ["urn:app.com:scopes:view"]
        },
        {
          "method": "POST",
          "scopes" : ["urn:app.com:scopes:create"]
        }
      ]
    }
  ]
}

다음은 각 구성 옵션에 대한 설명입니다:

  • enforcement-mode

    정책이 실행되는 방식을 지정합니다.

    • ENFORCING

      (기본 모드) 특정 리소스와 연결된 정책이 없는 경우에도 기본적으로 요청이 거부됩니다.

    • PERMISSIVE

      특정 리소스와 연결된 정책이 없어도 요청이 허용됩니다.

    • DISABLED

      정책 평가를 완전히 비활성화하고 모든 리소스에 대한 액세스를 허용합니다. enforcement-modeDISABLED인 경우에도 애플리케이션은 인가 컨텍스트(Authorization Context)를 통해 Keycloak이 부여한 모든 권한을 얻을 수 있습니다.

  • on-deny-redirect-to

    서버에서 "액세스 거부" 메시지를 받았을 때 클라이언트 요청이 리다이렉트되는 URL을 정의합니다. 기본적으로 어댑터는 403 HTTP 상태 코드로 응답합니다.

  • path-cache

    정책 실행자가 애플리케이션의 경로와 Keycloak에 정의된 리소스 간의 연관관계를 추적하는 방식을 정의합니다. 캐시는 경로와 보호된 리소스 간의 연관관계를 캐싱함으로써 Keycloak 서버에 대한 불필요한 요청을 방지하기 위해 필요합니다.

    • lifespan

      항목이 만료되어야 하는 시간(밀리초)을 정의합니다. 제공하지 않으면 기본값은 30000입니다. 캐시를 완전히 비활성화하려면 0을 설정할 수 있습니다. 캐시 만료를 비활성화하려면 -1을 설정할 수 있습니다.

    • max-entries

      캐시에 유지되어야 하는 항목의 한도를 정의합니다. 제공하지 않으면 기본값은 1000입니다.

  • paths

    보호할 경로를 지정합니다. 이 구성은 선택 사항입니다. 정의되지 않으면 정책 실행자는 Keycloak에서 애플리케이션에 대해 정의한 리소스를 가져와 모든 경로를 탐색합니다. 이 리소스들은 애플리케이션의 일부 경로를 나타내는 URIS와 함께 정의됩니다.

    • name

      특정 경로와 연관시킬 서버의 리소스 이름입니다. path와 함께 사용하면 정책 실행자는 리소스의 URIS 속성을 무시하고 제공된 경로를 대신 사용합니다.

    • path

      (필수) 애플리케이션의 컨텍스트 경로에 상대적인 URI입니다. 이 옵션이 지정되면 정책 실행자는 동일한 값의 URI를 가진 리소스를 서버에서 쿼리합니다. 현재는 매우 기본적인 경로 매칭 로직이 지원됩니다. 유효한 경로의 예시:

      • 와일드카드: /*
      • 접미사: /*.html
      • 하위 경로: /path/*
      • 경로 매개변수: /resource/{id}
      • 정확한 일치: /resource
      • 패턴: /{version}/resource, /api/{version}/resource, /api/{version}/resource/*
    • methods

      보호할 HTTP 메서드(예: GET, POST, PATCH)와 서버의 특정 리소스에 대한 스코프와의 연관 방식을 지정합니다.

      • method

        HTTP 메서드의 이름입니다.

      • scopes

        메서드와 연관된 스코프를 포함하는 문자열 배열입니다. 스코프를 특정 메서드와 연관시키면, 보호된 리소스(또는 경로)에 액세스하려는 클라이언트는 목록에 지정된 모든 스코프에 대한 권한을 부여하는 RPT를 제공해야 합니다. 예를 들어 create 스코프를 가진 POST 메서드를 정의하면, 해당 경로에 POST 요청을 수행할 때 RPT에는 create 스코프에 대한 액세스 권한이 포함되어 있어야 합니다.

      • scopes-enforcement-mode

        메서드와 연관된 스코프에 대한 실행 모드를 참조하는 문자열입니다. 값은 ALL 또는 ANY가 될 수 있습니다. ALL이면 해당 메서드를 사용하여 리소스에 액세스하기 위해 정의된 모든 스코프가 부여되어야 합니다. ANY이면 해당 메서드를 사용하여 리소스에 액세스하기 위해 최소한 하나의 스코프가 부여되어야 합니다. 기본적으로 실행 모드는 ALL로 설정됩니다.

    • enforcement-mode

      정책이 실행되는 방식을 지정합니다.

      • ENFORCING

        (기본 모드) 특정 리소스와 연결된 정책이 없는 경우에도 기본적으로 요청이 거부됩니다.

      • DISABLED

    • claim-information-point

      Keycloak 서버로 해석되어 전달되어야 하는 하나 이상의 클레임(claim) 집합을 정의합니다. 이를 통해 정책에서 이 클레임들을 사용할 수 있게 됩니다. 자세한 내용은 클레임 정보 지점(Claim Information Point)을 참조하세요.

  • lazy-load-paths

    어댑터가 애플리케이션의 경로와 연관된 리소스를 서버에서 가져오는 방식을 지정합니다. true로 설정하면 정책 실행자는 요청된 경로에 따라 리소스를 온디맨드(on-demand)로 가져옵니다. 이 구성은 배포 시 서버에서 모든 리소스를 가져오지 않으려는 경우(경로를 제공하지 않은 경우) 또는 paths의 일부 집합만 정의하고 나머지는 온디맨드로 가져오려는 경우에 특히 유용합니다.

  • http-method-as-scope

    스코프를 HTTP 메서드에 매핑하는 방식을 지정합니다. true로 설정하면 정책 실행자는 현재 요청의 HTTP 메서드를 사용하여 액세스를 허용할지 여부를 확인합니다. 활성화된 경우 Keycloak의 리소스가 보호하는 각 HTTP 메서드를 나타내는 스코프와 연관되어 있는지 확인하세요.

  • claim-information-point

    Keycloak 서버로 해석되어 전달되어야 하는 하나 이상의 전역(global) 클레임 집합을 정의합니다. 이를 통해 정책에서 이 클레임들을 사용할 수 있게 됩니다. 자세한 내용은 클레임 정보 지점(Claim Information Point)을 참조하세요.

클레임 정보 지점(Claim Information Point)

클레임 정보 지점(Claim Information Point, CIP)은 클레임을 해석하고 이를 Keycloak 서버로 전달하여 액세스 컨텍스트에 대한 추가 정보를 정책에 제공하는 역할을 합니다. 다음과 같은 다양한 소스에서 클레임을 해석할 수 있도록 policy-enforcer에 구성 옵션으로 정의할 수 있습니다:

  • HTTP 요청(매개변수, 헤더, 본문 등)
  • 외부 HTTP 서비스
  • 구성에 정의된 정적 값
  • Claim Information Provider SPI 구현을 통한 기타 소스

클레임을 Keycloak 서버로 전달할 때 정책은 사용자가 누구인지뿐만 아니라 특정 트랜잭션에서 누가(who), 무엇을(what), 왜(why), 언제(when), 어디서(where), 어떤 것을(which)에 기반한 컨텍스트와 내용도 고려하여 결정을 내릴 수 있습니다. 이는 모두 컨텍스트 기반 인가(Contextual-based Authorization)에 관한 것으로, 세분화된 인가 결정을 지원하기 위해 런타임 정보를 활용하는 방법입니다.

HTTP 요청에서 정보 얻기

다음은 HTTP 요청에서 클레임을 추출하는 방법을 보여주는 여러 예시입니다:

keycloak.json

{
  "paths": [
    {
      "path": "/protected/resource",
      "claim-information-point": {
        "claims": {
          "claim-from-request-parameter": "{request.parameter['a']}",
          "claim-from-header": "{request.header['b']}",
          "claim-from-cookie": "{request.cookie['c']}",
          "claim-from-remoteAddr": "{request.remoteAddr}",
          "claim-from-method": "{request.method}",
          "claim-from-uri": "{request.uri}",
          "claim-from-relativePath": "{request.relativePath}",
          "claim-from-secure": "{request.secure}",
          "claim-from-json-body-object": "{request.body['/a/b/c']}",
          "claim-from-json-body-array": "{request.body['/d/1']}",
          "claim-from-body": "{request.body}",
          "claim-from-static-value": "static value",
          "claim-from-multiple-static-value": ["static", "value"],
          "param-replace-multiple-placeholder": "Test {keycloak.access_token['/custom_claim/0']} and {request.parameter['a']}"
        }
      }
    }
  ]
}

외부 HTTP 서비스에서 정보 얻기

다음은 외부 HTTP 서비스에서 클레임을 추출하는 방법을 보여주는 여러 예시입니다:

keycloak.json

{
  "paths": [
    {
      "path": "/protected/resource",
      "claim-information-point": {
        "http": {
          "claims": {
            "claim-a": "/a",
            "claim-d": "/d",
            "claim-d0": "/d/0",
            "claim-d-all": [
              "/d/0",
              "/d/1"
            ]
          },
          "url": "http://mycompany/claim-provider",
          "method": "POST",
          "headers": {
            "Content-Type": "application/x-www-form-urlencoded",
            "header-b": [
              "header-b-value1",
              "header-b-value2"
            ],
            "Authorization": "Bearer {keycloak.access_token}"
          },
          "parameters": {
            "param-a": [
              "param-a-value1",
              "param-a-value2"
            ],
            "param-subject": "{keycloak.access_token['/sub']}",
            "param-user-name": "{keycloak.access_token['/preferred_username']}",
            "param-other-claims": "{keycloak.access_token['/custom_claim']}"
          }
        }
      }
    }
  ]
}

정적 클레임(Static claims)

keycloak.json

{
  "paths": [
    {
      "path": "/protected/resource",
      "claim-information-point": {
        "claims": {
          "claim-from-static-value": "static value",
          "claim-from-multiple-static-value": ["static", "value"]
        }
      }
    }
  ]
}

Claim Information Provider SPI

Claim Information Provider SPI는 내장 공급자 중 요구 사항을 충족하는 것이 없는 경우 개발자가 다른 클레임 정보 지점을 지원하기 위해 사용할 수 있습니다.

예를 들어, 새로운 CIP 공급자를 구현하려면 org.keycloak.adapters.authorization.ClaimInformationPointProviderFactoryClaimInformationPointProvider를 구현하고, 애플리케이션의 클래스패스에 META-INF/services/org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory 파일도 제공해야 합니다.

org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory 예시:

public class MyClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<MyClaimInformationPointProvider> {

    @Override
    public String getName() {
        return "my-claims";
    }

    @Override
    public void init(PolicyEnforcer policyEnforcer) {

    }

    @Override
    public MyClaimInformationPointProvider create(Map<String, Object> config) {
        return new MyClaimInformationPointProvider(config);
    }
}

모든 CIP 공급자는 위의 MyClaimInformationPointProviderFactory.getName 메서드에 정의된 것처럼 이름과 연관되어야 합니다. 이 이름은 policy-enforcer 구성의 claim-information-point 섹션에서 구성을 구현에 매핑하는 데 사용됩니다.

요청을 처리할 때 정책 실행자는 MyClaimInformationPointProviderFactory.create 메서드를 호출하여 MyClaimInformationPointProvider의 인스턴스를 얻습니다. 호출 시 이 특정 CIP 공급자에 대해 정의된 모든 구성(claim-information-point를 통해)이 맵으로 전달됩니다.

ClaimInformationPointProvider 예시:

public class MyClaimInformationPointProvider implements ClaimInformationPointProvider {

    private final Map<String, Object> config;

    public MyClaimInformationPointProvider(Map<String, Object> config) {
        this.config = config;
    }

    @Override
    public Map<String, List<String>> resolve(HttpFacade httpFacade) {
        Map<String, List<String>> claims = new HashMap<>();

        // put whatever claim you want into the map

        return claims;
    }
}

인가 컨텍스트 얻기

정책 실행이 활성화되면 서버에서 얻은 권한은 org.keycloak.AuthorizationContext를 통해 사용할 수 있습니다. 이 클래스는 권한을 얻거나 특정 리소스 또는 스코프에 대해 권한이 부여되었는지 확인하는 데 사용할 수 있는 여러 메서드를 제공합니다.

서블릿 컨테이너에서 인가 컨텍스트 얻기:

HttpServletRequest request = // obtain javax.servlet.http.HttpServletRequest
AuthorizationContext authzContext = (AuthorizationContext) request.getAttribute(AuthorizationContext.class.getName());

참고(NOTE): 인가 컨텍스트는 서버가 내리고 반환한 결정에 대해 더 많은 제어권을 부여하는 데 도움이 됩니다. 예를 들어, 이를 사용하여 리소스 또는 스코프와 연관된 권한에 따라 항목을 숨기거나 표시하는 동적 메뉴를 구성할 수 있습니다.

if (authzContext.hasResourcePermission("Project Resource")) {
    // user can access the Project Resource
}

if (authzContext.hasResourcePermission("Admin Resource")) {
    // user can access administration resources
}

if (authzContext.hasScopePermission("urn:project.com:project:create")) {
    // user can create new projects
}

AuthorizationContext는 Keycloak Authorization Services의 주요 기능 중 하나를 나타냅니다. 위의 예시에서 볼 수 있듯이 보호된 리소스는 이를 관리하는 정책과 직접 연관되지 않습니다.

역할 기반 접근 제어(RBAC)를 사용한 유사한 코드를 살펴보겠습니다:

if (User.hasRole('user')) {
    // user can access the Project Resource
}

if (User.hasRole('admin')) {
    // user can access administration resources
}

if (User.hasRole('project-manager')) {
    // user can create new projects
}

두 예시 모두 동일한 요구 사항을 처리하지만 방식이 다릅니다. RBAC에서는 역할이 해당 리소스에 대한 액세스를 암묵적으로만 정의합니다. Keycloak을 사용하면 RBAC, 속성 기반 접근 제어(ABAC, attribute-based access control) 또는 기타 BAC 변형을 사용하든 리소스에 직접 집중하는 더 관리하기 쉬운 코드를 작성할 수 있습니다. 특정 리소스 또는 스코프에 대한 권한이 있거나 없는 것뿐입니다.

이제 보안 요구 사항이 변경되어 프로젝트 관리자 외에도 PMO가 새 프로젝트를 생성할 수 있게 되었다고 가정해 보겠습니다.

보안 요구 사항은 변경되지만 Keycloak을 사용하면 새로운 요구 사항을 처리하기 위해 애플리케이션 코드를 변경할 필요가 없습니다. 애플리케이션이 리소스 및 스코프 식별자를 기반으로 하면 인가 서버에서 특정 리소스와 연관된 권한 또는 정책의 구성만 변경하면 됩니다. 이 경우 Project Resource 및/또는 스코프 urn:project.com:project:create와 연관된 권한과 정책이 변경됩니다.

AuthorizationContext를 사용하여 Authorization Client 인스턴스 얻기

AuthorizationContext를 사용하여 애플리케이션에 구성된 Keycloak 인가 클라이언트에 대한 참조를 얻을 수도 있습니다:

ClientAuthorizationContext clientContext = ClientAuthorizationContext.class.cast(authzContext);
AuthzClient authzClient = clientContext.getClient();

경우에 따라 정책 실행자로 보호되는 리소스 서버는 인가 서버가 제공하는 API에 액세스해야 할 수 있습니다. AuthzClient 인스턴스를 사용하면 리소스 서버가 서버와 상호작용하여 프로그래밍 방식으로 리소스를 생성하거나 특정 권한을 확인할 수 있습니다.

TLS/HTTPS 구성

서버가 HTTPS를 사용하는 경우 정책 실행자를 다음과 같이 구성해야 합니다:

{
  "truststore": "path_to_your_trust_store",
  "truststore-password": "trust_store_password"
}

위의 구성은 Authorization Client에 TLS/HTTPS를 활성화하여 HTTPS 체계를 사용하여 Keycloak 서버에 원격으로 액세스할 수 있도록 합니다.

참고(NOTE): Keycloak 서버 엔드포인트에 액세스할 때 TLS/HTTPS를 활성화하는 것을 강력히 권장합니다.