로컬 모의 서버

keenauth mock serve는 네트워크 접근이 없는 CI 작업과 로컬 개발을 위해 OpenID Connect, SAML 2.0, SCIM 2.0 서버와 /v1 관리 API의 메모리 내 버전을 사용자의 컴퓨터에서 실행합니다. KeenAuth 환경, API 키, 인터넷 연결이 필요하지 않습니다. 모든 데이터는 메모리에만 보관되며 프로세스가 종료되면 삭제됩니다.

모의 서버는 KeenAuth가 아닙니다. 아래에 나열된 흐름만 구현하고, 모든 요청을 자동으로 승인하며, 실제 서비스보다 훨씬 적게 검사합니다. 실제 동작의 기준은 호스팅된 development 환경뿐입니다. 릴리스하기 전에 그 환경을 대상으로 엔드투엔드 테스트를 실행하십시오.

서버 시작

keenauth mock serve

기본적으로 서버는 127.0.0.1:4010에서 수신합니다. 이는 다른 컴퓨터가 접근할 수 없는 루프백 주소입니다. --port로 포트를 바꿀 수 있습니다(0이면 빈 포트를 고릅니다). --host로 주소를 바꿀 수 있으며, 루프백이 아닌 주소를 지정하면 모의 서버가 네트워크에 노출되고 CLI가 경고를 출력합니다. Ctrl+C로 중지합니다.

이 명령은 자격 증명을 읽지 않으며 KeenAuth로 아무것도 보내지 않습니다.

엔드포인트

경로 용도
/.well-known/openid-configuration OIDC 디스커버리 문서(/.well-known/oauth-authorization-server에서도 제공)
/.well-known/jwks.json 공개 테스트 키 집합(키 ID keenauth-test-1)
/oauth2/authorize 인가 엔드포인트: PKCE(S256)를 사용하는 인가 코드 흐름
/oauth2/token 토큰 엔드포인트: authorization_code 및 refresh_token grant
/oauth2/userinfo UserInfo 엔드포인트(openid 스코프가 있는 Bearer 액세스 토큰)
/saml-idp/<application_id>/metadata SAML 2.0 아이덴티티 프로바이더 메타데이터
/saml-idp/<application_id>/sso SAML 싱글 사인온 엔드포인트(HTTP-Redirect 및 HTTP-POST 바인딩)
/scim/v2/<directory_id>/… SCIM 2.0 엔드포인트: Users, Groups, ServiceProviderConfig, ResourceTypes, Schemas
/v1/… 메모리 내 관리 API: 관리 API의 OpenAPI 문서에 있는 모든 작업

디스커버리 문서와 SAML 메타데이터의 엔드포인트 URL은 서버가 수신하는 로컬 주소를 가리킵니다. <application_id>와 <directory_id>는 원하는 대로 정할 수 있는 이름입니다(A-Z, a-z, 0-9, -, _로 이루어진 1~64자). 미리 등록할 필요는 없습니다.

동작 방식

토큰과 검증기

토큰은 오프라인 테스트 키트의 공개 테스트 키로 서명되며, 로컬 주소가 아니라 발급자 https://test.keenauth.invalid를 가집니다. 최상위 도메인 .invalid는 예약되어 있으므로 운영 환경의 검증기는 이 토큰을 절대 받아들이지 않습니다. SDK 검증기는 테스트 코드에서 명시적으로 켠 테스트 모드에서만 이 토큰을 받아들입니다.

// Go: accept tokens from the mock server in a test.
v, err := keenauthtest.NewVerifier(verify.Config{Audience: "https://test.keenauth.invalid/oauth2/userinfo"})
if err != nil {
	t.Fatal(err)
}
claims, err := v.VerifyAccessToken(ctx, accessToken, nil)

TypeScript(createTestVerifier)와 Python(keenauth.testing.verifier)에서도 같은 테스트 모드를 사용합니다. 디스커버리 문서의 issuer가 가져온 URL과 같아야 한다고 요구하는 일반 OIDC 클라이언트 라이브러리는 모의 서버의 디스커버리 문서를 거부합니다. 이 경우 엔드포인트 URL을 직접 설정하거나 development 환경을 대상으로 테스트하십시오.

예제: curl로 전체 흐름 실행

이 예제에는 curl, openssl, jq가 필요합니다. 먼저 다른 터미널에서 서버를 시작하십시오.

BASE=http://127.0.0.1:4010
REDIRECT=http://127.0.0.1:8080/callback
VERIFIER=$(openssl rand -hex 32)
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')

# 1. Authorize: the server answers with a redirect that carries the code.
LOCATION=$(curl -s -o /dev/null -w '%{redirect_url}' --get "$BASE/oauth2/authorize" \
  --data-urlencode response_type=code --data-urlencode client_id=app_local \
  --data-urlencode redirect_uri="$REDIRECT" --data-urlencode 'scope=openid email offline_access' \
  --data-urlencode state=xyz --data-urlencode code_challenge="$CHALLENGE" \
  --data-urlencode code_challenge_method=S256)
