리눅스 Keycloak 설치 OAuth 인증 서버 구현
ChatGPT와 블로그를 연동하기 위해 Keycloak 인증 서버를 구축한 기록이다. OAuth와 Keycloak의 역할을 설명하고, Debian 리눅스 환경에서 Keycloak 설치, MariaDB 연결, 관리자·Realm·클라이언트 생성, Scope와 Audience 설정을 진행했다. 아파치의 HTTPS 공개 주소와 인증 서버 메타데이터를 확인하는 과정까지 다루는 AI 기반 블로그 구조화 데이터 엔티티 관리 시스템 개발 기록 3편이다.
OAuth와 Keycloak의 역할과 용도
OAuth란 무엇인가?
OAuth(Open Authorization)는 사용자가 자신의 암호를 다른 웹사이트나 어플리케이션에 직접 제공하지 않고도, 자신의 정보나 기능에 접근할 권한을 위임할 수 있도록 하는 표준 인가(Delegated Authorization) 프로토콜이다. 말이 어렵지, 흔히 보는 네이버 ID로 로그인이나 구글 ID로 로그인같은 소셜 로그인을 생각하면 된다. (엄밀히는 OAuth 기반의 OIDC가 인증을 담당한다.)
네이버나 구글은 이미 사용자의 가입 정보를 가지고 있다. 다른 사이트에서 네이버나 구글에게 이 정보의 일부를 요청하면, 사용자는 제공할 범위를 확인하고 허용할 수 있다. 허용된 사이트는 사용자의 네이버 암호 대신 발급받은 토큰(Token)을 이용해 정해진 범위의 기능이나 데이터에 제한적으로 접근할 수 있게 된다.
Keycloak이란 무엇인가?
Keycloak은 사용자 계정과 로그인, 접근 권한을 관리하는 오픈소스 인증·인가 서버 프로그램으로, 다른 사이트나 어플리케이션이 사용자의 권한 부여를 맡기는 네이버나 구글의 역할을 담당한다.
어플리케이션에 허용할 권한을 설정하고, 사용자 로그인과 필요한 확인 절차를 거쳐 토큰을 발급하면, 서비스를 제공하는 어플리케이션이 토큰의 유효성과 권한을 확인해 요청을 처리한다. 즉, Keycloak은 로그인이나 권한 부여를 담당하고, 실제 데이터 조회나 작업 수행은 연결된 서비스가 담당하는 것이다.
Keycloak 설치 절차
다음은 본 필자가 AI 기반 블로그 구조화 데이터 엔티티 관리 시스템을 개발하며 실제 Keycloak을 설치한 과정을 정리한 기록이다.
Step 1. 사전 준비
-
Java 설치
Keycloak은 자바를 기반으로 동작하며, 2026년 10월 기준 OpenJDK 17·21·25를 사용할 수 있다. 본 필자는 17을 사용했다.
sudo apt-get install --no-install-recommends openjdk-17-jre-headless java -version
openjdk version "17.0.20.1" 2026-08-18 OpenJDK Runtime Environment (build 17.0.20.1+1-1-deb12u1-Debian) OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-deb12u1-Debian, mixed mode, sharing)
-
Keycloak 전용 실행 계정 생성
본 필자는
sudo adduser --system --group --home /opt/keycloak --no-create-home oauth가끔 암호를 잊어먹어 고생하지만가급적 필요한 목적에 따라 다른 계정을 사용하는 편이다.www-data같은 기존 계정으로 실행해도 되지만, Keycloak 실행용 시스템 계정과 그룹을 생성한다. -
Keycloak 다운로드
임의의 디렉토리에 Keycloak을 다운로드받는다. 웹에서 다운로드 받은 후 업로드해도 되지만 본 필자는 curl을 이용해 직접 다운로드했다.
curl -fL -o keycloak-26.8.0.tar.gz https://github.com/keycloak/keycloak/releases/download/26.8.0/keycloak-26.8.0.tar.gz -
Keycloak 데이터베이스 준비
데이터베이스에 Keycloak이 사용할 사용자 계정과 데이터베이스를 생성해 준다. 본 필자는 phpMyAdmin을 통해 생성했다.
데이터베이스 생성
Step 2. Keycloak 설치
Keycloak은 별도의 설치 프로그램이 존재하지 않는다. 원하는 디렉토리에 복사한 다음 설정을 적용해 빌드(Build)하고 실행하는 방식이다.
-
압축 해제와 설치 경로 정리
다운로드한 파일의 압축을 해제하고 원하는 디렉토리로 이동시킨다. 본 필자는
/opt/keycloak를 사용했다.sudo tar -xzf keycloak-26.8.0.tar.gz -C /opt/ sudo mv /opt/keycloak-26.8.0 /opt/keycloak
-
프로그램과 데이터 디렉토리 권한 설정
프로그램 파일은 root 소유로 유지하고, 데이터 디렉토리에 실행 계정인
oauth의 권한을 부여한다.sudo chown -R root:root /opt/keycloak sudo install -d -o oauth -g oauth -m 750 /opt/keycloak/data
-
Keycloak 버전 확인
sudo -u oauth /opt/keycloak/bin/kc.sh --versionbin/kc.sh를 실행해 설치한 배포본과 Java 실행 환경을 확인한다.Keycloak 26.8.0 JVM: 17.0.20.1 (Debian OpenJDK 64-Bit Server VM 17.0.20.1+1-1-deb12u1-Debian) OS: Linux 6.1.0-37-amd64 amd64
-
빌드 실행
MariaDB용으로 Keycloak을 빌드한다.
sudo env KC_DB=mariadb /opt/keycloak/bin/kc.sh buildQuarkus augmentation completed in 7157ms Server configuration updated and persisted.
Step 3. Keycloak 설정과 실행, 데이터베이스 연결
준비한 데이터베이스에 Keycloak을 연결하고 데이터베이스 스키마와 함께 초기 관리자 접속 정보를 설정한다.
-
데이터베이스 연결과 최초 실행
전용 계정인
oauth로 Keycloak을 실행한다. 데이터베이스 암호는 명령어에 직접 적지 않고 터미널에서 입력받아 환경 변수로 전달한다.sudo -u oauth bash -c ' read -r -s -p "Keycloak DB Password: " DB_PASSWORD < /dev/tty echo >&2 export KC_DB_URL="jdbc:mariadb://데이터베이스 주소와 포트/keycloak-oauth" export KC_DB_USERNAME="keycloak-oauth" export KC_DB_PASSWORD="$DB_PASSWORD" unset DB_PASSWORD exec /opt/keycloak/bin/kc.sh start --optimized \ --http-enabled=true \ --http-host=127.0.0.1 \ --http-port=8180 \ --hostname=http://127.0.0.1:8180'Initializing database schema. Using changelog META-INF/jpa-changelog-master.xml KC-SERVICES0050: Initializing master realm Bootstrap completed in 9.541 seconds Keycloak 26.8.0 on JVM (powered by Quarkus 3.40.1) started in 13.687s. Listening on: http://127.0.0.1:8180 Profile prod activated.
현재 데이터베이스에는 아무것도 없기 때문에
MIGRATION_MODEL테이블이나 일부 컬럼이 없다는 경고가 난다. 최초 실행 후 데이터베이스를 확인해 보면 테이블들이 생성된 것을 볼 수 있다.
Keycloak 초기화 후 데이터베이스에 생성된 테이블들 -
초기 임시 관리자 ID 생성
초기 관리자 ID를
temp-admin으로 설정한다. 계정을 생성하기 전에 Ctrl + C를 눌러 실행 중인 Keycloak을 중지한다. Keycloak이 중지된 상태에서 다음 관리자 생성 명령을 실행한다.sudo -u oauth bash -c ' read -r -s -p "Keycloak DB Password: " DB_PASSWORD < /dev/tty echo >&2 export KC_DB_URL="jdbc:mariadb://데이터베이스 주소와 포트/keycloak-oauth" export KC_DB_USERNAME="keycloak-oauth" export KC_DB_PASSWORD="$DB_PASSWORD" unset DB_PASSWORD exec /opt/keycloak/bin/kc.sh bootstrap-admin user \ --optimized \ --username temp-admin' -
Keycloak 재실행
임시 관리자 생성 후 Keycloak을 다시 실행한다. 이번에는 로컬 캐시를 사용하는
--cache=local옵션이 포함되어 있다.sudo -u oauth bash -c ' read -r -s -p "Keycloak DB Password: " DB_PASSWORD < /dev/tty echo >&2 export KC_DB_URL="jdbc:mariadb://데이터베이스 주소와 포트/keycloak-oauth" export KC_DB_USERNAME="keycloak-oauth" export KC_DB_PASSWORD="$DB_PASSWORD" unset DB_PASSWORD exec /opt/keycloak/bin/kc.sh start --optimized \ --cache=local \ --http-enabled=true \ --http-host=127.0.0.1 \ --http-port=8180 \ --hostname=http://127.0.0.1:8180'
이제 웹 브라우저로 127.0.0.1:8180에 접속하면 Keycloak 관리자 페이지에 접속할 수 있는 단계까지 왔다.
Step 4. SSH 터널 접속과 Keycloak 관리자 설정
Keycloak은 서버 내부의 127.0.0.1에서만 요청을 받도록 설정했다. 외부 작업 PC의 브라우저에서 접속하기 위해 SSH 터널을 구성하고, 이를 통해 관리자 계정을 설정한다.
-
윈도우 PowerShell에서 SSH 터널 연결
Keycloak을 실행하고 있는 터미널은 유지한 채로, 작업 중인 윈도우 PC에서 PowerShell 창을 열어 다음 명령을 실행한다. 예시 주소와 SSH 포트, ID는 실제 서버의 설정으로 바꾼다.
ssh -N -L 127.0.0.1:8180:127.0.0.1:8180 ID@123.456.789.012 -p 22작업 PC의
127.0.0.1:8180으로 접속하면 SSH 연결을 통해 서버 내부의 Keycloak으로 전달된다. 연결한 PowerShell 창은 종료하지 않는다. -
관리자 콘솔 접속
작업 PC의 웹 브라우저에서 다음 주소로 접속한다.
http://127.0.0.1:8180/admin/master/console/
Keycloak 관리자 콘솔 로그인 -
임시 관리자 로그인과 master Realm 확인
초기 설정용 임시 관리자 계정인
temp-admin으로 로그인하면 환영 메시지와 함께 관리자 페이지가 나타난다. 좌측 상단의 Realm 선택 영역에서 관리용 master Realm이 선택되어 있는지 확인한다.
master Realm의 관리자 화면 -
정식 관리자 계정 생성
좌측 메뉴에서 Users를 선택하고 Add user를 누른다. 사용자 이름과 필요한 정보를 입력한 뒤 Create를 눌러 계정을 생성한다. 다음은 생성된 사용자의 상세 정보 화면이다.
관리자 계정 정보 -
관리자 암호 설정
생성한 사용자의 Credentials 탭에서 Set password를 누른다. 암호와 확인값을 입력하고 Save를 눌러 저장한다. Temporary를 켜면 해당 사용자는 최초 로그인 때 암호를 변경해야 한다. 다음은 암호가 등록된 상태이다.
관리자 암호 등록 상태 -
역할 할당 화면 열기
같은 사용자의 Role mapping 탭을 열고 Assign role을 눌러 역할 할당 창을 연다.
Role mapping 탭에서 역할 할당 시작 -
admin 역할 할당
역할 할당 창에서 Realm roles 목록을 표시하고 admin을 검색한다. 해당 역할의 체크박스를 선택한 뒤 Assign을 눌러 할당한다. master Realm의 admin은 모든 Realm을 관리할 수 있는 역할이다.
admin 역할 선택과 할당 -
새 관리자 계정으로 권한 확인
로그아웃한 뒤 새 관리자 계정으로 다시 로그인한다. 좌측의 Manage realms에서 Create realm 화면에 접근할 수 있는지 확인한다. 마지막으로 Users 메뉴에서 불필요한 임시 관리자 ID를 삭제한다.
임시 사용자 삭제
ChatGPT 연동을 위한 OAuth 클라이언트와 권한 설정
ChatGPT가 사용자 MCP 서버에 접근해 사용자 데이터베이스를 조회하는 시나리오에 맞춰, 사용자 로그인을 요청할 클라이언트와 토큰을 검사할 클라이언트를 구분하고, 허용할 조회 범위와 토큰의 사용 대상을 설정한다. 시나리오의 상세 배경은 다음의 두 글을 참고하기 바란다.
- AI 기반 블로그 구조화 데이터 엔티티 관리 시스템 개발 기록 1 – MCP와 API의 차이
- AI 기반 블로그 구조화 데이터 엔티티 관리 시스템 개발 기록 2 – PHP MCP 구현과 API 연동
Step 1. 서비스 Realm과 OAuth 클라이언트 생성
-
서비스용 Realm 생성
관리자 콘솔의 Manage realms에서 Create realm을 선택한다. Realm name에 이름을 입력하고 Enabled를 켠 상태에서 Create를 누른다.
stdtbank Realm 생성 관리자 계정이 있는 master Realm과 서비스 사용자 및 클라이언트가 등록될 영역을 분리한 것이다. 이후 작업은 좌측 상단에서 생성한 Realm을 선택한 상태로 진행한다.
-
ChatGPT용 클라이언트 생성
좌측 메뉴에서 Clients를 선택하고 Create client를 누른다. Client type은 OpenID Connect, Client ID는
chatgpt-stdtbank로 지정한 뒤 Next를 누른다.
클라이언트 생성 여기서 클라이언트는 Keycloak에 등록하는 어플리케이션 정보이다. ChatGPT가 사용자 로그인과 권한 부여를 요청할 때 자신을 구분하는 데 사용한다.
-
인증 흐름과 PKCE 설정
Capability config에서 인증 방식을 설정한다.
클라이언트 인증 흐름과 PKCE 설정 - Client authentication : Off
- 클라이언트 Secret을 요구하지 않는 Public Client로 구성한다.
- Standard flow : On
- 사용자를 Keycloak 로그인 화면으로 보내고, 로그인 후 받은 인증 코드를 토큰으로 교환하는 표준 절차를 사용한다.
- Implicit flow : Off
- 인증 코드 교환 없이 인증 응답에서 토큰을 직접 전달받는 방식은 사용하지 않도록 한다.
- Direct access grants : Off
- ChatGPT가 사용자의 Keycloak ID와 암호를 직접 받아 토큰을 요청하는 방식은 사용하지 않도록 한다.
- PKCE Method : S256
- PKCE 검증값을 SHA-256으로 해시해 인증 요청에 전달한다.
-
인증 후 돌아갈 주소 (콜백 주소) 등록
Next를 눌러 Login settings로 이동해 사용자가 로그인 후 다시 돌아갈 콜백 주소를 Valid redirect URIs에 등록한다.
이 주소는 ChatGPT의 MCP 등록 화면에서 사용자 지정 OAuth 클라이언트를 선택하면 표시되는 콜백 URL에서 확인할 수 있다. 기본값은
https://chatgpt.com/connector_platform_oauth_redirect이지만 달라질 수 있다.Valid redirect URIs에 ChatGPT 콜백 주소인
https://chatgpt.com/connector_platform_oauth_redirect를 입력한다.
ChatGPT 콜백 주소
Step 2. 클라이언트 권한 생성과 연결
클라이언트가 요청할 수 있는 권한의 범위를 Scope라고 한다. 클라이언트에 Scope를 연결해 주는 방식으로 클라이언트의 권한을 제어할 수 있다. 읽기 권한만 허용하도록 MCP 서버와 API의 접근 권한을 함께 구성한다.
-
권한 범위(Scope) 생성
좌측의 Client scopes를 선택하고 Create client scope를 눌러 생성 화면을 연다.
Client Scope 생성 (설정 입력 전 화면) Name에
stdtbank:read를 입력하고 Protocol은 OpenID Connect로 둔다. Include in token scope를 켜 토큰의 scope 값에 포함되도록 설정한 뒤 Save를 누른다. -
클라이언트에 Scope 연결
Clients에서 chatgpt-stdtbank를 열고, 해당 클라이언트의 Client scopes 탭에서 Add client scope를 누른다.
조회용 Scope 선택과 Optional 할당 stdtbank:read의 체크박스를 선택하고, Add 버튼 옆의 화살표를 눌러 Optional을 선택한다. 추가 후 Assigned type이 Optional인지 확인한다.
stdtbank:read의 Optional 할당 상태 Optional로 할당했으므로 인증 요청에서도 이 Scope를 요청하도록 구성해야 한다. 이후 MCP 서버는 전달받은 토큰에 필요한 Scope가 포함되어 있는지 확인한다.
Step 3. Audience와 토큰 구성 확인
오디언스(Audience)는 토큰을 전달받아 처리할 서비스, 즉 토큰의 사용 대상을 나타낸다. MCP 서버는 이 값을 검사해 내 토큰이 맞는지 확인한다.
-
오디언스 매퍼(Audience Mapper) 등록
Clients에서 chatgpt-stdtbank를 열고 Client scopes 탭으로 이동한다. 전용 Scope인 chatgpt-stdtbank-dedicated를 열어 Mappers 탭의 파란색 Add mapper를 누른다.
Add mapper를 선택 두 가지 메뉴 중 By configuration을 선택한다.
By configuration을 선택 - From predefined mappers
- 미리 정의된 매퍼를 선택해 추가한다.
- By configuration
- 매퍼 유형을 선택하고 이름과 반영할 값을 직접 설정한다.
Audience를 선택한다.
Audience 선택 사용할 이름과 MCP 서버의 공개 주소를 입력해 준다.
MCP 서버의 공개 주소를 Audience로 지정 - Name
- 매퍼를 구분할 이름
- Included Client Audience
- Keycloak에 등록된 클라이언트를 오디언스로 추가한다. 선택 해제
- Included Custom Audience
- MCP 서버의 공개 주소
- Add to ID token : Off
- 인증 정보 ID 토큰에 이 매퍼의 오디언스를 추가하지 않음
- Add to access token : On
- MCP 서버에 접근할 때 사용하는 토큰의
aud에 오디언스 포함 - Add to lightweight access token : Off
- Lightweight Access 토큰에 오디언스를 직접 추가하지 않음
- Add to token introspection : On
- MCP 서버의 토큰 상태 조회 요청에 오디언스를 포함해 응답
매퍼가 생성된 모습 -
토큰 사용자 추가
필요한 경우 토큰 평가에 사용할 서비스 사용자도 준비한다. stdtbank Realm을 선택한 상태에서 Users의 Add user를 눌러 시험용 사용자를 생성한다. 이후 실제 로그인에 사용할 수 있도록 Credentials에서 암호를 설정한다. 이 계정에는 master Realm의 관리자 역할을 부여하지 않는다.
-
토큰 구성 평가
매퍼를 등록했으니 토큰에 반영되는지 확인한다. Clients에서 chatgpt-stdtbank를 열고, Client scopes 탭의 Evaluate로 이동해 Scope와 사용자를 선택한다.
사용자와 요청 스코프 지정 - Scope parameter
openid와stdtbank:read를 선택한다. 앞에서 Optional로 연결한 조회용 Scope를 평가에 포함하는 것이다.- Users
- 토큰 구성을 확인할 서비스 사용자를 선택한다. 본 필자는 시험용 사용자 계정을 사용했다.
- Target audience
- 본 구성에서는 선택하지 않는다. 앞에서 매퍼에 입력한 MCP 공개 주소를 이 항목에 다시 입력하는 것은 아니다.
화면 아래 우측의 Generated access token을 열어 토큰에 반영될 내용을 확인한다.
오디언스와 클라이언트 정보 확인 이 화면은 현재 설정에 따라 생성될 토큰 정보를 미리 보는 것이다.
Step 4. MCP 서버 토큰 검증 클라이언트 생성
MCP 서버가 토큰 상태를 확인할 때 자신을 인증하는 데 사용할 Keycloak 클라이언트를 생성한다. MCP와 ChatGPT에 대응하는 클라이언트는 아래와 같이 분리한다.
chatgpt-stdtbank- ChatGPT가 사용자 로그인과 권한 부여를 요청할 때 사용하는 클라이언트
stdtbank-mcp-validator- MCP 서버가 Keycloak의 Introspection 엔드포인트에 토큰 상태를 질의할 때 자신을 인증하는 클라이언트
-
검증용 클라이언트 생성
좌측 상단에서 stdtbank Realm이 선택되어 있는지 확인한다. Clients에서 Create client를 누르고 다음과 같이 입력한다.
- Client type
- OpenID Connect
- Client ID
stdtbank-mcp-validator- Name
STDTBank MCP Token Validator
Next를 눌러 인증 설정으로 이동한다.
-
클라이언트 인증 활성화
화면 우측의 Capability config에서 클라이언트 인증을 켜고, 표준 절차를 사용하지 않게 설정한다. 세부 옵션 내용은 위에서 진행한 ChatGPT 클라이언트 생성 과정에 있다.
- Client authentication : On
- Standard flow : Off
- Implicit flow : Off
- Direct access grants : Off
- Service accounts roles : Off
- 이 구성에서는 클라이언트 자체의 Access Token을 발급받는 흐름도 사용하지 않는다.
설정을 마치고 저장하면 클라이언트가 생성된다.
MCP 토큰 검증용 클라이언트 생성 -
Client Secret 확인
생성한 클라이언트를 열고 Credentials 탭으로 이동한다. Client Authenticator가 Client Id and Secret인지 확인한다.
검증용 클라이언트의 인증 방식과 비밀정보를 확인 - Client ID
stdtbank-mcp-validator- Client Secret
- 해당 클라이언트임을 인증하는 키값. MCP 서버가 Introspection 요청을 보낼 때 Client ID와 함께 사용한다.
키값은 나중에 MCP 서버의 인증 검사 기능을 구성할 때 사용한다. MCP 서버는 전달받은 토큰의 상태를 Keycloak에 질의하면서, 검증용 Client ID와 이 키값을 함께 전송해 자신을 인증한다.
Keycloak의 HTTPS 공개 주소 구성
외부의 ChatGPT가 인증 정보를 확인하고 사용자를 로그인 화면으로 보내려면 외부에서 Keycloak에 접근할 수 있어야 한다. 아파치 웹 서버를 이용해 외부의 연결을 로컬로 전달하도록 구성한다.
- 외부 공개 주소
https://www.eqmaker.kr/auth/를 사용한다. 외부에서는 아파치가 HTTPS 요청을 받는다.- 내부 실행 주소
http://127.0.0.1:8180/auth/를 사용한다. Keycloak은 계속 서버 내부에서만 요청을 받는다.- 아파치의 역할
- 외부에서 받은 요청을 내부 Keycloak에 전달하고 응답을 돌려주는 리버스 프록시 역할을 한다.
Step 1. Keycloak 실행 경로 설정
최초 빌드에서는 KC_HTTP_RELATIVE_PATH를 지정하지 않아 기본 경로인 /를 사용했다. 이제 외부 공개 주소를 https://www.eqmaker.kr/auth/로 구성하기 위해 내부 Keycloak에도 /auth 경로를 적용한다.
Keycloak 서비스를 중지하고 MariaDB 설정과 함께 실행 경로를 지정해 다시 빌드한다.
sudo env KC_DB=mariadb KC_HTTP_RELATIVE_PATH=/auth /opt/keycloak/bin/kc.sh buildKeycloak 서비스를 다시 실행한다.
sudo -u oauth bash -c '
read -r -s -p "Keycloak DB Password: " DB_PASSWORD < /dev/tty
echo >&2
export KC_DB_URL="jdbc:mariadb://데이터베이스 주소와 포트/keycloak-oauth"
export KC_DB_USERNAME="keycloak-oauth"
export KC_DB_PASSWORD="$DB_PASSWORD"
unset DB_PASSWORD
exec /opt/keycloak/bin/kc.sh start --optimized \
--cache=local \
--http-enabled=true \
--http-host=127.0.0.1 \
--http-port=8180 \
--hostname=https://www.eqmaker.kr/auth \
--proxy-headers=xforwarded \
--proxy-trusted-addresses=127.0.0.1/8,::1/128'
기존의 실행 명령에서 다음과 같은 내용이 추가된다.
--hostname=https://www.eqmaker.kr/auth- Keycloak이 외부에 안내할 공개 주소를 지정한다.
--proxy-headers=xforwarded- 아파치가 전달하는 X-Forwarded 계열 헤더를 해석한다.
--proxy-trusted-addresses=127.0.0.1/8,::1/128- 프록시 헤더를 신뢰할 연결의 출발지를 루프백 주소로 제한한다.
설정이 적용되었는지 응답을 확인해 본다.
debian@eqmaker:~$ curl -sS -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:8180/auth/realms/stdtbank
HTTP 200
debian@eqmaker:~$ curl -sS -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:8180/realms/stdtbank
HTTP 404
Step 2. 아파치 리버스 프록시 설정
아파치 웹 서버에서 사이트 conf 파일에 리버스 프록시 설정을 추가하고 서비스를 재시작한다.
ProxyRequests Off
ProxyPreserveHost On
ProxyPass "/auth/realms/" "http://127.0.0.1:8180/auth/realms/"
ProxyPassReverse "/auth/realms/" "http://127.0.0.1:8180/auth/realms/"
ProxyPass "/auth/resources/" "http://127.0.0.1:8180/auth/resources/"
ProxyPassReverse "/auth/resources/" "http://127.0.0.1:8180/auth/resources/"
ProxyPass "/auth/admin/" "http://127.0.0.1:8180/auth/admin/"
ProxyPassReverse "/auth/admin/" "http://127.0.0.1:8180/auth/admin/"
ProxyPass "/.well-known/oauth-authorization-server/auth/realms/stdtbank" "http://127.0.0.1:8180/auth/.well-known/oauth-authorization-server/realms/stdtbank"
ProxyPassReverse "/.well-known/oauth-authorization-server/auth/realms/stdtbank" "http://127.0.0.1:8180/auth/.well-known/oauth-authorization-server/realms/stdtbank"
<LocationMatch "^/auth/(admin(?:/|$)|realms/master(?:/|$))">
Require all denied
</LocationMatch>
<LocationMatch "^/auth/(realms|resources)/">
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
RequestHeader set X-Forwarded-Host "www.eqmaker.kr"
</LocationMatch>
Step 3. 인증 서버 메타데이터 공개와 확인
외부에서 인증 서버의 메타데이터를 가져올 수 있는지 확인한다. 어플리케이션이 정해진 주소에서 메타데이터를 읽어 인증 절차에 필요한 주소와 지원 기능을 확인하는 과정을 Discovery라고 한다.
-
OIDC Discovery 확인
먼저 stdtbank Realm의 OIDC Discovery 문서를 조회한다. 웹 브라우저를 통해 Keycloak에 접속해 확인할 수 있다. 본 필자의 경우 아래의 주소를 사용했다.
https://www.eqmaker.kr/auth/realms/stdtbank/.well-known/openid-configuration응답의 issuer와 각 엔드포인트가 서버 내부 주소가 아닌 공개 HTTPS 주소를 사용하는지 확인한다.
"issuer": "https://www.eqmaker.kr/auth/realms/stdtbank", "authorization_endpoint": "https://www.eqmaker.kr/auth/realms/stdtbank/protocol/openid-connect/auth", "token_endpoint": "https://www.eqmaker.kr/auth/realms/stdtbank/protocol/openid-connect/token", "introspection_endpoint": "https://www.eqmaker.kr/auth/realms/stdtbank/protocol/openid-connect/token/introspect", "jwks_uri": "https://www.eqmaker.kr/auth/realms/stdtbank/protocol/openid-connect/certs"
issuer- 토큰을 발급하는 주체의 식별자, MCP 서버가 토큰 발급자를 확인할 때 사용
authorization_endpoint- 사용자의 로그인과 권한 부여 절차를 시작하는 주소
token_endpoint- 인증 코드를 토큰으로 교환하는 등의 요청을 처리하는 주소
introspection_endpoint- MCP 서버가 토큰의 상태와 정보를 확인하는 주소
jwks_uri- 토큰의 서명을 검증하는 데 사용하는 공개키 정보 주소
-
OAuth 서버 메타데이터 확인
RFC 8414 OAuth의 메타데이터도 외부에서 접근이 가능한지 확인한다. 본 필자의 경우 아래의 주소를 이용해 검증했다.
- https://www.eqmaker.kr/.well-known/oauth-authorization-server/auth/realms/stdtbank : 접속 가능
- https://www.eqmaker.kr/auth/realms/master : 접속 불가
자동 실행 등록
부팅시 자동 실행 되도록 등록해 준다. 이에 대해서는 리눅스 부팅 자동 명령 실행: cron과 systemd을 참고하기 바란다.
마무리
최종적으로 외부에서 OAuth 인증 서버 메타데이터와 OIDC Discovery 문서를 조회하고, 문서에 정의된 엔드포인트가 공개 HTTPS 주소를 사용하는 것까지 확인했다. 외부 어플리케이션이 Keycloak을 찾아 인증을 요청할 수 있도록 준비한 것이다. 실제 로그인과 토큰 발급, 서비스의 권한 검사가 이어지면 여러 서비스를 하나의 인증 체계로 연결하고 접근 권한을 제어할 수 있다.
본 필자는 ChatGPT와 블로그의 기능을 연동하기 위해 OAuth를 사용하게 되었다. 비록 소 잡는 칼로 병아리를 잡는 데 사용한 듯싶으나, 보안과 관련된 것은 항상 검증된 방법과 솔루션을 사용해야 한다. 물론 솔루션을 가져다 놓는 것만으로 끝나는 것은 아니고, 설정과 실제 동작도 확인해야 한다.
축약할 것은 축약하고, 본 필자가 생각하기에 굳이 기록으로 남겨 두지 않아도 될 것 같은 내용들은 넘겼는데도, 지금 이 문단을 적으며 보니 에디터의 숫자가 이미 36,000자를 넘기고 있다. 한 글에 다 담기에는 꽤나 벅찬 양이지 않았나 싶다. 음향 엔지니어 강좌의 콘솔 설명보다도 더 머리가 아프고 길고 복잡한 과정이었다. (필자 잘했어~)

