개발가이드 페이지 추가

This commit is contained in:
Rinjae
2026-06-10 19:29:24 +09:00
parent d9830fb47d
commit 4927c301f3
11 changed files with 3202 additions and 672 deletions
@@ -0,0 +1,422 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
<body>
<th:block layout:fragment="contentFragment">
<section class="oauth2-2legged">
<!-- Section 1: Hero -->
<header class="oauth2-2legged__hero">
<div class="oauth2-2legged__hero-body">
<span class="oauth2-2legged__hero-eyebrow">
<span class="oauth2-2legged__hero-eyebrow-dot"></span>
개발 가이드 · 2-Legged · Client Credentials
</span>
<h1 class="oauth2-2legged__hero-title">OAuth 2.0 · 2-Legged 구현 가이드</h1>
<p class="oauth2-2legged__hero-lead">DJBank Open API를 호출하기 위한 ClientID/Secret 기반 토큰 발급과</p>
<p class="oauth2-2legged__hero-lead">Bearer 인증 호출 방법을 단계별로 설명합니다.</p>
<div class="oauth2-2legged__hero-chips">
<span class="oauth2-2legged__chip oauth2-2legged__chip--primary">Server-to-Server</span>
<span class="oauth2-2legged__chip">cURL 예제</span>
<span class="oauth2-2legged__chip">application/x-www-form</span>
</div>
</div>
<div class="oauth2-2legged__hero-illust" aria-hidden="true">
<svg viewBox="0 0 280 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Server-to-Server Token Flow">
<defs>
<marker id="o2leg-hero-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto">
<path d="M0,0 L10,5 L0,10 Z" fill="#0049b4"/>
</marker>
</defs>
<rect x="0" y="0" width="280" height="200" rx="20" fill="#FFFFFF"/>
<rect x="40" y="90" width="80" height="50" rx="8" fill="#EDF9FE" stroke="#0049b4"/>
<text x="80" y="120" text-anchor="middle" font-size="11" font-weight="700" fill="#0049b4">Client</text>
<rect x="160" y="90" width="80" height="50" rx="8" fill="#EDF9FE" stroke="#0049b4"/>
<text x="200" y="112" text-anchor="middle" font-size="10" font-weight="700" fill="#0049b4">DJBank</text>
<text x="200" y="126" text-anchor="middle" font-size="10" font-weight="700" fill="#0049b4">Open API</text>
<line x1="120" y1="115" x2="160" y2="115" stroke="#0049b4" stroke-width="2" marker-end="url(#o2leg-hero-arrow)"/>
<g transform="translate(60,30)">
<rect width="160" height="32" rx="6" fill="#1A1A2E"/>
<text x="14" y="21" font-family="'Fira Code', monospace" font-size="12" fill="#00D4FF">Bearer eyJhbG..</text>
</g>
<text x="140" y="170" text-anchor="middle" font-size="11" font-weight="600" fill="#64748B">Server-to-Server Token Flow</text>
</svg>
</div>
</header>
<!-- Section 2: 사전 준비 -->
<section class="oauth2-2legged__prereq" aria-labelledby="o2leg-prereq-title">
<span class="oauth2-2legged__eyebrow">PREREQUISITE</span>
<h2 class="oauth2-2legged__h2" id="o2leg-prereq-title">시작 전 준비 사항</h2>
<div class="oauth2-2legged__prereq-grid">
<article class="oauth2-2legged__prereq-card">
<span class="oauth2-2legged__prereq-num">1</span>
<div class="oauth2-2legged__prereq-body">
<h3 class="oauth2-2legged__prereq-title">앱 등록</h3>
<p>개발자포털 [내 앱] 메뉴에서 신규</p>
<p>애플리케이션을 등록합니다.</p>
</div>
</article>
<article class="oauth2-2legged__prereq-card">
<span class="oauth2-2legged__prereq-num">2</span>
<div class="oauth2-2legged__prereq-body">
<h3 class="oauth2-2legged__prereq-title">ClientID / Secret 발급</h3>
<p>승인 완료 후 발급된 자격증명을</p>
<p>서버 환경변수에 안전하게 보관.</p>
</div>
</article>
<article class="oauth2-2legged__prereq-card">
<span class="oauth2-2legged__prereq-num">3</span>
<div class="oauth2-2legged__prereq-body">
<h3 class="oauth2-2legged__prereq-title">Scope 확인</h3>
<p>호출하려는 API에 필요한 scope</p>
<p>권한이 부여됐는지 확인합니다.</p>
</div>
</article>
</div>
</section>
<!-- Section 3: 전체 인증·호출 시퀀스 -->
<section class="oauth2-2legged__sequence" aria-labelledby="o2leg-seq-title">
<span class="oauth2-2legged__eyebrow">SEQUENCE</span>
<h2 class="oauth2-2legged__h2" id="o2leg-seq-title">전체 인증·호출 시퀀스</h2>
<div class="oauth2-2legged__sequence-diagram">
<svg viewBox="0 0 1120 282" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OAuth 2.0 2-Legged Sequence">
<defs>
<marker id="o2leg-arrow-primary" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto">
<path d="M0,0 L10,5 L0,10 Z" fill="#0049b4"/>
</marker>
<marker id="o2leg-arrow-gray" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto">
<path d="M0,0 L10,5 L0,10 Z" fill="#64748B"/>
</marker>
</defs>
<g>
<rect x="120" y="24" width="240" height="48" rx="24" fill="#EDF9FE" stroke="#0049b4"/>
<text x="240" y="54" text-anchor="middle" font-size="14" font-weight="700" fill="#0049b4">Client Application</text>
<line x1="240" y1="72" x2="240" y2="254" stroke="#94A3B8" stroke-dasharray="4 4"/>
</g>
<g>
<rect x="760" y="24" width="240" height="48" rx="24" fill="#FFFFFF" stroke="#0049b4"/>
<text x="880" y="54" text-anchor="middle" font-size="14" font-weight="700" fill="#0049b4">DJBank Open API</text>
<line x1="880" y1="72" x2="880" y2="254" stroke="#94A3B8" stroke-dasharray="4 4"/>
</g>
<text x="560" y="102" text-anchor="middle" font-size="12" font-weight="700" fill="#0049b4">① POST /dj/oauth/token</text>
<text x="560" y="118" text-anchor="middle" font-size="11" font-weight="500" fill="#64748B">grant_type=client_credentials, client_id, client_secret, scope</text>
<line x1="240" y1="128" x2="880" y2="128" stroke="#0049b4" stroke-width="2" marker-end="url(#o2leg-arrow-primary)"/>
<text x="560" y="160" text-anchor="middle" font-size="12" font-weight="700" fill="#64748B">② access_token 발급</text>
<line x1="880" y1="170" x2="240" y2="170" stroke="#64748B" stroke-width="2" marker-end="url(#o2leg-arrow-gray)"/>
<text x="560" y="188" text-anchor="middle" font-size="11" font-weight="500" fill="#64748B">{ access_token, token_type:"bearer", expires_in:86400, scope, jti }</text>
<text x="560" y="216" text-anchor="middle" font-size="12" font-weight="700" fill="#0049b4">③ GET /api/v1/... · Authorization: Bearer &lt;access_token&gt;</text>
<line x1="240" y1="226" x2="880" y2="226" stroke="#0049b4" stroke-width="2" marker-end="url(#o2leg-arrow-primary)"/>
<text x="560" y="248" text-anchor="middle" font-size="12" font-weight="700" fill="#64748B">④ 검증 후 API 응답</text>
<line x1="880" y1="254" x2="240" y2="254" stroke="#64748B" stroke-width="2" marker-end="url(#o2leg-arrow-gray)"/>
</svg>
</div>
</section>
<!-- Section 4: STEP 1 — 액세스 토큰 요청 -->
<section class="oauth2-2legged__step" aria-labelledby="o2leg-step1-title">
<span class="oauth2-2legged__eyebrow">STEP 1</span>
<h2 class="oauth2-2legged__h2" id="o2leg-step1-title">액세스 토큰 요청</h2>
<p class="oauth2-2legged__desc">ClientID/Secret 으로 토큰 엔드포인트에 POST 합니다.</p>
<div class="oauth2-2legged__endpoint-box">
<span class="oauth2-2legged__method">POST</span>
<code class="oauth2-2legged__endpoint-path">https://openapi.djbank.co.kr/dj/oauth/token</code>
<span class="oauth2-2legged__endpoint-content-type">x-www-form-urlencoded</span>
</div>
<div class="oauth2-2legged__step-grid">
<div class="oauth2-2legged__panel">
<h3 class="oauth2-2legged__panel-title">요청 파라미터</h3>
<table class="oauth2-2legged__table">
<thead>
<tr>
<th>PARAMETER</th>
<th>REQ</th>
<th>설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>grant_type</code></td>
<td><span class="oauth2-2legged__req-badge oauth2-2legged__req-badge--required">필수</span></td>
<td>고정값 "client_credentials"</td>
</tr>
<tr>
<td><code>client_id</code></td>
<td><span class="oauth2-2legged__req-badge oauth2-2legged__req-badge--required">필수</span></td>
<td>발급받은 클라이언트 식별자</td>
</tr>
<tr>
<td><code>client_secret</code></td>
<td><span class="oauth2-2legged__req-badge oauth2-2legged__req-badge--required">필수</span></td>
<td>발급받은 비밀 키 (서버 보관)</td>
</tr>
<tr>
<td><code>scope</code></td>
<td><span class="oauth2-2legged__req-badge oauth2-2legged__req-badge--optional">선택</span></td>
<td>호출 권한 범위 (공백 구분)</td>
</tr>
</tbody>
</table>
<p class="oauth2-2legged__warning">⚠ Content-Type: application/x-www-form-urlencoded 헤더 필수</p>
</div>
<div class="oauth2-2legged__code-panel">
<span class="oauth2-2legged__code-tag">cURL</span>
<pre class="oauth2-2legged__code-block"><span class="o2leg-cm"># 액세스 토큰 발급</span>
curl -X <span class="o2leg-y">POST</span> <span class="o2leg-c">'https://openapi.djbank.co.kr/dj/oauth/token'</span> \
-H <span class="o2leg-c">'Content-Type: application/x-www-form-urlencoded'</span> \
-d <span class="o2leg-c">'grant_type=client_credentials'</span> \
-d <span class="o2leg-c">'client_id=YOUR_CLIENT_ID'</span> \
-d <span class="o2leg-c">'client_secret=YOUR_CLIENT_SECRET'</span> \
-d <span class="o2leg-c">'scope=read.accounts'</span>
<span class="o2leg-g"># 응답: 200 OK + JSON</span></pre>
</div>
</div>
</section>
<!-- Section 5: STEP 2 — 액세스 토큰 응답 -->
<section class="oauth2-2legged__step" aria-labelledby="o2leg-step2-title">
<span class="oauth2-2legged__eyebrow">STEP 2</span>
<h2 class="oauth2-2legged__h2" id="o2leg-step2-title">액세스 토큰 응답</h2>
<p class="oauth2-2legged__desc">정상 발급 시 200 OK 와 함께 다음 JSON 을 반환합니다.</p>
<div class="oauth2-2legged__step-grid">
<div class="oauth2-2legged__code-panel">
<span class="oauth2-2legged__code-tag oauth2-2legged__code-tag--ok">200 OK · JSON</span>
<pre class="oauth2-2legged__code-block">{
<span class="o2leg-c">"access_token"</span>: <span class="o2leg-y">"eyJhbGciOiJSUzI1NiJ9..."</span>,
<span class="o2leg-c">"token_type"</span>: <span class="o2leg-y">"bearer"</span>,
<span class="o2leg-c">"expires_in"</span>: <span class="o2leg-p">86400</span>,
<span class="o2leg-c">"scope"</span>: <span class="o2leg-y">"read.accounts"</span>,
<span class="o2leg-c">"jti"</span>: <span class="o2leg-y">"f47ac10b-58cc-4372-..."</span>
}
<span class="o2leg-g"># 응답 본문은 캐싱하여 expires_in 동안 재사용 가능</span></pre>
</div>
<div class="oauth2-2legged__panel">
<h3 class="oauth2-2legged__panel-title">응답 필드</h3>
<table class="oauth2-2legged__table">
<thead>
<tr>
<th>FIELD</th>
<th>TYPE</th>
<th>설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="oauth2-2legged__field-name">access_token</code></td>
<td>string</td>
<td>Bearer 호출에 사용할 토큰</td>
</tr>
<tr>
<td><code class="oauth2-2legged__field-name">token_type</code></td>
<td>string</td>
<td>고정값 "bearer"</td>
</tr>
<tr>
<td><code class="oauth2-2legged__field-name">expires_in</code></td>
<td>int</td>
<td>유효 시간(초). 기본 86400</td>
</tr>
<tr>
<td><code class="oauth2-2legged__field-name">scope</code></td>
<td>string</td>
<td>실제 부여된 권한 범위</td>
</tr>
<tr>
<td><code class="oauth2-2legged__field-name">jti</code></td>
<td>string</td>
<td>토큰 고유 ID (감사/취소용)</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- Section 6: STEP 3 — API 호출 -->
<section class="oauth2-2legged__step" aria-labelledby="o2leg-step3-title">
<span class="oauth2-2legged__eyebrow">STEP 3</span>
<h2 class="oauth2-2legged__h2" id="o2leg-step3-title">발급 토큰으로 API 호출</h2>
<p class="oauth2-2legged__desc">Authorization 헤더에 Bearer 토큰을 실어 보호 자원 API 를 호출합니다.</p>
<div class="oauth2-2legged__step-grid">
<div class="oauth2-2legged__panel">
<h3 class="oauth2-2legged__panel-title">필수 헤더</h3>
<div class="oauth2-2legged__header-box">
<code class="oauth2-2legged__header-key">Authorization:</code>
<code class="oauth2-2legged__header-value">Bearer eyJhbGciOiJSUzI1NiJ9...</code>
</div>
<h4 class="oauth2-2legged__panel-subtitle">토큰 재사용 가이드</h4>
<ul class="oauth2-2legged__tips">
<li>동일 토큰은 expires_in(기본 86400초) 동안 재사용</li>
<li>만료 임박 시 재발급 후 교체 (예: TTL의 80% 시점)</li>
<li>매 호출마다 토큰을 새로 발급하지 마세요</li>
<li>권한이 다른 API 는 scope 별로 토큰을 분리 발급</li>
</ul>
</div>
<div class="oauth2-2legged__code-panel">
<span class="oauth2-2legged__code-tag">cURL</span>
<pre class="oauth2-2legged__code-block"><span class="o2leg-cm"># 보호 자원 API 호출</span>
curl -X <span class="o2leg-y">GET</span> \
<span class="o2leg-c">'https://openapi.djbank.co.kr/api/v1/accounts'</span> \
-H <span class="o2leg-c">'Authorization: Bearer eyJhbGciOiJSUzI..'</span> \
-H <span class="o2leg-c">'Accept: application/json'</span>
<span class="o2leg-g"># 응답</span>
HTTP/1.1 <span class="o2leg-g">200 OK</span>
{ <span class="o2leg-c">"accounts"</span>: [ ... ] }</pre>
</div>
</div>
</section>
<!-- Section 7: 대표 에러 응답 (표) -->
<section class="oauth2-2legged__errors" aria-labelledby="o2leg-errors-title">
<span class="oauth2-2legged__eyebrow">ERROR CODES</span>
<h2 class="oauth2-2legged__h2" id="o2leg-errors-title">대표 에러 응답</h2>
<div class="oauth2-2legged__panel">
<table class="oauth2-2legged__table oauth2-2legged__table--errors">
<thead>
<tr>
<th>HTTP</th>
<th>ERROR CODE</th>
<th>의미</th>
<th>해결 가이드</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="oauth2-2legged__status-badge oauth2-2legged__status-badge--400">400</span></td>
<td><code>invalid_request</code></td>
<td>필수 파라미터 누락/형식 오류</td>
<td>grant_type, client_id, client_secret 확인</td>
</tr>
<tr>
<td><span class="oauth2-2legged__status-badge oauth2-2legged__status-badge--400">401</span></td>
<td><code>invalid_client</code></td>
<td>자격증명 불일치 / 미승인</td>
<td>ClientID·Secret 재확인, 앱 상태 점검</td>
</tr>
<tr>
<td><span class="oauth2-2legged__status-badge oauth2-2legged__status-badge--400">401</span></td>
<td><code>invalid_token</code></td>
<td>만료/위·변조된 토큰</td>
<td>토큰 재발급 후 재시도</td>
</tr>
<tr>
<td><span class="oauth2-2legged__status-badge oauth2-2legged__status-badge--403">403</span></td>
<td><code>insufficient_scope</code></td>
<td>scope 권한 부족</td>
<td>필요 scope 추가 신청 후 재발급</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- Section 8: 에러 응답 예시 (단일 사례) -->
<section class="oauth2-2legged__error-example" aria-labelledby="o2leg-err-ex-title">
<span class="oauth2-2legged__eyebrow">ERROR RESPONSE</span>
<h2 class="oauth2-2legged__h2" id="o2leg-err-ex-title">에러 응답 예시</h2>
<p class="oauth2-2legged__desc">자격증명 불일치 시 401 Unauthorized 와 함께 다음 JSON 을 반환합니다.</p>
<div class="oauth2-2legged__step-grid">
<div class="oauth2-2legged__code-panel oauth2-2legged__code-panel--error">
<span class="oauth2-2legged__code-tag oauth2-2legged__code-tag--error">401 Unauthorized · JSON</span>
<pre class="oauth2-2legged__code-block">{
<span class="o2leg-c">"error"</span>: <span class="o2leg-y">"invalid_client"</span>,
<span class="o2leg-c">"error_description"</span>:
<span class="o2leg-y">"Client authentication failed"</span>,
<span class="o2leg-c">"status"</span>: <span class="o2leg-p">401</span>,
<span class="o2leg-c">"timestamp"</span>: <span class="o2leg-y">"2026-06-10T11:23:45Z"</span>
}
<span class="o2leg-g"># 처리: ClientID·Secret 확인 후 재시도</span></pre>
</div>
<div class="oauth2-2legged__panel">
<h3 class="oauth2-2legged__panel-title">응답 필드</h3>
<table class="oauth2-2legged__table">
<thead>
<tr>
<th>FIELD</th>
<th>TYPE</th>
<th>설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="oauth2-2legged__field-name">error</code></td>
<td>string</td>
<td>에러 코드 (예: invalid_client)</td>
</tr>
<tr>
<td><code class="oauth2-2legged__field-name">error_description</code></td>
<td>string</td>
<td>사람이 읽을 수 있는 설명</td>
</tr>
<tr>
<td><code class="oauth2-2legged__field-name">status</code></td>
<td>int</td>
<td>HTTP 상태 코드</td>
</tr>
<tr>
<td><code class="oauth2-2legged__field-name">timestamp</code></td>
<td>string</td>
<td>에러 발생 시각 (ISO 8601)</td>
</tr>
</tbody>
</table>
<p class="oauth2-2legged__error-note">⚠ error 코드는 RFC 6749 표준 코드 또는 DJBank 확장 코드</p>
</div>
</div>
</section>
<!-- Section 9: API 목록 CTA -->
<a class="oauth2-2legged__cta" th:href="@{/apis}">
<div class="oauth2-2legged__cta-body">
<span class="oauth2-2legged__cta-eyebrow">EXPLORE</span>
<h2 class="oauth2-2legged__cta-title">API 목록 보러가기</h2>
<p class="oauth2-2legged__cta-desc">사용 가능한 DJBank Open API 카탈로그를 확인하세요.</p>
<span class="oauth2-2legged__cta-button">API 목록 →</span>
</div>
<span class="oauth2-2legged__cta-deco oauth2-2legged__cta-deco--lg" aria-hidden="true"></span>
<span class="oauth2-2legged__cta-deco oauth2-2legged__cta-deco--sm" aria-hidden="true"></span>
</a>
</section>
</th:block>
</body>
<th:block layout:fragment="contentScript">
</th:block>
</html>