CODE=$(printf '%s' "$LOCATION" | sed -E 's/.*[?&]code=([^&]+).*/\1/')

# 2. Exchange the code for tokens.
TOKENS=$(curl -s "$BASE/oauth2/token" -d grant_type=authorization_code -d client_id=app_local \
  --data-urlencode code="$CODE" --data-urlencode redirect_uri="$REDIRECT" -d code_verifier="$VERIFIER")

# 3. Call UserInfo with the access token.
curl -s "$BASE/oauth2/userinfo" -H "Authorization: Bearer $(printf '%s' "$TOKENS" | jq -r .access_token)"

# 4. Refresh.
curl -s "$BASE/oauth2/token" -d grant_type=refresh_token -d client_id=app_local \
  --data-urlencode refresh_token="$(printf '%s' "$TOKENS" | jq -r .refresh_token)"

3단계는 테스트 사용자를 출력합니다:

{"email":"test.user@example.test","email_verified":true,"sub":"usr_01m3vnbwg0e8mbe7h7xxp6rmkc"}

SAML 아이덴티티 프로바이더

개발 중인 SAML 서비스 프로바이더의 아이덴티티 프로바이더로 모의 서버를 사용할 수 있습니다. 서비스 프로바이더가 메타데이터 URL을 가리키게 하고 로그인하면, 모의 서버가 테스트 사용자에 대한 서명된 응답을 돌려줍니다. 경로는 KeenAuth가 이후 릴리스에서 계획하고 있는 호스팅 SAML 아이덴티티 프로바이더를 따릅니다.

서명 키는 공개되어 있습니다. 공개된 레이블에서 파생되므로, 누구나 이 인증서로 검증되는 응답에 서명할 수 있습니다. SHA-256 지문은 fcc2fc37b9ddb800721283156f9eb26ff1b80f099d996a65650f8cb469a9be81이고, 주체에는 TEST ONLY라고 적혀 있습니다. 서비스 프로바이더의 개발 및 테스트 설정에만 등록하고, 운영 환경에는 절대 등록하지 마십시오. KeenAuth는 자체 SAML 연결에서 이를 강제합니다. 스테이징과 운영 환경에서는 메타데이터를 가져오거나 인증서를 추가할 때, 그리고 로그인할 때 이 키와 .invalid 아래의 모든 엔티티 ID를 거부합니다. 개발 환경은 이를 허용합니다.

예제: curl로 SAML 로그인

이 예제에는 curl과 openssl이 필요합니다. 먼저 다른 터미널에서 서버를 시작하십시오.

BASE=http://127.0.0.1:4010
APP=app_local
ACS=http://127.0.0.1:8080/saml/acs

# 1. IdP metadata: entity ID, signing certificate and SSO URL for your service provider.
curl -s "$BASE/saml-idp/$APP/metadata" -o idp-metadata.xml

# 2. Send an AuthnRequest with the HTTP-POST binding (base64, no compression).
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
REQUEST=$(printf '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_req1" Version="2.0" IssueInstant="%s" AssertionConsumerServiceURL="%s"><saml:Issuer>https://sp.example.com</saml:Issuer></samlp:AuthnRequest>' "$NOW" "$ACS" | openssl base64 -A)
curl -s "$BASE/saml-idp/$APP/sso" --data-urlencode SAMLRequest="$REQUEST" --data-urlencode RelayState=xyz -o form.html

# 3. The page posts SAMLResponse to the ACS URL by itself; decode it to look at the signed response.
sed -n 's/.*name="SAMLResponse" value="\([^"]*\)".*/\1/p' form.html | openssl base64 -d -A > response.xml
grep -o 'InResponseTo="[^"]*"' response.xml | head -1

마지막 명령은 InResponseTo="_req1"을 출력합니다. 서비스 프로바이더는 idp-metadata.xml의 인증서로 response.xml을 검증합니다.

SCIM 2.0 엔드포인트

SCIM 엔드포인트는 프로비저닝 커넥터나 사용자와 그룹을 만드는 스크립트처럼, SCIM 클라이언트를 네트워크 없이 테스트하는 데 사용합니다. 각 <directory_id>는 프로세스가 살아 있는 동안 유지되는, 별도의 빈 디렉터리입니다.

예제: curl로 사용자 프로비저닝

이 예제에는 curl과 jq가 필요합니다. 먼저 다른 터미널에서 서버를 시작하십시오.

SCIM=http://127.0.0.1:4010/scim/v2/dir_local
AUTH='Authorization: Bearer keenauth-mock-scim-token'
CT='Content-Type: application/scim+json'

