Moving the authorized client to JdbcOAuth2AuthorizedClientService makes tokens work across replicas, so the session-in-Redis plus tokens-in-PostgreSQL split holds. The table then shows what sharing cannot fix: the primary key is (client_registration_id, principal_name) with no session in it, so a second login for the same user updates the same row rather than adding one. The refresh token sits in bytea as the raw JWT, readable with convert_from, and logout clears only the Redis session while the plaintext token row and the Keycloak SSO session both survive. The schema itself failed silently first because the default DDL uses blob, which PostgreSQL does not have, and continue-on-error swallowed it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
3.4 KiB
YAML
81 lines
3.4 KiB
YAML
server:
|
|
port: ${SERVER_PORT:8083}
|
|
servlet:
|
|
session:
|
|
cookie:
|
|
name: AP3_SESSION
|
|
http-only: true
|
|
same-site: lax
|
|
|
|
spring:
|
|
application:
|
|
name: keycloak-bff
|
|
datasource:
|
|
# B-2: authorized client 전용. Keycloak 과 같은 PostgreSQL 인스턴스지만
|
|
# 테이블이 다르다(oauth2_authorized_client). 운영이라면 분리를 검토한다.
|
|
url: ${BFF_DB_URL:jdbc:postgresql://localhost:5432/keycloak}
|
|
username: ${BFF_DB_USER:keycloak}
|
|
password: ${BFF_DB_PASSWORD:keycloak}
|
|
sql:
|
|
init:
|
|
# Spring Security 가 제공하는 DDL 을 그대로 쓴다.
|
|
# always 로 두면 매 기동마다 실행되므로 CREATE TABLE IF NOT EXISTS 가 아닌
|
|
# 스크립트에서는 실패한다 → continue-on-error 로 넘긴다.
|
|
mode: ${SPRING_SQL_INIT_MODE:always}
|
|
# ★ PostgreSQL 은 -postgres 판본을 써야 한다. 기본 판본은 `blob` 타입을
|
|
# 쓰는데 PostgreSQL 에는 그 타입이 없다(`bytea` 다). continue-on-error 가
|
|
# 그 실패를 삼켜서 "테이블이 조용히 안 생기는" 상태가 됐었다.
|
|
schema-locations: classpath:org/springframework/security/oauth2/client/oauth2-client-schema-postgres.sql
|
|
continue-on-error: true
|
|
data:
|
|
redis:
|
|
host: ${REDIS_HOST:localhost}
|
|
port: ${REDIS_PORT:6379}
|
|
session:
|
|
# Application Session 만 Redis 로 간다. OAuth2AuthorizedClient 는
|
|
# 이 설정과 무관하며 여전히 InMemory 다 — 조회 키가 다르기 때문이다(B-0).
|
|
store-type: ${SPRING_SESSION_STORE_TYPE:redis}
|
|
timeout: ${SPRING_SESSION_TIMEOUT:30m}
|
|
redis:
|
|
namespace: bff:session
|
|
security:
|
|
oauth2:
|
|
client:
|
|
registration:
|
|
keycloak:
|
|
provider: keycloak
|
|
client-id: bff-confidential
|
|
client-secret: ${KEYCLOAK_CLIENT_SECRET}
|
|
client-authentication-method: client_secret_basic
|
|
authorization-grant-type: authorization_code
|
|
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
|
|
scope:
|
|
- openid
|
|
- profile
|
|
- email
|
|
provider:
|
|
keycloak:
|
|
# 브라우저가 리다이렉트되는 주소와 BFF 가 서버끼리 부르는 주소는 다르다.
|
|
# 앞의 것은 외부에서 닿는 이름이어야 하고, 뒤의 것은 클러스터 안 주소여도 된다.
|
|
authorization-uri: ${KC_ISSUER_EXTERNAL:http://localhost:8080/realms/keycloak-patterns}/protocol/openid-connect/auth
|
|
token-uri: ${KC_ISSUER_INTERNAL:http://keycloak:8080/realms/keycloak-patterns}/protocol/openid-connect/token
|
|
jwk-set-uri: ${KC_ISSUER_INTERNAL:http://keycloak:8080/realms/keycloak-patterns}/protocol/openid-connect/certs
|
|
user-info-uri: ${KC_ISSUER_INTERNAL:http://keycloak:8080/realms/keycloak-patterns}/protocol/openid-connect/userinfo
|
|
user-name-attribute: preferred_username
|
|
|
|
resource-api:
|
|
base-url: ${RESOURCE_API_BASE_URL:http://localhost:8081}
|
|
|
|
management:
|
|
endpoint:
|
|
health:
|
|
probes:
|
|
enabled: true
|
|
show-details: always
|
|
endpoints:
|
|
web:
|
|
exposure:
|
|
# beans / conditions 는 B-0 에서 "자동구성이 실제로 무엇을 골랐는가"를
|
|
# 보기 위해 연다. 운영에 그대로 두면 내부 구조가 노출된다.
|
|
include: health,info,beans,conditions,env
|