개발가이드 페이지 추가

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
+35
View File
@@ -265,6 +265,41 @@ API 키는 승인 워크플로우와 함께 관리됩니다:
8. `src/main/resources/templates/views/newfeature/`에 Thymeleaf 템플릿 추가
9. `application.yml``pages` 섹션에 라우트 설정 (또는 `@GetMapping`/`@PostMapping`으로 직접 매핑)
### 메뉴/네비게이션 항목 추가하기
**중요:** 메뉴 정의가 **두 곳으로 분리**되어 있다. yml 한 곳만 고치면 헤더 드롭다운에 안 보이고, 헤더만 고치면 브레드크럼이 비어 보인다. 메뉴 추가/이름 변경/순서 변경 시 **반드시 두 파일을 같이 수정**한다.
1. **`application.yml``page:` 트리** (브레드크럼·페이지 메타용)
- `PageService`가 URL → breadcrumb 매핑에 사용
- 부모 메뉴(`service`, `apis`, `community` 등) 아래 `children:`에 새 키를 추가
```yaml
page:
home:
children:
service:
children:
oauth2_guide:
name: "OAuth2 인증가이드"
path: "/service/oauth2-guide"
```
2. **`templates/views/fragment/djbank/header_container.html`** (상단 글로벌 네비)
- `<nav class="header-center"><ul class="nav-menu">` 블록에 정적 `<li>`로 하드코딩되어 있다 — yml 트리를 iterate 하지 **않음**
- 부모 메뉴의 `<ul class="sub-menu">`에 `<li><a th:href="@{/...}">메뉴명</a></li>` 직접 추가
3. **라우트도 같이 등록** (`application.yml`의 `portal.pages` 매핑 섹션, 또는 `@GetMapping`)
```yaml
portal:
pages:
- path-pattern: /service/oauth2-guide
method: GET
view-name: apps/service/oauth2-guide
```
4. **검증**: 변경 후 (a) 헤더 드롭다운에 항목이 보이는지, (b) 새 페이지 진입 시 브레드크럼에 메뉴명이 보이는지 둘 다 확인.
5. **재시작**: `application.yml` 변경은 `PortalPageHandlerMapper`가 `@PostConstruct`에서 한 번만 등록하므로 **자동 라이브 리로드 안 됨** — 수동 재시작 필요. HTML/CSS만 라이브 반영됨.
### MapStruct DTO 매핑
타입 안전한 DTO 변환을 위해 MapStruct 사용:
+14
View File
@@ -175,6 +175,14 @@ portal:
method: GET
view-name: apps/service/guide
- path-pattern: /service/oauth2-guide
method: GET
view-name: apps/service/oauth2-guide
- path-pattern: /service/oauth2-2legged-guide
method: GET
view-name: apps/service/oauth2-2legged-guide
- path-pattern: /dashboard
method: GET
view-name: apps/mypage/dashboard
@@ -258,6 +266,12 @@ page:
guide:
name: "회원가입 안내"
path: "/service/guide"
oauth2_guide:
name: "OAuth2 인증가이드"
path: "/service/oauth2-guide"
oauth2_2legged_guide:
name: "OAuth2 2-Legged 가이드"
path: "/service/oauth2-2legged-guide"
apis:
name: "API"
path: "#"
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -5,67 +5,36 @@
<body>
<th:block layout:fragment="contentFragment">
<div class="service-intro">
<!-- 메인 타이틀 섹션 -->
<header class="service-intro__header">
<h1 class="service-intro__title">DJBank 오픈뱅크 플랫폼</h1>
<div class="service-intro__underline"></div>
</header>
<section class="service-intro">
<h1 class="service-intro__title">DJBank 개발자포탈 소개</h1>
<p class="service-intro__lead">기업의 업무 흐름 속으로, 금융이 흘러 들어갑니다.</p>
<!-- 메인 콘텐츠: 2컬럼 레이아웃 -->
<div class="service-intro__content">
<!-- 왼쪽: 설명 카드들 -->
<div class="service-intro__text-column">
<div class="service-intro__card">
<p class="service-intro__desc">
DJBank이 제공하는 오픈API를 활용하여 핀테크 기업들이 새로운 금융서비스를 편리하게 개발할 수 있는 환경입니다.
제주은행의 디지털 기업금융 특화 브랜드 'DJ Bank'의 금융 서비스를<br>
핀테크 기업·개발자·ERP/SaaS 파트너가 손쉽게 연결해 활용할 수 있도록 지원하는<br>
통합 Open API 포털입니다.
</p>
</div>
<div class="service-intro__card">
<p class="service-intro__desc">
오픈뱅크 플랫폼이 제공하는 다양한 API를 활용해 핀테크 업체는 아이디어를 손쉽게 실현시킬 수 있습니다.
</p>
</div>
<div class="service-intro__card service-intro__card--highlight">
<p class="service-intro__desc service-intro__desc--white">
특히 JB금융의 과감한 투자와 제휴를 통해 핀테크 업체는 맞춤형 API를 제공받고 새로운 금융서비스를 신속하게 개발할 수 있습니다.
</p>
</div>
<div class="service-intro__spacer" aria-hidden="true"></div>
<h2 class="service-intro__section-title">구축 배경</h2>
<div class="service-intro__section-body">
<p>기존 기업금융은 서류 부담, 시간 지연, 대면중심 절차의 한계가 있었습니다. 또한 폐쇄적인 금융시스템 안에서 핀테크·SaaS 기업이 금융 기능을 자사 서비스에 결합하기 어려웠습니다.</p>
<p>DJ Bank는 ERP 데이터와 AI를 결합한 자율형 금융 플랫폼을 지향하며, 이 비전을 실현하기 위해 개방형 API 생태계를 구축했습니다. 기업이 본업의 흐름을 끊지 않고 금융을 이용할 수 있도록, 핵심 금융 자원을 파트너와 개발자에게 개방합니다.</p>
</div>
<!-- 오른쪽: 다이어그램 이미지 -->
<div class="service-intro__diagram-column">
<div class="service-intro__diagram-card">
<img th:src="@{/img/service/intro_diagram.png}" alt="DJBank 오픈뱅크 플랫폼 구성도" class="service-intro__diagram-img">
</div>
</div>
</div>
<div class="service-intro__spacer" aria-hidden="true"></div>
<!-- 하단 특징 카드 섹션 -->
<div class="service-intro__features">
<div class="service-intro__feature-card">
<div class="service-intro__feature-icon service-intro__feature-icon--fast">
<i class="fas fa-bolt"></i>
</div>
<h3 class="service-intro__feature-title">빠른 개발</h3>
<p class="service-intro__feature-desc">다양한 API를 활용하여 신속하게 금융 서비스를 개발할 수 있습니다.</p>
</div>
<div class="service-intro__feature-card">
<div class="service-intro__feature-icon service-intro__feature-icon--custom">
<i class="fas fa-sliders-h"></i>
</div>
<h3 class="service-intro__feature-title">맞춤형 API</h3>
<p class="service-intro__feature-desc">핀테크 업체의 니즈에 맞춘 맞춤형 API를 제공합니다.</p>
</div>
<div class="service-intro__feature-card">
<div class="service-intro__feature-icon service-intro__feature-icon--stable">
<i class="fas fa-shield-alt"></i>
</div>
<h3 class="service-intro__feature-title">안정적 운영</h3>
<p class="service-intro__feature-desc">JB금융의 투자로 안정적이고 신뢰할 수 있는 플랫폼을 제공합니다.</p>
</div>
</div>
</div>
<h2 class="service-intro__section-title">제공 API 카테고리</h2>
<ul class="service-intro__list">
<li>계좌 · 수신 - 법인 계좌개설·조회, 법인 파킹통장 연계</li>
<li>여신 · 대출 - ERP 연계 매출채권 담보대출, AX 솔루션자금</li>
<li>신용 · 평가 - 대안신용평가 전략모형 기반 기업 신용 조회</li>
<li>이체 · 결제 - 펌뱅킹, 실시간이체, 결제 API</li>
<li>인증 · 보안 - OAuth 2.0, 전자서명, 마이데이터 인증</li>
</ul>
</section>
</th:block>
</body>
@@ -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>
@@ -0,0 +1,272 @@
<!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-guide">
<!-- Section 1: Hero -->
<header class="oauth2-guide__hero">
<div class="oauth2-guide__hero-body">
<span class="oauth2-guide__hero-eyebrow">
<span class="oauth2-guide__hero-eyebrow-dot"></span>
개발자 가이드 · Authentication
</span>
<h1 class="oauth2-guide__hero-title">OAuth 2.0 인증 개요</h1>
<p class="oauth2-guide__hero-lead">DJBank Open API를 사용하기 위한 표준 인증 방식,</p>
<p class="oauth2-guide__hero-lead">OAuth 2.0의 개념과 동작 원리를 한눈에 정리했습니다.</p>
<div class="oauth2-guide__hero-chips">
<span class="oauth2-guide__chip">2-Legged</span>
<span class="oauth2-guide__chip">Client Credentials</span>
<span class="oauth2-guide__chip">Bearer Token</span>
</div>
</div>
<div class="oauth2-guide__hero-illust" aria-hidden="true">
<svg viewBox="0 0 280 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OAuth Shield Illustration">
<defs>
<linearGradient id="oguide-shield" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0049b4"/>
<stop offset="100%" stop-color="#0066d6"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="280" height="220" rx="24" fill="#FFFFFF"/>
<circle cx="40" cy="40" r="3" fill="#00D4FF"/>
<circle cx="60" cy="32" r="2" fill="#A78BFA"/>
<circle cx="240" cy="190" r="3" fill="#FFD93D"/>
<circle cx="220" cy="200" r="2" fill="#6BCF7F"/>
<g transform="translate(80,40)">
<path d="M60 0 L115 22 L115 70 C115 105 88 130 60 140 C32 130 5 105 5 70 L5 22 Z" fill="url(#oguide-shield)"/>
<path d="M30 70 L52 92 L92 50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g transform="translate(30,170)">
<rect width="118" height="34" rx="6" fill="#1A1A2E"/>
<text x="14" y="22" font-family="'Fira Code', monospace" font-size="12" fill="#00D4FF">access_token</text>
</g>
</svg>
</div>
</header>
<!-- Section 2: Definition -->
<section class="oauth2-guide__definition" aria-labelledby="oguide-def-title">
<div class="oauth2-guide__definition-icon" aria-hidden="true">
<svg viewBox="0 0 120 80" xmlns="http://www.w3.org/2000/svg">
<circle cx="40" cy="40" r="22" fill="none" stroke="#0049b4" stroke-width="6"/>
<path d="M62 40 L110 40 L110 56 M94 40 L94 56" stroke="#0049b4" stroke-width="6" fill="none" stroke-linecap="round"/>
<circle cx="40" cy="40" r="6" fill="#0049b4"/>
</svg>
</div>
<div class="oauth2-guide__definition-body">
<span class="oauth2-guide__eyebrow">DEFINITION</span>
<h2 class="oauth2-guide__h2" id="oguide-def-title">OAuth 2.0이란?</h2>
<p>사용자의 비밀번호를 직접 공유하지 않고, 안전하게 자원 접근 권한을</p>
<p>위임받기 위한 산업 표준 인증·인가 프로토콜(RFC 6749)입니다.</p>
<p>발급된 액세스 토큰(Bearer Token)으로 API를 호출합니다.</p>
</div>
</section>
<!-- Section 3: Core Parties -->
<section class="oauth2-guide__parties" aria-labelledby="oguide-parties-title">
<span class="oauth2-guide__eyebrow">CORE PARTIES</span>
<h2 class="oauth2-guide__h2" id="oguide-parties-title">OAuth 2.0 주요 구성</h2>
<div class="oauth2-guide__parties-grid">
<article class="oauth2-guide__party-card">
<div class="oauth2-guide__party-icon" aria-hidden="true">
<svg viewBox="0 0 56 44" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="56" height="44" rx="8" fill="#EDF9FE" stroke="#0049b4" stroke-width="2"/>
<line x1="0" y1="14" x2="56" y2="14" stroke="#0049b4" stroke-width="2"/>
<circle cx="10" cy="7" r="2" fill="#0049b4"/>
<circle cx="18" cy="7" r="2" fill="#0049b4"/>
<rect x="10" y="22" width="36" height="4" rx="2" fill="#0049b4" opacity="0.4"/>
<rect x="10" y="32" width="24" height="4" rx="2" fill="#0049b4" opacity="0.4"/>
</svg>
</div>
<div class="oauth2-guide__party-body">
<h3 class="oauth2-guide__party-title">Client</h3>
<p>API를 호출하는 애플리케이션.</p>
<p>기업 백엔드·배치·시스템 등.</p>
</div>
</article>
<article class="oauth2-guide__party-card">
<div class="oauth2-guide__party-icon" aria-hidden="true">
<svg viewBox="0 0 56 44" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="oguide-party-shield" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0049b4"/>
<stop offset="100%" stop-color="#0066d6"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="56" height="44" rx="8" fill="#FFFFFF" stroke="#c3dfea"/>
<path d="M28 6 L46 14 L46 30 C46 36 36 40 28 40 C20 40 10 36 10 30 L10 14 Z" fill="url(#oguide-party-shield)"/>
<path d="M20 24 L26 30 L36 18" stroke="#FFFFFF" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="oauth2-guide__party-body">
<h3 class="oauth2-guide__party-title">DJBank Open API</h3>
<p>토큰 발급과 API 응답을 모두 담당하는</p>
<p>DJBank의 단일 진입점입니다.</p>
</div>
</article>
</div>
</section>
<!-- Section 4: Grant Type -->
<section class="oauth2-guide__grant" aria-labelledby="oguide-grant-title">
<span class="oauth2-guide__eyebrow">GRANT TYPE</span>
<h2 class="oauth2-guide__h2" id="oguide-grant-title">지원 Grant Type</h2>
<p class="oauth2-guide__grant-sub">
DJBank는 <strong>Client Credentials (2-Legged)</strong> 방식을 지원합니다.
</p>
<article class="oauth2-guide__grant-card">
<span class="oauth2-guide__grant-badge">SUPPORTED</span>
<h3 class="oauth2-guide__grant-title">Client Credentials (2-Legged)</h3>
<p class="oauth2-guide__grant-desc">
서버 간 통신 / 시스템 호출에 사용. ClientID/Secret만으로 액세스 토큰을 발급받습니다.
</p>
<div class="oauth2-guide__grant-endpoint">
<div class="oauth2-guide__endpoint-row">
<span class="oauth2-guide__method">POST</span>
<code class="oauth2-guide__path">/dj/oauth/token</code>
</div>
<code class="oauth2-guide__endpoint-params">grant_type=client_credentials</code>
</div>
</article>
</section>
<!-- Section 5: How it works (sequence) -->
<section class="oauth2-guide__sequence" aria-labelledby="oguide-seq-title">
<span class="oauth2-guide__eyebrow">HOW IT WORKS</span>
<h2 class="oauth2-guide__h2" id="oguide-seq-title">기본 동작 흐름</h2>
<div class="oauth2-guide__sequence-diagram">
<svg viewBox="0 0 1120 216" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OAuth 2.0 Sequence Diagram">
<defs>
<marker id="oguide-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="oguide-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="160" y="20" width="200" height="44" rx="22" fill="#EDF9FE" stroke="#0049b4"/>
<text x="260" y="48" text-anchor="middle" font-size="14" font-weight="700" fill="#0049b4">Client (앱)</text>
<line x1="260" y1="64" x2="260" y2="200" stroke="#94A3B8" stroke-dasharray="4 4"/>
</g>
<g>
<rect x="760" y="20" width="200" height="44" rx="22" fill="#FFFFFF" stroke="#0049b4"/>
<text x="860" y="48" text-anchor="middle" font-size="14" font-weight="700" fill="#0049b4">DJBank Open API</text>
<line x1="860" y1="64" x2="860" y2="200" stroke="#94A3B8" stroke-dasharray="4 4"/>
</g>
<text x="560" y="90" text-anchor="middle" font-size="12" font-weight="700" fill="#0049b4">① ClientID + Secret 으로 토큰 요청</text>
<line x1="260" y1="100" x2="860" y2="100" stroke="#0049b4" stroke-width="2" marker-end="url(#oguide-arrow-primary)"/>
<text x="560" y="124" text-anchor="middle" font-size="12" font-weight="700" fill="#64748B">② access_token 발급</text>
<line x1="860" y1="134" x2="260" y2="134" stroke="#64748B" stroke-width="2" marker-end="url(#oguide-arrow-gray)"/>
<text x="560" y="162" text-anchor="middle" font-size="12" font-weight="700" fill="#0049b4">③ Authorization: Bearer ... 헤더로 API 호출</text>
<line x1="260" y1="172" x2="860" y2="172" stroke="#0049b4" stroke-width="2" marker-end="url(#oguide-arrow-primary)"/>
<text x="560" y="190" text-anchor="middle" font-size="12" font-weight="700" fill="#64748B">④ 검증 후 API 응답</text>
<line x1="860" y1="200" x2="260" y2="200" stroke="#64748B" stroke-width="2" marker-end="url(#oguide-arrow-gray)"/>
</svg>
</div>
</section>
<!-- Section 6: Bearer Token -->
<section class="oauth2-guide__bearer" aria-labelledby="oguide-bearer-title">
<span class="oauth2-guide__eyebrow">BEARER TOKEN</span>
<h2 class="oauth2-guide__h2" id="oguide-bearer-title">발급된 토큰으로 API 호출</h2>
<div class="oauth2-guide__bearer-grid">
<pre class="oauth2-guide__code-block"><span class="oauth2-guide__code-comment">// HTTP Request Header</span>
<span class="oauth2-guide__code-line">GET /api/v1/accounts HTTP/1.1</span>
<span class="oauth2-guide__code-line">Host: openapi.djbank.co.kr</span>
<span class="oauth2-guide__code-line oauth2-guide__code-line--highlight">Authorization: Bearer eyJhbGciOi...</span></pre>
<div class="oauth2-guide__lifecycle">
<h3 class="oauth2-guide__lifecycle-title">토큰 라이프사이클</h3>
<ol class="oauth2-guide__lifecycle-steps">
<li class="oauth2-guide__lifecycle-step"><span class="oauth2-guide__lifecycle-num">1</span><span class="oauth2-guide__lifecycle-label">발급</span></li>
<li class="oauth2-guide__lifecycle-step"><span class="oauth2-guide__lifecycle-num">2</span><span class="oauth2-guide__lifecycle-label">사용</span></li>
<li class="oauth2-guide__lifecycle-step"><span class="oauth2-guide__lifecycle-num">3</span><span class="oauth2-guide__lifecycle-label">만료</span></li>
<li class="oauth2-guide__lifecycle-step oauth2-guide__lifecycle-step--muted"><span class="oauth2-guide__lifecycle-num">4</span><span class="oauth2-guide__lifecycle-label">재발급</span></li>
</ol>
<p class="oauth2-guide__lifecycle-desc">TTL 내에는 토큰을 재사용하고,</p>
<p class="oauth2-guide__lifecycle-desc">만료 시 새 토큰을 발급받아 호출합니다.</p>
</div>
</div>
</section>
<!-- Section 7: Security -->
<section class="oauth2-guide__security" aria-labelledby="oguide-security-title">
<span class="oauth2-guide__eyebrow">SECURITY</span>
<h2 class="oauth2-guide__h2" id="oguide-security-title">기본 보안 권고</h2>
<div class="oauth2-guide__security-grid">
<article class="oauth2-guide__security-card">
<div class="oauth2-guide__security-icon oauth2-guide__security-icon--blue" aria-hidden="true">
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="M16 4 L24 8 L24 18 C24 24 16 28 16 28 C16 28 8 24 8 18 L8 8 Z" fill="#0049b4"/>
</svg>
</div>
<h3 class="oauth2-guide__security-title">HTTPS 필수</h3>
<p>모든 토큰·API 호출은</p>
<p>반드시 TLS 위에서만 수행합니다.</p>
</article>
<article class="oauth2-guide__security-card">
<div class="oauth2-guide__security-icon oauth2-guide__security-icon--yellow" aria-hidden="true">
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="14" width="16" height="14" rx="2" fill="#FFD93D"/>
<path d="M11 14 L11 9 C11 6 13 4 16 4 C19 4 21 6 21 9 L21 14" stroke="#FFD93D" stroke-width="2.5" fill="none"/>
</svg>
</div>
<h3 class="oauth2-guide__security-title">Secret 서버 보관</h3>
<p>client_secret 은 클라이언트 측</p>
<p>코드·브라우저에 절대 노출 금지.</p>
</article>
<article class="oauth2-guide__security-card">
<div class="oauth2-guide__security-icon oauth2-guide__security-icon--green" aria-hidden="true">
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="9" fill="none" stroke="#6BCF7F" stroke-width="2.5"/>
<path d="M16 11 L16 16 L19 19" stroke="#6BCF7F" stroke-width="2.5" stroke-linecap="round"/>
</svg>
</div>
<h3 class="oauth2-guide__security-title">Token TTL 활용</h3>
<p>expires_in 만큼 재사용하고,</p>
<p>만료 직전에 갱신을 트리거합니다.</p>
</article>
</div>
</section>
<!-- Section 8: CTA -->
<a class="oauth2-guide__cta" th:href="@{/service/oauth2-2legged-guide}">
<div class="oauth2-guide__cta-body">
<span class="oauth2-guide__cta-eyebrow">NEXT STEP</span>
<h2 class="oauth2-guide__cta-title">2-Legged 구현 가이드 보기</h2>
<p class="oauth2-guide__cta-desc">실제 토큰 발급·API 호출 방법을 코드 샘플과 함께 확인합니다.</p>
<span class="oauth2-guide__cta-button">개발 가이드 →</span>
</div>
<span class="oauth2-guide__cta-deco oauth2-guide__cta-deco--lg" aria-hidden="true"></span>
<span class="oauth2-guide__cta-deco oauth2-guide__cta-deco--sm" aria-hidden="true"></span>
</a>
</section>
</th:block>
</body>
<th:block layout:fragment="contentScript">
</th:block>
</html>
@@ -36,6 +36,8 @@
<ul class="sub-menu">
<li><a th:href="@{/service/intro}">API 포탈 소개</a></li>
<li><a th:href="@{/service/guide}">회원가입 안내</a></li>
<li><a th:href="@{/service/oauth2-guide}">OAuth2 인증가이드</a></li>
<li><a th:href="@{/service/oauth2-2legged-guide}">OAuth2 2-Legged 가이드</a></li>
</ul>
</li>
<li><a href="/apis" class="nav-link">오픈 API</a></li>