# 1. Create a user; the response carries the id, meta.version and an ETag header.
USER_ID=$(curl -s "$SCIM/Users" -H "$AUTH" -H "$CT" -d '{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "ada@example.test",
  "name": {"givenName": "Ada", "familyName": "Lovelace"},
  "emails": [{"value": "ada@example.test", "type": "work", "primary": true}]
}' | jq -r .id)

# 2. Find it with a filter.
curl -s -G "$SCIM/Users" -H "$AUTH" --data-urlencode 'filter=userName eq "ada@example.test"' | jq .totalResults

# 3. Deactivate it with PATCH, guarded by the version you read.
curl -s -X PATCH "$SCIM/Users/$USER_ID" -H "$AUTH" -H "$CT" -H 'If-Match: W/"1"' -d '{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [{"op": "replace", "path": "active", "value": false}]
}' | jq -c '{active, version: .meta.version}'

# 4. Create a group with the user as a member, then delete the user.
curl -s "$SCIM/Groups" -H "$AUTH" -H "$CT" -d '{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
  "displayName": "Engineering",
  "members": [{"value": "'"$USER_ID"'"}]
}' | jq -c '.members[0] | {display, type}'
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE "$SCIM/Users/$USER_ID" -H "$AUTH"

명령은 1, {"active":false,"version":"W/\"2\""}, {"display":"ada@example.test","type":"User"}, 204를 출력합니다.

관리 API (/v1)

관리 API 엔드포인트는 KeenAuth 관리 API를 호출하는 코드(SDK, CLI, 배포 스크립트)를 네트워크 없이 CI에서 테스트할 때 사용합니다. 경로는 호스팅된 것과 같습니다. 호스팅된 기본 URL 대신 http://127.0.0.1:4010/v1을 사용하면 나머지는 그대로입니다. 엔드포인트는 CLI에 내장된 관리 API의 OpenAPI 문서에서 생성되므로, 해당 버전 API의 모든 작업이 응답합니다.

development 환경과의 차이. 모의 서버는 본문이 필수 필드를 가진 JSON 객체인지만 검사합니다. 필드 형식, 길이, 열거값, 고유성은 검사하지 않으며, 잘못된 타입의 필드는 무시됩니다. If-Match, Idempotency-Key와 그 밖의 헤더는 무시하고 ETag를 반환하지 않습니다. 권한, 요금제 한도, 할당량, 속도 제한, 리전은 검사하지 않고, 개인 데이터를 가리지 않으며, 이메일을 보내지 않고, 작업을 실행하지 않습니다(접수된 작업은 생성된 그대로 남습니다). 환경은 하나뿐입니다. 레코드는 프로세스가 사는 동안만 존재하며 최대 10,000개입니다. 실제 API의 동작을 알아보려면 development 환경을 사용하십시오.

예제: curl로 만들기, 읽기, 변경, 삭제

이 예제에는 curl과 jq가 필요합니다. 먼저 다른 터미널에서 서버를 시작하십시오.

API=http://127.0.0.1:4010/v1
AUTH='Authorization: Bearer keenauth-mock-management-token'
CT='Content-Type: application/json'

# 1. Create an organization.
ORG_ID=$(curl -s "$API/organizations" -H "$AUTH" -H "$CT" -d '{"name": "Acme"}' | jq -r .id)

# 2. Read it back and list.
curl -s "$API/organizations/$ORG_ID" -H "$AUTH" | jq -c '{id: (.id | .[0:4]), name}'
curl -s "$API/organizations" -H "$AUTH" | jq -c '{count: (.data | length), list_metadata}'

# 3. Rename it with a merge patch.
curl -s -X PATCH "$API/organizations/$ORG_ID" -H "$AUTH" -H 'Content-Type: application/merge-patch+json' -d '{"name": "Acme Inc"}' | jq -r .name

# 4. Delete it; a second read is a 404 problem.
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE "$API/organizations/$ORG_ID" -H "$AUTH"
curl -s "$API/organizations/$ORG_ID" -H "$AUTH" | jq -c '{status, code}'

# 5. Without the token.
curl -s "$API/organizations" | jq -c '{status, code}'

명령은 {"id":"org_","name":"Acme"}, {"count":1,"list_metadata":{"after":null,"before":null}}, Acme Inc, 204, {"status":404,"code":"not_found"}, {"status":401,"code":"unauthenticated"}를 출력합니다.

지원하지 않는 기능

테스트 클록, DPoP, PAR, 요청 객체, 디바이스 흐름, 클라이언트 자격 증명, 토큰 교환, 로그아웃이나 취소, 호스팅 페이지는 없습니다. SAML 아이덴티티 프로바이더에는 싱글 로그아웃, 암호화된 어설션, 아티팩트 바인딩, IdP 시작 로그인이 없습니다. SCIM 엔드포인트에는 대량 작업, 정렬, /Me, 중첩 그룹이 없으며, 필터는 or, not, 괄호, gt/ge/lt/le를 지원하지 않습니다. 이것들은 development 환경에서 테스트하십시오.