암튼, 고생고생하며 이렇게 만든 OAuth 인증 시스템을 본 필자가 실제로 어떻게 써먹었는지 궁금하신 독자 제위께서는 다음 게시글을 한번 봐 주시라.
FAQ
- OAuth란 무엇이며 어떤 용도로 사용하는가?
- OAuth는 사용자가 자신의 암호를 다른 어플리케이션에 직접 제공하지 않고도, 특정 정보나 기능에 접근할 권한을 위임하는 표준 인가 프로토콜이다. 외부 서비스 연동과 API 접근 등에 사용하며, 어플리케이션은 발급받은 Access Token으로 허용된 작업을 요청한다.
- Keycloak은 무엇이며 어떤 기능을 제공하는가?
- Keycloak은 사용자 계정과 로그인, 권한 부여를 관리하는 오픈소스 인증·인가 서버이다. OAuth 2.0과 OpenID Connect를 지원하며, 토큰 발급과 상태 조회, 통합 로그인(SSO), 다중 인증, 외부 계정 연동 등의 기능을 제공한다.
- Keycloak의 Realm이란 무엇이며 master Realm은 무엇이 다른가?
- Realm은 사용자와 자격 증명, 역할, 클라이언트 등을 구분해 관리하는 영역이다. master Realm은 Keycloak 최초 실행 시 생성되는 특별한 관리 영역으로, 다른 Realm을 생성하고 관리하는 계정과 권한을 구성하는 데 사용한다. 일반 서비스의 사용자와 클라이언트는 별도의 Realm에서 관리할 수 있다.
- Keycloak에서 말하는 클라이언트는 사용자나 프로그램과 무엇이 다른가?
- 클라이언트는 Keycloak에 인증과 권한 부여를 요청하는 어플리케이션의 등록 정보이다. Client ID와 인증 방식, 허용할 콜백 주소 등을 설정한다. 사용자 계정과는 별개이며, 클라이언트를 등록한다고 새로운 프로그램이 생성되는 것은 아니다.
- OAuth 토큰의 Audience는 무엇이며 왜 확인해야 하는가?
- Audience는 토큰을 전달받아 처리할 대상 서비스를 나타내며, 토큰의
aud항목에 표시된다. 서비스를 제공하는 서버는 이 값을 검사해 자신을 대상으로 발급된 토큰인지 확인한다. - OAuth의 Scope란 무엇이며 실제 접근 권한은 어떻게 제한하는가?
- Scope는 어플리케이션이 요청하거나 토큰에 부여된 권한 범위를 나타낸다. 이름을 정하는 것만으로 작업이 제한되는 것은 아니며, 서비스를 제공하는 서버가 Scope와 실제 작업 권한을 검사해야 한다.
- Keycloak의 Realm 주소와 issuer 주소를 인터넷에 공개해도 되는가?
- 서비스 Realm의 공개 주소와 issuer는 외부 어플리케이션이 인증 서버를 확인하는 데 사용하는 정보이므로 공개해야 한다. 주소를 아는 것만으로 관리 권한이나 데이터 접근 권한을 얻지는 않는다.
- Keycloak의 KC_HTTP_RELATIVE_PATH는 무엇이며 기본값은 무엇인가?
- Keycloak의 웹 서비스 기본 URL 경로를 지정하는 설정이며, 별도 설정이 없다면 기본값은
/이다./auth로 설정하면 Realm 등의 주소 앞에 /auth 경로가 붙는다. 프로그램 설치 디렉토리를 변경하거나 아파치 프록시 설정을 자동으로 생성하는 것은 아니다. - Keycloak Discovery 문서의 모든 엔드포인트를 브라우저에서 열어 JSON이 나와야 정상인가?
- 아니다. Discovery 문서는 브라우저로 조회할 수 있지만, 일부 엔드포인트에는 매개변수가 있어야 한다. 주소를 직접 열었을 때의 응답만으로 기능의 정상 여부를 판단할 수 없으며, 실제 HTTP 500 오류가 발생한다면 서버 로그를 확인해야 한다.
- Keycloak의 Valid redirect URIs에 입력하는 ChatGPT 콜백 주소는 누가 정하며 어디에서 확인하는가?
- ChatGPT 측에서 제공하는 주소이다. 사용자가 자신의 서버에 만드는 주소가 아니며, ChatGPT의 MCP 등록 화면에 표시되는 콜백 URL을 확인해 Keycloak에 허용 주소로 등록한다. 실제 인증 요청의 redirect_uri가 등록한 주소와 일치해야 한다.