<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>i-mini</title>
    <link>https://1mini2.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 8 May 2026 17:52:25 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>mini_world</managingEditor>
    <image>
      <title>i-mini</title>
      <url>https://tistory1.daumcdn.net/tistory/3784289/attach/eeef8fdd7cfe4eb4a4d9ac6cd4f9e34d</url>
      <link>https://1mini2.tistory.com</link>
    </image>
    <item>
      <title>[Kafka #2] 카프카 프로듀서</title>
      <link>https://1mini2.tistory.com/192</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로듀서란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서는 어플리케이션에서 Kafka로 메세지를 발행(produce)하는 역할을 수행한다.&lt;br /&gt;프로듀서는 특정 토픽에 전송하고, 카프카 브로커가 이를 파티션단위로 저장한다.&lt;br /&gt;이벤트 스트리밍(사용자행동기록)/ 성능 매트릭 기록/ 로그수집/ 실시간 데이터 파이프라인에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NDrwg/btsQCrZUP96/0DI7PZy4Je996Z9zBwh58K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NDrwg/btsQCrZUP96/0DI7PZy4Je996Z9zBwh58K/img.png&quot; data-alt=&quot;https://www.confluent.io/blog/kafka-producer-internals-preparing-event-data/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NDrwg/btsQCrZUP96/0DI7PZy4Je996Z9zBwh58K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNDrwg%2FbtsQCrZUP96%2F0DI7PZy4Je996Z9zBwh58K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1818&quot; height=&quot;846&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.confluent.io/blog/kafka-producer-internals-preparing-event-data/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서에서 생성하는 메세지는 키-밸류 형식이다.&lt;br /&gt;어플리케이션 코드에서 이벤트/데이터를 메세지 객체로 생성하고, 메세지가 전송될 토픽을 지정한다.&lt;br /&gt;이때, 메세지의 키는 어떤 파티션으로 전송될지 지정된다. (키가 없으면 라운드로빈)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메세지를 브로커에 전송할때는 크게 동기/비동기 방식이 있지만, 보통 비동기 전송 방식을 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Fire and forget: 메세지 전송만하고 확인 안함&lt;/li&gt;
&lt;li&gt;Synchronous send: 메세지 동기전송 ( send() 호출 후 Future객체 대기)&lt;/li&gt;
&lt;li&gt;Asynchronous send : 메세지 비동기전송 ( send() 호출 후 콜백)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서는 세가지 필수 속성값을 갖는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bootstrap.servers: 프로듀서가 사용할 호스트 목록 지정&lt;/li&gt;
&lt;li&gt;key.serializer: 키값으로 쓰일 객체를 직렬화 하기 위한 시리얼라이저 클래스 이름&lt;/li&gt;
&lt;li&gt;value.serializer: 밸류값으로 쓰일 객체를 직렬화 하기 위한 시리얼라이저 클래스 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로듀서 주요 파라메터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.confluent.io/platform/current/clients/producer.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.confluent.io/platform/current/clients/producer.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758031969938&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Kafka Producer for Confluent Platform | Confluent Documentation&quot; data-og-description=&quot;The full list of configuration settings are available in Kafka Producer Configurations. The key configuration settings and how they affect the producer&amp;rsquo;s behavior are highlighted below. Core Configuration These settings are the same for Java, C/C++, Pyth&quot; data-og-host=&quot;docs.confluent.io&quot; data-og-source-url=&quot;https://docs.confluent.io/platform/current/clients/producer.html&quot; data-og-url=&quot;https://docs.confluent.io/platform/current/clients/producer.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.confluent.io/platform/current/clients/producer.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.confluent.io/platform/current/clients/producer.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Kafka Producer for Confluent Platform | Confluent Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The full list of configuration settings are available in Kafka Producer Configurations. The key configuration settings and how they affect the producer&amp;rsquo;s behavior are highlighted below. Core Configuration These settings are the same for Java, C/C++, Pyth&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.confluent.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서의 주요 파라메터는 위 링크에서 확인 할 수 있다.&amp;nbsp;&lt;br /&gt;전체가 아닌 중요한 부분만 짧게 확인해보자.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.8837%;&quot;&gt;메시지 지속성&lt;/td&gt;
&lt;td style=&quot;width: 14.4186%;&quot;&gt;acks&lt;/td&gt;
&lt;td style=&quot;width: 70.6976%;&quot;&gt;이 설정을 통해 Kafka에 작성된 메시지의 내구성을 제어할 수 있다.&lt;br /&gt;&lt;b&gt;- 기본값&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;all: &lt;/span&gt;&lt;/b&gt;파티션 리더가 쓰기를 수락할 뿐만 아니라 모든 동기화된 복제본에 성공적으로 복제됨을 보장 (신뢰성 높지만 레코드 전송 느림) &lt;br /&gt;&lt;b&gt;- 1:&lt;/b&gt; &lt;span&gt;1&lt;/span&gt;파티션 리더로부터 쓰기가 성공했다는 명시적인 확인이 필요함&lt;br /&gt;&lt;b&gt;- 0:&lt;/b&gt; 처리량을 극대화하지만 브로커가 응답을 보내지 않으므로 메시지가 브로커 로그에 성공적으로 기록되었다는 보장은 없음 (신뢰성 낮지만 레코드 전송 빠름)&lt;br /&gt;&lt;br /&gt;다만, 컨슈머가 값을 읽을 수 있는 시간까지의 의미의 종단간 지연의 경우 세값이 모두 같다.&lt;br /&gt;카프카는 일관성을 위해 모든 인-싱크 레플리카에 복제가 완료된 후에 컨슈머가 레코드를 읽을 수 있기 때문이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.8837%;&quot;&gt;메시지 순서&lt;/td&gt;
&lt;td style=&quot;width: 14.4186%;&quot;&gt;retries&lt;/td&gt;
&lt;td style=&quot;width: 70.6976%;&quot;&gt;일반적으로 메시지는 프로듀서 클라이언트가 수신한 순서대로 브로커에 기록되지만,이 설정에 따라 순서가 변경될 수 있다.&lt;br /&gt;&lt;br /&gt;- 기본값 0, &lt;span style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: left;&quot;&gt;메시지 재시도를 활성화 수&lt;br /&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.8837%;&quot;&gt;메시지 순서&lt;/td&gt;
&lt;td style=&quot;width: 14.4186%;&quot;&gt;max.in.flight.requests.per.connection&lt;/td&gt;
&lt;td style=&quot;width: 70.6976%;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: left;&quot;&gt;순서를 변경하지 않고 재시도를 활성화하려면 이 속성을 1로 설정한다.&lt;br /&gt;&lt;/span&gt;프로듀서가 서버로부터응답을 받지 못한 상태에서 전송할 수 있는 최대 메세지의 수를 결정하는 옵션이다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시리얼라이저&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시리얼라이저란, 데이터를 한 형태에서 다른 형태로 변환하는 도구를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;브로커는 바이트 단위로 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 프로듀서가 메세지를 브로커에 보내려면 메세지의 키와 값을 바이트 배열로 변환하야 하며,&lt;br /&gt;이 작업을 하는것이 시리얼라이저 이다. (객체 -&amp;gt; 바이트 변환)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카에서 기본으로 제공하는 시리얼라이저가 존재하지만, 스키마 기반의 시리얼라이저(Apache Avro, Protobuf, Thrift)를 사용하는것이 좋다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RU95H/btsQBUVBvOH/kFCrcKvVKdpxKRg9amdvG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RU95H/btsQBUVBvOH/kFCrcKvVKdpxKRg9amdvG0/img.png&quot; data-alt=&quot;https://medium.com/@affanhasan88/how-to-publish-and-consume-avro-encoded-apache-kafka-messages-using-java-44ed42890637&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RU95H/btsQBUVBvOH/kFCrcKvVKdpxKRg9amdvG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRU95H%2FbtsQBUVBvOH%2FkFCrcKvVKdpxKRg9amdvG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;216&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://medium.com/@affanhasan88/how-to-publish-and-consume-avro-encoded-apache-kafka-messages-using-java-44ed42890637&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Json/String 직렬화는 쉽지만 명확한 데이터 구조(스키마)가 없기때문에 필드를 변경하려고 할때 문제가 발생한다.&lt;br /&gt;이때, 스키마 기반의 시리얼라이저를 사용하면 데이터 구조가 바뀌어도 호환성을 유지할 수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 스키마 레지스트리가 존재하며, 메세지를 직렬화 할때 데이터와 스키마 정보를 함께 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파티션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토픽은 여러개의 파티션으로 나뉘어 저장되며, 각 파티션은 순서가 보장된 로그 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서에서 메세지를 보낼 때 토픽/키/값/파티션 정보를 ProducerRecord에 담는다.&amp;nbsp;&lt;br /&gt;(메세지 작성 과정에서 파티션 지정 가능한 구조)&lt;/p&gt;
&lt;pre id=&quot;code_1758033373312&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# https://docs.confluent.io/platform/current/clients/confluent-kafka-python/html/index.html?_gl=1*1jdp8yp*_gcl_aw*R0NMLjE3NTgwMzE5MTkuRUFJYUlRb2JDaE1JMklmRW5MdmRqd01WNkRKN0J4M250Q24zRUFBWUFTQUFFZ0tBaFBEX0J3RQ..*_gcl_au*MjA3MjYxMjE4OC4xNzU4MDMxMTI4*_ga*ODYxODc0NTAwLjE3NTgwMzExMjg.*_ga_D2D3EGKSGD*czE3NTgwMzExMjgkbzEkZzEkdDE3NTgwMzMzNDAkajYwJGwwJGgw&amp;amp;_ga=2.269062833.1731004784.1758031128-861874500.1758031128#confluent_kafka.Producer.produce
produce(topic[, value][, key][, partition][, on_delivery][, timestamp][, headers])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서에서 파티션 번호를 지정한 경우에는 지정한 파티션으로 바로 전송된다. (직접 제어)&lt;br /&gt;키가 있는경우에는 키를 해시하여 파티션을 정한다.&lt;br /&gt;키가 없는경우 라운드로빈 또는 스티키 파티셔너 방식으로 파티션을 고른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영시 주의할점&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핫파티션: 특정 파티션만 과부하 (병목)&lt;/li&gt;
&lt;li&gt;파티션 수 변경 문제: hash(key) / numPartitions 이기때문에, 파티션 개수가 달라지면 기존 키의 파티션 위치가 달라져 순서가 깨짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;헤더&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 메세지는 키/ 값/ 토픽/ 파티션/ 오프셋으로 구성되지만 헤더를 추가할 수 있다.&lt;br /&gt;메세지의 값과는 별도로 추가적인 메타데이터를 헤더로 담을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더는 메세지 본문을 건드리지 않고 부가 정보를 전달할때 유용하게 쓰일 수 있다.&lt;br /&gt;예를들어 트레이싱/로깅/라우팅정보전달/ 메세지 포멧 버전/ 우선순위/ 보안&amp;amp;인증 등에 쓰일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할점은 헤더는 메세지 본문과 독립적으로 구성되어 별도 처리가 가능하고,&lt;br /&gt;브로커는 헤더에 관심이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인터셉터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터셉터(Interceptor)는 프로듀서와 컨슈머 양쪽에 다 있는 개념이며,&lt;br /&gt;메세지를 브로커에 보내거나 읽기 전에 가로채서 추가 동작을 할 수 있게 해주는 훅(hook) 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서 인터셉터로 프로듀서가 메세지를 브로커에 전송하기 직전 또는 전송 결과를 받은 직후에 동작하는 콜백 로직을 추가할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 운영 편의성과 모니터링 목적으로 쓰이며, 코드를 변경하지 않고 작동을 변경해야 하는 경우일때 유용하게 쓰일 수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>   학습노트/낙서장</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/192</guid>
      <comments>https://1mini2.tistory.com/192#entry192comment</comments>
      <pubDate>Tue, 16 Sep 2025 23:46:43 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka #1] 카프카 개요</title>
      <link>https://1mini2.tistory.com/191</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka는&amp;nbsp;설계할&amp;nbsp;때&amp;nbsp;Commit&amp;nbsp;Log&amp;nbsp;개념을&amp;nbsp;시스템&amp;nbsp;전체&amp;nbsp;아키텍처에&amp;nbsp;적용함&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;토픽의 파티션 = 일종의 Commit Log&lt;/b&gt; : 메시지가 오면 뒤에 차곡차곡 추가(Append)만 하고, 절대 지우거나 덮어쓰지 않음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오프셋(Offset) = 커서(cursor)&lt;/b&gt;: 메시지를 읽는 소비자(Consumer)는 &amp;ldquo;내가 어디까지 읽었는지&amp;rdquo;를 오프셋으로 기억함&lt;br /&gt;(데이터는 그대로 두고, 읽는 쪽 위치만 바꾸는 구조)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재처리(Replay)&lt;/b&gt;: 소비자가 오프셋을 옛날로 되돌리면, 같은 로그를 다시 읽으면서 과거 이벤트를 재현할 수 있음 &lt;br /&gt;(데이터 파이프라인이나 장애 복구에 매우 유용)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내구성과 복제&lt;/b&gt;&lt;br /&gt;Commit Log는 보통 분산 시스템에서 &amp;ldquo;진실의 원본(Source of Truth)&amp;rdquo; 역할을 함&lt;br /&gt;Kafka도 여러 브로커에 파티션을 복제(Replication)해서, 장애가 나도 Commit Log처럼 메세지를 안전하게 보존함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, Kafka의 시작은 Commit Log 개념을 시스템 전체 아키텍처에 적용함으로서 시작했지만,&lt;br /&gt;&lt;b&gt;대규모 분산 환경에 맞게 확장한 형태임&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파티션을 여러 개 두어 병렬성과 확장성 제공&lt;/li&gt;
&lt;li&gt;데이터 보존 기간(retention)을 설정해서 오래된 메시지는 삭제 가능&lt;/li&gt;
&lt;li&gt;단순한 로그 저장을 넘어서 &amp;ldquo;실시간 스트리밍 플랫폼&amp;rdquo; 역할 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카프카 주요 개념&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 336px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 38px;&quot;&gt;&lt;b&gt;메시지&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 38px;&quot;&gt;Kafka에서 주고받는 데이터의 기본 단위. (e.g. 편지)&lt;br /&gt;각 메시지는 키(key), 값(value), 타임스탬프로 구성되어있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 17px;&quot;&gt;&lt;b&gt;배치&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;네트워크 효율성을 위해 메시지를 묶어서 한 번에 처리하는 것&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;메세지를&amp;nbsp;&amp;nbsp;&lt;/span&gt;배치단위로 모아서 쓰는것으로, 네트워크&amp;nbsp;오버헤드를&amp;nbsp;줄일&amp;nbsp;수&amp;nbsp;있음&lt;/span&gt;&lt;br /&gt;단, 지연(latency)와 처리량(throughput)t사이의 트레이드 오프 있음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 17px;&quot;&gt;&lt;b&gt;스키마&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 17px;&quot;&gt;메시지의 구조를 정의하는 규칙&lt;br /&gt;Kafka에서는&amp;nbsp;주로&amp;nbsp;Avro,&amp;nbsp;Protobuf,&amp;nbsp;JSON&amp;nbsp;Schema&amp;nbsp;같은&amp;nbsp;포맷을&amp;nbsp;사용하며,&amp;nbsp;Schema&amp;nbsp;Registry를&amp;nbsp;이용해&amp;nbsp;관리함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 38px;&quot;&gt;&lt;b&gt;&lt;b&gt;토픽&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 38px;&quot;&gt;메시지가 저장되는 카테고리나 폴더 같은 개념 (e.g. 데이터베이스의 테이블/ 파일시스템의 폴더)&lt;br /&gt;&quot;주문&quot;, &quot;결제&quot;, &quot;사용자 로그&quot; 같은 주제별로 분류할 수 있음&amp;nbsp;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 57px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 57px;&quot;&gt;&lt;b&gt;&lt;b&gt;파티션&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 57px;&quot;&gt;나의 토픽을 여러 개의 작은 통으로 나누는 것&lt;br /&gt;큰 창고를 여러 구역으로 나누어서 물건을 분산 저장하는 것과 같고, 여러 서버에서 동시에 처리할 수 있어서 성능이 좋아짐&lt;br /&gt;파티션 내에서는 메시지 순서가 보장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 19px;&quot;&gt;&lt;b&gt;&lt;b&gt;프로듀서&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 19px;&quot;&gt;메시지를 생산하여 Kafka에 보내는 애플리케이션 (Publisher/Writer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 19px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;컨슈머&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 19px;&quot;&gt;Kafka에서 메시지를 읽어가는 애플리케이션 (Subscriber/Reader)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 95px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 95px;&quot;&gt;&lt;b&gt;&lt;b&gt;컨슈머 그룹&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 95px;&quot;&gt;&lt;span&gt;여러 컨슈머가 하나의 팀처럼 협력해서, 토픽의 메시지를 나눠 처리하도록 하는 장치&lt;br /&gt;&lt;/span&gt;메세지 순서는 파티션이 보장함, 각 파티션이 어떤 컨슈머가 처리할 수 있도록 매핑(ownership)해주는 역할을 컨슈머 그룹이 담당함&lt;br /&gt;여러 컨슈머가 일을 분담해서 처리 속도가 빨라지며, 컨슈머를 수평 확장할 수 있기때문에 &lt;b&gt;부하분산, 장애 대응&lt;/b&gt;이 자동으로 이루어짐&lt;br /&gt;추가로, 파티션 개수 이상으로 컨슈머를 늘려도 성능 향상없음 (컨슈머 놀게됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 19px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;브로커&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 19px;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Kafka 서버 하나를 의미함&amp;nbsp;&lt;br /&gt;브로커는 프로듀서로부터 메세지를 받아 오프셋을 할당한 뒤 디스크 저장소에 쓰고,&lt;br /&gt;컨슈머의 읽기(fetch) 요청을 처리하고 발행된 메세지를 보냄&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; height: 17px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;클러스터&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 17px;&quot;&gt;&lt;span&gt;&lt;span&gt;브로커 여러 대를 묶어 놓은 집합&lt;br /&gt;토픽의 파티션들이 여러 브로커에 분산 저장될 수 있도록 하며, 분산저장으로 안정성과 성능을 높임&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt; &lt;span&gt;파티션 리더 (Leader)&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;메시지를 실제로 처리하는 주인 역할&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;프로듀서(Producer)가 메시지를 보낼 때 &amp;rarr; 항상 리더 파티션에 기록&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;컨슈머(Consumer)가 메시지를 읽을 때 &amp;rarr; 항상 리더 파티션에서 읽음&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span&gt;팔로워 (Follower)&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;리더가 받은 메시지를 그대로 복제(Replication)해서 보관&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;평소에는 요청을 직접 처리하지 않고, 리더를 따라가기만 함&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;리더에 장애가 생기면, 팔로워 중 하나가 자동으로 승격되어 새로운 리더가 됨 &amp;rarr; 고가용성(HA) 보장&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;br /&gt;&lt;b&gt;컨트롤러(Controller)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; 클러스터의&amp;nbsp;메타데이터를&amp;nbsp;관리하고&amp;nbsp;리더&amp;nbsp;선출&amp;nbsp;등을&amp;nbsp;담당&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;컨트롤러의 역할:&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;- 어떤 브로커가 살아있는지 감시 (헬스 체크)&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;- 리더 브로커가 죽으면 &amp;rarr; 팔로워 중 하나를 새로운 리더로 지정&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;- 파티션과 리더의 할당(assignment)을 관리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Broker 주요 파라메터들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 참고링크: &lt;a href=&quot;https://docs.confluent.io/platform/current/installation/configuration/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.confluent.io/platform/current/installation/configuration/index.html&lt;/a&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 943px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 75px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 75px;&quot;&gt;broker.id&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 75px;&quot;&gt;클러스터 내에서 브로커를 구분하는 ID(정수)임&lt;br /&gt;KRaft&amp;nbsp;모드&amp;nbsp;도입&amp;nbsp;이후에는&amp;nbsp;점차&amp;nbsp;node.id로&amp;nbsp;대체되는&amp;nbsp;중&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;bull;&amp;nbsp;&lt;/span&gt;node.id KRaft 모드(Zookeeper 없는 Kafka 모드)에서 추가된 개념&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;bull; node.id&amp;nbsp;=&amp;nbsp;universal&amp;nbsp;identifier&amp;nbsp;(브로커&amp;nbsp;+&amp;nbsp;컨트롤러)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;bull; broker.id&amp;nbsp;=&amp;nbsp;브로커&amp;nbsp;프로세스&amp;nbsp;전용&amp;nbsp;identifier&amp;nbsp;(과거&amp;nbsp;호환성&amp;nbsp;때문에&amp;nbsp;남아있음)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 17px;&quot;&gt;listener&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 17px;&quot;&gt;listeners &amp;rarr; 브로커가 실제로 바인드(bind) 해서 네트워크를 수신하는 주소 (내부 바인드 인터페이스).&lt;br /&gt;advertised.listeners &amp;rarr; 브로커가 클러스터 메타데이터에 알리고, 클라이언트가 접속할 때 쓰라고 알려주는 주소.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;# 브로커가 내부적으로 바인드할 주소&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;listeners=PLAINTEXT://0.0.0.0:9092&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;# 클러스터 메타데이터에 알릴 주소 (외부에서 접속 가능한 주소)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;advertised.listeners=PLAINTEXT://broker1.mycompany.com:9092&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 17px;&quot;&gt;log.dir&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 17px;&quot;&gt;&lt;span&gt;Kafka는 &lt;b&gt;토픽 파티션의 실제 데이터(로그 세그먼트)&lt;/b&gt;를 이 디렉터리들 안에 저장한다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;브로커는 새 &lt;span&gt;&lt;b&gt;파티션&lt;/b&gt;&lt;/span&gt;을 생성할 때 , 디&lt;/span&gt;렉토리 별 저장된 파티션 수를 비교해서 가장 적은 파티션을 가진 디렉토리에 새 파티션을 저장한다. (&lt;b&gt;디스크 용량을 기준으로 하지 않고, 파티션 개수 기준으로 균등 분산&lt;/b&gt;)&lt;br /&gt;&lt;br /&gt;&amp;bull; log.dir &amp;rarr; 단일 디렉터리 경로만 지정할 때 사용 (예전 설정 방식)&lt;br /&gt;&amp;bull; log.dirs &amp;rarr; 여러 디렉터리를 지정할 때 사용 (콤마로 구분)&lt;br /&gt;&lt;br /&gt;하나의 파티션은 여러 개의 &lt;b&gt;로그 세그먼트 파일(.log, .index, .timeindex 등)로 쪼개져서 저장&lt;/b&gt;됨.&lt;br /&gt;같은 파티션의 세그먼트들은 반드시 같은 디렉토리에 모여 저장됨.&lt;br /&gt;파티션은 리더/팔로워 동기화 단위라서, 데이터가 한 디렉토리에 모여 있어야 함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 150px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 150px;&quot;&gt;num.recovery.&lt;br /&gt;threads.per.&lt;br /&gt;data.dir&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 150px;&quot;&gt;Kafka 브로커 재시작/종료 시 로그 복구 작업에 쓰는 스레드 개수. (&lt;span&gt;&lt;b&gt;디렉터리당 병렬 스레드 개수)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;log.dirs 에 지정한 디렉터리 1개당 몇 개의 스레드를 쓸지 결정.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp;총 스레드 수 = log.dirs 개수 &amp;times; &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;nu&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;m.recovery.&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;threads&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;.per.&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;data.dir&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp;스레드가 많으면&amp;nbsp; 복구 속도&amp;uarr; (재기동 빠름)/ &lt;span style=&quot;color: #ee2323;&quot;&gt;과하면 CPU&amp;middot;디스크 경합&amp;uarr; (오히려 느려짐).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;Kafka 브로커가 재시작할 때는 각 &lt;span&gt;log.dirs&lt;/span&gt; 안의 &lt;span&gt;&lt;b&gt;모든 파티션 로그 세그먼트&lt;/b&gt;&lt;/span&gt;를 검증하고 인덱스를 재생성하기때문에&amp;nbsp;&lt;br /&gt;&lt;/span&gt;설정에 따라 몇시간 차이날수도있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 94px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 94px;&quot;&gt;auto.create.&lt;br /&gt;topics.enable&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 94px;&quot;&gt;브로커가 자동으로 토픽을 생성할 수 있는지 여부를 설정.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;true (기본값): 클라이언트(Producer, Consumer, get metadata 등)가 없는 토픽에 대해 요청을 보내면 Kafka 브로커가 자동으로 새 토픽을 생성&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt; false: 없는 토픽에 대해 요청하면 에러 반환하고, 토픽을 만들땐 명시적으로 만들어주어야 함&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 132px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 132px;&quot;&gt;auto.leader.&lt;br /&gt;rebalance.enable&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 132px;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;리더 파티션 자동 재분배&lt;/b&gt;&lt;span&gt; 여부 설정&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Kafka에서 각 &lt;span&gt;&lt;b&gt;파티션&lt;/b&gt;&lt;/span&gt;은 여러 개의 복제본(replica)을 가지며, 그 중 하나가 &lt;span&gt;&lt;b&gt;리더(leader)&lt;/b&gt;&lt;/span&gt; 역할을 하며, &lt;b&gt;리더 파티션이 특정 브로커에 몰리면&lt;/b&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;트래픽 불균형이 생기기 때문에 자동으로 리더를 다른 브로커로 옮기는 설정&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&amp;nbsp;리더 파티션&lt;/span&gt; &amp;rarr; 클라이언트(Producer/Consumer)의 모든 read/write 트래픽 처리&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;bull;&amp;nbsp;팔로워(follower) 파티션&lt;span&gt; &amp;rarr; 리더를 복제만 수행&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&amp;bull;&amp;nbsp;&lt;/span&gt;leader.imbalance.check.interval.seconds (기본 300초 = 5분) 마다 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 239px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 239px;&quot;&gt;num.partitions&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 239px;&quot;&gt;&lt;span&gt;&lt;span&gt;Kafka에서 새 토픽을 자동 생성할 때 기본으로 몇 개의 파티션을 만들지 정하는 브로커 단위 설정임 (default:1)&lt;br /&gt;토픽을 생성할 때 파티션 개수를 명시하지 않으면 이 값이 기본으로 사용됨&lt;br /&gt;&lt;br /&gt;&amp;bull; 파티션&amp;nbsp;수는&amp;nbsp;&lt;b&gt;Kafka의 병렬 처리 단위&lt;/b&gt;&lt;br /&gt;&amp;bull; 파티션이&amp;nbsp;많을수록&amp;nbsp;Consumer&amp;nbsp;Group이&amp;nbsp;더&amp;nbsp;많은&amp;nbsp;Consumer&amp;nbsp;인스턴스를&amp;nbsp;병렬로&amp;nbsp;돌릴&amp;nbsp;수&amp;nbsp;있음&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;nbsp;&amp;rarr;&amp;nbsp;처리량&amp;uarr;&lt;/span&gt;&lt;br /&gt;&amp;bull; 하지만 파티션이 많을수록 메타데이터 관리, 파일 핸들, 리더 선출 &lt;span style=&quot;color: #006dd7; text-align: start;&quot;&gt;&amp;rarr;&amp;nbsp;&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;오버헤드&lt;span style=&quot;color: #006dd7; text-align: start;&quot;&gt;&amp;uarr;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;bull; num.partitions는 운영 환경에서의 디폴트 안전값일 뿐, 실제 토픽은 필요에 맞게 직접 파티션 수를 정해야 함&lt;br /&gt;&lt;span style=&quot;color: #ee2323; text-align: start;&quot;&gt;&amp;bull; 이미 생성된 토픽의 파티션 수는 늘릴수만있고 줄일수없음. (데이터 재배치 문제 때문)&lt;br /&gt;&amp;bull;&lt;span style=&quot;text-align: start;&quot;&gt; 파티션을 늘릴때 기존 메시지는 그대로 있고, 새 메시지만 새로운 파티션에 배치되기때문에 컨슈머 처리 순서에 영향을 준다 (메세지 리벨런싱 이런거 없음), 운영에 영향을 줄수있기때문에 신중!주의!해야한다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323; text-align: start;&quot;&gt;&amp;bull;&lt;/span&gt;&lt;span style=&quot;text-align: start; color: #ee2323;&quot;&gt;&lt;span&gt; 카프카는 &amp;ldquo;키 &amp;rarr; 파티션&amp;rdquo;으로 보냄, (&lt;span style=&quot;color: #ee2323; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323; text-align: start;&quot;&gt;partition = hash(key) % (파티션 개수) )&lt;/span&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;같은 키의 메시지는 항상 같은 파티션으로 가므로, 그 파티션 안에서는 순서가 보장되는것.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;bull;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;그런데 파티션 개수가 늘리면 예전 파티션이 아닌 다른 파티션으로 가게 됨 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp; 특정 키의 메시지가 두 파티션에 흩어져 도착하고, 컨슈머 그룹은 파티션 단위로 읽으니 키 단위 순서가 깨짐&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 132px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 132px;&quot;&gt;default.&lt;br /&gt;replication.factor&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 132px;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;파티션의 복제본 수를 말한다. 복제본수와 최소 동기 replica수(&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;min.insync.replicas)를 함께 고려해야하며,&lt;br /&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;replication.factor 값은&amp;nbsp;&lt;/span&gt;min.insync.replicas보다 항상 1이상 크게 설정해야한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Replication Factor (RF)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;default.replication.factor&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;min.insync.replicas&lt;/span&gt;&lt;span&gt; 는 &lt;/span&gt;&lt;b&gt;데이터 내구성(durability)과 가용성(availability)&lt;/b&gt;&lt;span&gt; 에 직결되는 핵심 설정임&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&amp;bull; default.replication.factor&amp;nbsp;&amp;rarr;&amp;nbsp;토픽&amp;nbsp;생성&amp;nbsp;시&amp;nbsp;기본&amp;nbsp;복제본&amp;nbsp;개수&amp;nbsp;(내구성&amp;nbsp;수준)&lt;br /&gt;&amp;bull; min.insync.replicas&amp;nbsp;&amp;rarr;&amp;nbsp;acks=all&amp;nbsp;시&amp;nbsp;&amp;ldquo;성공&amp;rdquo;으로&amp;nbsp;인정할&amp;nbsp;최소&amp;nbsp;동기&amp;nbsp;replica&amp;nbsp;수&amp;nbsp;(데이터&amp;nbsp;안전성)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 36px;&quot;&gt;log.retention.ms&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 36px;&quot;&gt;&lt;span&gt;&lt;span&gt;메세지 보존 주기 설정 (default: 168)&lt;br /&gt;log.retention.ms, log.retention.minutes, log.retention.hours 옵션값이 제공되나 항상 가장 작은 단위의 설정값이 우선권을 갖기때문에 log.retention.ms을 설정하는것이 좋다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt; 시간기준 보존은 디스크에 저장된 각 로그 세그먼트 파일의 마지막 수정시간(mtime)을 기준으로 작동한다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 17px;&quot;&gt;log.retention.bytes&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 17px;&quot;&gt;&lt;span&gt;&lt;span&gt;메세지 보존 용량 설정 (default: -1)&lt;br /&gt;파티션 단위로 적용된다. (예, 8개의 파티션을 가진 토픽에 값이 1GB라면, 토픽의 최대 저장용량은 8GB임&lt;br /&gt;&amp;bull;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;만약 log.retention.ms/&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;log.retention.bytes두개 다 설정했다면 두 조건 중 하나만 성립해도 메세지 삭제됨&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 17px;&quot;&gt;log.segment.bytes&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 17px;&quot;&gt;&lt;span&gt;&lt;span&gt;단일 로그 파일의 최대 크기 (default: &lt;span style=&quot;background-color: #ffffff; color: #191924; text-align: start;&quot;&gt;1073741824 (1 gibibyte))&lt;br /&gt;&lt;br /&gt;주의할점은 위에서 말한 로그 보존설정은 로그 세그먼트(파일)에 적용되는것이지 각 로그 메세지에 적용되는것이 아니다.&lt;br /&gt;즉, log.segment.bytes에 지정된 크기에 다 다르지 않는다면 계속해서 로그 세그먼트를 닫지않고 사용한다.&lt;br /&gt;&lt;br /&gt;log.segment.bytes값이 너무 작으면, 파일을 너무 자주 닫고 새로 생성해야한다 (디스크 쓰기 효율성 감소)&lt;br /&gt;log.segment.bytes값이 너무 크면, log.retention 설정에 영향을 미친다. (예, 하루에 100M 메세지가 쓰이는 경우 1G까지 10일이 소요됨, retiontion이 1주일로 잡혀져있다면 17일분의 메세지가 저장되어있는것)&lt;br /&gt;&lt;br /&gt;로그 세그먼트의크기는 타임스탬프 기준으로 요프셋을 찾는 기능에도 영향을 미친다.&lt;br /&gt;클라이언트가 특정 타임스탬프를 기준으로 파티션의 오프셋을 요청하면 카프카는 해당 시각에쓰여진 로그 세그먼트를 찾고, 로그 세그먼트 맨 앞에 있는 오프셋이 응답으로 리턴된다.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&amp;bull; 세그먼트가&amp;nbsp;너무&amp;nbsp;크면&amp;nbsp;&amp;rarr;&amp;nbsp;하나의&amp;nbsp;세그먼트가&amp;nbsp;긴&amp;nbsp;시간(며칠,&amp;nbsp;몇&amp;nbsp;주)&amp;nbsp;동안&amp;nbsp;데이터를&amp;nbsp;담음.&lt;br /&gt;&amp;nbsp; &amp;nbsp;그러면 &amp;ldquo;특정 시각&amp;rdquo;을 찾을 때 granularity(정밀도)가 떨어짐&lt;br /&gt;&amp;nbsp; &amp;nbsp;예: 1GB 세그먼트에 10일치 메시지가 들어 있다면, &amp;ldquo;8월 5일&amp;rdquo;을 요청해도 &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Kafka는 세그먼트 맨 앞 오프셋(예: 8월 1일)을 반환할 수 있음.&lt;br /&gt;&amp;bull; 세그먼트가&amp;nbsp;작으면&amp;nbsp;&amp;rarr;&amp;nbsp;파일은&amp;nbsp;자주&amp;nbsp;생기지만,&amp;nbsp;타임스탬프&amp;nbsp;기반&amp;nbsp;오프셋&amp;nbsp;조회가&amp;nbsp;더&amp;nbsp;정밀해짐.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 9.41865%; height: 17px;&quot;&gt;log.roll.ms&lt;br /&gt;log.roll.hours&lt;/td&gt;
&lt;td style=&quot;width: 50.872%; height: 17px;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;새로운 로그 세그먼트가 롤아웃되기까지의 최대 시간 (default: null)&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;세그먼트 용량(log.segment.bytes)에 도달하지 않아도, 지정 시간이 지나면 세그먼트를 닫고 새 세그먼트를 만듦&lt;br /&gt;&lt;span&gt;&lt;span&gt;log.roll.ms(밀리초&amp;nbsp;단위)가&amp;nbsp;우선&amp;nbsp;적용됨&lt;br /&gt;retention 정책 적용을 보장하기 위해 설정하는 것이며, 값이 너무 짧으면 세그먼트가 과도하게 쪼개져 성능 저하 발생 가능.&lt;br /&gt;브로커가 해당 세그먼트를 연 시각부터 타이머가 시작되며, 대부분 브로커 시작(재기동) 시점에 여러 파티션이 동시에 새 세그먼트를 열기 때문에&amp;nbsp; 동시롤링 (파일 flush + 인덱스 파일 sync + 새 파일 오픈)에 부하가 있을 수 있음&amp;nbsp;&lt;br /&gt;짧은 시간 안에 엄청난 I/O burst 발생 &amp;rarr; 디스크 성능 스파이크&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.41865%;&quot;&gt;min.insync.replicas&lt;/td&gt;
&lt;td style=&quot;width: 50.872%;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;쓰기가&amp;nbsp;성공적으로&amp;nbsp;완료되기&amp;nbsp;위해&amp;nbsp;최소한&amp;nbsp;몇&amp;nbsp;개의&amp;nbsp;복제본이&amp;nbsp;쓰기를&amp;nbsp;확인해야&amp;nbsp;하는지&amp;nbsp;지정하는 옵션&lt;br /&gt;메시지는 모든 동기화된 복제본에 복제되고 min.insync.replicas 조건이 충족될 때까지 소비자에게 표시되지 않음&lt;br /&gt;min.insync.replicas와&amp;nbsp;acks를&amp;nbsp;함께&amp;nbsp;사용하면&amp;nbsp;더&amp;nbsp;강력한&amp;nbsp;내구성&amp;nbsp;보장을&amp;nbsp;강제할&amp;nbsp;수&amp;nbsp;있음&lt;br /&gt;&lt;br /&gt;&amp;bull; acks=all&amp;nbsp;과&amp;nbsp;min.insync.replicas=2&amp;nbsp;(RF=3일&amp;nbsp;때)&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;rarr; 메시지가 최소 2개 replica에 성공적으로 쓰여야 성공 응답&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;rarr; 브로커 1대 죽어도 데이터는 안전&lt;br /&gt;&lt;br /&gt;다만, 이 값을 늘림으로써 추가적인 오버헤드가 발생하면서 성능이 떨어질 수 있고,&lt;br /&gt;데이터 일부 유실은 가능하지만 높은 처리량이 필요하다면 기본값인 1에서 변경하지 않는 구성도 가능함&lt;br /&gt;&lt;br /&gt;*&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;acks&lt;span&gt; 는 &lt;/span&gt;프로듀서(producer)가 메시지를 보낸 후 &amp;ldquo;성공&amp;rdquo;으로 간주할 때 브로커 쪽에서 몇 개의 replica가 응답해야 하는지&lt;span&gt;를 정하는 옵션&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.41865%;&quot;&gt;message.max.bytes&lt;/td&gt;
&lt;td style=&quot;width: 50.872%;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Kafka에서 허용되는 최대 레코드 배치 크기(압축이 활성화된 경우 압축 후 크기) (default: 1048588 (1M)&lt;br /&gt;메세지 크기가 커지면 네트워크 연결과 요청을 처리하는 브로커 스레드의 요청당 작업 시간 및 &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;I/O처리량이&lt;/span&gt; 증가하기 때문에 성능에 큰 영향을 미친다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;message.max.bytes 값은 fetch.message.max.bytes(컨슈머), replica.fetch.max.byte(브로커) 값과 설정이 맞아야 한다.&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;하드웨어 선택 기준&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 디스크&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.7442%;&quot;&gt;처리량&lt;/td&gt;
&lt;td style=&quot;width: 83.2558%;&quot;&gt;브로커 디스크의 처리량은 프로듀서 클라이언트 성능에 가장 큰 영향을 미친다. (브로커 로컬 저장소에 커밋되기때문)&lt;br /&gt;즉, 디스크 쓰기속도가 빨라지면 쓰기 지연이 줄어든다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.7442%;&quot;&gt;용량&lt;/td&gt;
&lt;td style=&quot;width: 83.2558%;&quot;&gt;필요한 디스크 용량은 특정 시점에 얼마나 많은 메세지가 보존되어야 하는지에 따라 결정된다.&lt;br /&gt;예를들어 하루 1TB의 트래픽을 받고, 1주일 보존해야 한다면, 브로커는 최소 7TB가 필요하다.&lt;br /&gt;이때, 트래픽 변동 및 저장해야할 다른 파일들을 고려하여 최소 10%의 오버헤드를 고려해야한다.&lt;br /&gt;또한 복제방식에 따라서도 달라진다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 메모리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kafka 힙 메모리 (JVM Heap)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kafka 자체는 힙 메모리를 많이 쓰지 않음&lt;/li&gt;
&lt;li&gt;초당 150,000 메시지, 200MB/s 처리량을 다루는 브로커도 5GB 힙 정도면 충분&lt;/li&gt;
&lt;li&gt;따라서 브로커 JVM 옵션은 보통 4~8GB 사이에서 안정적으로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;페이지 캐시 (OS Page Cache)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핵심 성능 포인트는 JVM 힙이 아니라 운영체제 페이지 캐시&lt;/li&gt;
&lt;li&gt;컨슈머는 보통 &amp;ldquo;프로듀서가 막 쓴 메시지를 바로 읽는&amp;rdquo; 패턴 &amp;rarr; OS 페이지 캐시에 남아 있는 로그 세그먼트를 바로 읽음&lt;/li&gt;
&lt;li&gt;디스크에서 읽는 게 아니라 메모리 캐시에서 읽기 때문에 성능이 크게 향상됨&lt;/li&gt;
&lt;li&gt;즉, Kafka 성능 = 가용한 페이지 캐시 메모리 크기에 크게 좌우됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;총 시스템 메모리 배분
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VM 힙: 4~8GB (너무 크게 잡을 필요 없음)&lt;/li&gt;
&lt;li&gt;나머지 대부분: OS 페이지 캐시 용도로 확보&lt;/li&gt;
&lt;li&gt;프로덕션&amp;nbsp;최소&amp;nbsp;체감선:&amp;nbsp;RAM&amp;nbsp;16GB(아주&amp;nbsp;낮은&amp;nbsp;트래픽),&amp;nbsp;권장&amp;nbsp;32GB+&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;운영 고려사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kafka 브로커와 다른 애플리케이션을 같은 서버에서 돌리지 않는 것이 권장&lt;/li&gt;
&lt;li&gt;이유: OS 페이지 캐시 메모리를 서로 뺏어 쓰게 되면 Kafka가 캐시 효율을 잃음 &amp;rarr; 컨슈머 성능 저하&lt;/li&gt;
&lt;li&gt;따라서 전용 서버(전용 VM/노드) 위에 Kafka를 올려야 안정적인 처리량을 확보할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3. 네트워크&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 대역폭은 Kafka가 처리할 수 있는 트래픽 최대치를 결정하는 핵심 자원&lt;/li&gt;
&lt;li&gt;디스크 용량과 함께 클러스터 크기 산정의 가장 중요한 요소&lt;/li&gt;
&lt;li&gt;주요 고려사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로듀서 &amp;rarr; 브로커 유입 트래픽보다, 다수 컨슈머가 동시에 가져가는 아웃바운드 트래픽이 훨씬 클 수 있음&lt;/li&gt;
&lt;li&gt;클러스터 내부의 복제/미러링도 모두 네트워크를 소모 &amp;rarr; 네트워크 사용량은 예측보다 커질 수 있음&lt;/li&gt;
&lt;li&gt;네트워크가 포화 상태가 되면 &amp;rarr; 복제 지연 &amp;rarr; ISR(In-Sync&amp;nbsp;Replicas) 축소 &amp;rarr; 리더 선출 지연 &amp;rarr; 장애 위험으로 이어짐&lt;/li&gt;
&lt;li&gt;예 RF=3, min.insync.replicas=2 환경&lt;br /&gt;- 원래 ISR={Leader, F1, F2} &amp;rarr; 정상 &lt;span style=&quot;color: #006dd7;&quot;&gt;(ISR, Leader와 동기화된 replica 집합)&lt;/span&gt;&lt;br /&gt;- F1이 지연되면 ISR={Leader, F2} &amp;rarr; ISR 축소 발생&lt;br /&gt;- 만약 Leader까지 장애 나면 &amp;rarr; F1은 뒤처져 있어서 데이터 유실 위험&lt;br /&gt;- 더 심각하게는 ISR이 min.insync.replicas보다 작아지면 acks=all 쓰기가 실패해서, Producer가 계속 에러를 보게 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;4. CPU&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;CPU는 다른 자원보다는 성능에 영향을 덜 미친다.&lt;br /&gt;하지만, &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;메세지 체크섬 확인, 오프셋 부여 등을 위해 메세지의 압축의 해제하며, 이때 CPU가 쓰인다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;운영체제 튜닝&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가상 메모리(Virtual Memory) 튜닝&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 페이지 캐시(Page Cache) 활용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;정의&lt;/span&gt;: 리눅스 커널이 디스크 I/O를 빠르게 하기 위해 파일 데이터를 메모리에 캐싱하는 영역&lt;/li&gt;
&lt;li&gt;&lt;span&gt;운영 권장&lt;/span&gt;: Kafka 브로커 JVM 힙은 4~8GB로 작게 두고, &lt;span&gt;나머지 물리 메모리를 페이지 캐시로 최대 확보&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Dirty Page 관리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Dirty Page&lt;/span&gt;: 페이지 캐시에 있지만 아직 디스크에 flush되지 않은 데이터&lt;/li&gt;
&lt;li&gt;관련 커널 파라미터:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vm.dirty_background_ratio
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백그라운드 flush를 시작하는 비율&lt;/li&gt;
&lt;li&gt;10 미만 권장&lt;/li&gt;
&lt;li&gt;단, &lt;span&gt;0&lt;/span&gt;으로 두면 디스크에 계속 즉시 쓰려 하기 때문에 성능 스파이크 발생 가능 &amp;rarr; ❌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;vm.dirty_ratio
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 메모리 대비 허용할 최대 dirty page 비율&lt;/li&gt;
&lt;li&gt;보통 &lt;span&gt;60~80&lt;/span&gt; 권장 (너무 낮으면 flush 잦아지고, 너무 높으면 flush 몰림으로 I/O burst 발생)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Swap 설정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;권장&lt;/span&gt;: Kafka 브로커 서버는 &lt;span&gt;swap off&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;이유: 스왑 발생 시 I/O 지연이 급격히 증가하여 Kafka 지연&amp;middot;ISR 축소 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 파일 디스크립터(File Descriptor)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kafka는 파티션&amp;middot;세그먼트&amp;middot;네트워크 연결 수만큼 FD를 소모&lt;/li&gt;
&lt;li&gt;&lt;span&gt;필요량 = &lt;/span&gt;파티션 수 &amp;times; (파티션당 세그먼트 개수) + 브로커 네트워크 연결 수&lt;/li&gt;
&lt;li&gt;따라서 &lt;span&gt;ulimit -n (open files)&lt;/span&gt; 값을 충분히 크게 설정해야 함 (수십만 단위 권장)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 기타 VM 관련 커널 파라미터&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vm.max_map_count
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mmap 가능한 영역 개수 제한&lt;/li&gt;
&lt;li&gt;Kafka는 세그먼트를 파일 단위로 mmap 하므로 &lt;span&gt;매우 큰 값 권장&lt;/span&gt; (예: 1,000,000 이상)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;vm.overcommit_memory
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 오버커밋 정책&lt;/li&gt;
&lt;li&gt;&lt;span&gt;0&lt;/span&gt;(기본): Heuristic 모드, 일반적으로 안전 &amp;rarr; Kafka 운영 시 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1755613313261&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;############################################
# /etc/sysctl.conf 예시
############################################
# Dirty Page 관리
vm.dirty_background_ratio = 5      # 백그라운드 flush 시작 비율 (10 미만 권장)
vm.dirty_ratio = 70                # 최대 dirty page 비율 (60~80 권장)
# mmap 가능한 영역 수 (Kafka 세그먼트 파일이 많아질 때 필요)
vm.max_map_count = 1048576         # 1,000,000 이상 권장
# 메모리 오버커밋 정책
vm.overcommit_memory = 0           # 기본 heuristic 모드 (Kafka 운영에 안전)
# Swap 관련 (swapoff -a 로 꺼두는 것이 일반적)
vm.swappiness = 1                  # 혹시 swap을 켠 상태라면 최소화

############################################
# /etc/security/limits.conf 예시
############################################
# Kafka 브로커 유저 (예: kafka) 기준
kafka soft nofile 500000
kafka hard nofile 500000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;디스크 튜닝&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일시스템은 XFS 사용&lt;/li&gt;
&lt;li&gt;noatime 옵션 지정&lt;/li&gt;
&lt;li&gt;largeio 옵션 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1755613386181&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# /etc/fstab 예시
/dev/nvme1n1  /data/kafka  xfs  noatime,nodiratime,largeio,inode64  0 0&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;네트워크 튜닝&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 소켓 버퍼 (Socket Buffer) 설정&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755613738752&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;net.core.wmem_default = 131072     # 송신 버퍼 기본값 (128KB)
net.core.rmem_default = 131072     # 수신 버퍼 기본값 (128KB)
net.core.wmem_max = 2097152        # 송신 버퍼 최대값 (2MB)
net.core.rmem_max = 2097152        # 수신 버퍼 최대값 (2MB)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* TCP 소켓 설정&lt;/p&gt;
&lt;pre id=&quot;code_1755613759183&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 송수신 버퍼 자동 조정 범위
net.ipv4.tcp_wmem = 4096 65536 2097152   # [최소, 기본, 최대]
net.ipv4.tcp_rmem = 4096 87380 2097152   # [최소, 기본, 최대]

# TCP 윈도우 스케일 (65KB 이상의 윈도우 크기를 지원 (고대역폭 환경 필수))
net.ipv4.tcp_window_scaling = 1

# TCP SYN 백로그
# 동시에 처리 가능한 미완성 연결(half-open) 큐 크기
# Kafka 브로커에 다수 클라이언트 연결 시 1024 이상 권장
net.ipv4.tcp_max_syn_backlog = 4096

# NIC 수신 대기열 (Network Device Backlog)
# NIC 드라이버가 커널 네트워크 스택으로 패킷을 전달하기 전의 큐 크기
# Kafka는 burst 트래픽이 많으므로 기본값(1000)을 10배 이상 늘려주면 안전
net.core.netdev_max_backlog = 10000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>   학습노트/낙서장</category>
      <category>Kafka</category>
      <category>kafka compoments</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/191</guid>
      <comments>https://1mini2.tistory.com/191#entry191comment</comments>
      <pubDate>Sun, 17 Aug 2025 22:16:57 +0900</pubDate>
    </item>
    <item>
      <title>macOS에서Docker Colima 사용</title>
      <link>https://1mini2.tistory.com/190</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Desktop, orbstack은 기업환경에서 사용할 수 없어서 완전무료인 Colima를 사용하여 Docker 개발환경을 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS에서는 Docker Engine이 macOS에서 직접 동작하지 않음&lt;br /&gt;&amp;bull; Docker의 핵심인 Docker Engine (dockerd)는 리눅스 커널 기능에 의존함 (예: cgroups, namespaces 등)&lt;br /&gt;&amp;bull; macOS는 리눅스 커널이 아니기 때문에 Docker Engine을 직접 실행할 수 없음&lt;br /&gt;&amp;bull; 그래서 Linux 가상 머신(VM) 위에서 Docker Engine을 실행해야 함. 이때 Colima 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;패키지설명&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;docker&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Docker CLI 도구 (도커 명령어 사용)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;colima&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Docker Engine을 실행할 macOS용 경량 VM&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설치&lt;/h4&gt;
&lt;pre id=&quot;code_1750000418425&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install docker
brew install docker-compose
brew install docker-buildx
brew install colima&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행&lt;/h4&gt;
&lt;pre id=&quot;code_1750000493495&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 실행
colima start

# Docker가 Colima를 바라보는지 확인
docker context ls

NAME       DESCRIPTION                               DOCKER ENDPOINT                                       ERROR
colima *   colima                                    unix:///Users/{dir}/.colima/default/docker.sock
default    Current DOCKER_HOST based configuration   unix:///var/run/docker.sock

# 작동 테스트
docker run hello-world&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;동작확인&lt;/h4&gt;
&lt;pre id=&quot;code_1750001045590&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Colima 상태 확인
colima status

INFO[0000] colima is running using macOS Virtualization.Framework
INFO[0000] arch: aarch64
INFO[0000] runtime: docker
INFO[0000] mountType: sshfs
INFO[0000] socket: unix:///Users/{dir}/.colima/default/docker.sock

# Colima 목록 확인
colima list

PROFILE    STATUS     ARCH       CPUS    MEMORY    DISK      RUNTIME    ADDRESS
default    Running    aarch64    2       2GiB      100GiB    docker

# Colima 리소스 변경
colima stop
colima start --cpu 4 --memory 8 --disk 60

# Colima VM 접속
colima ssh&lt;/code&gt;&lt;/pre&gt;</description>
      <category>   학습노트/낙서장</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/190</guid>
      <comments>https://1mini2.tistory.com/190#entry190comment</comments>
      <pubDate>Mon, 16 Jun 2025 00:27:42 +0900</pubDate>
    </item>
    <item>
      <title>[keycloak 실습 #2] GoogleWorkSpace + Keycloak+ AWS IdentityCenter(SSO) 연동까지</title>
      <link>https://1mini2.tistory.com/189</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;  개요&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;⚠️ 주의: 이번 실습은 비용이 들어갑니다. (AWS/GoogleWorkSpace/Domain..)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 5.56.53.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qrBj8/btsNsTrUv0C/u7ZSp646EYActD5aquPeW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qrBj8/btsNsTrUv0C/u7ZSp646EYActD5aquPeW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qrBj8/btsNsTrUv0C/u7ZSp646EYActD5aquPeW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqrBj8%2FbtsNsTrUv0C%2Fu7ZSp646EYActD5aquPeW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1794&quot; height=&quot;462&quot; data-filename=&quot;스크린샷 2025-04-20 오후 5.56.53.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Workspace를 그룹웨어로 사용하는 환경에서는 Google 계정을 중심으로 다양한 서비스에 접근할 수 있도록 구성할 수 있다. &lt;br /&gt;이때, Google Workspace를 직접 사용하는 것도 가능하지만, 중앙 인증 및 인가 솔루션인 &lt;b&gt;Keycloak을 중간에 두고 구성&lt;/b&gt;하면 다음과 같은 장점이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;통합 사용자 및 그룹 관리&lt;/b&gt;&lt;br /&gt;&amp;bull; Google Workspace의 사용자 정보를 Role 또는 Group 단위로 다양한 서비스에 대한 접근 제어를 통합 관리할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중앙 권한 제어 및 유연한 확장성&lt;/b&gt;&lt;br /&gt;&amp;bull; &amp;ldquo;dev팀&amp;rdquo;, &amp;ldquo;infra팀&amp;rdquo;과 같이 그룹을 구성하고, 그룹별로 접근 가능한 AWS 계정이나 ArgoCD 프로젝트를 분리하여 제어 할 수 있음&lt;br /&gt;&amp;bull; 추후 SAML 또는 OIDC 기반의 외부 서비스가 추가되더라도, Keycloak을 중심으로 쉽게 통합할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유연한 Attribute 및 Claim 매핑&lt;/b&gt;&lt;br /&gt;&amp;bull; Google Workspace에서 전달되는 claim 값을 Keycloak에서 필터링하거나 변환하여 전달할 수 있음 &lt;br /&gt;&amp;nbsp; &amp;nbsp;(예: 이메일 &amp;rarr; preferred_username)&lt;br /&gt;&amp;bull; AWS Identity Center 등에서 Role Mapping 시 필요한 claim 값 (groups, roles 등)도 유연하게 조정 가능함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일관된 사용자 경험 제공&lt;/b&gt;&lt;br /&gt;&amp;bull; 사용자는 Google Workspace 계정으로 로그인만 하면 되고, Keycloak이 중계자 역할을 하여 다양한 서비스까지 자연스럽게 SSO가 연계됨&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 정리하면 Google WorkSpace에 비로 연동되지 않는 경우에도 Keycloak에 연동(SAML/OIDC/Federate)할 수 있고, Group, Role-mapping, Attribute/Claim-mapping으로 일관된 정책으로 솔루션을 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt; &amp;nbsp; GoogleWorkSpace &amp;lt;-&amp;gt; Keycloak 연동&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Keycloak 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 실습에서는 공인 도메인을 가지고 있는 Keycloak이 필요하다.&lt;br /&gt;Keycloak 구성에 대한 내용은 이전 실습에서도 많이 다루고 있으니 패스 하도록 한다.&lt;br /&gt;실습을 위한 Realm을 새로 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.35.43.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EcEJv/btsNsRVcIGq/u7tgYuittTu93T97zfYFek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EcEJv/btsNsRVcIGq/u7tgYuittTu93T97zfYFek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EcEJv/btsNsRVcIGq/u7tgYuittTu93T97zfYFek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEcEJv%2FbtsNsRVcIGq%2Fu7tgYuittTu93T97zfYFek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;393&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.35.43.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Google WorkSpace 생성 및 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google WorkSpace는 &lt;b&gt;14일&lt;/b&gt;동안 비용없이 사용해볼 수 있다.&lt;br /&gt;이 &lt;a href=&quot;https://workspace.google.com/intl/ko/lp/business/?utm_source=google&amp;amp;utm_medium=cpc&amp;amp;utm_campaign=1710070-Workspace-APAC-KR-ko-BKWS-EXA-HV-Hybrid&amp;amp;utm_content=text-ad-none-none-DEV_c-CRE_612845648308-ADGP_Hybrid+%7C+BKWS+-+EXA+%7C+Txt-Workspace-N/A-KWID_43700072289734123-kwd-1165071827472&amp;amp;userloc_9196705-network_g&amp;amp;utm_term=KW_google%20workspace&amp;amp;gad_source=1&amp;amp;gclid=EAIaIQobChMI4uzFjqLmjAMVTwZ7Bx0fVDvhEAAYASAAEgJb1_D_BwE&amp;amp;gclsrc=aw.ds&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;에서 Business Starter (사용자당 $7.56) 로 테스트 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정을 생성했다면 &lt;a href=&quot;https://console.cloud.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Google Cloud&lt;/a&gt; 에서 새로운 프로젝트를 생성해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.16.25.png&quot; data-origin-width=&quot;2842&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TWnn4/btsNsOqPT3O/tGpylNv9oyuDAATuSZb7hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TWnn4/btsNsOqPT3O/tGpylNv9oyuDAATuSZb7hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TWnn4/btsNsOqPT3O/tGpylNv9oyuDAATuSZb7hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTWnn4%2FbtsNsOqPT3O%2FtGpylNv9oyuDAATuSZb7hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;205&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.16.25.png&quot; data-origin-width=&quot;2842&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.22.05.png&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd8eAY/btsNsc65RNK/RJPJ0i4BHK3N8plBHztIUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd8eAY/btsNsc65RNK/RJPJ0i4BHK3N8plBHztIUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd8eAY/btsNsc65RNK/RJPJ0i4BHK3N8plBHztIUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd8eAY%2FbtsNsc65RNK%2FRJPJ0i4BHK3N8plBHztIUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.22.05.png&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 프로젝트를 생성했다면 Google 인증 플랫폼(&lt;a href=&quot;https://console.cloud.google.com/auth/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://console.cloud.google.com/auth/overview&lt;/a&gt;)을 구성 해야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.27.07.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq5gTw/btsNr5FRCiL/Tlby7W6Ndwc09d9Eq46wYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq5gTw/btsNr5FRCiL/Tlby7W6Ndwc09d9Eq46wYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq5gTw/btsNr5FRCiL/Tlby7W6Ndwc09d9Eq46wYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq5gTw%2FbtsNr5FRCiL%2FTlby7W6Ndwc09d9Eq46wYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;291&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.27.07.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 OAuth Client를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.27.35.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgrRNL/btsNtfVQ3th/QuQJs5rqRnfTnFbH5l3Zx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgrRNL/btsNtfVQ3th/QuQJs5rqRnfTnFbH5l3Zx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgrRNL/btsNtfVQ3th/QuQJs5rqRnfTnFbH5l3Zx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgrRNL%2FbtsNtfVQ3th%2FQuQJs5rqRnfTnFbH5l3Zx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;232&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.27.35.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;승인된 리디렉션 URL에 &lt;span style=&quot;color: #006dd7;&quot;&gt;https://${Keycloak도메인}/realms/${Realm이름}/broker/google/endpoint&lt;/span&gt; 을 작성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.30.36.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;2004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSZjmQ/btsNtjqa2vM/KgXIRCZKTfUzkkbAIMaOU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSZjmQ/btsNtjqa2vM/KgXIRCZKTfUzkkbAIMaOU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSZjmQ/btsNtjqa2vM/KgXIRCZKTfUzkkbAIMaOU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSZjmQ%2FbtsNtjqa2vM%2FKgXIRCZKTfUzkkbAIMaOU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;487&quot; data-filename=&quot;스크린샷 2025-04-20 오후 6.30.36.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;2004&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성이 완료 후 Client ID &amp;amp; Secret 을 복사한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 7.58.18.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XntB5/btsNptgRYCN/CIdAM5aoEjohi0gHNBMgD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XntB5/btsNptgRYCN/CIdAM5aoEjohi0gHNBMgD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XntB5/btsNptgRYCN/CIdAM5aoEjohi0gHNBMgD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXntB5%2FbtsNptgRYCN%2FCIdAM5aoEjohi0gHNBMgD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;428&quot; data-filename=&quot;스크린샷 2025-04-20 오후 7.58.18.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. Keycloak + Google Identity Provider 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Keycloak으로 이동해서 IdentityProvider에 Google을 등록한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 7.59.25.png&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRdsVn/btsNtdX3MhT/uarOHVk5GZcLb9FX0r8qQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRdsVn/btsNtdX3MhT/uarOHVk5GZcLb9FX0r8qQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRdsVn/btsNtdX3MhT/uarOHVk5GZcLb9FX0r8qQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRdsVn%2FbtsNtdX3MhT%2FuarOHVk5GZcLb9FX0r8qQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;363&quot; data-filename=&quot;스크린샷 2025-04-20 오후 7.59.25.png&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2 단계에서 생성한 Client의 ID/Secret을 입력한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.00.55.png&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dN2wsa/btsNrvkw5jw/PWBUjoqcEHnkkExCrqKsr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dN2wsa/btsNrvkw5jw/PWBUjoqcEHnkkExCrqKsr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dN2wsa/btsNrvkw5jw/PWBUjoqcEHnkkExCrqKsr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdN2wsa%2FbtsNrvkw5jw%2FPWBUjoqcEHnkkExCrqKsr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;436&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.00.55.png&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4. Keycloak 에 Google User로 로그인 해보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 연동이 되었으니, Google 사용자로 로그인 해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://keycloak.alpha.grepp.co/realms/1mini2-realm/account&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://${&lt;span style=&quot;color: #006dd7; text-align: start;&quot;&gt;${Keycloak도메인}&lt;/span&gt;}/realms/&lt;span style=&quot;color: #006dd7; text-align: start;&quot;&gt;${Realm이름}&lt;/span&gt;/account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.12.25.png&quot; data-origin-width=&quot;2630&quot; data-origin-height=&quot;1496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQsJjn/btsNsjkCMAR/3C6DK7mhki7LTskZPhjQ60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQsJjn/btsNsjkCMAR/3C6DK7mhki7LTskZPhjQ60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQsJjn/btsNsjkCMAR/3C6DK7mhki7LTskZPhjQ60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQsJjn%2FbtsNsjkCMAR%2F3C6DK7mhki7LTskZPhjQ60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;398&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.12.25.png&quot; data-origin-width=&quot;2630&quot; data-origin-height=&quot;1496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.15.43.png&quot; data-origin-width=&quot;2998&quot; data-origin-height=&quot;1128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLtFIS/btsNsMs3935/KrP4rtL83GI9K8UsKmKN91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLtFIS/btsNsMs3935/KrP4rtL83GI9K8UsKmKN91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLtFIS/btsNsMs3935/KrP4rtL83GI9K8UsKmKN91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLtFIS%2FbtsNsMs3935%2FKrP4rtL83GI9K8UsKmKN91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;263&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.15.43.png&quot; data-origin-width=&quot;2998&quot; data-origin-height=&quot;1128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 로그인이 성공했다면 Keycloak Realm으로 가서 사용자 목록을 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.14.57.png&quot; data-origin-width=&quot;2998&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rko4k/btsNsRAVZrE/NIsor8qYZAKIQA5K0OHWYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rko4k/btsNsRAVZrE/NIsor8qYZAKIQA5K0OHWYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rko4k/btsNsRAVZrE/NIsor8qYZAKIQA5K0OHWYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frko4k%2FbtsNsRAVZrE%2FNIsor8qYZAKIQA5K0OHWYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;226&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.14.57.png&quot; data-origin-width=&quot;2998&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 로그인 한 사용자가 생성되어있다. (사용자는 첫 로그인 후 생성된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;  Keycloak &amp;lt;-&amp;gt; AWS IdentityCenter(AWS SSO) SAML 연동&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 단계에서 Google WorkSpace와 Keycloak을 연동했고,&lt;br /&gt;Keycloak에 google Workspace 계정으로 로그인까지 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 AWS IdentitiyCenter와 Keycloak연동을 통해 &lt;br /&gt;결과적으로는 Google WorkSpace계정으로 AWS 까지 사용할 수 있도록 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1. AWS 계정 구성 (&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/controltower/latest/userguide/what-is-control-tower.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ControlTower&lt;/a&gt;+&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/singlesignon/latest/userguide/what-is.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IdentityCenter&lt;/a&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;⚠️ 주의&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;로그인 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://us-east-1.console.aws.amazon.com/iam/&quot;&gt;https://us-east-1.console.aws.amazon.com/iam/&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에서 루트사용자 MFA를 등록하기를 강력 권장함&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위한 AWS계정을 생성한다.&lt;br /&gt;이때, Organization, ControlTower, IdentityCenter를 모두 활성화한 구성으로 테스트를 진행한다. (&lt;a href=&quot;https://signin.aws.amazon.com/signup?request_type=register&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS 계정생성 링크)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.38.19.png&quot; data-origin-width=&quot;2804&quot; data-origin-height=&quot;1348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwBORY/btsNrxDWp7g/jRb0nOvkluV5hHEHMtp0xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwBORY/btsNrxDWp7g/jRb0nOvkluV5hHEHMtp0xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwBORY/btsNrxDWp7g/jRb0nOvkluV5hHEHMtp0xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwBORY%2FbtsNrxDWp7g%2FjRb0nOvkluV5hHEHMtp0xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;337&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.38.19.png&quot; data-origin-width=&quot;2804&quot; data-origin-height=&quot;1348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;a href=&quot;https://ap-northeast-2.console.aws.amazon.com/controltower/home/landing?region=ap-northeast-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;control tower 서비스&lt;/a&gt;에서 랜딩존을 활성화 한다. &lt;br /&gt;(랜딩존은 편한방식으로 생성하면 된다. 이 실습에서는 IdentityCenter의 기능만 사용한다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.45.30.png&quot; data-origin-width=&quot;3176&quot; data-origin-height=&quot;1220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaIgAH/btsNthlDxrR/tY5OtQzFkSQxyIdysEx0pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaIgAH/btsNthlDxrR/tY5OtQzFkSQxyIdysEx0pK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaIgAH/btsNthlDxrR/tY5OtQzFkSQxyIdysEx0pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaIgAH%2FbtsNthlDxrR%2FtY5OtQzFkSQxyIdysEx0pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;269&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.45.30.png&quot; data-origin-width=&quot;3176&quot; data-origin-height=&quot;1220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.56.39.png&quot; data-origin-width=&quot;3176&quot; data-origin-height=&quot;2252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TR0Hk/btsNsPJ66QN/pOW5sQF5kB20tcDJ51U7m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TR0Hk/btsNsPJ66QN/pOW5sQF5kB20tcDJ51U7m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TR0Hk/btsNsPJ66QN/pOW5sQF5kB20tcDJ51U7m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTR0Hk%2FbtsNsPJ66QN%2FpOW5sQF5kB20tcDJ51U7m0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;496&quot; data-filename=&quot;스크린샷 2025-04-20 오후 8.56.39.png&quot; data-origin-width=&quot;3176&quot; data-origin-height=&quot;2252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.15.55.png&quot; data-origin-width=&quot;3176&quot; data-origin-height=&quot;2172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zYmKw/btsNsi67jzm/MCHgm5bgGp7zOC9vD9Mq91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zYmKw/btsNsi67jzm/MCHgm5bgGp7zOC9vD9Mq91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zYmKw/btsNsi67jzm/MCHgm5bgGp7zOC9vD9Mq91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzYmKw%2FbtsNsi67jzm%2FMCHgm5bgGp7zOC9vD9Mq91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;479&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.15.55.png&quot; data-origin-width=&quot;3176&quot; data-origin-height=&quot;2172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Control Tower가 활성화 되었다면, &lt;a href=&quot;https://ap-northeast-2.console.aws.amazon.com/singlesignon/home?region=ap-northeast-2#&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IdentityCenter 서비스&lt;/a&gt;로 이동하고,&lt;br /&gt;설정 &amp;gt; 작업증명 소스 탭에서 자격증명 소스 변경을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.16.37.png&quot; data-origin-width=&quot;3176&quot; data-origin-height=&quot;2172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tel5o/btsNsN6Jglo/8rCWRYmE6Y6brcQS5pnz00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tel5o/btsNsN6Jglo/8rCWRYmE6Y6brcQS5pnz00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tel5o/btsNsN6Jglo/8rCWRYmE6Y6brcQS5pnz00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTel5o%2FbtsNsN6Jglo%2F8rCWRYmE6Y6brcQS5pnz00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;479&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.16.37.png&quot; data-origin-width=&quot;3176&quot; data-origin-height=&quot;2172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 외부자격증명 공급자를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.18.24.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be9VLB/btsNslCLRWV/lPmJcyQAmC2sJ4eA6q4k4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be9VLB/btsNslCLRWV/lPmJcyQAmC2sJ4eA6q4k4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be9VLB/btsNslCLRWV/lPmJcyQAmC2sJ4eA6q4k4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe9VLB%2FbtsNslCLRWV%2FlPmJcyQAmC2sJ4eA6q4k4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;219&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.18.24.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타데이터 파일을 다운로드 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.19.10.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byV8nF/btsNtiEQ0r6/m9apN0SF1YhCSUe5nvcAL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byV8nF/btsNtiEQ0r6/m9apN0SF1YhCSUe5nvcAL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byV8nF/btsNtiEQ0r6/m9apN0SF1YhCSUe5nvcAL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyV8nF%2FbtsNtiEQ0r6%2Fm9apN0SF1YhCSUe5nvcAL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;447&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.19.10.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. KeyCloak AWS Client 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 KeyCloak으로 가서 AWS Client를 생성하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.24.09.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBSwaB/btsNsNS68yC/w1o9kkC5rWnbyaI5WT751K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBSwaB/btsNsNS68yC/w1o9kkC5rWnbyaI5WT751K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBSwaB/btsNsNS68yC/w1o9kkC5rWnbyaI5WT751K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBSwaB%2FbtsNsNS68yC%2Fw1o9kkC5rWnbyaI5WT751K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.24.09.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import하면 기본적인 부분은 자동으로 설정되며, Name작성 및&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Always display in UI 부분만 On으로 변경하고 Save한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.25.47.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cR6vT9/btsNrxjG5XX/QEiLDqIOGr4tSWRVTe753K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cR6vT9/btsNrxjG5XX/QEiLDqIOGr4tSWRVTe753K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cR6vT9/btsNrxjG5XX/QEiLDqIOGr4tSWRVTe753K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcR6vT9%2FbtsNrxjG5XX%2FQEiLDqIOGr4tSWRVTe753K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;412&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.25.47.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Home URL에 &lt;span style=&quot;color: #006dd7;&quot;&gt;/realms/${Realm이름}/protocol/saml/clients/&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;${Client이름}&lt;/span&gt; 만 추가한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.28.41.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EKx0D/btsNsbHaf4A/HW3PzMgmsa4zkWx2TodQX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EKx0D/btsNsbHaf4A/HW3PzMgmsa4zkWx2TodQX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EKx0D/btsNsbHaf4A/HW3PzMgmsa4zkWx2TodQX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEKx0D%2FbtsNsbHaf4A%2FHW3PzMgmsa4zkWx2TodQX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;459&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.28.41.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2090&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. KeyCloak SAML파일 다운로드 및 AWS IdentityCenter 설정 완료하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Realms Settings로 가서 SAML&amp;nbsp;2.0&amp;nbsp;Identity&amp;nbsp;Provider&amp;nbsp;Metadata를 다운받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.31.26.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdiwhU/btsNtggeYbr/0okMk7yF2RcvDBNHwAtkGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdiwhU/btsNtggeYbr/0okMk7yF2RcvDBNHwAtkGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdiwhU/btsNtggeYbr/0okMk7yF2RcvDBNHwAtkGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdiwhU%2FbtsNtggeYbr%2F0okMk7yF2RcvDBNHwAtkGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;459&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.31.26.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2090&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 1번에서 설정중이었던 AWS콘솔로 들어가서 방금 다운받은 SAML 2.0 Identity Provider Metadata 파일을 업로드 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.33.12.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpSe6L/btsNrc6ABxP/JkZNePxu5SKgfZaxi2SseK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpSe6L/btsNrc6ABxP/JkZNePxu5SKgfZaxi2SseK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpSe6L/btsNrc6ABxP/JkZNePxu5SKgfZaxi2SseK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpSe6L%2FbtsNrc6ABxP%2FJkZNePxu5SKgfZaxi2SseK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;459&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.33.12.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2090&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.34.40.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k4jPp/btsNr41h1Tf/NkgsrDYLSFSeNBETa0mF21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k4jPp/btsNr41h1Tf/NkgsrDYLSFSeNBETa0mF21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k4jPp/btsNr41h1Tf/NkgsrDYLSFSeNBETa0mF21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk4jPp%2FbtsNr41h1Tf%2FNkgsrDYLSFSeNBETa0mF21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;507&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.34.40.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 자격증명 소스가 외부Idp(Keycloak)으로 설정되었다.&lt;br /&gt;사용자 및 그룹 설정을 Keycloak과 연동하기 위해서는 SCIM설정이 필요하다.&amp;nbsp;&lt;br /&gt;따라서 아래 자동프로비저닝 부분에서 활성화를 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.35.22.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r9jIe/btsNtsN1p20/k7EFbx96ECXk5p9Ckn0pkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r9jIe/btsNtsN1p20/k7EFbx96ECXk5p9Ckn0pkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r9jIe/btsNtsN1p20/k7EFbx96ECXk5p9Ckn0pkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr9jIe%2FbtsNtsN1p20%2Fk7EFbx96ECXk5p9Ckn0pkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;337&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.35.22.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동프로비저닝을 활성화 하면 아래처럼 SCIM 엔드포인트와 토큰이 나오는데,&lt;br /&gt;혹시모르니 따로 저장해둔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.36.09.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cE1LDt/btsNslCMnYM/LLYEeKPvqQiVivDgqPGXN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cE1LDt/btsNslCMnYM/LLYEeKPvqQiVivDgqPGXN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cE1LDt/btsNslCMnYM/LLYEeKPvqQiVivDgqPGXN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcE1LDt%2FbtsNslCMnYM%2FLLYEeKPvqQiVivDgqPGXN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;353&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.36.09.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 AWS IdentityCenter 로그인 페이지에 접속해보면, google 로그인이 보일것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.43.57.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biQZWG/btsNtsgbP1O/viGqOXlvrufjhAyYfUbd80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biQZWG/btsNtsgbP1O/viGqOXlvrufjhAyYfUbd80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biQZWG/btsNtsgbP1O/viGqOXlvrufjhAyYfUbd80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiQZWG%2FbtsNtsgbP1O%2FviGqOXlvrufjhAyYfUbd80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;300&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.43.57.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.45.51.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdAqdD/btsNskKCRVN/esKIb08ljfdsHsmd7KAru0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdAqdD/btsNskKCRVN/esKIb08ljfdsHsmd7KAru0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdAqdD/btsNskKCRVN/esKIb08ljfdsHsmd7KAru0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdAqdD%2FbtsNskKCRVN%2FesKIb08ljfdsHsmd7KAru0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.45.51.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문제는 Google계정으로 로그인 후 IdentityCenter에 접근하지 못한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.46.08.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmNHhh/btsNqrXdNvM/NHes3i5sjeyzpA1VO3rsqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmNHhh/btsNqrXdNvM/NHes3i5sjeyzpA1VO3rsqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmNHhh/btsNqrXdNvM/NHes3i5sjeyzpA1VO3rsqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmNHhh%2FbtsNqrXdNvM%2FNHes3i5sjeyzpA1VO3rsqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.46.08.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이유는 AWS IdentityCenter에 사용자가 생성되지않았기 때문인데, 아래 &lt;a href=&quot;https://1mini2.tistory.com/189#[%EC%95%8C%EA%B3%A0%EA%B0%88%EA%B2%83]_SCIM%EC%9D%B4_%EB%AD%94%EC%A7%80?&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[알고갈것]SCIM이 뭔지?&lt;/a&gt; 에서 내용을 다룬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.47.47.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1068&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PlmyB/btsNttGemFN/zh8Hj1OlDjkHb21YrY5Bfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PlmyB/btsNttGemFN/zh8Hj1OlDjkHb21YrY5Bfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PlmyB/btsNttGemFN/zh8Hj1OlDjkHb21YrY5Bfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPlmyB%2FbtsNttGemFN%2Fzh8Hj1OlDjkHb21YrY5Bfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;234&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.47.47.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1068&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 이 동기화 문제를 해결하기 위해 아래 SCIM 설정을 진행해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;  Keycloak &amp;lt;-&amp;gt; AWS IdentityCenter(AWS SSO) SCIM 설정&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Keycloak과 AWS IdentityCenter사이에 SAML설정 후에도 사용자/그룹이 동기화 되지 않는 이슈가 있다.&lt;br /&gt;일단 이번에는 스크립트를 통해 일단 원리를 이해하고,&lt;br /&gt;나중에는 cron등 등록해서 자동으로 프로비저닝 하게 해주어야 한다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1. 임시로 사용할 AWS AccessKey 생성 및 IdentityCenter ID 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트만 사용하고 제거할 IAM 사용자를 만든다. (&lt;a href=&quot;https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-2#/users&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 10.02.01.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwYwOS/btsNtrVTyy8/HIxShFs4fUMposzCZDlBp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwYwOS/btsNtrVTyy8/HIxShFs4fUMposzCZDlBp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwYwOS/btsNtrVTyy8/HIxShFs4fUMposzCZDlBp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwYwOS%2FbtsNtrVTyy8%2FHIxShFs4fUMposzCZDlBp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;443&quot; data-filename=&quot;스크린샷 2025-04-20 오후 10.02.01.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2020&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1745154184334&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;AllowIdentityStore&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;identitystore:*&quot;
            ],
            &quot;Resource&quot;: [
                &quot;*&quot;
            ]
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한 설정 후 AccessKey를 생성하고 AccessKey,SecretKey를 복사해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IdentityCenterID도 스크립트에서 쓰이니 복사해두자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.19.43.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m4vS0/btsNr7RdHnM/JYyVn3avFtrPAXkCvHFcmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m4vS0/btsNr7RdHnM/JYyVn3avFtrPAXkCvHFcmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m4vS0/btsNr7RdHnM/JYyVn3avFtrPAXkCvHFcmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm4vS0%2FbtsNr7RdHnM%2FJYyVn3avFtrPAXkCvHFcmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;474&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.19.43.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. Keycloak admin-cli 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SCIM 스크립트는 Keycloak API 접근용 클라이언트가 필요하다. &lt;br /&gt;AWS Identity Center 연결을 위한 SAML 클라이언트와는 별도로 관리 API 접근용 클라이언트(일반적으로 admin-cli)를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Client목록에서 admin-cli를 클릭하고, 아래와 같이 설정을 수정한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 10.14.53.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YzgjH/btsNtcEUTWr/sJ13oFgFEWoX0Um8KZKsvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YzgjH/btsNtcEUTWr/sJ13oFgFEWoX0Um8KZKsvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YzgjH/btsNtcEUTWr/sJ13oFgFEWoX0Um8KZKsvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYzgjH%2FbtsNtcEUTWr%2FsJ13oFgFEWoX0Um8KZKsvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;541&quot; data-filename=&quot;스크린샷 2025-04-20 오후 10.14.53.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;2464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 해당 Client에서 유저와 그룹을 쿼리할 수 있도록 권한을 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 10.39.35.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjG1xw/btsNpUL6OoP/WWnLxi8vCJgtKwsOgf5320/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjG1xw/btsNpUL6OoP/WWnLxi8vCJgtKwsOgf5320/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjG1xw/btsNpUL6OoP/WWnLxi8vCJgtKwsOgf5320/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjG1xw%2FbtsNpUL6OoP%2FWWnLxi8vCJgtKwsOgf5320%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;289&quot; data-filename=&quot;스크린샷 2025-04-20 오후 10.39.35.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 이 클라이언트의 Credentials을 복사한다. 스크립트에 사용할 예정이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 10.24.13.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMjCX8/btsNsa2B7ox/NYx4T0hK4tIdWYgmhUCDI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMjCX8/btsNsa2B7ox/NYx4T0hK4tIdWYgmhUCDI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMjCX8/btsNsa2B7ox/NYx4T0hK4tIdWYgmhUCDI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMjCX8%2FbtsNsa2B7ox%2FNYx4T0hK4tIdWYgmhUCDI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;251&quot; data-filename=&quot;스크린샷 2025-04-20 오후 10.24.13.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. SCIM 스크립트 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;스크립트가 너무 길어서 아래 접근글로 넣어놨다.&lt;br /&gt;로컬에서 python scim.py 실행하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정할 부분은 변수 부분이다.&lt;/p&gt;
&lt;pre id=&quot;code_1745158742882&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Keycloak Settings
KEYCLOAK_URL = &quot;your-keycloak-url&quot;
KEYCLOAK_REALM = &quot;your-realm&quot;
KEYCLOAK_CLIENT_ID = &quot;admin-cli&quot;
KEYCLOAK_CLIENT_SECRET_KEY = &quot;your-secret-key&quot;

# AWS Settings
AWS_IDENTITY_STORE_ID = &quot;your-identity-store-id&quot;
AWS_ACCESS_KEY = &quot;your-access-key&quot;
AWS_SECRET_KEY = &quot;your-secret-key&quot;
AWS_REGION = &quot;your-region&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 아래 &lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;열어서 스크립트 확인&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SCIM 스크립트&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745158614609&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from keycloak import KeycloakAdmin, KeycloakOpenIDConnection
import boto3
import logging
import traceback
from datetime import datetime

# Keycloak Settings
KEYCLOAK_URL = &quot;your-keycloak-url&quot;
KEYCLOAK_REALM = &quot;your-realm&quot;
KEYCLOAK_CLIENT_ID = &quot;admin-cli&quot;
KEYCLOAK_CLIENT_SECRET_KEY = &quot;your-secret-key&quot;

# AWS Settings
AWS_IDENTITY_STORE_ID = &quot;your-identity-store-id&quot;
AWS_ACCESS_KEY = &quot;your-access-key&quot;
AWS_SECRET_KEY = &quot;your-secret-key&quot;
AWS_REGION = &quot;your-region&quot;

class Keycloak:
    def __init__(self):
        connection = KeycloakOpenIDConnection(
            server_url=KEYCLOAK_URL,
            realm_name=KEYCLOAK_REALM,
            client_id=KEYCLOAK_CLIENT_ID,
            client_secret_key=KEYCLOAK_CLIENT_SECRET_KEY,
            verify=True
        )
        self.admin_client = KeycloakAdmin(
            server_url=KEYCLOAK_URL,
            realm_name=KEYCLOAK_REALM,
            client_id=KEYCLOAK_CLIENT_ID,
            client_secret_key=KEYCLOAK_CLIENT_SECRET_KEY,
            connection=connection
        )
        self.logger = logging.getLogger(__name__)

    def list_users(self):
        try:
            return self.admin_client.get_users({})
        except Exception as e:
            self.logger.error(f&quot;Keycloak list_users error: {e}&quot;)
            return []

    def list_groups(self):
        try:
            return self.admin_client.get_groups({})
        except Exception as e:
            self.logger.error(f&quot;Keycloak list_groups error: {e}&quot;)
            return []

    def get_group_members(self, group_id):
        try:
            return self.admin_client.get_group_members(group_id)
        except Exception as e:
            self.logger.error(f&quot;Keycloak get_group_members error: {e}&quot;)
            return []

class KeycloakAWSSync:
    def __init__(self, dry_run=False):
        self.setup_logging()
        self.logger.info(&quot;Initializing sync components&quot;)

        self.dry_run = dry_run
        self.keycloak = Keycloak()
        self.aws_client = boto3.client(
            'identitystore',
            aws_access_key_id=AWS_ACCESS_KEY,
            aws_secret_access_key=AWS_SECRET_KEY,
            region_name=AWS_REGION
        )
        self.identity_store_id = AWS_IDENTITY_STORE_ID

    def setup_logging(self):
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[logging.StreamHandler(), logging.FileHandler(&quot;sync.log&quot;)]
        )
        self.logger = logging.getLogger(__name__)

    def sync_users(self):
        users = self.keycloak.list_users()
        existing_users = self.aws_client.list_users(IdentityStoreId=self.identity_store_id)[&quot;Users&quot;]

        aws_usernames = {u[&quot;UserName&quot;]: u for u in existing_users if u.get(&quot;UserType&quot;) == &quot;keycloak&quot;}
        keycloak_usernames = {u[&quot;username&quot;]: u for u in users}

        for username, user in keycloak_usernames.items():
            first = user.get(&quot;firstName&quot;, &quot;&quot;)
            last = user.get(&quot;lastName&quot;, &quot;&quot;)
            display = f&quot;{first} {last}&quot;
            if username in aws_usernames:
                self.logger.info(f&quot;Updating user: {username}&quot;)
                if not self.dry_run:
                    self.aws_client.update_user(
                        IdentityStoreId=self.identity_store_id,
                        UserId=aws_usernames[username][&quot;UserId&quot;],
                        Operations=[
                            {&quot;AttributePath&quot;: &quot;userName&quot;, &quot;AttributeValue&quot;: username},
                            {&quot;AttributePath&quot;: &quot;name.givenName&quot;, &quot;AttributeValue&quot;: first},
                            {&quot;AttributePath&quot;: &quot;name.familyName&quot;, &quot;AttributeValue&quot;: last},
                            {&quot;AttributePath&quot;: &quot;displayName&quot;, &quot;AttributeValue&quot;: display}
                        ]
                    )
                else:
                    self.logger.info(f&quot;[DRY-RUN] Would update user: {username}&quot;)
            else:
                self.logger.info(f&quot;Creating user: {username}&quot;)
                if not self.dry_run:
                    self.aws_client.create_user(
                        IdentityStoreId=self.identity_store_id,
                        UserName=username,
                        Name={&quot;GivenName&quot;: first, &quot;FamilyName&quot;: last},
                        DisplayName=display,
                        UserType=&quot;keycloak&quot;
                    )
                else:
                    self.logger.info(f&quot;[DRY-RUN] Would create user: {username}&quot;)

        for username, user in aws_usernames.items():
            if username not in keycloak_usernames:
                self.logger.info(f&quot;Deleting user: {username}&quot;)
                if not self.dry_run:
                    self.aws_client.delete_user(
                        IdentityStoreId=self.identity_store_id,
                        UserId=user[&quot;UserId&quot;]
                    )
                else:
                    self.logger.info(f&quot;[DRY-RUN] Would delete user: {username}&quot;)

    def sync_groups(self):
        groups = self.keycloak.list_groups()
        existing_groups = self.aws_client.list_groups(IdentityStoreId=self.identity_store_id)[&quot;Groups&quot;]

        aws_groups = {g[&quot;DisplayName&quot;].strip().lower(): g for g in existing_groups if g[&quot;DisplayName&quot;].startswith(&quot;keycloak-&quot;)}
        keycloak_groupnames = {f&quot;keycloak-{g['name']}&quot;: g for g in groups}
        keycloak_keys = set(k.strip().lower() for k in keycloak_groupnames.keys())

        for display, group in keycloak_groupnames.items():
            norm_display = display.strip().lower()
            if norm_display in aws_groups:
                self.logger.info(f&quot;Updating group: {display}&quot;)
                if not self.dry_run:
                    self.aws_client.update_group(
                        IdentityStoreId=self.identity_store_id,
                        GroupId=aws_groups[norm_display][&quot;GroupId&quot;],
                        Operations=[
                            {&quot;AttributePath&quot;: &quot;displayName&quot;, &quot;AttributeValue&quot;: display},
                            {&quot;AttributePath&quot;: &quot;description&quot;, &quot;AttributeValue&quot;: f&quot;Updated at {datetime.now().isoformat()}&quot;}
                        ]
                    )
                else:
                    self.logger.info(f&quot;[DRY-RUN] Would update group: {display}&quot;)
            else:
                self.logger.info(f&quot;Creating group: {display}&quot;)
                if not self.dry_run:
                    self.aws_client.create_group(
                        IdentityStoreId=self.identity_store_id,
                        DisplayName=display,
                        Description=f&quot;Keycloak synced group. Created at {datetime.now().isoformat()}&quot;
                    )
                else:
                    self.logger.info(f&quot;[DRY-RUN] Would create group: {display}&quot;)

        for display, group in aws_groups.items():
            if display not in keycloak_keys:
                self.logger.info(f&quot;Deleting group not found in Keycloak: {display}&quot;)
                if not self.dry_run:
                    self.aws_client.delete_group(
                        IdentityStoreId=self.identity_store_id,
                        GroupId=group[&quot;GroupId&quot;]
                    )
                else:
                    self.logger.info(f&quot;[DRY-RUN] Would delete group: {display}&quot;)

    def sync_group_memberships(self):
        groups = self.keycloak.list_groups()
        for group in groups:
            group_name = group[&quot;name&quot;]
            group_id = group[&quot;id&quot;]
            prefixed_name = f&quot;keycloak-{group_name}&quot;
            try:
                group_resp = self.aws_client.get_group_id(
                    IdentityStoreId=self.identity_store_id,
                    AlternateIdentifier={
                        'UniqueAttribute': {
                            'AttributePath': 'displayName',
                            'AttributeValue': prefixed_name
                        }
                    }
                )
                aws_group_id = group_resp[&quot;GroupId&quot;]
            except Exception:
                self.logger.warning(f&quot;Group {prefixed_name} not found in AWS&quot;)
                continue

            # Keycloak members
            kc_members = self.keycloak.get_group_members(group_id)
            kc_usernames = {m[&quot;username&quot;] for m in kc_members}

            # AWS members
            aws_members = self.aws_client.list_group_memberships(
                IdentityStoreId=self.identity_store_id,
                GroupId=aws_group_id
            ).get(&quot;GroupMemberships&quot;, [])

            aws_user_map = {}
            for mem in aws_members:
                mem_id = mem[&quot;MemberId&quot;].get(&quot;UserId&quot;)
                if mem_id:
                    user_detail = self.aws_client.describe_user(
                        IdentityStoreId=self.identity_store_id,
                        UserId=mem_id
                    )
                    aws_user_map[user_detail[&quot;UserName&quot;]] = mem[&quot;MembershipId&quot;]

            # Add missing users
            for username in kc_usernames:
                if username not in aws_user_map:
                    self.logger.info(f&quot;Adding user {username} to group {prefixed_name}&quot;)
                    if not self.dry_run:
                        try:
                            user_resp = self.aws_client.get_user_id(
                                IdentityStoreId=self.identity_store_id,
                                AlternateIdentifier={
                                    'UniqueAttribute': {
                                        'AttributePath': 'userName',
                                        'AttributeValue': username
                                    }
                                }
                            )
                            aws_user_id = user_resp[&quot;UserId&quot;]
                            self.aws_client.create_group_membership(
                                IdentityStoreId=self.identity_store_id,
                                GroupId=aws_group_id,
                                MemberId={'UserId': aws_user_id}
                            )
                        except Exception as e:
                            self.logger.warning(f&quot;Failed to add {username} to group {prefixed_name}: {e}&quot;)
                    else:
                        self.logger.info(f&quot;[DRY-RUN] Would add user {username} to group {prefixed_name}&quot;)

            # Remove stale users
            for username, membership_id in aws_user_map.items():
                if username not in kc_usernames:
                    self.logger.info(f&quot;Removing user {username} from group {prefixed_name}&quot;)
                    if not self.dry_run:
                        self.aws_client.delete_group_membership(
                            IdentityStoreId=self.identity_store_id,
                            MembershipId=membership_id
                        )
                    else:
                        self.logger.info(f&quot;[DRY-RUN] Would remove user {username} from group {prefixed_name}&quot;)

    def run_sync(self):
        self.sync_users()
        self.sync_groups()
        self.sync_group_memberships()

if __name__ == &quot;__main__&quot;:
    syncer = KeycloakAWSSync(dry_run=False)  # Set to False for actual sync
    syncer.run_sync()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Keyclock에서 사용자나 그룹을 생성하고 스크립트를 돌리면 아래처럼 싱크가 맞춰진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.23.27.png&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R53Hy/btsNsczo3WC/yy9CK1P9cu07MFSGs0NNkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R53Hy/btsNsczo3WC/yy9CK1P9cu07MFSGs0NNkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R53Hy/btsNsczo3WC/yy9CK1P9cu07MFSGs0NNkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR53Hy%2FbtsNsczo3WC%2Fyy9CK1P9cu07MFSGs0NNkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;155&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.23.27.png&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak콘솔과 AWS IdentityCenter 콘솔에서 sync가 잘 되었는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4. Google Workspace 계정으로 AWS IdentityCenter로그인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 Google Workspace계정으로 AWS IdentityCenter에 로그인 해보자.&lt;br /&gt;그 전에 사용자에 매핑된 계정/권한이 잘 반영되는지 확인하기 위해 설정을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아무 계정이나 선택하고, 그룹과 권한세트를 할당하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.27.13.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;1520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0BrqA/btsNtrhkRwE/DOV4odIsbSW44ZtUhIVUN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0BrqA/btsNtrhkRwE/DOV4odIsbSW44ZtUhIVUN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0BrqA/btsNtrhkRwE/DOV4odIsbSW44ZtUhIVUN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0BrqA%2FbtsNtrhkRwE%2FDOV4odIsbSW44ZtUhIVUN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;334&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.27.13.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;1520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.29.09.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9JLHR/btsNthsth7H/Bxe5LIoYSZKScToxUiWni0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9JLHR/btsNthsth7H/Bxe5LIoYSZKScToxUiWni0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9JLHR/btsNthsth7H/Bxe5LIoYSZKScToxUiWni0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9JLHR%2FbtsNthsth7H%2FBxe5LIoYSZKScToxUiWni0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;292&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.29.09.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.29.32.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;1762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5nkjf/btsNtix9FyF/taksDV1efUu7o7HfDqO0nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5nkjf/btsNtix9FyF/taksDV1efUu7o7HfDqO0nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5nkjf/btsNtix9FyF/taksDV1efUu7o7HfDqO0nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5nkjf%2FbtsNtix9FyF%2FtaksDV1efUu7o7HfDqO0nK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;387&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.29.32.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;1762&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.30.22.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;1924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYFeI8/btsNtkvX6f5/hub8Vk9MIFHDOtG69KIKS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYFeI8/btsNtkvX6f5/hub8Vk9MIFHDOtG69KIKS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYFeI8/btsNtkvX6f5/hub8Vk9MIFHDOtG69KIKS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYFeI8%2FbtsNtkvX6f5%2Fhub8Vk9MIFHDOtG69KIKS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;423&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.30.22.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;1924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Keycloak 콘솔에서 해당 그룹에 google workspace 사용자를 추가한다.&lt;br /&gt;추가한 후 SCIM 싱크 실행해줘야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.31.00.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;978&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1lb4x/btsNsTexepD/8LBr6uSDv951axhXokaHgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1lb4x/btsNsTexepD/8LBr6uSDv951axhXokaHgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1lb4x/btsNsTexepD/8LBr6uSDv951axhXokaHgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1lb4x%2FbtsNsTexepD%2F8LBr6uSDv951axhXokaHgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;215&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.31.00.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;978&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시한번 로그인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.43.57.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biQZWG/btsNtsgbP1O/viGqOXlvrufjhAyYfUbd80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biQZWG/btsNtsgbP1O/viGqOXlvrufjhAyYfUbd80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biQZWG/btsNtsgbP1O/viGqOXlvrufjhAyYfUbd80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiQZWG%2FbtsNtsgbP1O%2FviGqOXlvrufjhAyYfUbd80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;300&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.43.57.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.45.51.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdAqdD/btsNskKCRVN/esKIb08ljfdsHsmd7KAru0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdAqdD/btsNskKCRVN/esKIb08ljfdsHsmd7KAru0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdAqdD/btsNskKCRVN/esKIb08ljfdsHsmd7KAru0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdAqdD%2FbtsNskKCRVN%2FesKIb08ljfdsHsmd7KAru0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2025-04-20 오후 9.45.51.png&quot; data-origin-width=&quot;3190&quot; data-origin-height=&quot;1242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 오류없이 제대로 접근이 된것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.34.50.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;978&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yEspg/btsNsqw8ZKD/Cdot1bBegbCig2byicwIn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yEspg/btsNsqw8ZKD/Cdot1bBegbCig2byicwIn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yEspg/btsNsqw8ZKD/Cdot1bBegbCig2byicwIn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyEspg%2FbtsNsqw8ZKD%2FCdot1bBegbCig2byicwIn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;215&quot; data-filename=&quot;스크린샷 2025-04-20 오후 11.34.50.png&quot; data-origin-width=&quot;3186&quot; data-origin-height=&quot;978&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;[알고갈것] SCIM이 뭔지?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SAML&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SAML(Security Assertion Markup Language)은 &lt;b&gt;인증(Authentication)&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 정보를 교환하는 프로토콜임&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 사용자가 한 번 로그인하면 여러 서비스에 접근할 수 있게 하는 SSO(Single Sign-On) 구현&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작동 방식&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Keycloak(IdP)이 사용자를 인증하고 SAML 어설션(assertion)을 AWS Identity Center(SP)에 전달&lt;/li&gt;
&lt;li&gt;AWS는 이 어설션을 신뢰하고 사용자에게 접근 권한 부여&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한계&lt;/b&gt;: 사용자 계정의 프로비저닝(생성/수정/삭제)은 처리하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SCIM&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SCIM(System for Cross-domain Identity Management)은 &lt;b&gt;사용자 프로비저닝(User Provisioning)&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 을 자동화하는 프로토콜이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: IdP와 SP 간 사용자 정보 자동 동기화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작동 방식&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Keycloak의 사용자/그룹 정보를 AWS Identity Center로 자동 동기화&lt;/li&gt;
&lt;li&gt;사용자 생성, 수정, 삭제, 그룹 멤버십 변경 등을 자동으로 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수동으로 사용자를 관리할 필요 없음&lt;/li&gt;
&lt;li&gt;사용자 정보 불일치 방지&lt;/li&gt;
&lt;li&gt;사용자 온보딩/오프보딩 자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&amp;nbsp;SCIM 스크립트는 Keycloak과 AWS Identity Center 사이에서 아래 작업을 수행한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Keycloak에서 사용자/그룹 정보 추출&lt;/li&gt;
&lt;li&gt;AWS Identity Center의 SCIM 엔드포인트로 이 정보 전송&lt;/li&gt;
&lt;li&gt;양쪽 시스템의 사용자/그룹 상태를 지속적으로 동기화&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 SCIM 스크립트는 아래처럼 구성된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주기적으로 실행되도록 스케줄링(예: cron 작업)&lt;/li&gt;
&lt;li&gt;AWS Lambda 함수로 구현&lt;/li&gt;
&lt;li&gt;컨테이너화된 애플리케이션으로 실행&lt;/li&gt;
&lt;li&gt;CI/CD 파이프라인의 일부로 실행 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SAML&lt;/b&gt;: 로그인(인증) 정보를 교환하여 SSO 구현&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SCIM&lt;/b&gt;: 사용자 계정 정보를 자동으로 동기화하여 계정 관리 자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 프로토콜을 함께 사용하면 사용자는 Google Workspace로 로그인하고, 이 인증 정보가 Keycloak을 통해 AWS Identity Center로 전달되며(SAML), 동시에 사용자 계정 정보도 자동으로 동기화(SCIM)되어 완전한 IAM 솔루션을 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고링크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 참고 블로그: &lt;a href=&quot;https://blog.beachgeek.co.uk/keycloak-on-aws-part-two/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.beachgeek.co.uk/keycloak-on-aws-part-two/&lt;/a&gt;&lt;/p&gt;</description>
      <category>  Infra/KeyCloak</category>
      <category>aws identitycenter keycloak</category>
      <category>aws identitycenter 키클락</category>
      <category>aws keycloak</category>
      <category>aws sso</category>
      <category>aws sso keycloak</category>
      <category>google aws sso</category>
      <category>google keycloak aws</category>
      <category>google계정으로 aws sso</category>
      <category>keycloak aws</category>
      <category>keycloak google workspace</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/189</guid>
      <comments>https://1mini2.tistory.com/189#entry189comment</comments>
      <pubDate>Sun, 20 Apr 2025 20:08:56 +0900</pubDate>
    </item>
    <item>
      <title>[keycloak 실습 #1] LDAP + Keycloak+ Grafana 연동까지</title>
      <link>https://1mini2.tistory.com/186</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;  개요&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LDAP이란?&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 참고자료: &lt;a href=&quot;https://www.okta.com/identity-101/what-is-ldap/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.okta.com/identity-101/what-is-ldap/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LDAP(Lightweight Directory Access Protocol)이란, 디렉터리 서비스를 위한 프로토콜이다.&lt;br /&gt;일반적으로 회사에서 부서 및 사용자를 관리하기 위해 사용하며, 사용자/그룹/권한 등의 정보를 저장 및 조회 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740231688028&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dc=example,dc=org (회사)
    ├── ou=Development (개발부서)
    │   ├── cn=developer1
    │   └── cn=developer2
    ├── ou=Sales (영업부서)
    │   ├── cn=sales1
    │   └── cn=sales2
    └── ou=HR (인사부서)
        ├── cn=hr1
        └── cn=hr2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; dc, ou, cn 등 각 객체들에 대해 간단히 설명하자면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dc (Domain Component): 도메인 구성요소&lt;/li&gt;
&lt;li&gt;ou (Organizational Unit): 조직 단위/부서&lt;/li&gt;
&lt;li&gt;cn (Common Name): 일반적인 이름&lt;/li&gt;
&lt;li&gt;uid (User ID): 사용자 식별자&lt;/li&gt;
&lt;li&gt;gid&amp;nbsp;(Group&amp;nbsp;ID):&amp;nbsp;그룹&amp;nbsp;식별자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 후 사용자를 생성할때 각 객체를 만나게 될 예정이니 약간은 익숙해지도록 하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;LDAP과 Keycloak의 Federate (연동)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LDAP은 그룹 및 사용자 정보를 저장하고 관리하는 시스템이다.&lt;br /&gt;LDAP의 사용자/그룹을 ketcloak하고 연동(federate)해서 여러 어플리케이션에 SSO(single-sing-on)할 수 있도록 구성할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LDAP: 사용자 및 그룹의 중앙 저장소 역할&lt;/li&gt;
&lt;li&gt;Keycloak: 인증/인가 처리 및 SSO 제공 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 테스트 해보자  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;  테스트 환경 구성&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. minikube 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;helm으로 쉽게 설치할 수 있기때문에, 로컬에 minikube를 설치한다.&lt;br /&gt;minikube는 각 환경마다 설치 매뉴얼을 아주 자세히 쉽고 간단하게 설명해 놓았으니 &lt;a href=&quot;https://minikube.sigs.k8s.io/docs/start/?arch=%2Fmacos%2Farm64%2Fstable%2Fbinary+download&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 따라서 설치하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740227259108&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;minikube start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 9.34.04.png&quot; data-origin-width=&quot;2734&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFNTGX/btsMuxXachZ/o81IRbZmTY6gNs46GTpMrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFNTGX/btsMuxXachZ/o81IRbZmTY6gNs46GTpMrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFNTGX/btsMuxXachZ/o81IRbZmTY6gNs46GTpMrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFNTGX%2FbtsMuxXachZ%2Fo81IRbZmTY6gNs46GTpMrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;121&quot; data-filename=&quot;스크린샷 2025-02-22 오후 9.34.04.png&quot; data-origin-width=&quot;2734&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl 명령어가 없다면 여기 &lt;a href=&quot;https://kubernetes.io/docs/tasks/tools/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 따라서 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Keycloak 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많이 사용하는 bitnami keycloak helm chart를 이용한다.&lt;br /&gt;아래 명령어로 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740227577118&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak \
  --set auth.adminUser=admin \
  --set auth.adminPassword=admin \
  --set postgresql.auth.username=bn_keycloak \
  --set postgresql.auth.password=password \
  --set postgresql.auth.database=keycloak \
  --set postgresql.auth.postgresPassword=password&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 9.48.37.png&quot; data-origin-width=&quot;2748&quot; data-origin-height=&quot;1676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uOZn3/btsMsSVNWHd/PPCWpKnoeyjVPDC05sTkAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uOZn3/btsMsSVNWHd/PPCWpKnoeyjVPDC05sTkAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uOZn3/btsMsSVNWHd/PPCWpKnoeyjVPDC05sTkAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuOZn3%2FbtsMsSVNWHd%2FPPCWpKnoeyjVPDC05sTkAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;366&quot; data-filename=&quot;스크린샷 2025-02-22 오후 9.48.37.png&quot; data-origin-width=&quot;2748&quot; data-origin-height=&quot;1676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료 되었다면 포트 포워딩을 통해 로컬 브라우저에서 접근 가능하도록 설정한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740227910752&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Keycloak 서비스로 포트포워딩
kubectl port-forward svc/keycloak 8082:80 &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저로 localhost:8082 접속해서 로그인 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 9.50.36.png&quot; data-origin-width=&quot;3314&quot; data-origin-height=&quot;2078&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P4YL1/btsMtbHxcdd/yUTTAXHWJVYdRB4Uz8AlX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P4YL1/btsMtbHxcdd/yUTTAXHWJVYdRB4Uz8AlX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P4YL1/btsMtbHxcdd/yUTTAXHWJVYdRB4Uz8AlX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP4YL1%2FbtsMtbHxcdd%2FyUTTAXHWJVYdRB4Uz8AlX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;376&quot; data-filename=&quot;스크린샷 2025-02-22 오후 9.50.36.png&quot; data-origin-width=&quot;3314&quot; data-origin-height=&quot;2078&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. OpenLDAP 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 참고링크: &lt;a href=&quot;https://artifacthub.io/packages/helm/helm-openldap/openldap&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://artifacthub.io/packages/helm/helm-openldap/openldap&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LDAP을 제공하는 여러 솔루션이 있는데, 그 중 대표적인게 Windows Active Directory이고,&amp;nbsp;&lt;br /&gt;오픈소스로는 OpenLDAP이 있다. 아래 명렁어 대로 간단하게 OpenLDAP을 설치해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1740229178012&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git clone https://github.com/jp-gouin/helm-openldap.git
cd helm-openldap
helm install openldap .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 9.59.42.png&quot; data-origin-width=&quot;2748&quot; data-origin-height=&quot;1298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5lOGf/btsMttH3iuV/CdBLfT6k8fakdkAr0KU4A0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5lOGf/btsMttH3iuV/CdBLfT6k8fakdkAr0KU4A0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5lOGf/btsMttH3iuV/CdBLfT6k8fakdkAr0KU4A0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5lOGf%2FbtsMttH3iuV%2FCdBLfT6k8fakdkAr0KU4A0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;283&quot; data-filename=&quot;스크린샷 2025-02-22 오후 9.59.42.png&quot; data-origin-width=&quot;2748&quot; data-origin-height=&quot;1298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료 되었다면 포트 포워딩을 통해 로컬 브라우저에서 접근 가능하도록 설정한다.&amp;nbsp;&lt;br /&gt;openLDAP webadmin에 접속하려면 비밀번호를 알아야 하기떄문에 비밀번호 확인 명령어로 비밀번호를 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740229297783&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# openldap admin으로 포트 포워딩
kubectl port-forward svc/openldap-phpldapadmin 8083:80 &amp;amp;

# 비밀번호 확인 명령어
kubectl get secret --namespace default openldap -o jsonpath=&quot;{.data.LDAP_ADMIN_PASSWORD}&quot; | base64 --decode; echo
kubectl get secret --namespace default openldap -o jsonpath=&quot;{.data.LDAP_CONFIG_ADMIN_PASSWORD}&quot; | base64 --decode; echo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저로 localhost:8083 접속해서 로그인 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 10.01.51.png&quot; data-origin-width=&quot;3386&quot; data-origin-height=&quot;1848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ex2gO4/btsMtRBPJlX/jnKSsqRR7eWMKBzsnGeYNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ex2gO4/btsMtRBPJlX/jnKSsqRR7eWMKBzsnGeYNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ex2gO4/btsMtRBPJlX/jnKSsqRR7eWMKBzsnGeYNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fex2gO4%2FbtsMtRBPJlX%2FjnKSsqRR7eWMKBzsnGeYNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;327&quot; data-filename=&quot;스크린샷 2025-02-22 오후 10.01.51.png&quot; data-origin-width=&quot;3386&quot; data-origin-height=&quot;1848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;  LDAP 간단하게 설정하기&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 개요에서 언급한것과 같이 LDAP은 조직 (사용자/그룹) 중앙 저장소 역할이다.&lt;br /&gt;OpenLDAP에 접속하여 기본적인 객체들을 생성해보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.  Group 생성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.09.34.png&quot; data-origin-width=&quot;2712&quot; data-origin-height=&quot;1224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tp5YK/btsMugBpyy8/p2s7AJzj1hIgUN8zheZJWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tp5YK/btsMugBpyy8/p2s7AJzj1hIgUN8zheZJWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tp5YK/btsMugBpyy8/p2s7AJzj1hIgUN8zheZJWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTp5YK%2FbtsMugBpyy8%2Fp2s7AJzj1hIgUN8zheZJWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;271&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.09.34.png&quot; data-origin-width=&quot;2712&quot; data-origin-height=&quot;1224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그룹(Groups)&lt;/b&gt;은 사용자를 묶는 사용자를 묶는 단위이며, 권한과 정책을 관리하는데 사용한다.&lt;br /&gt;admin, read-only처럼 권한을 분리하고 정책을 적용하는 단위가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ou=groups에 공통으로 사용할 그룹을 3개 생성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;administrator&lt;/li&gt;
&lt;li&gt;editor&lt;/li&gt;
&lt;li&gt;readonlyuser&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 3.59.24.png&quot; data-origin-width=&quot;3604&quot; data-origin-height=&quot;1880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAkFhn/btsMuTMu1ZI/JmqHHwV0oNtalbToMLYnqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAkFhn/btsMuTMu1ZI/JmqHHwV0oNtalbToMLYnqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAkFhn/btsMuTMu1ZI/JmqHHwV0oNtalbToMLYnqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAkFhn%2FbtsMuTMu1ZI%2FJmqHHwV0oNtalbToMLYnqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;313&quot; data-filename=&quot;스크린샷 2025-02-23 오전 3.59.24.png&quot; data-origin-width=&quot;3604&quot; data-origin-height=&quot;1880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. OU 생성 (Sales/Marketing 조직)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ou는 트리 형태로 만들 수 있다. 일반적으로는 조직의 부서/팀 형태로 많이 구성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740234818319&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# OU구성 예시
dc=example,dc=org
├── ou=headquarters
│   ├── ou=development
│   │   ├── ou=web-team
│   │   └── ou=mobile-team
│   ├── ou=marketing
│   │   ├── ou=digital
│   │   └── ou=branding
│   └── ou=sales
│   │   ├── ou=domestic
│   │   └── ou=international
├── ou=branch-busan-office
│   └── ou=sales
│   │   ├── ou=retail
│   │   └── ou=wholesale
└── ou=groups
    ├── ou=admin-groups
    └── ou=user-groups&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 테스트이므로 간단하게 2개의 ou를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.34.46.png&quot; data-origin-width=&quot;2698&quot; data-origin-height=&quot;1324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBeOsi/btsMuztWUDK/LhLeadcjkUlHyiReRkjWf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBeOsi/btsMuztWUDK/LhLeadcjkUlHyiReRkjWf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBeOsi/btsMuztWUDK/LhLeadcjkUlHyiReRkjWf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBeOsi%2FbtsMuztWUDK%2FLhLeadcjkUlHyiReRkjWf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;1324&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.34.46.png&quot; data-origin-width=&quot;2698&quot; data-origin-height=&quot;1324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sales&lt;/li&gt;
&lt;li&gt;marketing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.36.35.png&quot; data-origin-width=&quot;2684&quot; data-origin-height=&quot;940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yta0i/btsMt8DktyC/WMYIcWqapudhZazYwq3Pf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yta0i/btsMt8DktyC/WMYIcWqapudhZazYwq3Pf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yta0i/btsMt8DktyC/WMYIcWqapudhZazYwq3Pf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyta0i%2FbtsMt8DktyC%2FWMYIcWqapudhZazYwq3Pf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;210&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.36.35.png&quot; data-origin-width=&quot;2684&quot; data-origin-height=&quot;940&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. User 생성&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 조직에 속할 사용자를 만들어보자. sales, marketing ou에 사용자를 만들고, 위에서 생성한 Group에 한명씩 매핑한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.12.13.png&quot; data-origin-width=&quot;4428&quot; data-origin-height=&quot;2402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JeXKz/btsMtPjKZev/b4cFHchp9D0FCGkdeSrtS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JeXKz/btsMtPjKZev/b4cFHchp9D0FCGkdeSrtS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JeXKz/btsMtPjKZev/b4cFHchp9D0FCGkdeSrtS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJeXKz%2FbtsMtPjKZev%2Fb4cFHchp9D0FCGkdeSrtS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;325&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.12.13.png&quot; data-origin-width=&quot;4428&quot; data-origin-height=&quot;2402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.15.11.png&quot; data-origin-width=&quot;3132&quot; data-origin-height=&quot;1458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br9GQv/btsMs7SKXM9/VJX94EkbWZqJm9aNk4zpV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br9GQv/btsMs7SKXM9/VJX94EkbWZqJm9aNk4zpV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br9GQv/btsMs7SKXM9/VJX94EkbWZqJm9aNk4zpV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr9GQv%2FbtsMs7SKXM9%2FVJX94EkbWZqJm9aNk4zpV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;279&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.15.11.png&quot; data-origin-width=&quot;3132&quot; data-origin-height=&quot;1458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 OU에 3명씩 총 6명의 사용자가 생성 완료 되었다면, 다음단계로 넘어가보자!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.17.16.png&quot; data-origin-width=&quot;2584&quot; data-origin-height=&quot;1722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIyFHc/btsMslRyVXD/6uaKecWs5bhyxEaBkk6PC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIyFHc/btsMslRyVXD/6uaKecWs5bhyxEaBkk6PC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIyFHc/btsMslRyVXD/6uaKecWs5bhyxEaBkk6PC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIyFHc%2FbtsMslRyVXD%2F6uaKecWs5bhyxEaBkk6PC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;400&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.17.16.png&quot; data-origin-width=&quot;2584&quot; data-origin-height=&quot;1722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;  Keycloak LDAP Federate&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Realm 단위로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;LDAP 연동이 가능하다.&lt;br /&gt;먼저, my-LDAP이라는 Realm을 생성하자.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1. LDAP Realm 생성&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.49.46.png&quot; data-origin-width=&quot;2280&quot; data-origin-height=&quot;1292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEvBlc/btsMsMBChpe/ZChuZVR8FB5QQPnyAyh3Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEvBlc/btsMsMBChpe/ZChuZVR8FB5QQPnyAyh3Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEvBlc/btsMsMBChpe/ZChuZVR8FB5QQPnyAyh3Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEvBlc%2FbtsMsMBChpe%2FZChuZVR8FB5QQPnyAyh3Nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;340&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.49.46.png&quot; data-origin-width=&quot;2280&quot; data-origin-height=&quot;1292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Keycloak LDAP 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 my-LDAP realm에서 LDAP연동을 진행한다.&lt;br /&gt;왼쪽 하단&amp;nbsp; [User Federation] -&amp;gt; [Add Ldap providers]를 클릭한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.51.11.png&quot; data-origin-width=&quot;2706&quot; data-origin-height=&quot;1650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Br7Us/btsMr0G4HiY/uxXKxGVfLfiZ15JfLwoB70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Br7Us/btsMr0G4HiY/uxXKxGVfLfiZ15JfLwoB70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Br7Us/btsMr0G4HiY/uxXKxGVfLfiZ15JfLwoB70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBr7Us%2FbtsMr0G4HiY%2FuxXKxGVfLfiZ15JfLwoB70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;366&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.51.11.png&quot; data-origin-width=&quot;2706&quot; data-origin-height=&quot;1650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 항목을 살펴보면서 설정해보자. 설정해야할 부분은 빨간 네모로 표시해두었고,&amp;nbsp;&lt;br /&gt;설명은 각 캡쳐 하위에 달아놓았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.57.21.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;2246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwnK6o/btsMrDkNnNG/dQkLvDY8VoaKYPz4HyUqk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwnK6o/btsMrDkNnNG/dQkLvDY8VoaKYPz4HyUqk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwnK6o/btsMrDkNnNG/dQkLvDY8VoaKYPz4HyUqk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwnK6o%2FbtsMrDkNnNG%2FdQkLvDY8VoaKYPz4HyUqk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;497&quot; data-filename=&quot;스크린샷 2025-02-22 오후 11.57.21.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;2246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vendor: Other (openldap 이므로 other 선택)&lt;/li&gt;
&lt;li&gt;Connection URL: ldap://openldap.default:389 (LDAP 서버 주소)&lt;/li&gt;
&lt;li&gt;Bind DN(Distinguished Name): cn=admin,dc=example,dc=org (관리자 계정)&lt;/li&gt;
&lt;li&gt;Bind Credential: (관리자 비밀번호)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 &lt;b&gt;DN&lt;/b&gt;은 아래와 같은 형태로 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740236819190&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cn=lee.leader,ou=marketing,ou=headquarters,dc=example,dc=org
└─────────┴──────────────┴───────────────┴─────────┴────────
     │           │              │             │         │
     │           │              │             │         └─ 최상위 도메인
     │           │              │             └─ 도메인
     │           │              └─ 본사
     │           └─ 마케팅 부서
     └─ 사용자 이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.05.54.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWXRiB/btsMtpMimAR/er2gUyaPpRVOy0dSDHZg2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWXRiB/btsMtpMimAR/er2gUyaPpRVOy0dSDHZg2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWXRiB/btsMtpMimAR/er2gUyaPpRVOy0dSDHZg2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWXRiB%2FbtsMtpMimAR%2Fer2gUyaPpRVOy0dSDHZg2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;386&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.05.54.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Edit mode (WRITABLE): LDAP 서버에 대한 접근 모드 설정 (WRITABLE/READ_ONLY/UNSYNCED)&lt;/li&gt;
&lt;li&gt;Users DN: 사용자들이 저장된 기본 DN 위치&lt;/li&gt;
&lt;li&gt;Username LDAP attribute: 사용자 이름으로 사용될 LDAP 속성 (cn (Common Name)이 일반적으로 사용됨)&lt;/li&gt;
&lt;li&gt;RDN LDAP attribute: 엔트리의 고유 식별자로 사용할 속성&lt;/li&gt;
&lt;li&gt;UUID LDAP attribute: 사용자의 고유 식별자로 사용되는 속성 (objectGUID는 전역적으로 고유한 식별자)&lt;/li&gt;
&lt;li&gt;User object classes: LDAP 사용자 객체의 클래스 정의 (person, organizationalPerson, user는 표준 사용자 객체 클래스&lt;/li&gt;
&lt;li&gt;User LDAP filter: 사용자 검색 시 적용할 필터 (특정 조건으로 사용자를 필터링할 때 사용)&lt;/li&gt;
&lt;li&gt;Search scope (Subtree): LDAP 검색 범위 설정 (Subtree: 하위 모든 레벨 검색/OneLevel: 직계 하위 레벨만 검색)&lt;/li&gt;
&lt;li&gt;Read timeout: LDAP 검색 작업의 제한 시간 설정&lt;/li&gt;
&lt;li&gt;Pagination: 대량의 검색 결과를 페이지 단위로 가져올지 설정 (메모리 사용 효율화를 위해 ON 권장)&lt;/li&gt;
&lt;li&gt;Referral: LDAP&amp;nbsp;리퍼럴(다른&amp;nbsp;LDAP&amp;nbsp;서버로의&amp;nbsp;참조)&amp;nbsp;처리&amp;nbsp;방식&amp;nbsp;설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.16.21.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dayILr/btsMtcNfgnL/B6jrMiloasIiUjQ9VCbcUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dayILr/btsMtcNfgnL/B6jrMiloasIiUjQ9VCbcUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dayILr/btsMtcNfgnL/B6jrMiloasIiUjQ9VCbcUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdayILr%2FbtsMtcNfgnL%2FB6jrMiloasIiUjQ9VCbcUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;330&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.16.21.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Synchronization settings (동기화 설정)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Import users: LDAP의 사용자를 Keycloak으로 가져올지 여부를 설정 (ON/OFF)&lt;/li&gt;
&lt;li&gt;Sync Registrations: Keycloak에서 생성된 사용자를 LDAP에 동기화할지 여부를 설정 (ON/OFF)&lt;/li&gt;
&lt;li&gt;Batch size: 한 번에 동기화할 사용자의 수를 지정 (성능과 메모리 사용량 조절)&lt;/li&gt;
&lt;li&gt;Periodic full sync: 전체 사용자 데이터를 주기적으로 동기화할지 설정 (ON/OFF)&lt;/li&gt;
&lt;li&gt;Periodic changed users sync: 변경된 사용자만 주기적으로 동기화할지 설정 (ON/OFF)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Kerberos integration (Kerberos 통합)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Allow Kerberos authentication: Kerberos 인증 허용 여부 설정 (ON/OFF)&lt;/li&gt;
&lt;li&gt;Use Kerberos for password authentication: 비밀번호 인증에 Kerberos 사용할지 여부 설정 (ON/OFF)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;* 참고: Kerberos는 네트워크 인증 프로토콜로, 싱글 사인온(SSO)을 구현하는 데 사용되는 보안 시스템이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.20.16.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCmzyZ/btsMtcfmO6F/DohG3BG6NKgb4y7TkJjqMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCmzyZ/btsMtcfmO6F/DohG3BG6NKgb4y7TkJjqMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCmzyZ/btsMtcfmO6F/DohG3BG6NKgb4y7TkJjqMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCmzyZ%2FbtsMtcfmO6F%2FDohG3BG6NKgb4y7TkJjqMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;353&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.20.16.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cache settings (캐시 설정)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Cache policy: LDAP 조회 결과를 캐시하는 정책 설정 (DEFAULT/EVICT_DAILY/EVICT_WEEKLY/MAX_LIFESPAN/NO_CACHE)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Advanced settings (고급 설정)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Enable&amp;nbsp;the&amp;nbsp;LDAPv3&amp;nbsp;password&amp;nbsp;modify&amp;nbsp;extended&amp;nbsp;operation:&amp;nbsp;&lt;br /&gt;LDAPv3의 확장된 비밀번호 수정 작업 활성화 여부 (ON/OFF)&lt;/li&gt;
&lt;li&gt;Validate password policy: LDAP 서버의 비밀번호 정책 검증 활성화 여부 (ON/OFF)&lt;/li&gt;
&lt;li&gt;Trust Email: LDAP에서 가져온 이메일 주소를 신뢰할지 여부 설정 (ON이면 이메일 검증 절차 생략) (ON/OFF)&lt;/li&gt;
&lt;li&gt;Connection trace: LDAP 연결 디버깅을 위한 상세 로그 활성화 여부 (ON/OFF)&lt;/li&gt;
&lt;li&gt;Query Supported Extensions: LDAP 서버가 지원하는 확장 기능 조회 버튼&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 LDAP 연동이 완료 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.21.43.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0yBGo/btsMsN1wrUq/TBZSKoCtjMdwzXEi6CXUTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0yBGo/btsMsN1wrUq/TBZSKoCtjMdwzXEi6CXUTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0yBGo/btsMsN1wrUq/TBZSKoCtjMdwzXEi6CXUTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0yBGo%2FbtsMsN1wrUq%2FTBZSKoCtjMdwzXEi6CXUTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;353&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.21.43.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Users 탭으로 이동해서 사용자가 보이는지 점검해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.22.15.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UXYKi/btsMuuGaApW/4PUDMPjcS7BzLTaO3wQsw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UXYKi/btsMuuGaApW/4PUDMPjcS7BzLTaO3wQsw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UXYKi/btsMuuGaApW/4PUDMPjcS7BzLTaO3wQsw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUXYKi%2FbtsMuuGaApW%2F4PUDMPjcS7BzLTaO3wQsw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;353&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.22.15.png&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 잘 보인다.&lt;br /&gt;여기까지만 하면 실제 환경에서 어떻게 쓰이는지 이해하기 쉽지 않으니, 샘플 어플리케이션으로 테스트를 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;  어플리케이션 추가 (Ldap 사용자로 Keycloak SSO로 Grafana 로그인하기)&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1. Grafana 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 설치링크: &lt;a href=&quot;https://grafana.com/docs/grafana/latest/setup-grafana/installation/helm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://grafana.com/docs/grafana/latest/setup-grafana/installation/helm/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;널리 사용하는 Grafana를 설치하고, 사용자 로그인까지 테스트 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1740238253595&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# helm 저장소 등록 및 업데이트
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

# helm 다운받고 압축풀기
helm fetch grafana/grafana
tar -zxvf grafana-8.10.1.tgz

# 설치하기
cd grafana
helm install grafana .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.48.46.png&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebKAUW/btsMtdrVOQE/TXWhG61ZbeHUgekCS5O8qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebKAUW/btsMtdrVOQE/TXWhG61ZbeHUgekCS5O8qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebKAUW/btsMtdrVOQE/TXWhG61ZbeHUgekCS5O8qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebKAUW%2FbtsMtdrVOQE%2FTXWhG61ZbeHUgekCS5O8qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;201&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.48.46.png&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치했다면 역시 포트 포워딩을 통해 로컬 브라우저에서 접속 가능하도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1740238422622&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Grafana 서비스로 포트포워딩
kubectl port-forward svc/grafana 8084:80 &amp;amp;

# Grafana admin 비밀번호 확인
kubectl get secret --namespace default grafana -o jsonpath=&quot;{.data.admin-password}&quot; | base64 --decode ; echo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저로 localhost:8084 접속해서 로그인 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.34.25.png&quot; data-origin-width=&quot;2756&quot; data-origin-height=&quot;1676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kPGSd/btsMsPSqhqK/F5FTBboucHr489dVYishC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kPGSd/btsMsPSqhqK/F5FTBboucHr489dVYishC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kPGSd/btsMsPSqhqK/F5FTBboucHr489dVYishC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkPGSd%2FbtsMsPSqhqK%2FF5FTBboucHr489dVYishC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;365&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.34.25.png&quot; data-origin-width=&quot;2756&quot; data-origin-height=&quot;1676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. Keycloak LDAP Mapper 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Keycloak 단계에서 사용자 정보를 가져오는것은 확인했지만, 잘 보면 &lt;u&gt;&lt;b&gt;사용자에 그룹정보가 빠져있다&lt;/b&gt;&lt;/u&gt;.&lt;br /&gt;LDAP의 Group정보를 가져오기위해서는 추가적인 Mapper를 설정해주어야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- 그룹 정보 동기화 (group-ldap-mapper)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 LDAP의 그룹을 가져와 보자.&lt;br /&gt;LDAP연동한 곳에서 새로운 매퍼를 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.20.34.png&quot; data-origin-width=&quot;3744&quot; data-origin-height=&quot;1546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0lLUY/btsMr7zajiM/XR0oRvu0NoWbCgALsJlF0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0lLUY/btsMr7zajiM/XR0oRvu0NoWbCgALsJlF0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0lLUY/btsMr7zajiM/XR0oRvu0NoWbCgALsJlF0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0lLUY%2FbtsMr7zajiM%2FXR0oRvu0NoWbCgALsJlF0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3744&quot; height=&quot;1546&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.20.34.png&quot; data-origin-width=&quot;3744&quot; data-origin-height=&quot;1546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 매퍼는 그룹을 동기화 시키기 위해 설정한다.&lt;br /&gt;LDAP 기본설정이아닌 openLDAP 그룹(posixGroup)설정에 맞춰져 있으니 AD등 다른 LDAP서버를 사용한다면,&lt;br /&gt;설정되어있는 LDAP 정보를 잘 보고 사용해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.26.33.png&quot; data-origin-width=&quot;3006&quot; data-origin-height=&quot;2338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dA3XWt/btsMt66CfzQ/SBmCkTkLkB2tjL7LheqR70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dA3XWt/btsMt66CfzQ/SBmCkTkLkB2tjL7LheqR70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dA3XWt/btsMt66CfzQ/SBmCkTkLkB2tjL7LheqR70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdA3XWt%2FbtsMt66CfzQ%2FSBmCkTkLkB2tjL7LheqR70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3006&quot; height=&quot;2338&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.26.33.png&quot; data-origin-width=&quot;3006&quot; data-origin-height=&quot;2338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Name: group-ldap-mapper&lt;/li&gt;
&lt;li&gt;Mapper type: group-ldap-mapper&lt;/li&gt;
&lt;li&gt;Group Name LDAP Attribute: cn&lt;/li&gt;
&lt;li&gt;Group Object Class: posixGroup&lt;/li&gt;
&lt;li&gt;Preserve Group Ingeritance: off&lt;/li&gt;
&lt;li&gt;Ignore Missing Group: off&lt;/li&gt;
&lt;li&gt;Membership LDAP Attribute: gidNumber&lt;/li&gt;
&lt;li&gt;Membership Attribute Type: UID&lt;/li&gt;
&lt;li&gt;Membership&amp;nbsp;User&amp;nbsp;LDAP&amp;nbsp;Attribute&lt;/li&gt;
&lt;li&gt;LDAP FIlter: (빈값)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Mode: READ_ONLY&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;User Groups Retrieve Strategy: LODA_GROUPS_BY_MEMBER_ATTRIBUTE&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이렇게 설정하고나서 동기화를 해준다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.33.45.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AJ7ZI/btsMtb1XaEy/7ZJ3qaBWRifTumRkqzILsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AJ7ZI/btsMtb1XaEy/7ZJ3qaBWRifTumRkqzILsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AJ7ZI/btsMtb1XaEy/7ZJ3qaBWRifTumRkqzILsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAJ7ZI%2FbtsMtb1XaEy%2F7ZJ3qaBWRifTumRkqzILsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;235&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.33.45.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenLDAP에서 설정했던것과 같이 3개의 그룹이 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.34.48.png&quot; data-origin-width=&quot;2566&quot; data-origin-height=&quot;1238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXyXBc/btsMsPrgKRX/pN0D5XggSJpmvoGfuU7D5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXyXBc/btsMsPrgKRX/pN0D5XggSJpmvoGfuU7D5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXyXBc/btsMsPrgKRX/pN0D5XggSJpmvoGfuU7D5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXyXBc%2FbtsMsPrgKRX%2FpN0D5XggSJpmvoGfuU7D5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;289&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.34.48.png&quot; data-origin-width=&quot;2566&quot; data-origin-height=&quot;1238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 안에 들어가면 유저가 하나도 없는데 그 이유는 아직 사용자의 gidNumber를 가져오지 않았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;- 사용자의 그룹 정보 동기화 (user-attribute-ldap-mapper)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 사용자의 gidNumber를 가져오기 위해 매퍼를 하나 더 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.36.52.png&quot; data-origin-width=&quot;2566&quot; data-origin-height=&quot;1172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Oh06e/btsMrE42cF4/Dv27jwnXbDP7cilCgTN5c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Oh06e/btsMrE42cF4/Dv27jwnXbDP7cilCgTN5c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Oh06e/btsMrE42cF4/Dv27jwnXbDP7cilCgTN5c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOh06e%2FbtsMrE42cF4%2FDv27jwnXbDP7cilCgTN5c0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.36.52.png&quot; data-origin-width=&quot;2566&quot; data-origin-height=&quot;1172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LDAP에서 사용자와 그룹 정보는 gidMember 값으로 매핑되기때문에, &lt;br /&gt;keycloak에서도 이 정보를 가지고 있어야 사용자를 그룹에 올바르게 매핑할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.39.10.png&quot; data-origin-width=&quot;3474&quot; data-origin-height=&quot;1708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JL9lT/btsMtnulHCp/yuWOkJrloq1Nat7urHHhHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JL9lT/btsMtnulHCp/yuWOkJrloq1Nat7urHHhHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JL9lT/btsMtnulHCp/yuWOkJrloq1Nat7urHHhHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJL9lT%2FbtsMtnulHCp%2FyuWOkJrloq1Nat7urHHhHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3474&quot; height=&quot;1708&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.39.10.png&quot; data-origin-width=&quot;3474&quot; data-origin-height=&quot;1708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Sync all users를 클릭하면 사용자 정보를 업데이트 하는것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.40.47.png&quot; data-origin-width=&quot;2574&quot; data-origin-height=&quot;1566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4vC5O/btsMr6NRpRD/GjaBsZO6Y35bp5yJKs4lE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4vC5O/btsMr6NRpRD/GjaBsZO6Y35bp5yJKs4lE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4vC5O/btsMr6NRpRD/GjaBsZO6Y35bp5yJKs4lE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4vC5O%2FbtsMr6NRpRD%2FGjaBsZO6Y35bp5yJKs4lE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;365&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.40.47.png&quot; data-origin-width=&quot;2574&quot; data-origin-height=&quot;1566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 그룹에 사용자가 할당되어있다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.42.25.png&quot; data-origin-width=&quot;2574&quot; data-origin-height=&quot;1518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1wK9a/btsMsTHeyF2/GbQeZm61McC4KybxQ2KbS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1wK9a/btsMsTHeyF2/GbQeZm61McC4KybxQ2KbS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1wK9a/btsMsTHeyF2/GbQeZm61McC4KybxQ2KbS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1wK9a%2FbtsMsTHeyF2%2FGbQeZm61McC4KybxQ2KbS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;354&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.42.25.png&quot; data-origin-width=&quot;2574&quot; data-origin-height=&quot;1518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. Keycloak Client 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak으로 Grafana 인증/인가를 처리할 예정이니, Client를 생성한다.&lt;br /&gt;물론 위에서 만든 my-LDAP Realm에서 만들어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.47.10.png&quot; data-origin-width=&quot;3196&quot; data-origin-height=&quot;2608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWQXQW/btsMsNAwS48/CMIMnynaKkoaN8UtZ8Cufk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWQXQW/btsMsNAwS48/CMIMnynaKkoaN8UtZ8Cufk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWQXQW/btsMsNAwS48/CMIMnynaKkoaN8UtZ8Cufk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWQXQW%2FbtsMsNAwS48%2FCMIMnynaKkoaN8UtZ8Cufk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3196&quot; height=&quot;2608&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.47.10.png&quot; data-origin-width=&quot;3196&quot; data-origin-height=&quot;2608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Client type: OpenID Connect&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;ClientID: grafana&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Name: grafana&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Client authentication: on&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Root URL: http://127.0.0.1:8084&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Home URL: http://127.0.0.1:8084&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Valid redirect URIs: http://127.0.0.1:8084/login/generic_oauth&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Web origins: http://127.0.0.1:8084&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4. LDAP Group Keycloak Role 매핑&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이제 앞에서 설정한 LDAP Group과 Client의 Role을 연결해주어야 한다.&lt;br /&gt;우선 Client Role탭에서 Role을 생성한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.51.38.png&quot; data-origin-width=&quot;2622&quot; data-origin-height=&quot;1054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba07VX/btsMsQp96gT/52o4TuW3mlZVmTERC6jSl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba07VX/btsMsQp96gT/52o4TuW3mlZVmTERC6jSl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba07VX/btsMsQp96gT/52o4TuW3mlZVmTERC6jSl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba07VX%2FbtsMsQp96gT%2F52o4TuW3mlZVmTERC6jSl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;241&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.51.38.png&quot; data-origin-width=&quot;2622&quot; data-origin-height=&quot;1054&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GrafanaAdmin&lt;/li&gt;
&lt;li&gt;GrafanaEditor&lt;/li&gt;
&lt;li&gt;GrafanaViewer&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Group으로 가서 각 그룹과 매칭되는 Client Role을 매핑한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.56.25.png&quot; data-origin-width=&quot;4328&quot; data-origin-height=&quot;2218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVLu3g/btsMsNHlJEE/SMUyQEkdCUOIOxkoGwGIX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVLu3g/btsMsNHlJEE/SMUyQEkdCUOIOxkoGwGIX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVLu3g/btsMsNHlJEE/SMUyQEkdCUOIOxkoGwGIX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVLu3g%2FbtsMsNHlJEE%2FSMUyQEkdCUOIOxkoGwGIX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4328&quot; height=&quot;2218&quot; data-filename=&quot;스크린샷 2025-02-23 오전 4.56.25.png&quot; data-origin-width=&quot;4328&quot; data-origin-height=&quot;2218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;administrartor(LDAP Group) --[mapping]-- GrafanaAdmin(ClientRole)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;editor(LDAP Group) --[mapping]--&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;GrafanaEditor(ClientRole)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;readonlyuser(LDAP Group) --[mapping]--&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;GrafanaViewer(ClientRole)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 LDAP Group설정과 Client Role설정이 매핑된다.&lt;br /&gt;매핑이 잘 되었는지 확인하려면 [사용자 -&amp;gt; Role mapping탭]에서 Hide&amp;nbsp;inherited&amp;nbsp;roles를 해제하고 보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.00.31.png&quot; data-origin-width=&quot;2604&quot; data-origin-height=&quot;1494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N7pSq/btsMsnohtrs/gKgSejD7U4ytypLKUKKvt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N7pSq/btsMsnohtrs/gKgSejD7U4ytypLKUKKvt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N7pSq/btsMsnohtrs/gKgSejD7U4ytypLKUKKvt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN7pSq%2FbtsMsnohtrs%2FgKgSejD7U4ytypLKUKKvt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;344&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.00.31.png&quot; data-origin-width=&quot;2604&quot; data-origin-height=&quot;1494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4. Client Role을 Protocal Mapper를 통해 Claim에 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Role이 사용자 정보에 포함되어있다.&lt;br /&gt;이제 Protocal Mapper를 통해 Claim에 추가해주어야 한다. (&quot;어떤 정보를&quot; &quot;어떤 형식으로&quot; 토큰에 넣을지 지정)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.21.53.png&quot; data-origin-width=&quot;2686&quot; data-origin-height=&quot;2704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OMmb4/btsMsn2Vmri/UX6cwXvvEIrXXkkntGZlP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OMmb4/btsMsn2Vmri/UX6cwXvvEIrXXkkntGZlP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OMmb4/btsMsn2Vmri/UX6cwXvvEIrXXkkntGZlP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOMmb4%2FbtsMsn2Vmri%2FUX6cwXvvEIrXXkkntGZlP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2686&quot; height=&quot;2704&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.21.53.png&quot; data-origin-width=&quot;2686&quot; data-origin-height=&quot;2704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 정말 끝났다!!&lt;br /&gt;마지막으로 client secret을 저장한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.55.19.png&quot; data-origin-width=&quot;2686&quot; data-origin-height=&quot;1266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CWBVB/btsMs8D4dvp/K7QpWijAjgXODnpbIOgeR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CWBVB/btsMs8D4dvp/K7QpWijAjgXODnpbIOgeR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CWBVB/btsMs8D4dvp/K7QpWijAjgXODnpbIOgeR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCWBVB%2FbtsMs8D4dvp%2FK7QpWijAjgXODnpbIOgeR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;283&quot; data-filename=&quot;스크린샷 2025-02-23 오전 12.55.19.png&quot; data-origin-width=&quot;2686&quot; data-origin-height=&quot;1266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;5. Grafana oauth 설정 (grafana.ini)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Grafana Keycloak SSO 문서: &lt;a href=&quot;https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/keycloak/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/keycloak/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위에서 받아놓은 grafana helm charts를 수정해주어야 한다.&lt;br /&gt;grafana 디렉토리 내 values.yaml을 찾아 아래 내용을 추가한다. 주의해야할점은 기존 정보를 건드리지 않는것이다!&lt;/p&gt;
&lt;pre id=&quot;code_1740255148003&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  server:
    # Grafana의 외부 접근 URL 설정
    root_url: http://127.0.0.1:8084

  auth.generic_oauth:
    # OAuth 인증 활성화 여부
    enabled: true
    # OAuth 제공자 이름 설정
    name: Keycloak-OAuth
    # 새로운 사용자의 자동 회원가입 허용
    allow_sign_up: true
    # OAuth 클라이언트 ID
    client_id: grafana
    # OAuth 클라이언트 시크릿 키
    client_secret: &quot;Qi3lPg5eU9HA8Hc3LHyseWuhAH936qYU&quot;
    # 요청할 OAuth 스코프 (권한 범위)
    scopes: &quot;openid email profile offline_access roles&quot;
    # 로그인에 사용할 사용자 속성 경로
    login_attribute_path: &quot;username&quot;
    # 사용자 이름으로 사용할 속성 경로
    name_attribute_path: &quot;name&quot;
    # 이메일로 사용할 속성 경로
    email_attribute_path: &quot;email&quot;
    # OAuth 인증 엔드포인트 URL (사용자인증 경로)
    auth_url: &quot;http://127.0.0.1:8082/realms/my-LDAP/protocol/openid-connect/auth&quot;
    # OAuth 토큰 발급 엔드포인트 URL
    token_url: http://keycloak.default.svc.cluster.local/realms/my-LDAP/protocol/openid-connect/token
    # 사용자 정보를 가져올 API URL
    api_url: http://keycloak.default.svc.cluster.local/realms/my-LDAP/protocol/openid-connect/userinfo
    # 사용자 역할 매핑 규칙 (GrafanaAdmin -&amp;gt; Admin, GrafanaViewer -&amp;gt; Editor, 기타 -&amp;gt; Viewer)
    role_attribute_path: &quot;contains(roles[*], 'GrafanaAdmin') &amp;amp;&amp;amp; 'Admin' || contains(roles[*], 'GrafanaEditor') &amp;amp;&amp;amp; 'Editor' || contains(roles[*], 'GrafanaViewer') &amp;amp;&amp;amp; 'Viewer'&quot;
    # Grafana 관리자 권한 할당 허용
    allow_assign_grafana_admin: true
    # Grafana 조직 역할 동기화 건너뛰기 비활성화
    skip_org_role_sync: false
    # 엄격한 역할 속성 매칭 사용
    role_attribute_strict: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 수정했다면 이제 helm을 업데이트 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740255201942&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm upgrade grafana . -f values.yaml --set assertNoLeakedSecrets=false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헬름 업데이트 후 터널링이 끊겨있을텐데 한번 더 연결해주자!&lt;/p&gt;
&lt;pre id=&quot;code_1740255241656&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl port-forward svc/grafana 8084:80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;6. Grafana 에 LDAP 계정으로 로그인하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 드디어 접속해보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 1.01.56.png&quot; data-origin-width=&quot;2734&quot; data-origin-height=&quot;1498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cplVur/btsMrGojdHB/uPg9KJiZYJgpzQnYtna0a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cplVur/btsMrGojdHB/uPg9KJiZYJgpzQnYtna0a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cplVur/btsMrGojdHB/uPg9KJiZYJgpzQnYtna0a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcplVur%2FbtsMrGojdHB%2FuPg9KJiZYJgpzQnYtna0a0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;329&quot; data-filename=&quot;스크린샷 2025-02-23 오전 1.01.56.png&quot; data-origin-width=&quot;2734&quot; data-origin-height=&quot;1498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sign in with Keycloak-OAuth을 클릭하면 Keycloak 로그인 페이지로 redirect 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LDAP 계정에 이메일 설정이 안되어있기 때문에, 이메일 정보를 추가하고 Submit하면 Grafana에 로그인 되며,&lt;br /&gt;사용자 Profile을 확인하면 admin에 매핑되어있는걸 확인할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.32.56.png&quot; data-origin-width=&quot;3050&quot; data-origin-height=&quot;2708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMDqmt/btsMsjNfk8d/kyzGKtUJykRhDl2tDyLDy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMDqmt/btsMsjNfk8d/kyzGKtUJykRhDl2tDyLDy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMDqmt/btsMsjNfk8d/kyzGKtUJykRhDl2tDyLDy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMDqmt%2FbtsMsjNfk8d%2FkyzGKtUJykRhDl2tDyLDy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3050&quot; height=&quot;2708&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.32.56.png&quot; data-origin-width=&quot;3050&quot; data-origin-height=&quot;2708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;7. 추가) 트러블슈팅&amp;nbsp; &quot;IdP&amp;nbsp;did&amp;nbsp;not&amp;nbsp;return&amp;nbsp;a&amp;nbsp;role&amp;nbsp;attribute,&amp;nbsp;please&amp;nbsp;contact&amp;nbsp;your&amp;nbsp;administrator&quot;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.41.57.png&quot; data-origin-width=&quot;2602&quot; data-origin-height=&quot;1820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFMn3J/btsMtOd4f3D/KQ46gcqKIWcberCMloWZGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFMn3J/btsMtOd4f3D/KQ46gcqKIWcberCMloWZGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFMn3J/btsMtOd4f3D/KQ46gcqKIWcberCMloWZGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFMn3J%2FbtsMtOd4f3D%2FKQ46gcqKIWcberCMloWZGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;420&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.41.57.png&quot; data-origin-width=&quot;2602&quot; data-origin-height=&quot;1820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1740257055883&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Grafana logs
logger=context userId=0 orgId=0 uname= t=2025-02-22T20:43:15.60593592Z level=info msg=&quot;Request Completed&quot; method=GET path=/login/generic_oauth status=302 remote_addr=127.0.0.1 time_ms=0 duration=683.167&amp;micro;s size=309 referer=http://127.0.0.1:8084/login handler=/login/:name status_source=server
logger=oauth.generic_oauth t=2025-02-22T20:43:15.709432879Z level=warn msg=&quot;Failed to extract role&quot; err=&quot;[oauth.role_attribute_strict_violation] idP did not return a role attribute, but role_attribute_strict is set&quot;
logger=authn.service t=2025-02-22T20:43:15.709525962Z level=error msg=&quot;Failed to authenticate request&quot; client=auth.client.generic_oauth error=&quot;[auth.oauth.userinfo.error] failed to get user info: [oauth.role_attribute_strict_violation] could not evaluate any valid roles using IdP provided data&quot;
logger=context userId=0 orgId=0 uname= t=2025-02-22T20:43:15.712088504Z level=info msg=&quot;Request Completed&quot; method=GET path=/login/generic_oauth status=302 remote_addr=127.0.0.1 time_ms=28 duration=28.130417ms size=29 referer= handler=/login/:name status_source=server&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;만약&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;IdP did not return a role attribute, please contact your administrator 오류를 만났다면,&lt;br /&gt;아래 명령어를 통해 Claim으로 roles 추가가 잘 된건지 한번 더 점검해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1740256659942&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X POST &quot;http://localhost:8082/realms/my-LDAP/protocol/openid-connect/token&quot; \
-H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
-d &quot;client_id=grafana&quot; \
-d &quot;client_secret=Qi3lPg5eU9HA8Hc3LHyseWuhAH936qYU&quot; \
-d &quot;username=sadministrator&quot; \
-d &quot;password=1234&quot; \
-d &quot;grant_type=password&quot; | jq -r '.access_token' | jwt decode -&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.39.14.png&quot; data-origin-width=&quot;5180&quot; data-origin-height=&quot;2266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UM1QG/btsMtvy5B7k/89KISfjWkKQYveAO5jHoM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UM1QG/btsMtvy5B7k/89KISfjWkKQYveAO5jHoM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UM1QG/btsMtvy5B7k/89KISfjWkKQYveAO5jHoM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUM1QG%2FbtsMtvy5B7k%2F89KISfjWkKQYveAO5jHoM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;5180&quot; height=&quot;2266&quot; data-filename=&quot;스크린샷 2025-02-23 오전 5.39.14.png&quot; data-origin-width=&quot;5180&quot; data-origin-height=&quot;2266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이부분이 정상이라면 helm charts values의 role_attribute_path가 잘못되었을 가능성이 크다.&lt;br /&gt;Roles와 매핑이 잘 되는지 한번 더 점검해보자.&lt;/p&gt;</description>
      <category>  Infra/KeyCloak</category>
      <category>grafana sso</category>
      <category>keycloak</category>
      <category>keycloak federate</category>
      <category>keycloak grafana</category>
      <category>keycloak ldap</category>
      <category>keycloak protocal mapper</category>
      <category>keycloak sso</category>
      <category>keycloak 설치</category>
      <category>keycloak 예제</category>
      <category>role_attribute_path</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/186</guid>
      <comments>https://1mini2.tistory.com/186#entry186comment</comments>
      <pubDate>Sun, 20 Apr 2025 15:04:45 +0900</pubDate>
    </item>
    <item>
      <title>[keycloak 맛보기#9] 세션 및 토큰 관리</title>
      <link>https://1mini2.tistory.com/188</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak은 사용자 인증과 권한관리를 위한 Identity And Access management 솔루션이다.&lt;br /&gt;IAM 솔루션으로써 SSO(SingleSignOn), 사용자 Federation 등의 기능을 위해서는 세션 및 토큰 관리를 이해하는것이 매우 중요하다.&lt;br /&gt;이번 포스팅에서는 Keycloak의 세션 및 토큰 관리에 대해 다뤄보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어가기전에 세션과 토큰에 대한 아주 간단한 개념 정리를 해보자.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 152px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 19.3798%; height: 19px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 39.2635%; height: 19px;&quot;&gt;세션 (인증)&lt;/td&gt;
&lt;td style=&quot;width: 41.3566%; height: 19px;&quot;&gt;토큰 (인증+인가)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 19.3798%; height: 19px;&quot;&gt;기본 개념&lt;/td&gt;
&lt;td style=&quot;width: 39.2635%; height: 19px;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버에 저장되는 사용자 상태 정보&lt;/li&gt;
&lt;li&gt;클라이언트와 서버 간의 지속적인 연결 상태&lt;/li&gt;
&lt;li&gt;서버 측에서 메모리나 데이터베이스에 저장됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 41.3566%; height: 19px;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트에 저장되는 인증 및 권한 증명서&lt;/li&gt;
&lt;li&gt;자체적으로 필요한 정보를 포함하는 데이터 조각&lt;/li&gt;
&lt;li&gt;주로 클라이언트 측에서 관리됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 19.3798%; height: 19px;&quot;&gt;저장 위치&lt;/td&gt;
&lt;td style=&quot;width: 39.2635%; height: 19px;&quot;&gt;서버 측에 저장 (메모리/DB)&lt;/td&gt;
&lt;td style=&quot;width: 41.3566%; height: 19px;&quot;&gt;클라이언트 측에 저장 (로컬 스토리지, 쿠키 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 19.3798%; height: 19px;&quot;&gt;상태 관리&lt;/td&gt;
&lt;td style=&quot;width: 39.2635%; height: 19px;&quot;&gt;서버가 상태를 유지 (Stateful)&lt;/td&gt;
&lt;td style=&quot;width: 41.3566%; height: 19px;&quot;&gt;서버는 상태를 유지하지 않음 (Stateless)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 19.3798%; height: 19px;&quot;&gt;확장성&lt;/td&gt;
&lt;td style=&quot;width: 39.2635%; height: 19px;&quot;&gt;서버 확장 시 세션 동기화 문제 발생 가능성 O&lt;/td&gt;
&lt;td style=&quot;width: 41.3566%; height: 19px;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;서버가 상태를 유지하지 않기 때문에&amp;nbsp;&lt;/span&gt;서버 확장 영향 X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 19.3798%; height: 19px;&quot;&gt;인증 방식&lt;/td&gt;
&lt;td style=&quot;width: 39.2635%; height: 19px;&quot;&gt;세션 ID로 사용자 식별&lt;/td&gt;
&lt;td style=&quot;width: 41.3566%; height: 19px;&quot;&gt;서명된 토큰의 유효성 검증으로 인증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 19.3798%; height: 19px;&quot;&gt;보안&lt;/td&gt;
&lt;td style=&quot;width: 39.2635%; height: 19px;&quot;&gt;세션 ID 탈취 위험, 서버 측에서 세션 무효화 가능&lt;/td&gt;
&lt;td style=&quot;width: 41.3566%; height: 19px;&quot;&gt;서명 위조 방지, &lt;br /&gt;일단 발급된 토큰은 만료 전까지 무효화 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 19.3798%; height: 19px;&quot;&gt;Keycloak에서의 역할&lt;/td&gt;
&lt;td style=&quot;width: 39.2635%; height: 19px;&quot;&gt;사용자의 로그인 상태 관리, SSO 구현&lt;/td&gt;
&lt;td style=&quot;width: 41.3566%; height: 19px;&quot;&gt;클라이언트 애플리케이션에 사용자 정보 및 권한 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3798%;&quot;&gt;기타&lt;/td&gt;
&lt;td style=&quot;width: 39.2635%;&quot;&gt;UserSession, ClientSession, Single Sign-On Session ...&lt;/td&gt;
&lt;td style=&quot;width: 41.3566%;&quot;&gt;JWT(JSON Web Token),SAML 토큰..&lt;br /&gt;Access Token, Refresh Token, ID Token...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Keycloak에서 세션 및 토큰이 어떻게 사용 및 관리되는지 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ 세션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세션관리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션은 서버에 저장되는 &lt;b&gt;사용자 상태 정보&lt;/b&gt;이다.&lt;br /&gt;만약 세션이 서버에 저장되어있지 않다면, 사용자는 로그인 후 페이지 이동시마다 다시 로그인 해야할수도 있다.&lt;br /&gt;세션을 어떤 관점에서 관리해야 하는지 살펴보자면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 관점: 인증여부(로그인여부), 인증 기간(세션만료기간), 재인증 시기 등을 통해 사용자 경험에 영향 미침&amp;nbsp;&lt;/li&gt;
&lt;li&gt;보안 관점: 사용자 활동을 추적 및 제어 (토큰 검증/ 무효화 등)&lt;/li&gt;
&lt;li&gt;성능 관점: 활성 세션을 어떻게 관리하느냐에 따라 서버에 부하를 줄 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;세션 종류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에서 정확히 어떤 세션들을 사용하고있는지 찾아보려고 했지만 정리되어있는건 찾지 못했고,&lt;br /&gt;이전에&amp;nbsp; &lt;a href=&quot;https://1mini2.tistory.com/185#%E2%96%AA%EF%B8%8E_Cache_%EC%98%B5%EC%85%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[keycloak 맛보기 #7] Production Keycloak을 위한 설정&lt;/a&gt; 부분에서 Keycloak 다중 노드 클러스터 아키텍쳐를 공부할때, 아래&amp;nbsp; 세션들이 Embedded ISPN Cache에 저장된다는 것을 언급한적이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;authenticationSessions: 인증 프로세스 중인 세션 (인증 흐름이 완료되지 않은 상태의 세션, (예: 사용자명/비밀번호 입력, MFA, 소셜 로그인 등)에서 사용되는 임시 세션)&lt;/li&gt;
&lt;li&gt;sessions: 사용자 세션 (인증이 완료된 사용자의 세션, SSO 기능이 이 세션을 기반으로 작동함)&lt;/li&gt;
&lt;li&gt;clientSessions: 클라이언트 세션 (특정 클라이언트와 연결된 세션)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak admin에서 어떻게 구현되어있는지 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-12 오전 12.54.44.png&quot; data-origin-width=&quot;3520&quot; data-origin-height=&quot;3038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l1k7c/btsMF2R0tmT/JRaHqV1wkRQ1cwtSfP49eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l1k7c/btsMF2R0tmT/JRaHqV1wkRQ1cwtSfP49eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l1k7c/btsMF2R0tmT/JRaHqV1wkRQ1cwtSfP49eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl1k7c%2FbtsMF2R0tmT%2FJRaHqV1wkRQ1cwtSfP49eK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3520&quot; height=&quot;3038&quot; data-filename=&quot;스크린샷 2025-03-12 오전 12.54.44.png&quot; data-origin-width=&quot;3520&quot; data-origin-height=&quot;3038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SSO&amp;nbsp;Session&amp;nbsp;Settings&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;SSO Session Idle: 사용자가 활동하지 않을 때 세션이 유지되는 시간 (기본값 30분)&lt;/li&gt;
&lt;li&gt;SSO Session Max: 사용자 세션의 최대 유지 시간 (기본값 10시간)&lt;/li&gt;
&lt;li&gt;사용자가 로그인 페이지에 Remember Me 옵션이 있는 경우 Idle 시간과 Max 시간도 별도로 설정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Client Session Settings&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Client Session Idle: 특정 클라이언트에 대한 세션이 비활성 상태로 유지되는 시간&lt;/li&gt;
&lt;li&gt;Client Session Max: 클라이언트 세션의 최대 유지 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Offline Session Settings&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Offline Session Idle: 오프라인 토큰 사용 시 세션이 유지되는 비활성 시간 (기본값 30일)&lt;/li&gt;
&lt;li&gt;Offline Session Max Limited: 오프라인 세션의 최대 유지 시간 제한 (기본적으로 비활성화)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Login Settings&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Login timeout: 로그인 프로세스가 완료되지 않을 경우 타임아웃 시간 (기본값 30분)&lt;/li&gt;
&lt;li&gt;Login action timeout: 로그인 액션(예: OTP 입력)의 타임아웃 시간 (기본값 5분)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[참고] Offline Session 이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오프라인 세션(Offline Session)은 Keycloak에서 제공하는 특별한 유형의 세션으로, &lt;b&gt;계속 유지되는 세션&lt;/b&gt;이라고 이해할 수 있다.&lt;br /&gt;실 상황에서는 모바일 앱 동작에서 사용할 수 있는데, 처음 로그인 후 몇 주 혹은 몇 달 동안 앱에 재 로그인(재인증)하지 않고 앱을 사용할 수 있는 이유가 백그라운드에서 Offline Session을 유지하고 있기 때문이다. (사용예시, IoT장비, 키오스크 및 공용 단말기 등)&lt;br /&gt;OfflineSession을 위해서는 OfflineToken이 사용되며, offline_access 스코프를 요청할 때 발급된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;세션의 계층관계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역 세션과 클라이언트별 별도 세션을 나누어서 관리할 수 있다.&lt;br /&gt;SSO Session은 전역 설정으로 SSO Session이 만료되면 모든 Client Session도 자동으로 종료된다.&lt;br /&gt;ClientSession은 종료되더라도 SSO Session과 다른 Client Session은 유지 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SSO Session (상위 레벨)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;사용자의 전체 인증 상태를 관리하는 세션&lt;/li&gt;
&lt;li&gt;Keycloak 서버에서 관리되는 중앙 집중식 세션&lt;/li&gt;
&lt;li&gt;여러 클라이언트에 걸쳐 공유됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Client Session (하위 레벨)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;특정 클라이언트 애플리케이션에 대한 세션&lt;/li&gt;
&lt;li&gt;SSO Session에 종속됨&lt;/li&gt;
&lt;li&gt;각 클라이언트별로 별도 생성됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 Keycloak에 로그인한 후 애플리케이션(A, B, C)에 로그인하면&lt;br /&gt;하나의 SSO Session과 각 애플리케이션에 대한 세 개의 Client Session이 생성된다.&lt;br /&gt;애플리케이션 A에서 로그아웃해도 SSO Session은 유지되며, B와 C에는 여전히 로그인된 상태이다.&lt;br /&gt;SSO Session이 타임아웃되거나 명시적으로 종료되면 모든 애플리케이션(A, B, C)에서 로그아웃되는 것 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;세션 확인 및 종료하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 사용자로 로그인(&lt;a style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; href=&quot;http://localhost:8082/realms/my-LDAP/account/&quot; data-testid=&quot;client-home-url-account&quot;&gt;http://localhost:8082/realms/my-LDAP/account/&lt;/a&gt;) 후 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 페이지에서 Session탭을 확인하면 &lt;b&gt;Realm내 세션 정보&lt;/b&gt;를 모두 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-12 오전 1.46.12.png&quot; data-origin-width=&quot;3434&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7FsAg/btsMH9WukLu/3FbaIf7KtSFWqynwrJ6Pl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7FsAg/btsMH9WukLu/3FbaIf7KtSFWqynwrJ6Pl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7FsAg/btsMH9WukLu/3FbaIf7KtSFWqynwrJ6Pl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7FsAg%2FbtsMH9WukLu%2F3FbaIf7KtSFWqynwrJ6Pl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3434&quot; height=&quot;906&quot; data-filename=&quot;스크린샷 2025-03-12 오전 1.46.12.png&quot; data-origin-width=&quot;3434&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actions 에서 Revocation, Sign out all active sessions를 수행할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Sign out all active sessions (모든 활성 세션 로그아웃):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;모든 활성 세션을 로그아웃 처리함&lt;/li&gt;
&lt;li&gt;모든 디바이스와 브라우저에서 로그아웃되며, 사용자는 다시 로그인하면 새 세션을 시작할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Revocation (세션 해지/ 더 강력한 제어)&lt;/b&gt;&lt;span&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;세션뿐만 아니라 관련된 토큰도 해지(무효화)함&lt;/li&gt;
&lt;li&gt;더 강력한 조치로, 이미 발급된 토큰(리프레시 토큰 포함)을 무효화함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오프라인 토큰과 같은 장기 토큰도 해지&lt;/b&gt;할 수 있음&lt;/li&gt;
&lt;li&gt;악의적인 접근이 의심될 때 유용한 보안 조치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clients &amp;gt; Sessions탭에서 &lt;b&gt;Client별 세션&lt;/b&gt;도 별도로 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-12 오전 1.47.30.png&quot; data-origin-width=&quot;3434&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/unCgl/btsMFIzomt1/kjVlfGJQUpiLt9WHtP8xdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/unCgl/btsMFIzomt1/kjVlfGJQUpiLt9WHtP8xdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/unCgl/btsMFIzomt1/kjVlfGJQUpiLt9WHtP8xdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FunCgl%2FbtsMFIzomt1%2FkjVlfGJQUpiLt9WHtP8xdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3434&quot; height=&quot;906&quot; data-filename=&quot;스크린샷 2025-03-12 오전 1.47.30.png&quot; data-origin-width=&quot;3434&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clients Session 목록을 확인할 수 있고, Actions에서 Clients Session을 삭제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Users &amp;gt; Session탭에서 &lt;b&gt;사용자별 세션&lt;/b&gt;을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-12 오전 1.56.45.png&quot; data-origin-width=&quot;3502&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7Kh7N/btsMHcfsPCI/UuPNnK18p4K316mmqLi3PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7Kh7N/btsMHcfsPCI/UuPNnK18p4K316mmqLi3PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7Kh7N/btsMHcfsPCI/UuPNnK18p4K316mmqLi3PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7Kh7N%2FbtsMHcfsPCI%2FUuPNnK18p4K316mmqLi3PK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3502&quot; height=&quot;898&quot; data-filename=&quot;스크린샷 2025-03-12 오전 1.56.45.png&quot; data-origin-width=&quot;3502&quot; data-origin-height=&quot;898&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actions를 보면 Impersonate, Delete를 수행할 수 있는데&amp;nbsp; Impersonate이 좀 특이하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Impersonate (가장하기):
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;관리자가 해당 사용자로 가장하여 시스템을 체험할 수 있는 기능&lt;/li&gt;
&lt;li&gt;사용자가 경험하는 화면이나 권한을 그대로 확인할 수 있어 문제 해결이나 테스트에 유용&lt;/li&gt;
&lt;li&gt;관리자는 사용자의 동의 없이 해당 계정으로 활동할 수 있으므로 보안상 주의가 필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;HTTP 쿠키와 세션&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 기본적으로 무상태(stateless) 프로토콜이기 때문에 각 요청은 독립적이며 이전 요청과의 연관성을 유지하지 않는다. &lt;br /&gt;Stateless 프로토콜의 한계를 극복하여 사용자의 로그인 상태를 유지하고 세션 기반의 인증 시스템을 구현(사용자의 로그인 상태를 유지) 하기 위해 쿠키를 활용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-12 오전 2.04.21.png&quot; data-origin-width=&quot;5296&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ugHbs/btsMI5MH3MW/yoVpZpYf1D8YZKkujKIRsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ugHbs/btsMI5MH3MW/yoVpZpYf1D8YZKkujKIRsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ugHbs/btsMI5MH3MW/yoVpZpYf1D8YZKkujKIRsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FugHbs%2FbtsMI5MH3MW%2FyoVpZpYf1D8YZKkujKIRsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;5296&quot; height=&quot;1852&quot; data-filename=&quot;스크린샷 2025-03-12 오전 2.04.21.png&quot; data-origin-width=&quot;5296&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;세션 (Session)&lt;/b&gt;: Keycloak에서 세션은 서버 측에 저장되는 사용자의 인증 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AUTH_SESSION_ID: 인증 프로세스 중인 세션을 나타내며, 로그인 진행 중일 때 사용됨&lt;/li&gt;
&lt;li&gt;KEYCLOAK_IDENTITY: 사용자의 신원 정보를 포함하는 세션 데이터&lt;/li&gt;
&lt;li&gt;KEYCLOAK_SESSION: 사용자의 로그인 상태를 유지하는 메인 세션 식별자&lt;/li&gt;
&lt;li&gt;이러한 세션 정보는 Keycloak 서버의 캐시(일반적으로 Infinispan)에 저장됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쿠키 (Cookies):&lt;/b&gt; 쿠키는 클라이언트(브라우저) 측에 저장되는 정보로, 세션과 연결하는 역할을 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저는 Keycloak 서버에서 받은 쿠키를 저장합니다.&lt;/li&gt;
&lt;li&gt;쿠키에는 세션 ID가 포함되어 있어서 서버가 사용자의 세션을 식별할 수 있다.&lt;/li&gt;
&lt;li&gt;이미지에서 보이는 쿠키들(AUTH_SESSION_ID, KEYCLOAK_IDENTITY, KEYCLOAK_SESSION)은 서버의 해당 세션을 참조한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 과정에서는 사용자가 로그인을 시도하면 AUTH_SESSION_ID 쿠키가 생성되고, 인증이 완료되면 KEYCLOAK_SESSION과 KEYCLOAK_IDENTITY 쿠키가 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 유지를 위해서 브라우저는 요청마다 쿠키를 Keycloak 서버에 보내고, 서버는 쿠키의 세션ID를 사용하여 저장된 세션 정보를 조회한다. 이때, 세션이 유효하면 요청을 처리하고, 만료되면 재인증을 요구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 HttpOnly 플래그를 통해 자바스크립트를 통한 접근을 방지하고,&lt;br /&gt;Secure 플래그를 통해 HTTPS 연결에서만 쿠키가 전송되도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ 토큰&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;토큰 개요&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰과 세션의 연관관계를 살펴보자면,&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용자 로그인으로 사용자인증이 완료되면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;토큰발급과 동시에 세션이 생성되며, 사용자(=브라우저)에 토큰과 쿠키(=세션)값이 전달&lt;/b&gt;된다.&lt;br /&gt;세션이 무효화되면 해당 세션으로 발급된 토큰도 검증 시 무효화될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적으로 토큰은 유효시간을&amp;nbsp;&lt;/b&gt;은 짧게 설정하는 것이 보안에 유리하다. AccessToken의 경우 보통 5-15분 정도로 설정한다.&lt;br /&gt;그 이유는 짧은 수명은 토큰이 탈취되더라도 악용 가능한 시간을 제한하며, Refresh Token을 통해 새로운 Access Token을 얻을 수 있으므로 사용자 경험에 큰 영향 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 유효시간이 너무 짧으면 서버 성능에 영향을 미칠 수 있고, 너무 길면 탈취 위험이 존재하니 일반적인 권장사항을 기준으로 설정하고,&lt;br /&gt;필요한 경우에만 커스텀 하는것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;토큰 종류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak에서의 토큰은 사용자가 로그인(인증)에 성공한 후 발급받는 인증/인가 티켓이라고 할 수 있다.&lt;br /&gt;예를들어 놀이공원에 언제 입장 가능한지, 어떤 놀이기구를 탈 수 있는지 등이 적인 티켓이라고 생각하면 이해하기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 OAuth 2.0과 OpenID Connect(OIDC) 프로토콜에서 사용되며, 아래와 같은 특징을 가지고 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 75px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 8.8372%; height: 19px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 31.5116%; height: 19px;&quot;&gt;ID Token&lt;/td&gt;
&lt;td style=&quot;width: 28.0233%; height: 19px;&quot;&gt;Access&amp;nbsp;Token&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%; height: 19px;&quot;&gt;Refresh Token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 8.8372%; height: 19px;&quot;&gt;목적&lt;/td&gt;
&lt;td style=&quot;width: 31.5116%; height: 19px;&quot;&gt;&lt;span&gt;사용자의 신원(identity) 증명&lt;/span&gt;&lt;br /&gt;&lt;span&gt;사용자가 누구인지 증명하는 토큰&lt;br /&gt;OpenID Connect 프로토콜의 핵심 요소&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.0233%; height: 19px;&quot;&gt;&lt;span&gt;자원에 대한 접근 권한 증명&lt;/span&gt;&lt;br /&gt;&lt;span&gt;특정 리소스나 API에 접근할 수 있는 권한을 나타냄&lt;/span&gt;&lt;br /&gt;&lt;span&gt;OAuth 2.0 프로토콜의 핵심 요소&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%; height: 19px;&quot;&gt;&lt;span&gt;새로운 액세스 토큰을 얻기 위한 토큰&lt;/span&gt;&lt;br /&gt;&lt;span&gt;액세스 토큰이 만료되었을 때 새 토큰을 발급받는 용도&lt;/span&gt;&lt;br /&gt;&lt;span&gt;사용자가 다시 로그인할 필요 없이 인증 상태를 유지&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 8.8372%; height: 19px;&quot;&gt;포함 정보&lt;/td&gt;
&lt;td style=&quot;width: 31.5116%; height: 19px;&quot;&gt;&lt;span&gt;사용자 ID, 이름, &lt;br /&gt;이메일 등 사용자 프로필 정보,&lt;/span&gt;&lt;br /&gt;&lt;span&gt;토큰 발급자, 대상 클라이언트, &lt;br /&gt;만료 시간&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.0233%; height: 19px;&quot;&gt;&lt;span&gt;사용자 권한(roles, scopes)&lt;/span&gt;&lt;br /&gt;&lt;span&gt;토큰 발급자, 대상 서비스, 만료 시간&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%; height: 19px;&quot;&gt;&lt;span&gt;보통 사용자 식별 정보와 범위(scope) 정보&lt;/span&gt;&lt;br /&gt;&lt;span&gt;토큰 발급자, 대상 클라이언트, 만료 시간&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 8.8372%; height: 18px;&quot;&gt;용도&lt;/td&gt;
&lt;td style=&quot;width: 31.5116%; height: 18px;&quot;&gt;&lt;span&gt;클라이언트 애플리케이션이 로그인한 사용자를 식별&lt;/span&gt;&lt;br /&gt;&lt;span&gt;주로 인증(Authentication) 단계에서 사용&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.0233%; height: 18px;&quot;&gt;&lt;span&gt;보호된 API나 리소스 서버에 접근할 때 사용&lt;/span&gt;&lt;br /&gt;&lt;span&gt;주로 인가(Authorization) 단계에서 사용&lt;/span&gt;&lt;br /&gt;&lt;span&gt;API 요청 시 Authorization 헤더에 포함됨&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%; height: 18px;&quot;&gt;&lt;span&gt;액세스 토큰의 수명을 짧게 유지하면서도 사용자 경험 보장&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;클라이언트는 리프레시 토큰을 서버에 제출하여 새 액세스 토큰을 요청&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;일반적으로 액세스 토큰보다 긴 수명(hours, days)이나, Refresh Token Rotation 사용 가능&lt;br /&gt;클라이언트에&amp;nbsp;안전하게&amp;nbsp;저장되어야&amp;nbsp;함&lt;br /&gt;서버&amp;nbsp;측에서&amp;nbsp;취소&amp;nbsp;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 이전에 정리한 포스팅 [&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://1mini2.tistory.com/178&quot;&gt;keycloak 맛보기 #2] OpenID Connection 사용자 인증 이해하기&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에서 다루고 있으니,&amp;nbsp;&lt;br /&gt;이해 어려운 부분은 보고 이해하고 다시 오자!&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;토큰 설정 (Lifespan 등)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak Realm Settings에서 Tokens 설정 수정이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-12 오전 3.04.06.png&quot; data-origin-width=&quot;3380&quot; data-origin-height=&quot;1306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFczQ/btsMHwR9FVz/O8WJngeI1XBX8jDUuIOAZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFczQ/btsMHwR9FVz/O8WJngeI1XBX8jDUuIOAZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFczQ/btsMHwR9FVz/O8WJngeI1XBX8jDUuIOAZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFczQ%2FbtsMHwR9FVz%2FO8WJngeI1XBX8jDUuIOAZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3380&quot; height=&quot;1306&quot; data-filename=&quot;스크린샷 2025-03-12 오전 3.04.06.png&quot; data-origin-width=&quot;3380&quot; data-origin-height=&quot;1306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;General (일반 토큰 설정)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Default Signature Algorithm: 토큰 서명에 사용되는 기본 알고리즘 (RS256 설정)&lt;/li&gt;
&lt;li&gt;OAuth 2.0 Device Code Lifespan: 장치 인증 코드의 유효 기간 (10분)&lt;/li&gt;
&lt;li&gt;OAuth 2.0 Device Polling Interval: 장치 인증 폴링 간격 (5초)&lt;/li&gt;
&lt;li&gt;Short verification_uri in Device Authorization flow: 장치 인증 흐름에서 짧은 확인 URI 사용&lt;/li&gt;
&lt;li&gt;Lifetime&amp;nbsp;of&amp;nbsp;the&amp;nbsp;Request&amp;nbsp;URI&amp;nbsp;for&amp;nbsp;Pushed&amp;nbsp;Authorization&amp;nbsp;Request:&amp;nbsp;푸시된&amp;nbsp;인가&amp;nbsp;요청의&amp;nbsp;요청&amp;nbsp;URI&amp;nbsp;수명&amp;nbsp;(1분)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Refresh tokens&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Revoke Refresh Token: 리프레시 토큰 사용 후 해지 여부 (비활성화됨). &lt;br /&gt;(활성화하면 리프레시 토큰은 한 번만 사용 가능하며, 새 액세스 토큰을 얻을 때 새 리프레시 토큰이 발급됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Access tokens&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Access Token Lifespan: 일반 액세스 토큰의 유효 기간 (5분 권장)&lt;/li&gt;
&lt;li&gt;Access Token Lifespan For Implicit Flow: OAuth 2.0 암묵적 흐름에서 사용되는 액세스 토큰의 유효 기간 (15분 설정)&lt;/li&gt;
&lt;li&gt;Client Login Timeout: 클라이언트가 로그인을 완료해야 하는 제한 시간 (1분 설정)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Action tokens&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;User-Initiated Action Lifespan: 사용자가 시작한 작업 토큰의 유효 기간 (5분)&lt;/li&gt;
&lt;li&gt;Default Admin-Initiated Action Lifespan: 관리자가 시작한 작업 토큰의 기본 유효 기간 (12시간)&lt;/li&gt;
&lt;li&gt;Email Verification: 이메일 확인 링크의 유효 기간&lt;/li&gt;
&lt;li&gt;IdP account email verification: 외부 ID 제공자 계정 이메일 확인의 유효 기간&lt;/li&gt;
&lt;li&gt;Forgot password: 비밀번호 재설정 링크의 유효 기간&lt;/li&gt;
&lt;li&gt;Execute actions: 필수 작업 실행 링크의 유효 기간&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 Client별로 Access Token의 유효시간을 커스텀 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-12 오전 3.08.13.png&quot; data-origin-width=&quot;3380&quot; data-origin-height=&quot;1306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBNkYD/btsMI3nPEAd/PjMNv4cCSh2jIxoiCNBPd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBNkYD/btsMI3nPEAd/PjMNv4cCSh2jIxoiCNBPd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBNkYD/btsMI3nPEAd/PjMNv4cCSh2jIxoiCNBPd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBNkYD%2FbtsMI3nPEAd%2FPjMNv4cCSh2jIxoiCNBPd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3380&quot; height=&quot;1306&quot; data-filename=&quot;스크린샷 2025-03-12 오전 3.08.13.png&quot; data-origin-width=&quot;3380&quot; data-origin-height=&quot;1306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4️⃣&amp;nbsp; 세션, 쿠키, 토큰 Keycloak SSO 환경에서의&amp;nbsp; 상호작용&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-12 오전 2.34.53.png&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;2812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ubgwl/btsMHlDkZ2q/4Uxdd7ZkSdCmAQ2sG3CUv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ubgwl/btsMHlDkZ2q/4Uxdd7ZkSdCmAQ2sG3CUv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ubgwl/btsMHlDkZ2q/4Uxdd7ZkSdCmAQ2sG3CUv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUbgwl%2FbtsMHlDkZ2q%2F4Uxdd7ZkSdCmAQ2sG3CUv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1632&quot; height=&quot;2812&quot; data-filename=&quot;스크린샷 2025-03-12 오전 2.34.53.png&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;2812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;초기 접근 및 인증&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 A 애플리케이션에 접근하면 Keycloak으로 리다이렉트&lt;/li&gt;
&lt;li&gt;사용자 인증 후 Keycloak 서버에 세션이 생성되고 토큰 발급&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쿠키 설정 및 리다이렉트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Keycloak은 브라우저에 세션 쿠키를 설정하고 애플리케이션으로 리다이렉트&lt;/li&gt;
&lt;li&gt;토큰(ID Token, Access Token, Refresh Token)이 애플리케이션에 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;애플리케이션 접근&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 애플리케이션은 토큰을 검증하고 사용자에게 리소스를 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다른 앱 SSO 접근&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 B 애플리케이션에 접근하면 Keycloak은 쿠키를 통해 기존 세션 확인&lt;/li&gt;
&lt;li&gt;재인증 없이 B 애플리케이션용 토큰이 발급되어 SSO 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토큰 갱신&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Access Token이 만료되면 애플리케이션은 Refresh Token을 사용해 새 토큰을 요청&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;세션 종료&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그아웃 시 Keycloak은 세션을 종료하고 모든 SSO 애플리케이션에 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Infra/KeyCloak</category>
      <category>auth_session_id</category>
      <category>keycloak</category>
      <category>keycloak session</category>
      <category>keycloak token</category>
      <category>keycloak 실습</category>
      <category>keycloak_identity</category>
      <category>keycloak_session</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/188</guid>
      <comments>https://1mini2.tistory.com/188#entry188comment</comments>
      <pubDate>Wed, 12 Mar 2025 02:15:41 +0900</pubDate>
    </item>
    <item>
      <title>[keycloak 맛보기#8] 사용자 인증(Authentication)</title>
      <link>https://1mini2.tistory.com/187</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#configuring-authentication_server_administration_guide&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;  공식문서 링크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증은 Client(사람/기기/애플리케이션 등)의 신원을 검증하는것을 말한다.&lt;br /&gt;Keycloak은 OAuth2.0 기반의 인증/인가 프로세스를 가지고 있고, Keycloak에서의 인증은 어떻게 사용할 수 있는지 어떤 기능이 있는지 살펴보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;인증 흐름 (Authentication flows)&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#_authentication-flows&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;  공식문서 링크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;인증 흐름이란?&lt;/u&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인증 흐름&lt;/b&gt;이란 클라이언트가 시스템에 접근할 때 거치는 인증 단계의 순서와 방법을 정의한 것을 말한다.&lt;br /&gt;Keycloak에서는 인증 흐름을 &lt;b&gt;재사용&lt;/b&gt; 할 수 있도록 Template 형태로 정의할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak에서 인증을 어떻게 수행할지 그 방법과 순서를 &lt;b&gt;Authentication Flow&lt;/b&gt;로 정의하고,&lt;br /&gt;정의된 Authentication Flow를 특정 인증 상황에 &lt;b&gt;연결(Bind)해서 사용&lt;/b&gt;할 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak에서 사용할 수 있는 인증 흐름은 아래와 같다. 인증 Flow 탬플릿을 정의해서 Bind하여 사용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Browser Flow&lt;/b&gt;: 웹 브라우저 기반 로그인을 처리하는 기본 흐름 (사용자명/비밀번호, OTP, WebAuthn 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Client Authentication&lt;/b&gt;: 클라이언트(애플리케이션) 인증 방식을 정의하는 흐름&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Direct Grant Flow&lt;/b&gt;: 사용자 자격 증명을 직접 API로 전송하는 방식(REST API 인증용)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Docker Authentication&lt;/b&gt;: Docker 레지스트리 인증을 위한 특수 흐름&lt;/li&gt;
&lt;li&gt;&lt;b&gt;First Broker Login&lt;/b&gt;: 소셜 로그인이나 외부 IDP 최초 사용 시 계정 연결 방식 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Registration Flow&lt;/b&gt;: 새 사용자 등록 과정에서 사용되는 단계와 검증을 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reset Credentials Flow&lt;/b&gt;: 비밀번호 재설정 프로세스를 관리하는 흐름&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;재사용한다&quot;는 의미를 정확히 이해할 수 있게 실습을 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;[참고]&lt;/u&gt;&lt;u&gt;&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://1mini2.tistory.com/177#-_%EC%A3%BC%EC%9A%94_%EA%B6%8C%ED%95%9C_%EB%B6%80%EC%97%AC_%EC%9C%A0%ED%98%95(Grant_Types)&quot;&gt;OAuth2.0의 권한부여 유형(Grant Types)&lt;/a&gt;과 어떤 관계가 있는지?&lt;/u&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전 &lt;a href=&quot;https://1mini2.tistory.com/177#-_%EC%A3%BC%EC%9A%94_%EA%B6%8C%ED%95%9C_%EB%B6%80%EC%97%AC_%EC%9C%A0%ED%98%95(Grant_Types)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OAuth2.0 정리하는 포스팅&lt;/a&gt;에서 주요 &lt;b&gt;권한 부여 유형&lt;/b&gt;에 대해 살펴 봤었는데, 이것과 어떤 관계인건지 확인하보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Keycloak은 OAuth 2.0/OIDC 프로토콜을 구현하는 제품이며, Grant Types를 구현하기 위해 Authentication Flow를 사용한다.&lt;br /&gt;예를들면 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Authorization Code Grant&lt;/b&gt;: Browser Flow를 통해 구현 (사용자 로그인, 권한 동의 등의 단계 포함)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Client Credentials Grant&lt;/b&gt;: Client Authentication Flow를 통해 구현 (클라이언트 ID/시크릿 검증)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Resource Owner Password Grant&lt;/b&gt;: Direct Grant Flow를 통해 구현 (사용자 ID/비밀번호 직접 검증)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Device Authorization Grant&lt;/b&gt;: HTTP Challenge Flow를 통해 구현 (기기 인증 코드 검증)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, Grant Type은 OAuth2.0수준에서 정의된 프로토콜 수준의 명세이고, Keycloak의 Authentication Flow는 구현체인것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;인증 흐름 (Authentication flows) 실습해보기&lt;/u&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Keycloak을 실행해보자. &lt;br /&gt;이전 포스팅에서 Docker로 실행하는것과 K8s(minikube) helm으로 실행하는것 모두 실습해보았는데, 실행 방법 편한 방법으로 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1741447146026&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Docker로 실행하기
docker run -p 8080:8080 \
          -e KEYCLOAK_ADMIN=admin \
          -e KEYCLOAK_ADMIN_PASSWORD=admin \
          quay.io/keycloak/keycloak:24.0.4 \
          start-dev

# k8s(minikube) helm으로 실행하기
minikube start
helm install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak \
  --set auth.adminUser=admin \
  --set auth.adminPassword=admin \
  --set postgresql.auth.username=bn_keycloak \
  --set postgresql.auth.password=password \
  --set postgresql.auth.database=keycloak \
  --set postgresql.auth.postgresPassword=password
kubectl port-forward svc/keycloak 8082:80 &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 Realm을 만든다. (my-Authentication)&lt;br /&gt;개념만 이해할 예정이니 기존에 테스트 하던 Realm을 사용해도 상관없다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.21.19.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;1426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ofclF/btsMFEIIXw0/0lorKJQniHVXqELkoP09Mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ofclF/btsMFEIIXw0/0lorKJQniHVXqELkoP09Mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ofclF/btsMFEIIXw0/0lorKJQniHVXqELkoP09Mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FofclF%2FbtsMFEIIXw0%2F0lorKJQniHVXqELkoP09Mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2660&quot; height=&quot;1426&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.21.19.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;1426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 테스트를 위해 사용자를 만들어보자.&lt;br /&gt;나는 user01 사용자를 생성했다. 유저를 생성한 후에는 반드시 Credentials에서 비밀번호를 추가해주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.38.11.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNCrKa/btsMELvgyRC/3g0MsqwkBWxsphdL6BjzfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNCrKa/btsMELvgyRC/3g0MsqwkBWxsphdL6BjzfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNCrKa/btsMELvgyRC/3g0MsqwkBWxsphdL6BjzfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNCrKa%2FbtsMELvgyRC%2F3g0MsqwkBWxsphdL6BjzfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2678&quot; height=&quot;1718&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.38.11.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 로그인 테스트를 해보자.&lt;br /&gt;왼쪽 네비게이션바 Clients에서 account home URL을 클릭하면 사용자 로그인 페이지로 들어갈 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.39.18.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HF94P/btsMFt8ucIb/lrHJkp3rkKIO4LMteA6jkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HF94P/btsMFt8ucIb/lrHJkp3rkKIO4LMteA6jkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HF94P/btsMFt8ucIb/lrHJkp3rkKIO4LMteA6jkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHF94P%2FbtsMFt8ucIb%2FlrHJkp3rkKIO4LMteA6jkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2678&quot; height=&quot;1114&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.39.18.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 로그인 페이지에서 계정과 비밀번호를 입력하게 되어있고, 계정 정보를 입력하면 바로 로그인 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.40.33.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvx9G1/btsMDZabL3B/gtT0NJAZybKZ5aq9C2knkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvx9G1/btsMDZabL3B/gtT0NJAZybKZ5aq9C2knkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvx9G1/btsMDZabL3B/gtT0NJAZybKZ5aq9C2knkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcvx9G1%2FbtsMDZabL3B%2FgtT0NJAZybKZ5aq9C2knkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2678&quot; height=&quot;1242&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.40.33.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.41.17.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmKZkj/btsMFyu3dqJ/rF8bTNvQ9C0dsFIbR6A490/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmKZkj/btsMFyu3dqJ/rF8bTNvQ9C0dsFIbR6A490/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmKZkj/btsMFyu3dqJ/rF8bTNvQ9C0dsFIbR6A490/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmKZkj%2FbtsMFyu3dqJ%2FrF8bTNvQ9C0dsFIbR6A490%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2678&quot; height=&quot;1326&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.41.17.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 기본값으로 로그인을 했으니, 인증 방식을 변경해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 네비게이션바에서 `Authentication`을 클릭하자.&lt;br /&gt;사용가능한 인증 Flow가 정의되어있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.22.52.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;1732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOUjkL/btsMFIdf1Oz/TGvQXm6Q8ZMHZmlcFn8ZKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOUjkL/btsMFIdf1Oz/TGvQXm6Q8ZMHZmlcFn8ZKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOUjkL/btsMFIdf1Oz/TGvQXm6Q8ZMHZmlcFn8ZKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOUjkL%2FbtsMFIdf1Oz%2FTGvQXm6Q8ZMHZmlcFn8ZKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2660&quot; height=&quot;1732&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.22.52.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;1732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재사용 가능하다는것이 어떤 의미인지 테스트 해보자. 먼저 Browser Flow를 복제한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.27.26.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YGkoH/btsMFkcJiTD/q7TB8kfbFcK0haqKDQNmkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YGkoH/btsMFkcJiTD/q7TB8kfbFcK0haqKDQNmkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YGkoH/btsMFkcJiTD/q7TB8kfbFcK0haqKDQNmkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYGkoH%2FbtsMFkcJiTD%2Fq7TB8kfbFcK0haqKDQNmkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2660&quot; height=&quot;298&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.27.26.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.28.23.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/esLDZh/btsMEZz1V1h/TK9Gvv247gUl6us4fpfgX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/esLDZh/btsMEZz1V1h/TK9Gvv247gUl6us4fpfgX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/esLDZh/btsMEZz1V1h/TK9Gvv247gUl6us4fpfgX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FesLDZh%2FbtsMEZz1V1h%2FTK9Gvv247gUl6us4fpfgX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2660&quot; height=&quot;544&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.28.23.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 폼을 좀 바꿔보자.&lt;br /&gt;AS-IS, TO-BE를 확인하고 그대로 수정해도 좋고, 원하는 방식으로 수정해도 된다.&lt;br /&gt;아래의 경우에는 UsernameForm 단계를 추가하고, OTP를 Required로 추가했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 2.01.28.png&quot; data-origin-width=&quot;4192&quot; data-origin-height=&quot;2376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dN5t4x/btsMF3IsY6v/5tv3WPY1uEqk0yUInil8Ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dN5t4x/btsMF3IsY6v/5tv3WPY1uEqk0yUInil8Ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dN5t4x/btsMF3IsY6v/5tv3WPY1uEqk0yUInil8Ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdN5t4x%2FbtsMF3IsY6v%2F5tv3WPY1uEqk0yUInil8Ck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4192&quot; height=&quot;2376&quot; data-filename=&quot;스크린샷 2025-03-09 오전 2.01.28.png&quot; data-origin-width=&quot;4192&quot; data-origin-height=&quot;2376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 방금 수정한 Form을 Browser flow로 바인드 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.35.43.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bofIj2/btsMEWXA0lC/ZKfZBYRq8goqckAa3TWW5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bofIj2/btsMEWXA0lC/ZKfZBYRq8goqckAa3TWW5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bofIj2/btsMEWXA0lC/ZKfZBYRq8goqckAa3TWW5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbofIj2%2FbtsMEWXA0lC%2FZKfZBYRq8goqckAa3TWW5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2678&quot; height=&quot;1820&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.35.43.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;1820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.36.19.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb0nKp/btsMFtm6wvf/il8S42U4EvwPXKIfZC14E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb0nKp/btsMFtm6wvf/il8S42U4EvwPXKIfZC14E0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb0nKp/btsMFtm6wvf/il8S42U4EvwPXKIfZC14E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb0nKp%2FbtsMFtm6wvf%2Fil8S42U4EvwPXKIfZC14E0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2678&quot; height=&quot;420&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.36.19.png&quot; data-origin-width=&quot;2678&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 다시한번 사용자 로그인을 통해 인증  방식이 어떻게 변경 되었는지 확인할 수 있다.&lt;br /&gt;이전에는 계정 ID와 비밀번호를 한번에 입력한 후 바로 로그인 되었는데, &lt;br /&gt;my-test-browser 탬플릿으로 변경한 후 ID입력창, PW입력창, OTP설정이 강제된것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.47.29.png&quot; data-origin-width=&quot;2688&quot; data-origin-height=&quot;2358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvGSs9/btsMEEb4lJv/kuIq3OMyZvSmEPoRqtJEuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvGSs9/btsMEEb4lJv/kuIq3OMyZvSmEPoRqtJEuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvGSs9/btsMEEb4lJv/kuIq3OMyZvSmEPoRqtJEuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvGSs9%2FbtsMEEb4lJv%2FkuIq3OMyZvSmEPoRqtJEuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2688&quot; height=&quot;2358&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.47.29.png&quot; data-origin-width=&quot;2688&quot; data-origin-height=&quot;2358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;인증 정책 (Authentication Policies)&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 정책은 인증 방식에 대한 정의이다.&lt;br /&gt;위에서 설명했던 인증흐름이 &quot;어떤 순서로 인증할것인가&quot;에 대한 정의였다면,&amp;nbsp;&lt;br /&gt;인증 정책은 &quot;&lt;b&gt;어떻게 동작해야 하는가&lt;/b&gt;&quot;를 정의하는 것 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;인증 정책(Policies) 종류&lt;/u&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.55.51.png&quot; data-origin-width=&quot;3356&quot; data-origin-height=&quot;1476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRfA5u/btsMGfaZqLm/PKc8OfCaUitL1aXSafat5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRfA5u/btsMGfaZqLm/PKc8OfCaUitL1aXSafat5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRfA5u/btsMGfaZqLm/PKc8OfCaUitL1aXSafat5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRfA5u%2FbtsMGfaZqLm%2FPKc8OfCaUitL1aXSafat5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3356&quot; height=&quot;1476&quot; data-filename=&quot;스크린샷 2025-03-09 오전 12.55.51.png&quot; data-origin-width=&quot;3356&quot; data-origin-height=&quot;1476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Keycloak의 Authentication을 살펴보면 정말 다양한 인증 옵션을 제공하는것을 확인할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Password policy&lt;/b&gt;: 비밀번호 길이, 복잡성, 특수문자 요구사항 등 비밀번호 강도를 제어하는 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OTP Policy&lt;/b&gt;: 일회용 비밀번호 인증의 알고리즘, 자릿수, 유효기간 등을 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Webauthn Policy&lt;/b&gt;: 생체인증 등 FIDO2/WebAuthn 기반 2차 인증(MFA)을 위한 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Webauthn Passwordless Policy&lt;/b&gt;: 비밀번호 없이 WebAuthn만으로 인증할 수 있는 정책 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CIBA Policy&lt;/b&gt;: 클라이언트 주도 백채널 인증의 시간제한, 인증방식 등을 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 비밀번호 정책은 KISA홈페이지에만 가도 사용 가이드가 있으니, 자세한 내용은 패스한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;[참고] WebAuthn(Web Authentication)이란?&lt;/u&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://webauthn.guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;  webauthn.guide&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebAuthn(&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;Web Authentication)은 웹 상에서 비밀번호대신 &lt;b&gt;공개키 방식의 로그인을 지원하는 인증 기술&lt;/b&gt;이며,&lt;br /&gt;2019년 3월에 W3C(World Wide Web Consortium)와 FIDO 얼라이언스가 공동으로 개발하여 공식 웹 표준으로 승인되었다고 한다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;작동 방식:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;사이트에 등록할 때:&lt;/b&gt; &lt;br /&gt;1. 사용자가 등록을 시작하면 웹사이트(Keycloak)가 브라우저를 통해 인증 요청을 전송한다. &lt;br /&gt;2. 인증기(Authenticator- iCloud 키체인, 휴대전화/태블릿의 보안 모듈, USB 보안키 등)가 고유한 암호화 키 쌍을 생성한다.&lt;br /&gt;2. 개인 키는 인증기(Authenticator) 내부에 안전하게 저장되고, 공개 키는 브라우저를 통해 웹사이트에 전송되어 사용자 계정과 연결된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로그인할 때:&lt;/b&gt; &lt;br /&gt;1. 웹사이트는 브라우저를 통해 인증 요청(challenge)을 전송한다. &lt;br /&gt;2. 브라우저는 이 요청을 인증기(Authenticator)로 전달한다.&lt;br /&gt;3. 사용자가 지문, 얼굴 인식, PIN 코드 또는 버튼 터치 등으로 자신을 인증하면, 인증기는 저장된 개인 키를 사용하여 challenge에 서명하고 이 서명된 응답을 브라우저를 통해 웹사이트로 전송한다. &lt;br /&gt;4. 웹사이트는 등록된 공개 키로 서명을 검증하여 사용자를 인증한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;피싱 방지&lt;/b&gt;: 가짜 웹사이트는 실제 웹사이트의 키를 복제할 수 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비밀번호 없음&lt;/b&gt;: 기억할 비밀번호가 필요 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;편리함&lt;/b&gt;: 지문 하나로 빠르게 로그인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;강력한 보안&lt;/b&gt;: 비밀번호보다 훨씬 해킹하기 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Infra/KeyCloak</category>
      <category>keycloak authentication flows</category>
      <category>keycloak force otp</category>
      <category>keycloak login</category>
      <category>keycloak otp 강제</category>
      <category>keycloak 사용자 인증</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/187</guid>
      <comments>https://1mini2.tistory.com/187#entry187comment</comments>
      <pubDate>Sun, 9 Mar 2025 00:50:28 +0900</pubDate>
    </item>
    <item>
      <title>[keycloak 맛보기 #7] Production Keycloak을 위한 설정</title>
      <link>https://1mini2.tistory.com/185</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가기전에,&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&lt;a href=&quot;https://www.keycloak.org/server/configuration-production&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.keycloak.org/server/configuration-production&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak은 수백~수천명의 사용자에게 안전한 인증 및 권한부여를 제공할 수 있도록 설계되어있다.&lt;br /&gt;안전하고 안정적인 Keycloak을 프로덕션 환경에 배포하기 위해 설정해야 할 부분들을 확인 해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Keycloak의 Hostname 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.keycloak.org/server/hostname&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;* 공식문서: https://www.keycloak.org/server/hostname&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;✓ Hostname을 설정해야하는 이유&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keycloak은 보안상의 이유로 hostname 설정을 필수로한다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎ 설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak은 OIDC Discovery 엔드포인트, 사용자 비밀번호 변경 엔드포인트 등의 URL이 외부로 공개된다.&lt;br /&gt;이때, hostname이 설정되어있지 않다면 가짜 도메인으로 redirect하여 사용자 정보(token, password)를 탈취 당할수도 있다.&lt;br /&gt;따라서 Hostname을 명시적으로 설정하여 다른 도메인으로 토큰이 발급되는것을 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keycloak의 보안 강화를 위해 아래 3가지를 기본적으로 설정하는것이 바람직하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;항상 https 사용&lt;/li&gt;
&lt;li&gt;관리자 콘솔 분리&lt;/li&gt;
&lt;li&gt;프록시 설정 시 신뢰할수있는 프록시 주소 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎ 탈취 시나리오&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 조작된 Host헤더를 사용하여 Keycloak Redirect URL을 생성할때 악의적인 도메인을 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739687411201&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;사용자가 my.keycloak.com에서 로그인을 시도
&amp;darr;
공격자가 Host 헤더를 fake-site.com으로 변조
&amp;darr;
리다이렉트 URL이 fake-site.com으로 생성됨
&amp;darr;
사용자가 피싱 사이트로 리다이렉트됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 리버스 프록시 설정도 오용 될 수 있다.&lt;br /&gt;리버스 프록시 설정은 뒤에서 조금 더 다뤄보도록 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739687602904&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Client -&amp;gt; Reverse Proxy -&amp;gt; Keycloak
                      &amp;uarr;
                      | 
                      공격자가 X-Forwarded-Host 
                      헤더를 조작&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;✓ 세가지 주요 엔드포인트 그룹&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak은 각각 다른 목적을 가진 3가지 주요 엔드포인트가 있다.&lt;br /&gt;일반적인 환경에서 어떻게 쓰이는지 좀 더 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-16 오후 5.08.11.png&quot; data-origin-width=&quot;2026&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmGxEM/btsMkAHa21A/EgRzxpCrH5cHjzfSRZk25K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmGxEM/btsMkAHa21A/EgRzxpCrH5cHjzfSRZk25K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmGxEM/btsMkAHa21A/EgRzxpCrH5cHjzfSRZk25K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmGxEM%2FbtsMkAHa21A%2FEgRzxpCrH5cHjzfSRZk25K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2026&quot; height=&quot;806&quot; data-filename=&quot;스크린샷 2025-02-16 오후 5.08.11.png&quot; data-origin-width=&quot;2026&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;▪︎ frontend&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 및 애플리케이션이 직접 접근하는 공개 엔드포인트다.&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;로그인/로그아웃/&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;비밀번호 재설정&lt;/span&gt;&amp;nbsp; URL 또는 &lt;/span&gt;세션 관리와 관련된 엔드포인트 등이있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739686900599&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bin/kc.[sh|bat] start --hostname https://auth.my.com&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&amp;nbsp;&lt;/span&gt;Backend&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 애플리케이션과 Keycloak간의 내부통신을 위한 엔드포인트다.&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;인증 엔드포인트, 토큰 및 토큰 인트로스펙션 엔드포인트, userinfo 엔드포인트, JWKS URI 엔드포인트 등이 있다.&lt;br /&gt;&lt;/span&gt;기본적으로는 hostname-backchannel-dynamic&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;옵션이&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;false로 설정되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 조금 헷갈릴 수 있는 부분은 hostname-backchannel-dynamic&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span&gt; 옵션&amp;nbsp;&lt;/span&gt;&lt;/span&gt;설정을 true로 변경한다면&amp;nbsp;&lt;br /&gt;내부 통신을 위한 내부 endpoint가 활성화 된다는 뜻이라는 거다. &lt;br /&gt;서브도메인을 별도로 설정할 수 있는것이 아니다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 76.9763%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 37.093%;&quot;&gt;hostname-backchannel-dynamic=&lt;b&gt;False&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.8837%;&quot;&gt;hostname-backchannel-dynamic=&lt;b&gt;True&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 37.093%;&quot;&gt;-&amp;nbsp;백채널&amp;nbsp;통신도&amp;nbsp;--hostname&amp;nbsp;값&amp;nbsp;사용&lt;br /&gt;- 내부 서비스간 통신도 외부 URL을 통해 이루어짐&lt;/td&gt;
&lt;td style=&quot;width: 39.8837%;&quot;&gt;-&amp;nbsp;백채널&amp;nbsp;통신은&amp;nbsp;실제&amp;nbsp;요청이&amp;nbsp;들어온&amp;nbsp;호스트/IP&amp;nbsp;사용&lt;br /&gt;-&amp;nbsp;내부&amp;nbsp;통신&amp;nbsp;최적화&amp;nbsp;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span&gt;백엔드 엔드포인트는 아래 설정으로 활성화 할 수있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739687172421&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bin/kc.[sh|bat] start --hostname https://auth.my.com --hostname-backchannel-dynamic true&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt; &lt;/span&gt;Administrator&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리용 콘솔 엔드포인트다.&lt;br /&gt;별도의 호스트 네임을 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739687259903&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bin/kc.[sh|bat] start --hostname https://auth.my.com --hostname-admin https://admin.auth.my.com:8443&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. Keycloak TLS 활성화&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* 공식문서: &lt;a href=&quot;https://www.keycloak.org/server/enabletls&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.keycloak.org/server/enabletls&lt;/a&gt;,&amp;nbsp; &lt;a href=&quot;https://www.keycloak.org/server/keycloak-truststore&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.keycloak.org/server/keycloak-truststore&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;너무나 당연하게도, 외부 인터넷으로 노출된 Keycloak엔드포인트는 반드시 https/TLS를 사용해야 한다.&lt;br /&gt;다만, AWS를 사용하는 경우 ALB에서 SSL을 해제하여 외부 노출된 엔드포인트는 TLS설정을, 내부 네트워크에서는 평문통신을 사용하기도 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Keycloak은 PEM 형식의 파일이나 Java Keystore에서 필요한 인증서 인프라를 로드하도록 구성할 수 있으며, &lt;br /&gt;두 가지 모두 설정된 경우 PEM 파일이 Java Keystore보다 우선한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&lt;span&gt; AWS ALB(+ACM)에서 TLS/SSL 오프로드&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;[클라이언트] &amp;rarr; HTTPS &amp;rarr; [ALB(+ACM)] &amp;rarr; HTTP &amp;rarr; [Keycloak]&lt;/p&gt;
&lt;pre id=&quot;code_1739691404893&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bin/kc.[sh|bat] start \
 --hostname auth.my.com \
 --proxy edge \
 --http-enabled=true \
 --http-port=8080&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증서 관리가 AWS AWS&amp;nbsp;Certificate&amp;nbsp;Manager(ACM)로&amp;nbsp;간편화&lt;/li&gt;
&lt;li&gt;ACM에서 인증서 자동갱신&lt;/li&gt;
&lt;li&gt;ALB에서의 TLS/SSL 오프로딩으로 Keycloak 서버 부하 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt; End to End TLS/SSL 사용&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[클라이언트] &amp;rarr; HTTPS &amp;rarr; [ALB] &amp;rarr; HTTPS &amp;rarr; [Keycloak]&lt;/p&gt;
&lt;pre id=&quot;code_1739691536198&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# PEM 파일 사용방식
bin/kc.[sh|bat] start \
 --hostname auth.my.com \
 --proxy passthrough \
 --https-certificate-file=/path/to/cert.pem \
 --https-certificate-key-file=/path/to/key.pem \
 --http-enabled=false

# Java Keystore (JKS) 방식
###1. Keystore 생성
keytool -genkeypair \
  -alias keycloak \
  -keyalg RSA \
  -keysize 2048 \
  -validity 365 \
  -keystore keycloak.jks \
  -dname &quot;CN=auth.my.com&quot; \
  -storepass store123 \
  -keypass key123
### 2. Keycloak 시작 명령어
bin/kc.[sh|bat] start \
  --hostname auth.my.com \
  --proxy passthrough \
  --https-key-store-file=/path/to/keycloak.jks \
  --https-key-store-password=store123 \
  --https-key-store-type=JKS \
  --http-enabled=false&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전 구간 암호화로 더 높은 보안성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. Keycloak Database 설정&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* 공식문서:&lt;span&gt; &lt;a href=&quot;https://www.keycloak.org/server/db&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.keycloak.org/server/db&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Keycloak 서버를 구성할때 사용할 수 있는 database engine은 여러가지가 있다.&lt;br /&gt;(mariadb, mssql, mysql, oracle, postgres..)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음에 테스트로 설치할때는 dev-file 데이터베이스를 사용하기때문에, 프로덕션에 구성할땐 반드시 설정을 변경하여 사용해야 한다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; Postgres Database를 이용한 설정 예시&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;conf/keycloak.conf 파일에서 Database 를 위한 설정을 할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739692309358&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# conf/keycloak.conf
db=postgres # 데이터베이스 유형(mariadb, mysql, postgres..등이 있음)
db-username=keycloak # 데이터베이스 사용자 이름
db-password=password # 사용자 패스워드
db-url-host=keycloak-postgres # DB 엔드포인트&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정파일을 수정했다면 아래 명령어로 실행할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739692345715&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bin/kc.[sh|bat] build
bin/kc.[sh|bat] start --optimized&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정파일로 추가하는것이 아니라 그냥 커멘드로도 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739692385018&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bin/kc.[sh|bat] start --db postgres --db-url-host keycloak-postgres --db-username keycloak --db-password password&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 설정 옵션들과 주의사항&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;설정 가능한 옵션들은 &lt;a href=&quot;https://www.keycloak.org/server/db#_relevant_options&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt; 링크에서 확인할 수 있다.&amp;nbsp;&lt;br /&gt;oracle을 제외한 대부분의 데이터베이스 엔진 드라이버가 설치되어있고, 사용시 기본 드라이버를 사용하지만, 별도로 드라이버를 설정 할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;추가적으로 주의해야할 부분은 connection pool 부분일 것이다.&lt;br /&gt;프로덕션인 경우, max,min connection pool을 잘 설정해주어야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 데이터베이스 엔진에 따라 설정 방법이 공식문서에 잘 설명되어있으니, 공식문서를 통해 구성하는것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4. Keycloak&amp;nbsp; 다중 노드 클러스터 설정 - Cache&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* 공식문서:&lt;span&gt;&lt;span&gt; &lt;a href=&quot;https://www.keycloak.org/server/caching&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.keycloak.org/server/caching&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;아키텍쳐&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;Keycloak은 고가용성(HA) 및 다중 노트 클러스터링 설정을 할 수 있도록 설계되어있다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;여러대의 Keycloak노드를 사용하므로써, 특정 노드가 중단되도 장애를 방지할 수 있고, 부하에 따라 노드를 확장/축소 함으로써 가용성을 확보할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;Keycloak 클러스터링의 핵심은 &lt;b&gt;Reverse Proxy(LoadBalanceing)&lt;/b&gt; 및&amp;nbsp;&lt;b&gt;캐시 구성&lt;/b&gt;이라고 할 수 있다.&lt;br /&gt;Keycloak은 Infinispan을 기반으로 (key-value In-memory) 클러스터 캐시를 구성할 수 있도록 제공한다.&lt;br /&gt;캐시 구성이 클러스터 설정에 어떤역할을 하는지 확인해본다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-16 오후 6.00.58.png&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAjg5K/btsMkANXz2C/M2jNJR3hird1Hhu6HBeADk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAjg5K/btsMkANXz2C/M2jNJR3hird1Hhu6HBeADk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAjg5K/btsMkANXz2C/M2jNJR3hird1Hhu6HBeADk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAjg5K%2FbtsMkANXz2C%2FM2jNJR3hird1Hhu6HBeADk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2532&quot; height=&quot;1198&quot; data-filename=&quot;스크린샷 2025-02-16 오후 6.00.58.png&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; Cache 옵션&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;Keycloak의 &lt;span&gt;&lt;span&gt;캐시는 --cache 옵션으로 설정할 수 있고, 옵션은 local/ ispn이 있다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;--cache=local&lt;/span&gt; 인 경우 모든 캐시가 로컬에서만 동작(&lt;span style=&quot;color: #ee2323;&quot;&gt;클러스터링❌불가능&lt;/span&gt;):
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;sessions, clientSessions도 로컬에만 저장&lt;/li&gt;
&lt;li&gt;realms, users 등도 로컬에만 저장&lt;/li&gt;
&lt;li&gt;노드간 데이터 공유 없음&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;--cache=ispn*&lt;/span&gt; 인 경우 클러스터 내 공유되어야하는 데이터가 노드 외부에 캐시로 저장 (&lt;span style=&quot;color: #ee2323;&quot;&gt;클러스터링  가능&lt;/span&gt;):&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Session 등 클러스터 전체에 공유되어야 하는 데이터가 외부 캐시에 저장됨 (아래 표 참고)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 85.3488%; height: 190px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; height: 19px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; height: 19px;&quot;&gt;Local Cache&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; height: 19px;&quot;&gt;Distribute Cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 19px;&quot;&gt;목적&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;데이터베이스의 부하를 줄이기 위한 로컬 캐시&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;클러스터 전체에 공유되어야 하는 데이터 캐시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 95px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 95px;&quot;&gt;종류&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; height: 95px;&quot;&gt;-&amp;nbsp;realms:&amp;nbsp;Realm&amp;nbsp;데이터&lt;br /&gt;-&amp;nbsp;users:&amp;nbsp;사용자&amp;nbsp;데이터&lt;br /&gt;-&amp;nbsp;authorization:&amp;nbsp;권한&amp;nbsp;데이터&lt;br /&gt;-&amp;nbsp;keys:&amp;nbsp;공개키&amp;nbsp;데이터&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; height: 95px;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;authenticationSessions:&amp;nbsp;인증&amp;nbsp;프로세스&amp;nbsp;중인&amp;nbsp;세션&lt;/span&gt;&lt;br /&gt;-&amp;nbsp;sessions:&amp;nbsp;사용자&amp;nbsp;세션&lt;br /&gt;- clientSessions: 클라이언트 세션&lt;br /&gt;-&amp;nbsp;loginFailures:&amp;nbsp;로그인&amp;nbsp;실패&amp;nbsp;기록&lt;br /&gt;-&amp;nbsp;actionTokens:&amp;nbsp;일회용&amp;nbsp;액션&amp;nbsp;토큰&lt;br /&gt;등..&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 57px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 57px;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;height: 57px;&quot;&gt;- 노드별로 독립적으로 동작&lt;br /&gt;- 클라이언트 세션 캐시 기본 10,000개 엔트리 저장&lt;br /&gt;- 다른 노드의 변경사항은 work 캐시*를 통해 무효화&lt;/td&gt;
&lt;td style=&quot;height: 57px;&quot;&gt;- 세션&amp;nbsp;데이터가&amp;nbsp;특정&amp;nbsp;노드들에&amp;nbsp;분산&amp;nbsp;저장&lt;br /&gt;- owners&amp;nbsp;설정에&amp;nbsp;따라&amp;nbsp;복제본&amp;nbsp;수&amp;nbsp;결정&lt;br /&gt;- 노드&amp;nbsp;장애시&amp;nbsp;자동&amp;nbsp;복구&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;* &lt;b&gt;ispn&lt;/b&gt;은 Infinispan(ispn/&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://infinispan.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크)&lt;/a&gt;을 말하며, Redis나 Memcache와 같은 캐시 시스템이다.&lt;br /&gt;keycloak은 공식적으로 ispn만 지원한다.&lt;br /&gt;* &lt;b&gt;work cache&lt;/b&gt;는 캐시 무효화를 위한 방법이다. (&lt;a href=&quot;https://www.keycloak.org/server/caching#_cache_types_and_defaults&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;) 간단하게 설명하자면 A,B,C 3개의 노드 를 운영할때 A노드에서 User데이터가 변경되었다고 하면, A노드는 User의 데이터가 변경되었다고 &amp;nbsp;Work Cache에 무효화 메시지 전송하고, Work Cache가 모든 노드에 브로드캐스트하며 각 노드는 해당 데이터를 삭제한다. 이렇게 설정해야 데이터의 수정이 발생하더라도 모든 노드가 같은 데이터를 가지고 있을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Cache 설정 방법&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;일반 명령어로는 아래 설정으로 클러스터를 위한 캐시를 구성할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739696361038&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bin/kc.sh start \
  --cache=ispn \
  --cache-stack=kubernetes \
  --cache-owners=2 \
  --features-disabled=persistent-user-session \
  --spi-sticky-session-encoder-infinispan-should-attach-route=true \ 
  --metrics-enabled=true \
  --cache-metrics-histograms-enabled=true&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;--cache=ispn: 클러스터링 활성화&lt;/li&gt;
&lt;li&gt;--cache-stack=kubernetes: Discovery 방법 선택 (jdbc-ping, kubernetes)&lt;/li&gt;
&lt;li&gt;--cache-owners*=2: 각 캐시 엔트리 복제본 수&lt;/li&gt;
&lt;li&gt;--features-disabled=persistent-user-session: 세션 DB저장 비활성화&lt;/li&gt;
&lt;li&gt;--spi-sticky-session-encoder-infinispan-should-attach-route=true : 스티키 세션 활성화, 세션 소유 노드로 요청 라우팅, 캐시 접근 최적화&lt;/li&gt;
&lt;li&gt;--metrics-enabled=true: 모니터링 활성화&lt;/li&gt;
&lt;li&gt;--cache-metrics-histograms-enabled=true: 캐시 메트릭 히스토그램 활성화&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3; text-align: start;&quot;&gt;* --cache-owners 설정은 엔트리(데이터)의 복제본을 몇개 사용할건지 설정한다. &lt;br /&gt;이 설정은 너무 큰 값으로 설정하면 네트워크,CPU등에 부하를 줄 수 있으므로 , 일반적으로&amp;nbsp;2~3개의&amp;nbsp;복제본을&amp;nbsp;권장한다.&amp;nbsp;owners=2는&amp;nbsp;한&amp;nbsp;노드&amp;nbsp;장애를,&amp;nbsp;owners=3은&amp;nbsp;두&amp;nbsp;노드&amp;nbsp;장애를&amp;nbsp;허용할&amp;nbsp;수&amp;nbsp;있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;bitnami에서 제공하는 &lt;a href=&quot;https://artifacthub.io/packages/helm/bitnami/keycloak?modal=values&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;keycloak helm chart 설정&lt;/a&gt;으로는 아래와 같이 설정된다. (특별히 커스텀할게 없다)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739695420079&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# https://artifacthub.io/packages/helm/bitnami/keycloak?modal=values
## Keycloak cache configuration
## ref: https://www.keycloak.org/server/caching
## @param cache.enabled Switch to enable or disable the keycloak distributed cache for kubernetes.
## NOTE: Set to false to use 'local' cache (only supported when replicaCount=1).
## @param cache.stackName Set infinispan cache stack to use
## @param cache.stackFile Set infinispan cache stack filename to use
## @param cache.useHeadlessServiceWithAppVersion Set to true to create the headless service used for ispn containing the app version
##
cache:
  enabled: true
  stackName: kubernetes
  stackFile: &quot;&quot;
  useHeadlessServiceWithAppVersion: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;5. Keycloak&amp;nbsp; 다중 노드 클러스터 설정 - Reverse Proxy&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* 공식문서: &lt;a href=&quot;https://www.keycloak.org/server/reverseproxy&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.keycloak.org/server/reverseproxy&lt;/a&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;아키텍쳐&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;위에서 설명한데로, Keycloak은 고가용성(HA) 및 다중 노트 클러스터링 설정을 할 수 있도록 설계되어있으며,&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Keycloak 클러스터링의 핵심은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Reverse Proxy(LoadBalanceing)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&amp;nbsp;&lt;b&gt;캐시 구성&lt;/b&gt;이다.&lt;br /&gt;이번 단계에서는 Reverse Proxy(=API Gateway, LoadBalancer) 부분을 확인해보자.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-16 오후 8.38.59.png&quot; data-origin-width=&quot;2526&quot; data-origin-height=&quot;1186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9rFpg/btsMk1R7vvk/NB229J2WiSTH0xHnhhw0E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9rFpg/btsMk1R7vvk/NB229J2WiSTH0xHnhhw0E1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9rFpg/btsMk1R7vvk/NB229J2WiSTH0xHnhhw0E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9rFpg%2FbtsMk1R7vvk%2FNB229J2WiSTH0xHnhhw0E1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2526&quot; height=&quot;1186&quot; data-filename=&quot;스크린샷 2025-02-16 오후 8.38.59.png&quot; data-origin-width=&quot;2526&quot; data-origin-height=&quot;1186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; Reverse Proxy의 역할&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;리버스 프록시가 해주어야 하는 역할은 아래와 같다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 111px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 17.907%; height: 19px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 82.093%; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 17.907%; height: 19px;&quot;&gt;노드 부하 분산&lt;/td&gt;
&lt;td style=&quot;width: 82.093%; height: 19px;&quot;&gt;- 트래픽을&amp;nbsp;여러&amp;nbsp;Keycloak&amp;nbsp;노드에&amp;nbsp;균등하게&amp;nbsp;분배한다.&lt;br /&gt;- 부하 분산 알고리즘을 적절하게 선택하여 사용한다.&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; - Round Robin: 순차적 분배&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;-&amp;nbsp;Least&amp;nbsp;Connection:&amp;nbsp;연결&amp;nbsp;수가&amp;nbsp;적은&amp;nbsp;노드&amp;nbsp;우선&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;-&amp;nbsp;Response&amp;nbsp;Time:&amp;nbsp;응답&amp;nbsp;시간&amp;nbsp;기준&lt;br /&gt;&lt;br /&gt;예시&amp;nbsp;설정&amp;nbsp;(nginx):&lt;br /&gt;upstream&amp;nbsp;keycloak&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server&amp;nbsp;keycloak1:8080;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server&amp;nbsp;keycloak2:8080;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server&amp;nbsp;keycloak3:8080;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 17.907%; height: 36px;&quot;&gt;세션 어피니티&amp;nbsp;&lt;br /&gt;(Sticky Session)&lt;br /&gt;&lt;a href=&quot;https://www.keycloak.org/server/reverseproxy#_enable_sticky_sessions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서 링크&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 82.093%; height: 36px;&quot;&gt;- 캐시 히트율 향상/ 세션 데이터 접근 최적화/ 불필요한 노드간 통신 감소를 위해 &lt;br /&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;동일 사용자의 요청을 같은 노드로 라우팅&lt;br /&gt;&lt;/span&gt;- 필수 설정은 아니지만 설정하면 &lt;b&gt;부하 감소 및 처리 속도에 이점&lt;/b&gt;이 있음&lt;br /&gt;&lt;br /&gt;예시&amp;nbsp;설정&amp;nbsp;(nginx):&lt;br /&gt;upstream&amp;nbsp;keycloak&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ip_hash;&amp;nbsp;&amp;nbsp;#&amp;nbsp;또는&amp;nbsp;sticky&amp;nbsp;cookie&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server&amp;nbsp;keycloak1:8080;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server&amp;nbsp;keycloak2:8080;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 17.907%; height: 19px;&quot;&gt;헤더 전송&lt;br /&gt;&lt;a href=&quot;https://www.keycloak.org/server/reverseproxy#_configure_the_reverse_proxy_headers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서 링크&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 82.093%; height: 19px;&quot;&gt;- 클라이언트 정보를 Keycloak에 전달&lt;br /&gt;- 주요 헤더:&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- X-Forwarded-For: 실제 클라이언트 IP&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- X-Forwarded-Proto: 원본 프로토콜&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- X-Forwarded-Host: 원본 호스트&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- X-Real-IP: 실제 클라이언트 IP&lt;br /&gt;- --proxy-headers 옵션을 설정하지 않으면, Origin 체크 실패하고 403(&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;Forbidden)&lt;/span&gt;을 반환함&lt;br /&gt;&lt;br /&gt;예시&amp;nbsp;설정&amp;nbsp;(nginx):&lt;br /&gt;proxy_set_header&amp;nbsp;X-Forwarded-For&amp;nbsp;$proxy_add_x_forwarded_for;&lt;br /&gt;proxy_set_header&amp;nbsp;X-Forwarded-Proto&amp;nbsp;$scheme;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 17.907%; height: 18px;&quot;&gt;SSL/TLS 처리 최적화&lt;/td&gt;
&lt;td style=&quot;width: 82.093%; height: 18px;&quot;&gt;- SSL/TLS 오프로드를 Reverse Proxy에서 처리할 수 있음&lt;br /&gt;- &quot;&lt;a href=&quot;https://1mini2.tistory.com/185#2._Keycloak_TLS_%ED%99%9C%EC%84%B1%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2.&amp;nbsp;Keycloak&amp;nbsp;TLS&amp;nbsp;활성화&lt;/a&gt;&quot; 참고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.907%;&quot;&gt;노드 상태 모니터링&lt;/td&gt;
&lt;td style=&quot;width: 82.093%;&quot;&gt;- 타겟 노드의 상태를 모니터링하고, 비정상적인 노드에 트래픽 차단&lt;br /&gt;&lt;br /&gt;예시&amp;nbsp;설정&amp;nbsp;(nginx):&lt;br /&gt;health_check&amp;nbsp;uri=/health&amp;nbsp;interval=5s;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;노출할 엔드포인트 지정&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Reverse Proxy를 사용할때 모든 엔드포인트를 다 노출하는것이 아니라, 필요한 몇개의 엔드포인트만 노출하도록 설정한다. &lt;br /&gt;(&lt;a href=&quot;https://www.keycloak.org/server/reverseproxy#_configure_the_reverse_proxy_headers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서링크&lt;/a&gt;)&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 123px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 17.2093%; height: 19px;&quot;&gt;Keycloak Path&lt;/td&gt;
&lt;td style=&quot;width: 18.9535%; height: 19px;&quot;&gt;Reverse Proxy Path&lt;/td&gt;
&lt;td style=&quot;width: 10.2325%; height: 19px;&quot;&gt;Exposed&lt;/td&gt;
&lt;td style=&quot;width: 53.6047%; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.2093%; height: 17px;&quot;&gt;/&lt;/td&gt;
&lt;td style=&quot;width: 18.9535%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10.2325%; height: 17px;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;width: 53.6047%; height: 17px;&quot;&gt;불필요한&amp;nbsp;관리자&amp;nbsp;경로&amp;nbsp;노출될 우려가 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.2093%; height: 17px;&quot;&gt;/admin/&lt;/td&gt;
&lt;td style=&quot;width: 18.9535%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10.2325%; height: 17px;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;width: 53.6047%; height: 17px;&quot;&gt;불필요한&amp;nbsp;관리자&amp;nbsp;경로&amp;nbsp;노출될 우려가 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.2093%; height: 17px;&quot;&gt;/realms/&lt;/td&gt;
&lt;td style=&quot;width: 18.9535%; height: 17px;&quot;&gt;/realms/&lt;/td&gt;
&lt;td style=&quot;width: 10.2325%; height: 17px;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;width: 53.6047%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;-&amp;nbsp;OIDC&amp;nbsp;엔드포인트&amp;nbsp;필요&lt;br /&gt;-&amp;nbsp;인증/인가&amp;nbsp;프로세스&amp;nbsp;필수&lt;br /&gt;-&amp;nbsp;클라이언트&amp;nbsp;연동&amp;nbsp;필요&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.2093%; height: 17px;&quot;&gt;/resources/&lt;/td&gt;
&lt;td style=&quot;width: 18.9535%; height: 17px;&quot;&gt;/resources/&lt;/td&gt;
&lt;td style=&quot;width: 10.2325%; height: 17px;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;width: 53.6047%; height: 17px;&quot;&gt;-&amp;nbsp;UI&amp;nbsp;자원&amp;nbsp;제공&lt;br /&gt;-&amp;nbsp;정적&amp;nbsp;파일&amp;nbsp;서비스&lt;br /&gt;-&amp;nbsp;CDN&amp;nbsp;사용&amp;nbsp;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.2093%; height: 17px;&quot;&gt;/metrics&lt;/td&gt;
&lt;td style=&quot;width: 18.9535%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10.2325%; height: 17px;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;width: 53.6047%; height: 17px;&quot;&gt;시스템 정보/성능데이터/내부 모니터링 노출 불필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 17.2093%; height: 19px;&quot;&gt;/health&lt;/td&gt;
&lt;td style=&quot;width: 18.9535%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10.2325%; height: 19px;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;width: 53.6047%; height: 19px;&quot;&gt;시스템 상태 정보 노출 불필요&lt;br /&gt;내부 로드밸런서용으로 사용하며, 불필요한 공격 정보가 제공됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;▪︎&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기타 알면 좋은것..&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에는 reverse proxy로 사용할 수 있는 프록시로 nginx, haproxy, nginx를 말하지만, &lt;br /&gt;AWS환경에서는 간단하게 AWS ALB를 사용하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS ALB는 다른 프록시들과 비교해보자면, 기본적인 기능 (SSL/TLS 오프로드, 세션 어피니티, 헬스체크, 라우팅 알고리즘 선택 등의 기능 제공)은 똑같은데 관리할 필요가 거의 없다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Infra/KeyCloak</category>
      <category>aws keycloak</category>
      <category>keycloak</category>
      <category>keycloak architrcture</category>
      <category>keycloak helm</category>
      <category>keycloak hostname</category>
      <category>keycloak reverseproxy</category>
      <category>keycloak 개념</category>
      <category>keycloak 기초</category>
      <category>keycloak 아키텍쳐</category>
      <category>keycloak 이해하기</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/185</guid>
      <comments>https://1mini2.tistory.com/185#entry185comment</comments>
      <pubDate>Sun, 16 Feb 2025 16:58:29 +0900</pubDate>
    </item>
    <item>
      <title>[keycloak 맛보기 #6] Keycloak의 인가 전략</title>
      <link>https://1mini2.tistory.com/183</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요, 인가란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어가기 전에 인가(Authorization)이 무엇인지 한번 더 개념을 정리해보자.&lt;br /&gt;인가는 &quot;&lt;u&gt;이 사용자가 특정 리소스나 기능에 접근할 권한이 있는지 확인하는 과정&lt;/u&gt;&quot;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들자면 어떤 사용자가 ID/PW를 입력하고 사진첩 어플리케이션에 로그인했다면,&amp;nbsp;&lt;br /&gt;사용자 본인의 사진첩에만 접근 가능해야하며 다른 사용자의 사진첩에는 접근해서는 안된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 71.9768%; height: 95px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 19.3293%; height: 19px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 32.5898%; height: 19px;&quot;&gt;개념&lt;/td&gt;
&lt;td style=&quot;width: 37.0427%; height: 19px;&quot;&gt;예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 19.3293%; height: 38px;&quot;&gt;인증&lt;br /&gt;Authentication&lt;/td&gt;
&lt;td style=&quot;width: 32.5898%; height: 38px;&quot;&gt;누구인지 확인하는 과정&lt;/td&gt;
&lt;td style=&quot;width: 37.0427%; height: 38px;&quot;&gt;로그인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 19.3293%; height: 38px;&quot;&gt;인가&lt;br /&gt;Authorization&lt;/td&gt;
&lt;td style=&quot;width: 32.5898%; height: 38px;&quot;&gt;무엇을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있는지&amp;nbsp;확인하는&amp;nbsp;과정&lt;/td&gt;
&lt;td style=&quot;width: 37.0427%; height: 38px;&quot;&gt; 내 사진첩에만 접근&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 인가란 사용자가 허용된 리소스에만 접근하도록 하는것이 인가의 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인가를 위해 고려해야하는 요소는 정말 많다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 컨텍스트 (Who): 사용자 신원, 역할, 그룹, 속성, 조직 구조 내 위치&lt;/li&gt;
&lt;li&gt;리소스 컨텍스트 (What): 리소스 유형, 소유권, 민감도 수준, 계층 구조, 메타데이터&lt;/li&gt;
&lt;li&gt;접근 조건 (When/How): 시간/위치 기반 제약, 디바이스/네트워크 조건, 다중 인증, 접근 빈도&lt;/li&gt;
&lt;li&gt;운영 관점 (Operation): 권한 위임/상속, 긴급 접근, 임시 권한, 권한 취소&lt;/li&gt;
&lt;li&gt;감사 및 모니터링 (Audit): 접근 로그, 권한 변경 이력, 비정상 탐지, 컴플라이언스, 사용 패턴&lt;/li&gt;
&lt;li&gt;정책 관리 (Policy): 버전 관리, 충돌 해결, 테스트, 배포 전략, 예외 처리&lt;/li&gt;
&lt;li&gt;보안 고려사항: 최소 권한 원칙, 직무 분리, 권한 에스컬레이션 방지, 세션/토큰 관리&lt;/li&gt;
&lt;li&gt;확장성 및 성능: 캐싱, 분산 시스템, 정책 처리, 응답 시간, 장애 복구&lt;/li&gt;
&lt;li&gt;통합 고려사항: 레거시 통합, SSO, 외부 시스템 연동, API 보안, 프로토콜&lt;/li&gt;
&lt;li&gt;규정 준수: 산업&amp;nbsp;표준,&amp;nbsp;데이터&amp;nbsp;보호,&amp;nbsp;감사&amp;nbsp;요구사항,&amp;nbsp;법규,&amp;nbsp;개인정보&amp;nbsp;보호&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, &lt;b&gt;Keycloak에서는 인가를 어떻게 처리할것인가?&amp;nbsp;&lt;/b&gt;&lt;br /&gt;공식문서 &lt;a href=&quot;https://www.keycloak.org/docs/latest/authorization_services/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Authorization Services Guide&lt;/a&gt; 에서는 주요 접근제어모델과 정책 집행 포인트에 대해 제공하고있고, &lt;br /&gt;이전 실습에서도 아주 잠깐 다룬적이 있다. (&lt;a href=&quot;https://1mini2.tistory.com/179#%EC%8B%A4%EC%8A%B5%ED%95%98%EA%B8%B0%EC%A0%84%EC%97%90)_Access_Token%EC%9D%98_%EC%A0%91%EA%B7%BC%EA%B6%8C%ED%95%9C_%EC%A0%9C%ED%95%9C%EC%97%90_%EB%8C%80%ED%95%B4_%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; 실습&lt;/a&gt;)&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%; height: 247px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot; data-sheets-root=&quot;1&quot; data-sheets-baot=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; height: 19px;&quot;&gt;접근제어모델&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; height: 19px;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 38px;&quot;&gt;Attribute-based access control&lt;br /&gt;(ABAC)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;b&gt;사용자, 리소스, 환경의 속성을 기반으로 접근을 제어&lt;/b&gt;&lt;br /&gt;예: 사용자의 부서, 직급, 위치 등의 속성을 기반으로 접근 권한 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 38px;&quot;&gt;Role-based access control&lt;br /&gt;(RBAC)&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; height: 38px;&quot;&gt;&lt;b&gt;사용자에게 할당된 역할을 기반으로 접근을 제어&lt;/b&gt;&lt;br /&gt;예: Realm Role에 매핑된 사용자만 접근 권한 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 38px;&quot;&gt;User-based access control&lt;br /&gt;(UBAC)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;b&gt;특정 사용자를 기반으로 접근을 제어 (개별 사용자 단위의 세밀한 권한 제어 가능)&lt;/b&gt;&lt;br /&gt;예, user01에게만 접근 권한 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 38px;&quot;&gt;Context-based access control&lt;br /&gt;(CBAC)&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; height: 38px;&quot;&gt;&lt;b&gt;요청의 컨텍스트(시간, 위치, 디바이스 등)를 기반으로 접근을 제어&lt;/b&gt;&lt;br /&gt;예: 특정 IP 범위에서만 접근 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 19px;&quot;&gt;Rule-based access control:&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;JavaScript를 사용한 커스텀 규칙 정의 (복잡한 비즈니스 로직을 구현할 수 있음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 19px;&quot;&gt;Time-based access control:&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; height: 19px;&quot;&gt;시간 기반의 접근 제어 (특정 시간대나 기간 동안만 접근 허용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; height: 38px;&quot;&gt;Custom ACMs through SPI:&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Service Provider Interface를 통한 커스텀 접근 제어 메커니즘 구현 가능&lt;br /&gt;(필요한 경우 자체 정책 타입을 개발하여 확장 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 87px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 31.9767%; height: 19px;&quot;&gt;정책&amp;nbsp;집행&amp;nbsp;포인트&lt;/td&gt;
&lt;td style=&quot;width: 68.0233%; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.9767%;&quot;&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;Policy Information Point&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;(PIP/정책 정보)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 68.0233%;&quot;&gt;정책&amp;nbsp;평가에&amp;nbsp;필요한&amp;nbsp;정보&amp;nbsp;제공&lt;br /&gt;사용자&amp;nbsp;속성,&amp;nbsp;환경&amp;nbsp;정보&amp;nbsp;등&amp;nbsp;수집&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 31.9767%; height: 17px;&quot;&gt;Policy Administration Point&lt;br /&gt;(PAP/정책 관리)&lt;/td&gt;
&lt;td style=&quot;width: 68.0233%; height: 17px;&quot;&gt;정책을&amp;nbsp;생성,&amp;nbsp;관리,&amp;nbsp;저장하는&amp;nbsp;곳&lt;br /&gt;Keycloak&amp;nbsp;관리&amp;nbsp;콘솔에서&amp;nbsp;정책&amp;nbsp;설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 31.9767%; height: 17px;&quot;&gt;Policy Decision Point&lt;br /&gt;(PDP/정책 결정)&lt;/td&gt;
&lt;td style=&quot;width: 68.0233%; height: 17px;&quot;&gt;접근 허용/거부 결정&lt;br /&gt;정책과&amp;nbsp;속성&amp;nbsp;정보를&amp;nbsp;기반으로&amp;nbsp;판단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 31.9767%; height: 17px;&quot;&gt;Policy Enforcement Point&lt;br /&gt;(PEP/정책 집행)&lt;/td&gt;
&lt;td style=&quot;width: 68.0233%; height: 17px;&quot;&gt;실제&amp;nbsp;접근&amp;nbsp;제어&amp;nbsp;실행&lt;br /&gt;PDP의&amp;nbsp;결정을&amp;nbsp;강제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1739101886379&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. PAP (정책 관리) 엔드포인트
# 기본 URL
${keycloak_url}/admin/realms/${realm_name}/clients/${client_id}/authz/resource-server
# 정책 관리
GET    /policies                  # 정책 목록 조회
POST   /policies                  # 정책 생성
GET    /policies/{id}            # 특정 정책 조회
PUT    /policies/{id}            # 정책 업데이트
DELETE /policies/{id}            # 정책 삭제

2. PIP (정보 수집) 엔드포인트
# 사용자 정보
GET /admin/realms/${realm_name}/users/{id}
GET /admin/realms/${realm_name}/users/{id}/groups
GET /admin/realms/${realm_name}/users/{id}/role-mappings
# 리소스 정보
GET /authz/protection/resource_set
GET /authz/protection/resource_set/{id}

3. PDP (결정) 엔드포인트
# 권한 평가
POST /realms/${realm_name}/protocol/openid-connect/token
{
    &quot;grant_type&quot;: &quot;urn:ietf:params:oauth:grant-type:uma-ticket&quot;,
    &quot;audience&quot;: &quot;${client_id}&quot;,
    &quot;permission&quot;: &quot;${resource}#${scope}&quot;
}
# 정책 평가
POST /realms/${realm_name}/clients/${client_id}/authz/resource-server/policy/evaluate

4. PEP (실행) 엔드포인트
# 토큰 검증
GET /realms/${realm_name}/protocol/openid-connect/userinfo
GET /realms/${realm_name}/protocol/openid-connect/token/introspect
# 권한 확인
POST /realms/${realm_name}/clients/${client_id}/authz/resource-server/permission/ticket&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은&amp;nbsp; &lt;u&gt;&lt;b&gt;KeyCloak에서의 접근제어 메커니즘 중 RBAC, ABAC에 대해 확인&lt;/b&gt;&lt;/u&gt;해보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;RBAC&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(Role-Based&amp;nbsp;Access&amp;nbsp;Control)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;개념 이해하기&amp;nbsp;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak에서는 Role은 사용자 혹은 그룹에 매핑되어 사용된다. (&lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#managing-attribute-groups&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서 링크&lt;/a&gt;)&lt;br /&gt;Role이란, 일반적으로 조직 또는 애플리케이션 컨텍스트에서 사용자가 가지는 역할을 말한다.&lt;br /&gt;(e.g.&amp;nbsp;administrator,&amp;nbsp;system-manager,&amp;nbsp;people-manager,&amp;nbsp;audit-user,&amp;nbsp;readonly-user&amp;nbsp;..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak의 RBAC을 이해하기 위해서는 user, group의 개념도 함께 이해해야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  User&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Keycloak에서 인증과 인가의 주체이다.&lt;/li&gt;
&lt;li&gt;직접적으로 Role을 할당받을 수 있으며(UBAC), Group의 멤버가 될 수 있다.&lt;/li&gt;
&lt;li&gt;e.g. 사용자1(user01@test.com), 사용자2(user02@test.com)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  Group&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;여러 User를&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;묶어서 관리하는&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;단위이며, &lt;/span&gt;&lt;span&gt;Role을 할&lt;/span&gt;&lt;span&gt;당받을 수 있다.&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(GBAC)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;계층적 구조가 가능(하위 그룹 생성 가능&lt;/span&gt;&lt;span&gt;)하여&amp;nbsp;&lt;/span&gt;&lt;span&gt;여러 User를 한번&lt;/span&gt;&lt;span&gt;에 관리 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Role 관리 효율성 증가된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  Role&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keycloak에는 세 종류의 역할이 있다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 76.8598%; height: 108px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 9.09486%; height: 17px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 34.4322%; height: 17px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 9.09486%; height: 17px;&quot;&gt;Realm&amp;nbsp;Role&lt;br /&gt;[&lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#proc-creating-realm-roles_server_administration_guide&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;]&lt;/td&gt;
&lt;td style=&quot;width: 34.4322%; height: 17px;&quot;&gt;- 전역적(Global) 범위의 역할&lt;br /&gt;- &lt;b&gt;Realm 내의 모든 클라이언트 애플리케이션에서 공통&lt;/b&gt;으로 사용&lt;br /&gt;- 일반적으로 &lt;b&gt;조직 전체&lt;/b&gt;에 적용되는 광범위한 권한을 정의&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;⚠️주의&lt;span style=&quot;color: #f89009; text-align: start;&quot;&gt;⚠️&lt;/span&gt;&lt;span style=&quot;color: #f89009; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 전역적으로 사용 가능하지만, 자동으로 모든 사용자에게 할당되지는 않음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 9.09486%; height: 17px;&quot;&gt;Client&amp;nbsp;Role&lt;br /&gt;[&lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#con-client-roles_server_administration_guide&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;]&lt;/td&gt;
&lt;td style=&quot;width: 34.4322%; height: 17px;&quot;&gt;- 특정 클라이언트 애플리케이션에 한정된 역할&lt;br /&gt;- 해당 애플리케이션에 특화된 &lt;b&gt;세부적인 권한을 정의&lt;/b&gt;&lt;br /&gt;- 다른 클라이언트와&lt;b&gt; 독립적&lt;/b&gt;으로 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 57px;&quot;&gt;
&lt;td style=&quot;width: 9.09486%; height: 57px;&quot;&gt;Default&amp;nbsp;Role&lt;br /&gt;[&lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#_default_roles&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;]&lt;/td&gt;
&lt;td style=&quot;width: 34.4322%; height: 57px;&quot;&gt;-&amp;nbsp;새로운&amp;nbsp;사용자가&amp;nbsp;생성될&amp;nbsp;때&amp;nbsp;자동으로&amp;nbsp;할당되는&amp;nbsp;역할&lt;br /&gt;-&amp;nbsp;`&lt;span style=&quot;background-color: #dddddd;&quot;&gt;default-roles-{realm-name}&lt;/span&gt;`&amp;nbsp;Composite&amp;nbsp;Role을&amp;nbsp;통해&amp;nbsp;관리&lt;br /&gt;-&amp;nbsp;Realm&amp;nbsp;Role이나&amp;nbsp;Client&amp;nbsp;Role을&amp;nbsp;Default&amp;nbsp;Role로&amp;nbsp;설정&amp;nbsp;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Role들은&amp;nbsp;상호&amp;nbsp;베타적인&amp;nbsp;것이&amp;nbsp;아니며,&amp;nbsp;조합하여&amp;nbsp;계층적이고&amp;nbsp;세밀한&amp;nbsp;접근&amp;nbsp;제어가&amp;nbsp;가능해진다.&lt;br /&gt;아래&amp;nbsp;시나리오에서&amp;nbsp;각&amp;nbsp;Role들을&amp;nbsp;함께&amp;nbsp;사용하는&amp;nbsp;예시를&amp;nbsp;확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 4.28.59.png&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;1342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mfiFI/btsMaOAcypp/RvPlC0gkzKCejrNixjDSkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mfiFI/btsMaOAcypp/RvPlC0gkzKCejrNixjDSkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mfiFI/btsMaOAcypp/RvPlC0gkzKCejrNixjDSkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmfiFI%2FbtsMaOAcypp%2FRvPlC0gkzKCejrNixjDSkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;413&quot; height=&quot;314&quot; data-filename=&quot;스크린샷 2025-02-09 오후 4.28.59.png&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;1342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Realm Role &amp;amp; Client Role 혼용 시나리오]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;Realm Role (USER)&lt;/u&gt;&lt;br /&gt;- 시스템 전반의 기본 접근 권한&lt;br /&gt;- 모든 클라이언트 앱에서 유효&lt;br /&gt;- &quot;이 사용자가 앨범 서비스를 사용할 자격이 있는가?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;Client Role ({사용자ID}:all_access)&lt;/u&gt;&lt;br /&gt;&lt;span&gt;- 사용자&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;본인의 앨범에&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;대한 모든 권&lt;/span&gt;&lt;span&gt;한&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;- 조회&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;/수정/삭제 등 모든&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 기능 사용 가능&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;- &quot;이 사용자가 본&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;인의 앨범에 대한&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;모든 권한을 가지&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;는가?&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&quot;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 사용자가 사진앱을 사용할수는 있지만, 모든 사진첩이 아닌 &lt;b&gt;본인의 사진첩만 접근하여 수정&lt;/b&gt;할 수 있도록&lt;br /&gt;두 권한을 조합하여 계층적으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  정리&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 6.30.04.png&quot; data-origin-width=&quot;2432&quot; data-origin-height=&quot;1060&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpyiZx/btsMbqS85Y9/DiwehCY6OKV7jHQQZQK2R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpyiZx/btsMbqS85Y9/DiwehCY6OKV7jHQQZQK2R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpyiZx/btsMbqS85Y9/DiwehCY6OKV7jHQQZQK2R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpyiZx%2FbtsMbqS85Y9%2FDiwehCY6OKV7jHQQZQK2R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;266&quot; data-filename=&quot;스크린샷 2025-02-09 오후 6.30.04.png&quot; data-origin-width=&quot;2432&quot; data-origin-height=&quot;1060&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Role은 단독으로 사용할 수 없고, 반드시 User혹은 Group에 매핑되어야 한다.&lt;/li&gt;
&lt;li&gt;Keycloak에서는 RBAC, UBAC, GBAC 별개의 다른 개념이 아니라 같은 접근제어 메커니즘을 가지고 있다고 할 수 있다.&lt;/li&gt;
&lt;li&gt;모든 종류의 Role은 Group이나 User에게 직접 할당 가능하다.&lt;/li&gt;
&lt;li&gt;Group에 할당된 Role은 해당 Group의 모든 멤버가 상속받는다.&lt;/li&gt;
&lt;li&gt;한 사용자는 여러 경로(Default, Group, 직접 할당)로 Role을 가질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;실습) Role 사용 실습하기&amp;nbsp;&lt;/u&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RealmRole, User에 할당&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Realm Role은 아래와 같이 설정하면 되며, &lt;br /&gt;Client Role은 [Clients &amp;gt; 생성한 Client &amp;gt; Roles 탭 &amp;gt; Create role] 에서 생성할 수 있다.&lt;br /&gt;이 실습에서는 Realm Role만 간략히 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Realm Role을 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CcGo2/btsMbX3XvO1/ZsHLoSeBQoC2d4RSlUYiR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CcGo2/btsMbX3XvO1/ZsHLoSeBQoC2d4RSlUYiR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CcGo2/btsMbX3XvO1/ZsHLoSeBQoC2d4RSlUYiR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCcGo2%2FbtsMbX3XvO1%2FZsHLoSeBQoC2d4RSlUYiR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;245&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용자에게 Realm Role을 할당한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 5.49.19.png&quot; data-origin-width=&quot;3396&quot; data-origin-height=&quot;2180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOwD74/btsMa5aDPdj/SkDeKHZkDvUxLQK7qpMpVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOwD74/btsMa5aDPdj/SkDeKHZkDvUxLQK7qpMpVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOwD74/btsMa5aDPdj/SkDeKHZkDvUxLQK7qpMpVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOwD74%2FbtsMa5aDPdj%2FSkDeKHZkDvUxLQK7qpMpVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;488&quot; data-filename=&quot;스크린샷 2025-02-09 오후 5.49.19.png&quot; data-origin-width=&quot;3396&quot; data-origin-height=&quot;2180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 사용자에 연결된 Realm Role 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Users에 Realm Role이 잘 연결되었는지 점검한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjxCOB/btsMcy3AETv/EBvUUXRgRfYFKKdiXHW6s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjxCOB/btsMcy3AETv/EBvUUXRgRfYFKKdiXHW6s1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjxCOB/btsMcy3AETv/EBvUUXRgRfYFKKdiXHW6s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjxCOB%2FbtsMcy3AETv%2FEBvUUXRgRfYFKKdiXHW6s1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;207&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 호출할 예정이므로, &lt;span style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot;&gt;Direct access grants를 허용해준다.&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.09.04.png&quot; data-origin-width=&quot;3122&quot; data-origin-height=&quot;2920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMisXd/btsMcis96Qr/hxxQB6oM050kDbp9u6AGO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMisXd/btsMcis96Qr/hxxQB6oM050kDbp9u6AGO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMisXd/btsMcis96Qr/hxxQB6oM050kDbp9u6AGO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMisXd%2FbtsMcis96Qr%2FhxxQB6oM050kDbp9u6AGO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;465&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.09.04.png&quot; data-origin-width=&quot;3122&quot; data-origin-height=&quot;2920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 아래 환경변수 부분을 내 환경에 맞춰 변경하고, 터미널에서 실행해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1739096290993&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 1. keycloak 테스트서버 실행
docker run -p 8080:8080 \
          -e KEYCLOAK_ADMIN=admin \
          -e KEYCLOAK_ADMIN_PASSWORD=admin \
          quay.io/keycloak/keycloak \
          start-dev
          
# 2. 환경변수 설정
export ADMIN_USERNAME=&quot;admin&quot;
export ADMIN_PASSWORD=&quot;admin&quot;
export CLIENT_ID=&quot;admin-cli&quot;
export REALM_NAME=&quot;my-realm&quot;
export KEYCLOAK_URL=&quot;http://localhost:8080&quot;
export KEYCLOAK_USERNAME=user01

# 3. 관리자 토큰 획득
export ADMIN_TOKEN=$(curl -X POST &quot;${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token&quot; \
-H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
-d &quot;client_id=${CLIENT_ID}&quot; \
-d &quot;username=${ADMIN_USERNAME}&quot; \
-d &quot;password=${ADMIN_PASSWORD}&quot; \
-d &quot;grant_type=password&quot; | jq -r '.access_token')

# 4. 사용자 ID 조회
export USER_ID=$(curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users?username=${KEYCLOAK_USERNAME}&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq -r '.[0].id')

# 5. Role 매핑 확인 (관리자 토큰 사용)
curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/realm&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq '.' --color-output&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과로 아래와 같이 새로 생성한 my-realm-role이 매핑된것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.20.01.png&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lklZg/btsMcKiyig3/UVkB6UQGY23uZbz95O8u91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lklZg/btsMcKiyig3/UVkB6UQGY23uZbz95O8u91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lklZg/btsMcKiyig3/UVkB6UQGY23uZbz95O8u91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlklZg%2FbtsMcKiyig3%2FUVkB6UQGY23uZbz95O8u91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;659&quot; height=&quot;367&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.20.01.png&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;ClientRole, Group에 할당&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Client Role 생성&lt;br /&gt;[client &amp;gt; 'my-client' &amp;gt; Roles &amp;gt; create role] 으로 새로운 Role (my-client-role)을 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.30.34.png&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYwNTi/btsMbqZWxzC/i1B7w9I2ZNko8im1b347o1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYwNTi/btsMbqZWxzC/i1B7w9I2ZNko8im1b347o1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYwNTi/btsMbqZWxzC/i1B7w9I2ZNko8im1b347o1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYwNTi%2FbtsMbqZWxzC%2Fi1B7w9I2ZNko8im1b347o1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;206&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.30.34.png&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 그룹에 ClientRole을 매핑한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.32.08.png&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;2326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvGLAO/btsMcrp07le/62dLgCL9ijhjQakoTz4Pk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvGLAO/btsMcrp07le/62dLgCL9ijhjQakoTz4Pk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvGLAO/btsMcrp07le/62dLgCL9ijhjQakoTz4Pk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvGLAO%2FbtsMcrp07le%2F62dLgCL9ijhjQakoTz4Pk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;479&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.32.08.png&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;2326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.33.16.png&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;1088&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnLT7b/btsMcKpi7og/g2WX2kg0tp3tvXtZLYrWBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnLT7b/btsMcKpi7og/g2WX2kg0tp3tvXtZLYrWBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnLT7b/btsMcKpi7og/g2WX2kg0tp3tvXtZLYrWBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnLT7b%2FbtsMcKpi7og%2Fg2WX2kg0tp3tvXtZLYrWBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;221&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.33.16.png&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;1088&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 그룹에 ClientRole 매핑된것 확인&lt;/p&gt;
&lt;pre id=&quot;code_1739097488406&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 환경변수 설정
export ADMIN_USERNAME=&quot;admin&quot;
export ADMIN_PASSWORD=&quot;admin&quot;
export CLIENT_ID=&quot;my-client&quot;
export REALM_NAME=&quot;my-realm&quot;
export KEYCLOAK_URL=&quot;http://localhost:8080&quot;
export KEYCLOAK_USERNAME=&quot;user01&quot;
export KEYCLOAK_GROUPNAME=&quot;my-group&quot;

# 2. 관리자 토큰 획득
export ADMIN_TOKEN=$(curl -X POST &quot;${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token&quot; \
-H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
-d &quot;client_id=admin-cli&quot; \
-d &quot;username=${ADMIN_USERNAME}&quot; \
-d &quot;password=${ADMIN_PASSWORD}&quot; \
-d &quot;grant_type=password&quot; | jq -r '.access_token')

# 3. 사용자 ID 조회
export USER_ID=$(curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users?username=${KEYCLOAK_USERNAME}&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq -r '.[0].id')

# 4. Client ID(UUID) 조회
export MY_CLIENT_ID=$(curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients?clientId=${CLIENT_ID}&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq -r '.[0].id')

# 5. 사용자의 Client Role 매핑 확인 (직접 할당된 role)
echo &quot;Direct Client Roles:&quot;
curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${MY_CLIENT_ID}&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq '.' --color-output

# 6. 사용자의 Effective Client Role 매핑 확인 (그룹에서 상속받은 role 포함)
echo -e &quot;\nEffective Client Roles (including inherited from groups):&quot;
curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${MY_CLIENT_ID}/composite&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq '.' --color-output

# 7. 그룹 ID 조회 및 Client Role 매핑 확인
export MY_GROUP_ID=$(curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups?search=${KEYCLOAK_GROUPNAME}&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq -r '.[0].id')

echo -e &quot;\nGroup Client Roles:&quot;
curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups/${MY_GROUP_ID}/role-mappings/clients/${MY_CLIENT_ID}&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq '.' --color-output&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹에 연결된 ClientRole, 그리고 사용자에게 상속된 ClientRole도 위 명령어를 통해 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.46.39.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDYPIN/btsMbWYiC51/rctFgzySKYttHVRNbUd2ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDYPIN/btsMbWYiC51/rctFgzySKYttHVRNbUd2ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDYPIN/btsMbWYiC51/rctFgzySKYttHVRNbUd2ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDYPIN%2FbtsMbWYiC51%2FrctFgzySKYttHVRNbUd2ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;618&quot; height=&quot;209&quot; data-filename=&quot;스크린샷 2025-02-09 오후 7.46.39.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;ABAC &lt;span&gt;&amp;nbsp;&lt;/span&gt;(Attribute-Based Access Control)&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;개념 이해하기&amp;nbsp;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Keycloak에서 ABAC&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(Attribute-Based Access Control)은&lt;/span&gt;&amp;nbsp;사용자, 리&lt;/span&gt;&lt;span&gt;소스, 환경의 속성&lt;/span&gt;&lt;span&gt;(Attribute)을 기반으로&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;접근을 제어하는 방식이며, Role기반의 접근제어보다 더 유연하고 세밀한 접근제어가 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attribute도 마찬가지로 기본적으로는 &lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#user-profile&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;User&lt;/a&gt;와 &lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#managing-attribute-groups&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Group&lt;/a&gt;에 할당하여 사용하며,&amp;nbsp;&lt;br /&gt;그 외에도 &lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#_managing_attributes_&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;O&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot;&gt;&lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#_managing_attributes_&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;rganization&lt;/a&gt;, &lt;/span&gt;&lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#architecture&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Client&lt;/a&gt;, &lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#listing-available-identity-providers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Identity Provider&lt;/a&gt; 등 다양한 Keycloak 컴포넌트에 Attribute를 추가할 수 있다. &lt;br /&gt;(&lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#core-concepts-and-terms&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;core concepts and terms&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ABAC을 확인하기 전에 먼저 주요 개념부터 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt; &lt;span&gt; &amp;nbsp;&lt;/span&gt;Attribute&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Keycloak에서 엔티티(User, Group 등)의 추가 정보를 저장하는 Key-Value 형태의 데이터를 말한다.&lt;/li&gt;
&lt;li&gt;동적으로 추가/수정이 가능하며, 하나의 Key에 여러 Value를 가질 수 있으며, &lt;br /&gt;ABAC(Attribute Based Access Control)에서 접근 제어 결정에 사용된다.&lt;/li&gt;
&lt;li&gt;Group에서 User로 상속 가능 하다. (Group &amp;rarr; User)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;e.g.&amp;nbsp;department:&amp;nbsp;&quot;HR&quot;,&amp;nbsp;location:&amp;nbsp;[&quot;Seoul&quot;,&amp;nbsp;&quot;Busan&quot;]&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt; &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt; Protocol Mapper&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Attribute를 Token Claim으로 변환하는 설정이다.&lt;/li&gt;
&lt;li&gt;Keycloak의 JavaDoc 인터페이스 구현체 &lt;a href=&quot;https://www.keycloak.org/docs-api/25.0.6/javadocs/org/keycloak/protocol/class-use/ProtocolMapper.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mapper 종류:
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;User Property Mapper: 기본 사용자 속성 매핑&lt;/li&gt;
&lt;li&gt;User Attribute Mapper: 커스텀 사용자 속성 매핑&lt;/li&gt;
&lt;li&gt;Group Membership Mapper: 그룹 정보 매핑&lt;/li&gt;
&lt;li&gt;Role&amp;nbsp;Mapper:&amp;nbsp;역할&amp;nbsp;정보&amp;nbsp;매핑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1739110737346&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;name&quot;: &quot;department&quot;,     // Mapper의 이름 (관리자가 구분하기 위한 용도)
    &quot;protocol&quot;: &quot;openid-connect&quot;,     // 사용할 프로토콜 (보통 OpenID Connect 사용)
    &quot;protocolMapper&quot;: &quot;oidc-usermodel-attribute-mapper&quot;,// Mapper의 타입 (User Attribute를 매핑)
    &quot;config&quot;: {                            // 실제 매핑 설정
        &quot;user.attribute&quot;: &quot;department&quot;,         // Keycloak에 저장된 속성 이름
        &quot;claim.name&quot;: &quot;department&quot;,        // 토큰에 포함될 때 사용할 이름
        &quot;token.claim&quot;: &quot;true&quot;        // 토큰에 포함할지 여부
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt; &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; Token&amp;nbsp;Claim&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;&lt;span&gt;토큰에 포함되는&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;정보의 한 조각으로,&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용자나 토큰 자&lt;/span&gt;&lt;span&gt;체에 대한 statement를 말한다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;Protocol&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;Mapper를 통해&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;Attribute를 Claim으로 변&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;환하여 토큰에 포&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;함시킬 수 있다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;클라이언트&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;애플리케이션에서 사용자 정보&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;나 권한 확인에 사용한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1739109124982&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    // 기본 Claims
    &quot;sub&quot;: &quot;user123&quot;,
    &quot;iss&quot;: &quot;http://keycloak.example.com&quot;,
    &quot;exp&quot;: 1516239022,
    
    // User Attributes -&amp;gt; Claims
    &quot;user_attributes&quot;: {
        &quot;department&quot;: &quot;HR&quot;,
        &quot;location&quot;: [&quot;Seoul&quot;]
    },
    
    // Group Attributes -&amp;gt; Claims
    &quot;group_attributes&quot;: {
        &quot;costCenter&quot;: &quot;HR001&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt; &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt; Attribute와&amp;nbsp;&lt;/span&gt;Token Claim 관계&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.14.37.png&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LtMOq/btsMcR9NPLg/VMl7ccOtkKApLbAKDqOKi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LtMOq/btsMcR9NPLg/VMl7ccOtkKApLbAKDqOKi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LtMOq/btsMcR9NPLg/VMl7ccOtkKApLbAKDqOKi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLtMOq%2FbtsMcR9NPLg%2FVMl7ccOtkKApLbAKDqOKi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2136&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.14.37.png&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자나 그룹의 속성(attributes)은 protocol mapper설정을 통해 Token에 Claims 형태로 담겨 전달된다.&amp;nbsp;&lt;br /&gt;즉,Attribute는&amp;nbsp;Keycloak&amp;nbsp;내부&amp;nbsp;데이터이고,&amp;nbsp;Claim은&amp;nbsp;이&amp;nbsp;데이터를&amp;nbsp;토큰을&amp;nbsp;통해&amp;nbsp;외부로&amp;nbsp;전달하는&amp;nbsp;방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;실습) Attribute 사용방법&amp;nbsp;&lt;/u&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;User Attribute 사용하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 사용자 속성(Attributes)를 설정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. [realm settings &amp;gt; User profile &amp;gt; create attribute]를 클릭하고 department 속성을 만든다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.40.09.png&quot; data-origin-width=&quot;3348&quot; data-origin-height=&quot;1570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAuf8x/btsMcg9362H/ZhQIKRY6aRKsazBaJWzVA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAuf8x/btsMcg9362H/ZhQIKRY6aRKsazBaJWzVA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAuf8x/btsMcg9362H/ZhQIKRY6aRKsazBaJWzVA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAuf8x%2FbtsMcg9362H%2FZhQIKRY6aRKsazBaJWzVA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;565&quot; height=&quot;1570&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.40.09.png&quot; data-origin-width=&quot;3348&quot; data-origin-height=&quot;1570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. User Details에 새로 생긴 Department에 값을 입력한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.41.42.png&quot; data-origin-width=&quot;3348&quot; data-origin-height=&quot;1846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5h58J/btsMcIymROD/Ub7IfY0DxfsEWDddB7CdW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5h58J/btsMcIymROD/Ub7IfY0DxfsEWDddB7CdW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5h58J/btsMcIymROD/Ub7IfY0DxfsEWDddB7CdW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5h58J%2FbtsMcIymROD%2FUb7IfY0DxfsEWDddB7CdW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;1846&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.41.42.png&quot; data-origin-width=&quot;3348&quot; data-origin-height=&quot;1846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 사용자에 Attribute가 제대로 설정 되었는지 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739111993736&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 환경변수 설정
export ADMIN_USERNAME=&quot;admin&quot;
export ADMIN_PASSWORD=&quot;admin&quot;
export CLIENT_ID=&quot;my-client&quot;
export REALM_NAME=&quot;my-realm&quot;
export KEYCLOAK_URL=&quot;http://localhost:8080&quot;
export USER_NAME=&quot;user01&quot;

# 2. 관리자 토큰 획득
export ADMIN_TOKEN=$(curl -X POST &quot;${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token&quot; \
-H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
-d &quot;client_id=admin-cli&quot; \
-d &quot;username=${ADMIN_USERNAME}&quot; \
-d &quot;password=${ADMIN_PASSWORD}&quot; \
-d &quot;grant_type=password&quot; | jq -r '.access_token')

# 3. 사용자 ID 조회
export USER_ID=$(curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users?username=${USER_NAME}&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq -r '.[0].id')
echo $USER_ID

# 4. Client ID(UUID) 조회
export CLIENT_UUID=$(curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients?clientId=${CLIENT_ID}&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot; | jq -r '.[0].id')
echo $CLIENT_UUID

# 5. 현재 사용자 정보 조회
export USER_INFO=$(curl -X GET &quot;${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}&quot; \
-H &quot;Authorization: Bearer ${ADMIN_TOKEN}&quot; \
-H &quot;Content-Type: application/json&quot;)
echo $USER_INFO | jq

# 6. User Attribute 조회
echo &quot;Current User Attributes:&quot;
echo ${USER_INFO} | jq '.attributes' --color-output&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user attribute에 department가 추가된것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.44.38.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GMGQ1/btsMbDkiAYh/JlHBF3E124JfP404wGEyj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GMGQ1/btsMbDkiAYh/JlHBF3E124JfP404wGEyj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GMGQ1/btsMbDkiAYh/JlHBF3E124JfP404wGEyj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGMGQ1%2FbtsMbDkiAYh%2FJlHBF3E124JfP404wGEyj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;517&quot; height=&quot;278&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.44.38.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Protocol&amp;nbsp;Mapper를 설정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.55.43.png&quot; data-origin-width=&quot;4430&quot; data-origin-height=&quot;1988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIqwX9/btsMcsoWYwV/ondb5nxTcGQugwRHs6JPS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIqwX9/btsMcsoWYwV/ondb5nxTcGQugwRHs6JPS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIqwX9/btsMcsoWYwV/ondb5nxTcGQugwRHs6JPS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIqwX9%2FbtsMcsoWYwV%2Fondb5nxTcGQugwRHs6JPS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4430&quot; height=&quot;1988&quot; data-filename=&quot;스크린샷 2025-02-09 오후 11.55.43.png&quot; data-origin-width=&quot;4430&quot; data-origin-height=&quot;1988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 이제 AccessToken을 발급받고 Attribute가 포함되었는지 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739113328239&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 환경변수 설정
export USER_NAME=&quot;user01&quot;
export USER_PASSWORD=&quot;user01&quot;
export CLIENT_ID=&quot;my-client&quot;
export REALM_NAME=&quot;my-realm&quot;
export KEYCLOAK_URL=&quot;http://localhost:8080&quot;


# 1. 사용자 토큰 발급
export USER_TOKEN=$(curl -X POST &quot;${KEYCLOAK_URL}/realms/${REALM_NAME}/protocol/openid-connect/token&quot; \
-H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
-d &quot;client_id=${CLIENT_ID}&quot; \
-d &quot;username=${USER_NAME}&quot; \
-d &quot;password=${USER_PASSWORD}&quot; \
-d &quot;grant_type=password&quot;)

# 2. Access Token 추출
export ACCESS_TOKEN=$(echo $USER_TOKEN | jq -r '.access_token')

# 3. Access Token 디코딩 (JWT)
echo $ACCESS_TOKEN | cut -d&quot;.&quot; -f2 | tr -d '-' | tr '_' '/' | sed -e 's/$/\=/' | base64 -d | jq '.' --color-output&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;protocol&amp;nbsp;mapper까지 설정하고 나면 AccessToken에 Department가 추가되어 전달된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-10 오전 12.19.18.png&quot; data-origin-width=&quot;3122&quot; data-origin-height=&quot;1614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Zvbx/btsMbb9Inmo/RZHG4w6lemX1n3dviBs1GK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Zvbx/btsMbb9Inmo/RZHG4w6lemX1n3dviBs1GK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Zvbx/btsMbb9Inmo/RZHG4w6lemX1n3dviBs1GK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Zvbx%2FbtsMbb9Inmo%2FRZHG4w6lemX1n3dviBs1GK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3122&quot; height=&quot;1614&quot; data-filename=&quot;스크린샷 2025-02-10 오전 12.19.18.png&quot; data-origin-width=&quot;3122&quot; data-origin-height=&quot;1614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RBAC vs ABAC&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 하나만 사용하는것이 아니라&amp;nbsp;&lt;br /&gt;기본적인 권한은 RBAC으로 관리하고, 특수한 상황은 ABAC으로 보완하는 형태로 두 방식을 혼합하여 사용하는 경우가 많다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 8.79842%; height: 19px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 34.0309%; height: 19px;&quot;&gt;RBAC&lt;/td&gt;
&lt;td style=&quot;width: 57.1706%; height: 19px;&quot;&gt;ABAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 8.79842%; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 34.0309%; height: 19px;&quot;&gt;Role기반의 접근제어&lt;br /&gt;
&lt;div style=&quot;background-color: #181818; color: #d6d6dd;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;{&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;user01&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;roles&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;],&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;groups&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;/hr/manager&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;],&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;permissions&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;write&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;]&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 57.1706%; height: 19px;&quot;&gt;속성 기반의 접근제어&lt;br /&gt;
&lt;div style=&quot;background-color: #181818; color: #d6d6dd;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;{&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;department&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;HR&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;level&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;senior&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;Seoul&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;clearance&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;top-secret&quot;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; },&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;resource&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;document&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;classification&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;confidential&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;department&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;HR&quot;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; },&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;environment&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;time&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;09:00-18:00&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #82d2ce;&quot;&gt;&quot;ip_range&quot;&lt;/span&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e394dc;&quot;&gt;&quot;10.0.0.0/24&quot;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt; }&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d6d6dd;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 8.79842%; height: 19px;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;width: 34.0309%; height: 19px;&quot;&gt;미리&amp;nbsp;정의된&amp;nbsp;역할/그룹에&amp;nbsp;기반&lt;br /&gt;관리가&amp;nbsp;단순&lt;br /&gt;변경이&amp;nbsp;적은&amp;nbsp;환경에&amp;nbsp;적합&lt;br /&gt;확장성&amp;nbsp;제한적&lt;/td&gt;
&lt;td style=&quot;width: 57.1706%; height: 19px;&quot;&gt;다양한&amp;nbsp;속성&amp;nbsp;기반&amp;nbsp;결정&lt;br /&gt;유연한&amp;nbsp;정책&amp;nbsp;설정&amp;nbsp;가능&lt;br /&gt;상황에&amp;nbsp;따른&amp;nbsp;동적&amp;nbsp;접근&amp;nbsp;제어&lt;br /&gt;복잡한&amp;nbsp;규칙&amp;nbsp;지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 8.79842%; height: 17px;&quot;&gt;예시&lt;/td&gt;
&lt;td style=&quot;width: 34.0309%; height: 17px;&quot;&gt;@RolesAllowed(&quot;admin&quot;)&lt;br /&gt;public&amp;nbsp;void&amp;nbsp;adminMethod()&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;관리자만&amp;nbsp;접근&amp;nbsp;가능&lt;br /&gt;}&lt;/td&gt;
&lt;td style=&quot;width: 57.1706%; height: 17px;&quot;&gt;@Attribute(&quot;department&amp;nbsp;==&amp;nbsp;'HR'&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;time.between('09:00',&amp;nbsp;'18:00')&quot;)&lt;br /&gt;public&amp;nbsp;void&amp;nbsp;hrDocumentAccess()&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;HR부서&amp;nbsp;직원이&amp;nbsp;업무시간&amp;nbsp;내&amp;nbsp;접근&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.79842%;&quot;&gt;시나리오&lt;/td&gt;
&lt;td style=&quot;width: 34.0309%;&quot;&gt;조직&amp;nbsp;구조가&amp;nbsp;명확한&amp;nbsp;기업&amp;nbsp;시스템&lt;br /&gt;권한&amp;nbsp;체계가&amp;nbsp;단순한&amp;nbsp;웹&amp;nbsp;애플리케이션&lt;br /&gt;사용자&amp;nbsp;그룹이&amp;nbsp;명확한&amp;nbsp;시스템&lt;/td&gt;
&lt;td style=&quot;width: 57.1706%;&quot;&gt;복잡한&amp;nbsp;보안&amp;nbsp;요구사항이&amp;nbsp;있는&amp;nbsp;시스템&lt;br /&gt;시간/위치&amp;nbsp;기반&amp;nbsp;접근&amp;nbsp;제어&amp;nbsp;필요&lt;br /&gt;다양한&amp;nbsp;조건에&amp;nbsp;따른&amp;nbsp;동적&amp;nbsp;권한&amp;nbsp;필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  주의사항&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Role Explosion (역할 폭발) 문제&lt;/span&gt;를 주의해야한다. 역할은 세분화된 인가를 위한 역할 사용하는것을 지양하고, 조직 구조/직무를 반영하도록 하는것이 좋다. (ADMIN, READ_USER 등)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-10 오전 12.49.24.png&quot; data-origin-width=&quot;2452&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLvCE2/btsMbGg6NhG/UdlXAsXnLbTc6qHQSc07e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLvCE2/btsMbGg6NhG/UdlXAsXnLbTc6qHQSc07e0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLvCE2/btsMbGg6NhG/UdlXAsXnLbTc6qHQSc07e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLvCE2%2FbtsMbGg6NhG%2FUdlXAsXnLbTc6qHQSc07e0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;614&quot; height=&quot;205&quot; data-filename=&quot;스크린샷 2025-02-10 오전 12.49.24.png&quot; data-origin-width=&quot;2452&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할(Role)은 큰 범주로 정의한다. (admin, superuser, readonlyuser 혹은 department 등)&lt;/li&gt;
&lt;li&gt;세부 접근제어는 속성(attributes)를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1739116251005&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;accessPolicy&quot;: {
        &quot;role&quot;: &quot;MANAGER&quot;,
        &quot;requiredAttributes&quot;: {
            &quot;department&quot;: [&quot;HR&quot;, &quot;IT&quot;],
            &quot;location&quot;: &quot;Seoul&quot;,
            &quot;timeWindow&quot;: &quot;businessHours&quot;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-10 오전 12.52.46.png&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rqBDk/btsMbIe1vcn/wvxwRpf6na416sBLeKOso0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rqBDk/btsMbIe1vcn/wvxwRpf6na416sBLeKOso0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rqBDk/btsMbIe1vcn/wvxwRpf6na416sBLeKOso0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrqBDk%2FbtsMbIe1vcn%2FwvxwRpf6na416sBLeKOso0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;291&quot; height=&quot;914&quot; data-filename=&quot;스크린샷 2025-02-10 오전 12.52.46.png&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Role Explosion (역할 폭발) 문제란, &lt;br /&gt;세부적인 부분까지 전부 Role로 정의하여 Role이 엄청 많아지는것&lt;/p&gt;</description>
      <category>  Infra/KeyCloak</category>
      <category>ABAC</category>
      <category>keycloak</category>
      <category>keycloak attribute</category>
      <category>keycloak OIDC</category>
      <category>keycloak sample</category>
      <category>keycloak test</category>
      <category>keycloak 권한</category>
      <category>keycloak 설정</category>
      <category>RBAC</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/183</guid>
      <comments>https://1mini2.tistory.com/183#entry183comment</comments>
      <pubDate>Sun, 9 Feb 2025 14:47:13 +0900</pubDate>
    </item>
    <item>
      <title>[keycloak 맛보기 #5] Keycloak의 애플리케이션 통합</title>
      <link>https://1mini2.tistory.com/182</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 id=&quot;참고자료&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;책:&amp;nbsp;&lt;a href=&quot;https://www.yes24.com/Product/Goods/122459785&quot;&gt;https://www.yes24.com/Product/Goods/122459785&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;테스트 소스코드:&amp;nbsp;&lt;a href=&quot;https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition&quot;&gt;https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 id=&quot;실습_준비)_Keycloak_&amp;amp;_테스트_애플리케이션_세팅&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;통합 방식 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak과 애플리케이션을 통합할 때,&amp;nbsp; Embedded와 Proxied 방식이 있다. &lt;br /&gt;각 방법은 애플리케이션의 구조와 보안 요구 사항에 따라 선택할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 7.51935%; height: 19px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 41.8217%; height: 19px;&quot;&gt;Embedded&lt;/td&gt;
&lt;td style=&quot;width: 50.6589%; height: 19px;&quot;&gt;Proxied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 7.51935%; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 41.8217%; height: 19px;&quot;&gt;애플리케이션 코드 내에서 Keycloak 어댑터를 직접 사용하여 인증 및 권한 부여를 처리한다.&lt;/td&gt;
&lt;td style=&quot;width: 50.6589%; height: 19px;&quot;&gt;역방향 프록시를 사용하여&amp;nbsp; Keycloak과의 통신을 처리하는 역방향 프록시를 설정하여 애플리케이션 앞단에서 인증을 처리한다.&lt;br /&gt;애플리케이션 코드와 독립적으로 Keycloak과의 통합을 관리한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 7.51935%; height: 19px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 41.8217%; height: 19px;&quot;&gt;애플리케이션 코드에서 인증 흐름을 직접 제어할 수 있다.&lt;br /&gt;다양한 인증 및 권한 부여 시나리오를 구현할 수 있다.&lt;/td&gt;
&lt;td style=&quot;width: 50.6589%; height: 19px;&quot;&gt;애플리케이션 코드에서 인증 로직을 제거하여 코드가 단순해진다.&lt;br /&gt;인증 및 권한 부여 로직이 프록시에서 처리되므로 보안이 강화된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 7.51935%; height: 17px;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 41.8217%; height: 17px;&quot;&gt;복잡성 증가: 애플리케이션 코드에 인증 로직이 포함되어 복잡성이 증가할 수 있다.&lt;br /&gt;보안 위험: 클라이언트 측에서 직접 토큰을 관리할 경우 보안 위험이 증가할 수 있다.&lt;/td&gt;
&lt;td style=&quot;width: 50.6589%; height: 17px;&quot;&gt;프록시 설정이 복잡할 수 있으며, 추가적인 인프라가 필요할 수 있다.&lt;br /&gt;프록시에서 제공하는 기능에 의존하게 되어, 특정 인증 시나리오를 구현하는 데 제한이 있을 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 방식 모두 상황에 따라 선택할 수 있고, 때로는 함께 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://openid.net/developers/certified-openid-connect-implementations/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Certified OpenID Connect Implementations&lt;/a&gt; 에서 인증된 라이브러리를 사용하는것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 id=&quot;실습_준비)_Keycloak_&amp;amp;_테스트_애플리케이션_세팅&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Javascript 애플리케이션 통합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실습 진행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습을 위해 Keycloak을 준비한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734439491030&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -p 8080:8080 \
          -e KEYCLOAK_ADMIN=admin \
          -e KEYCLOAK_ADMIN_PASSWORD=admin \
          quay.io/keycloak/keycloak \
          start-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://localhost:8080/으로 접속하여 새로운 realm을 생성했다. (옵션/생략가능)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 9.50.37.png&quot; data-origin-width=&quot;2580&quot; data-origin-height=&quot;1180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lycxp/btsLk9yh4gA/aN1PMixUhyv8vmbla5gSZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lycxp/btsLk9yh4gA/aN1PMixUhyv8vmbla5gSZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lycxp/btsLk9yh4gA/aN1PMixUhyv8vmbla5gSZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flycxp%2FbtsLk9yh4gA%2FaN1PMixUhyv8vmbla5gSZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;309&quot; data-filename=&quot;스크린샷 2024-12-17 오후 9.50.37.png&quot; data-origin-width=&quot;2580&quot; data-origin-height=&quot;1180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 client를 생성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;client id: javascript&lt;/li&gt;
&lt;li&gt;root URL: http://localhost:8000&lt;/li&gt;
&lt;li&gt;valid redirect URIs: http://localhost:8000/*&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 10.03.00.png&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;3000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LfkWm/btsLlPeZqAg/CyoEqKpVkXHDqejaSK7QT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LfkWm/btsLlPeZqAg/CyoEqKpVkXHDqejaSK7QT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LfkWm/btsLlPeZqAg/CyoEqKpVkXHDqejaSK7QT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLfkWm%2FbtsLlPeZqAg%2FCyoEqKpVkXHDqejaSK7QT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;573&quot; data-filename=&quot;스크린샷 2024-12-17 오후 10.03.00.png&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;3000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 사용자도 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 9.56.38.png&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bocJUr/btsLliPhTa9/G0XkNkKKeFcm38DOo3BWoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bocJUr/btsLliPhTa9/G0XkNkKKeFcm38DOo3BWoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bocJUr/btsLliPhTa9/G0XkNkKKeFcm38DOo3BWoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbocJUr%2FbtsLliPhTa9%2FG0XkNkKKeFcm38DOo3BWoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;353&quot; data-filename=&quot;스크린샷 2024-12-17 오후 9.56.38.png&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 테스트 코드를 준비한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734440320644&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 테스트 코드 다운로드
git clone https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition.git
cd ch7/keycloak-js-adapter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;realm과 client를 생성했으니, 코드도 수정해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1734440362028&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ch7/keycloak-js-adapter/public/keycloak.json
{
  &quot;realm&quot;: &quot;myservice&quot;,
  &quot;auth-server-url&quot;: &quot;http://localhost:8080&quot;,
  &quot;ssl-required&quot;: &quot;external&quot;,
  &quot;resource&quot;: &quot;javascript&quot;,
  &quot;public-client&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1734440385057&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ch7/keycloak-js-adapter/app.js
import express from 'express';
import stringReplace from 'string-replace-middleware';

const app = express();
const port = 8000;

app.use(stringReplace({
  KC_URL: process.env.KC_URL || &quot;http://localhost:8080&quot;
}));

app.use('/', express.static('public'));

app.listen(port, () =&amp;gt; {
  console.log(`Listening on port ${port}.`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keycloak은 로컬의 8080포트에서 운영되며, 어플리케이션은 8000포트로 올라오게 된다.&lt;br /&gt;이제 실행해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1734440723476&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 경로 잘 들어와있는지 한번 더 확인
cd Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/ch7/keycloak-js-adapter
# 시작
npm install
npm start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 http://localhost:8000 접속해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 10.04.17.png&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpftcL/btsLmNUPaLy/RpBAkEX8K0PYxlrwJ5h0n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpftcL/btsLmNUPaLy/RpBAkEX8K0PYxlrwJ5h0n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpftcL/btsLmNUPaLy/RpBAkEX8K0PYxlrwJ5h0n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpftcL%2FbtsLmNUPaLy%2FRpBAkEX8K0PYxlrwJ5h0n1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;223&quot; data-filename=&quot;스크린샷 2024-12-17 오후 10.04.17.png&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접속하면 바로 keycloak으로 연결되며, 로그인 하면 페이지에 접근할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 10.04.21.png&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHhP16/btsLmkrZvpo/KIXKDfqHkASRvXua2FdGq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHhP16/btsLmkrZvpo/KIXKDfqHkASRvXua2FdGq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHhP16/btsLmkrZvpo/KIXKDfqHkASRvXua2FdGq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHhP16%2FbtsLmkrZvpo%2FKIXKDfqHkASRvXua2FdGq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;218&quot; data-filename=&quot;스크린샷 2024-12-17 오후 10.04.21.png&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript를 8000번 포트로 띄우기 위해 실행하는 이 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;app.js&lt;/b&gt;&lt;/span&gt;코드에서는&lt;br /&gt;string-replace-middleware를 사용하여 애플리케이션의 정적 파일을 제공할 때, 파일 내의 KC_URL 변수에 Keycloak 주소를 담는다.&lt;/p&gt;
&lt;pre id=&quot;code_1734440950371&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ch7/keycloak-js-adapter/app.js
import express from 'express';
import stringReplace from 'string-replace-middleware';

const app = express();
const port = 8000;

// 환경 변수 KC_URL을 사용하여 Keycloak 서버의 URL을 설정한다.
app.use(stringReplace({
  KC_URL: process.env.KC_URL || &quot;http://localhost:8080&quot;
}));

app.use('/', express.static('public'));

app.listen(port, () =&amp;gt; {
  console.log(`Listening on port ${port}.`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;keycloak.json&lt;/span&gt;&lt;/b&gt; Keycloak&amp;nbsp;클라이언트&amp;nbsp;설정을&amp;nbsp;포함하고&amp;nbsp;있으며,&amp;nbsp;Keycloak&amp;nbsp;라이브러리가&amp;nbsp;초기화될&amp;nbsp;때&amp;nbsp;해당&amp;nbsp;설정을&amp;nbsp;사용한다.&lt;br /&gt;(Keycloak JavaScript 어댑터 라이브러리가 자동으로 로드함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작동 방식은 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Keycloak 초기화: index.html에서 Keycloak() 객체를 생성하고 init 메서드를 호출할 때, 라이브러리는 기본적으로 keycloak.json 파일을 찾는다.&lt;/li&gt;
&lt;li&gt;설정 로드: keycloak.json 파일이 public 디렉토리에 위치해 있으면, Keycloak 라이브러리가 이 파일을 자동으로 로드하여 필요한 설정을 적용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1734441225561&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ch7/keycloak-js-adapter/public/keycloak.json
{
  &quot;realm&quot;: &quot;myservice&quot;,
  &quot;auth-server-url&quot;: &quot;http://localhost:8080&quot;,
  &quot;ssl-required&quot;: &quot;external&quot;,
  &quot;resource&quot;: &quot;javascript&quot;,
  &quot;public-client&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;index.html&lt;/span&gt;&lt;/b&gt;은 Keycloak을 사용하여 사용자 인증을 처리하는 웹 페이지다. 코드는 길어서 설명만 추가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1734441616116&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;KC_URL/js/keycloak.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분에서 KC_URL은 app.js에서 설정된 Keycloak 서버의 URL로 대체된다.&lt;br /&gt;이 과정은 Keycloak 초기화의 일부로, Keycloak() 객체를 생성하고 init 메서드를 호출하여 Keycloak을 초기화한다. &lt;br /&gt;이때 keycloak.json 파일이 자동으로 로드되며, Keycloak JavaScript 어댑터 라이브러리가 로드되어 클라이언트 측에서 Keycloak과의 통신을 처리할 수 있게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1734442077687&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 생략 --&amp;gt;
      document.getElementById(&quot;logout&quot;).addEventListener(&quot;click&quot;, () =&amp;gt; {
        keycloak.logout();
      });

      document.getElementById(&quot;showIdToken&quot;).addEventListener(&quot;click&quot;, () =&amp;gt; {
        output(keycloak.idTokenParsed);
      });

      document
        .getElementById(&quot;showAccessToken&quot;)
        .addEventListener(&quot;click&quot;, () =&amp;gt; {
          output(keycloak.tokenParsed);
        });

      document
        .getElementById(&quot;refreshToken&quot;)
        .addEventListener(&quot;click&quot;, async () =&amp;gt; {
          await keycloak.updateToken(-1);
          output(keycloak.idTokenParsed);
          showProfile();
        });

      document
        .getElementById(&quot;showMyAccount&quot;)
        .addEventListener(&quot;click&quot;, async () =&amp;gt; {
          await keycloak.accountManagement()
        });
&amp;lt;!-- 생략 --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 코드내 버튼을 통해 클릭 이벤트 리스너를 추가하여 Keycloak의 기능을 수행할 수 있도록 했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그아웃: keycloak.logout()을 호출하여 사용자를 로그아웃한다.&lt;/li&gt;
&lt;li&gt;토큰 표시: keycloak.idTokenParsed)을 통해 토큰과 액세스 토큰을 JSON 형식으로 출력한다.&lt;/li&gt;
&lt;li&gt;토큰 갱신: keycloak.updateToken()을 호출하여 토큰을 갱신한다.&lt;/li&gt;
&lt;li&gt;계정 관리: keycloak.accountManagement()를 호출하여 사용자의 계정 관리 페이지로 이동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 id=&quot;실습_준비)_Keycloak_&amp;amp;_테스트_애플리케이션_세팅&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Nodejs 애플리케이션 통합 (frontend)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;실습 진행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습을 진행해보자.&amp;nbsp;&lt;br /&gt;위 단계에서 keycloak을 도커로 띄우고 realm을 생성했으니 해당 단계는 건너뛰고 바로 client부터 생성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;client id: nodejs&lt;/li&gt;
&lt;li&gt;root URL: http://localhost:8000&lt;/li&gt;
&lt;li&gt;client authentication: on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.14.16.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;2936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Uwkuy/btsLnbVicQj/Ua295bD7uiVaypAPkLYZ3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Uwkuy/btsLnbVicQj/Ua295bD7uiVaypAPkLYZ3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Uwkuy/btsLnbVicQj/Ua295bD7uiVaypAPkLYZ3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUwkuy%2FbtsLnbVicQj%2FUa295bD7uiVaypAPkLYZ3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;521&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.14.16.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;2936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 client이므로, Credentials 탭에서 Secret을 복사해둔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.16.05.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;1510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxkTH7/btsLnxjw7Sn/pF5LeZhYlKgwA2jBzZHW3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxkTH7/btsLnxjw7Sn/pF5LeZhYlKgwA2jBzZHW3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxkTH7/btsLnxjw7Sn/pF5LeZhYlKgwA2jBzZHW3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxkTH7%2FbtsLnxjw7Sn%2FpF5LeZhYlKgwA2jBzZHW3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;276&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.16.05.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;1510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 코드를 준비한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734445039659&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 테스트 코드 다운로드
git clone https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition.git
cd ch7/nodejs/frontend&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keycloak 설정에 맞게 코드를 수정한다.&lt;br /&gt;위에서 복제한 client secret으로 값을 변경하고, 8000포트에서 애플리케이션이 시작될 수 있도록 코드를 수정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734445108153&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ch7/nodejs/frontend/app.js
var express = require('express');
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var cors = require('cors');

var app = express();

app.use(cors());

var memoryStore = new session.MemoryStore();

app.use(session({
    secret: 'io9bBSukiwEyUekE62g88aeI4CKYUBTi', // 위에서 복사한 client secret 입력
    resave: false,
    saveUninitialized: true,
    store: memoryStore
}));

var keycloak = new Keycloak({ store: memoryStore });

app.use(keycloak.middleware());

app.get('/', keycloak.protect(), function (req, res) {
    res.setHeader('content-type', 'text/plain');
    res.send('Welcome!');
});

app.listen(8000, function () {         // 애플리케이션 포트 8000으로 수정
    console.log('Started at port 8000'); 
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1734445143180&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ch7/nodejs/frontend/keycloak.json
{
  &quot;realm&quot;: &quot;myservice&quot;, 
  &quot;auth-server-url&quot;: &quot;${env.KC_URL:http://localhost:8080}&quot;,
  &quot;resource&quot;: &quot;nodejs&quot;,
  &quot;credentials&quot; : {
    &quot;secret&quot; : &quot;io9bBSukiwEyUekE62g88aeI4CKYUBTi&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실행해보자&lt;/p&gt;
&lt;pre id=&quot;code_1734445213134&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 경로 잘 들어와있는지 한번 더 확인
cd Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/ch7/nodejs/frontend
# 시작
npm install
npm start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 브라우저에서 http://localhost:8000을 접속하면 아래와 같은 결과가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.20.44.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;1120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F9TIv/btsLnbnt4HN/sOBRvST7FfjdZ7EpueCUs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F9TIv/btsLnbnt4HN/sOBRvST7FfjdZ7EpueCUs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F9TIv/btsLnbnt4HN/sOBRvST7FfjdZ7EpueCUs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF9TIv%2FbtsLnbnt4HN%2FsOBRvST7FfjdZ7EpueCUs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;203&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.20.44.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;1120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.20.49.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ux25C/btsLlqzCyJY/xvwStdU6DUMOfvutWKwONk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ux25C/btsLlqzCyJY/xvwStdU6DUMOfvutWKwONk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ux25C/btsLlqzCyJY/xvwStdU6DUMOfvutWKwONk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fux25C%2FbtsLlqzCyJY%2FxvwStdU6DUMOfvutWKwONk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;609&quot; height=&quot;85&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.20.49.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;keycloak.json&lt;/b&gt;&lt;/span&gt; 은 Keycloak 클라이언트 설정을 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 javascript 실습에서도 keycloak.json파일이 사용되었는데 이는 javascript환경에서 주로 사용되는 표준으로,&amp;nbsp;&lt;br /&gt;다른 언어에서는 XML등의 형식으로 사용할 수 도있다. (어뎁터에 따라 다름)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.js 파일 내 모듈이 초기화될 때(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;var keycloak = new Keycloak({ store: memoryStore });&lt;/span&gt;) &lt;br /&gt;해당 설정을 사용하여 Keycloak 서버와의 통신을 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734445750816&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ch7/nodejs/frontend/keycloak.json
{
  &quot;realm&quot;: &quot;myservice&quot;,
  &quot;auth-server-url&quot;: &quot;${env.KC_URL:http://localhost:8080}&quot;,
  &quot;resource&quot;: &quot;nodejs&quot;,
  &quot;credentials&quot; : {
    &quot;secret&quot; : &quot;io9bBSukiwEyUekE62g88aeI4CKYUBTi&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;app.js&lt;/span&gt;&lt;/b&gt;에서는 keycloak모듈을 불러오고 초기화 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keycloak.json 그리고 app.js 두 곳에서 client secret이 모두 사용되었는데, 목적이 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[app.js] Express 세션의 secret:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Express의 세션 미들웨어는 사용자 세션을 관리하기 위해 사용된다.&lt;/li&gt;
&lt;li&gt;app.js에 사용된 secret은 세션 ID 쿠키를 서명하는 데 사용되어, 클라이언트와 서버 간의 세션 데이터가 변조되지 않도록 보호한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;[keycloak.json] Keycloak의 secret:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Keycloak의 secret은 클라이언트 인증을 위해 사용된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Keycloak 서버와 애플리케이션 간의 안전한 통신을 보장하기 위해 사용되며, keycloak.json 파일에 저장된다.&lt;/li&gt;
&lt;li&gt;Keycloak이 클라이언트를 식별하고 인증하는 데 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;var memoryStore = new session.MemoryStore();&lt;/span&gt; 부분은 세션데이터를 로컬 메모리에 저장하겠다는 설정이며,&lt;br /&gt;메모리 스토어는 일반적으로 개발환경에서만 사용된다. (서버가 재시작되면 세션데이터 모두 지워짐)&amp;nbsp;&lt;br /&gt;따라서 보통은 별도의 데이터베이스(Mongodb, rdb, redis) 등을 활용하여 세션을 관리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734445564146&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var express = require('express');
var session = require('express-session');
// Keycloak 모듈 불러오기
// 이 모듈은 Node.js 애플리케이션에서 Keycloak과의 통합을 지원함
var Keycloak = require('keycloak-connect');
var cors = require('cors');

var app = express();

app.use(cors());

// 메모리스토어 설정
// 세션 데이터를 저장하기 위해 메모리 스토어를 생성
var memoryStore = new session.MemoryStore();

app.use(session({
    secret: 'io9bBSukiwEyUekE62g88aeI4CKYUBTi',
    resave: false,
    saveUninitialized: true,
    store: memoryStore
}));

// Keycloak 인스턴스 생성
// Keycloak 인스턴스를 생성하고, 세션 스토어를 전달한다. 
// 이 인스턴스는 Keycloak과의 상호작용을 관리한다.
var keycloak = new Keycloak({ store: memoryStore });

// Keycloak 미들웨어 사용
// Keycloak 미들웨어를 Express 애플리케이션에 추가한다.
// 이 미들웨어는 요청을 가로채고, 인증 및 권한 부여를 처리한다.
app.use(keycloak.middleware());

// Keycloak으로 보호할 경로 설정
// keycloak.protect()를 사용하여 특정 경로를 보호한다. 
// 이 경로에 접근하려면 사용자가 인증되어야 합니다.
app.get('/', keycloak.protect(), function (req, res) {
    res.setHeader('content-type', 'text/plain');
    res.send('Welcome!');
});

app.listen(8000, function () {
    console.log('Started at port 8000');
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 id=&quot;실습_준비)_Keycloak_&amp;amp;_테스트_애플리케이션_세팅&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Nodejs 애플리케이션 통합 ( backend)&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;실습 진행&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실습을 진행해보자. 위에서 생성한 client를 재활용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;client id: nodejs&lt;/li&gt;
&lt;li&gt;root URL: http://localhost:8000&lt;/li&gt;
&lt;li&gt;client authentication: on&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드를 준비한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734446622578&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 테스트 코드 다운로드
git clone https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition.git
cd ch7/nodejs/backend&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;keycloak 설정에 맞게 코드를 수정한다.&lt;br /&gt;위에서 복제한 client secret으로 값을 변경하고, 8001포트에서 애플리케이션이 시작될 수 있도록 코드를 수정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734446743555&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ch7/nodejs/backend/app.js
var express = require('express');
var Keycloak = require('keycloak-connect');

var app = express();

var keycloak = new Keycloak({});

app.use(keycloak.middleware());

app.get('/hello', keycloak.protect(), function (req, res) {
  res.setHeader('content-type', 'text/plain');
  res.send('Access granted to protected resource');
});

app.listen(8001, function () {
  console.log('Started at port 8001');
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1734446772116&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ch7/nodejs/backend/keycloak.json
{
  &quot;realm&quot;: &quot;myservice&quot;,
  &quot;bearer-only&quot;: true,
  &quot;auth-server-url&quot;: &quot;${env.KC_URL:http://localhost:8080}&quot;,
  &quot;resource&quot;: &quot;nodejs&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실행해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1734446832611&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 경로 잘 들어와있는지 한번 더 확인
cd Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/ch7/nodejs/backend
# 시작
npm install
npm start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 브라우저에서 http://localhost:8001에 접속하면 아래처럼 Cannot GET / 오류가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.48.58.png&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwHmV/btsLmoHNOL5/lYb1mPiCuc8oCdp7B04BAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwHmV/btsLmoHNOL5/lYb1mPiCuc8oCdp7B04BAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwHmV/btsLmoHNOL5/lYb1mPiCuc8oCdp7B04BAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmwHmV%2FbtsLmoHNOL5%2FlYb1mPiCuc8oCdp7B04BAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;111&quot; data-filename=&quot;스크린샷 2024-12-17 오후 11.48.58.png&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 AccessToken이 없기때문이다.&amp;nbsp;&lt;br /&gt;CLI로 AccessToken을 가져와보자.&lt;/p&gt;
&lt;pre id=&quot;code_1734447742888&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# curl -X POST http://localhost:8080/realms/myservice/protocol/openid-connect/token \
#	 -u &quot;nodejs:io9bBSukiwEyUekE62g88aeI4CKYUBTi&quot; \
#	 -H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
#	 -d &quot;username=test01&quot; -d &quot;password=1234&quot;  \
#	 -d &quot;grant_type=password&quot; | jq -r '.access_token'

curl -X POST http://localhost:8080/realms/${realm}/protocol/openid-connect/token \
    -u &quot;${client_id}:${client_secret}&quot; \
    -H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
    -d &quot;username=${user_name}&quot; -d &quot;password=${user_password}&quot; \
    -d &quot;grant_type=password&quot; | jq -r '.access_token'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과로 Access Token을 얻었다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(참고: 브라우저를 통하지 않고 사용자ID/PW를 이용하여 접근토큰을 받으려면&amp;nbsp; &lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;Direct access grants이 허용되어있어야 하며, keycloak은 기본값이 허용이다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-18 오전 12.06.09.png&quot; data-origin-width=&quot;2124&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQfvKV/btsLmHtI48f/rEoDM0dedSTYNHkVgI2qZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQfvKV/btsLmHtI48f/rEoDM0dedSTYNHkVgI2qZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQfvKV/btsLmHtI48f/rEoDM0dedSTYNHkVgI2qZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQfvKV%2FbtsLmHtI48f%2FrEoDM0dedSTYNHkVgI2qZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;172&quot; data-filename=&quot;스크린샷 2024-12-18 오전 12.06.09.png&quot; data-origin-width=&quot;2124&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 Access Token을 가지고 Backend 서버에 접근해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1734448499018&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 예시, 각 변수 올바르게 설정 필요
# export access_token=$(curl -X POST http://localhost:8080/realms/myservice/protocol/openid-connect/token \
# -u &quot;nodejs:io9bBSukiwEyUekE62g88aeI4CKYUBTi&quot; \
# -H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
# -d &quot;username=test01&quot; -d &quot;password=1234&quot;  \
# -d &quot;grant_type=password&quot; | jq -r '.access_token')

export access_token=$(curl -X POST http://localhost:8080/realms/${realm}/protocol/openid-connect/token \
-u &quot;${client_id}:${client_secret}&quot; \
-H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
-d &quot;username=${user_name}&quot; -d &quot;password=${user_password}&quot; \
-d &quot;grant_type=password&quot; | jq -r '.access_token')

echo $access_token

curl -v GET http://localhost:8001/hello \
-H &quot;Authorization: Bearer &quot;$access_token&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 아래와 같은 결과가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-18 오전 12.18.15.png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;1500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCBaWE/btsLlpU3kDo/qMfOMp5xHZ30g4rbwOEKO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCBaWE/btsLlpU3kDo/qMfOMp5xHZ30g4rbwOEKO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCBaWE/btsLlpU3kDo/qMfOMp5xHZ30g4rbwOEKO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCBaWE%2FbtsLlpU3kDo%2FqMfOMp5xHZ30g4rbwOEKO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;444&quot; data-filename=&quot;스크린샷 2024-12-18 오전 12.18.15.png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;1500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 AccessToken 없이 요청한다면 실패하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-18 오전 12.20.24.png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt9Vho/btsLljtTPMr/QR9lwRvHkLhJ2QMMLyzTJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt9Vho/btsLljtTPMr/QR9lwRvHkLhJ2QMMLyzTJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt9Vho/btsLljtTPMr/QR9lwRvHkLhJ2QMMLyzTJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt9Vho%2FbtsLljtTPMr%2FQR9lwRvHkLhJ2QMMLyzTJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;241&quot; data-filename=&quot;스크린샷 2024-12-18 오전 12.20.24.png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 실습한 nodejs/backend 는 Keycloak을 사용해서 사용자를 인증하는것이 아닌,&lt;br /&gt;AccessToken을 활용하여 인증된 요청을 처리하는 리소스 서버로서의 역할을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734449285885&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ch7/nodejs/backend/app.js
var express = require('express');
var Keycloak = require('keycloak-connect');

var app = express();

var keycloak = new Keycloak({});

// keycloak 미들웨어 사용
app.use(keycloak.middleware());

// 보호된 리소스에 요청이오면 Keycloak 서버에 해당 토큰의 유효성을 검증
app.get('/hello', keycloak.protect(), function (req, res) {
  res.setHeader('content-type', 'text/plain');
  res.send('Access granted to protected resource');
});

app.listen(8001, function () {
  console.log('Started at port 8001');
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keycloak.protect()를 사용하여 특정 경로에 대한 접근을 보호하며, 작동방식은 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;AccessToken 수신&lt;/b&gt;: 클라이언트가 요청을 보낼 때, Authorization 헤더에 AccessToken을 포함한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토큰 검증&lt;/b&gt;: keycloak.protect() 미들웨어가 요청을 가로채고, Keycloak 서버에 해당 토큰의 유효성을 검증한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증 처리&lt;/b&gt;: 토큰이 유효하면 요청을 처리하고, 그렇지 않으면 접근이 거부된다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>  Infra/KeyCloak</category>
      <author>mini_world</author>
      <guid isPermaLink="true">https://1mini2.tistory.com/182</guid>
      <comments>https://1mini2.tistory.com/182#entry182comment</comments>
      <pubDate>Tue, 17 Dec 2024 22:29:16 +0900</pubDate>
    </item>
  </channel>
</rss>