2-Legged 가이드 삭제 완료
- 관련 HTML 제거 - 메뉴 및 경로 수정
This commit is contained in:
@@ -1,24 +1,41 @@
|
||||
package com.eactive.apim.portal.common.breadcrumb;
|
||||
|
||||
import java.util.HashMap;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
@Service
|
||||
public class PageService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PageService.class);
|
||||
|
||||
private final PageStructure pageStructure;
|
||||
|
||||
@Value("${portal.dev.hot-reload-pages:false}")
|
||||
private boolean hotReloadEnabled;
|
||||
|
||||
private volatile long lastReloadedMtime = 0L;
|
||||
private volatile Path watchedPath;
|
||||
|
||||
public PageService(PageStructure pageStructure) {
|
||||
this.pageStructure = pageStructure;
|
||||
}
|
||||
|
||||
public List<Map> getBreadcrumb(String currentPath) {
|
||||
// URL에서 기본 경로와 쿼리 파라미터 분리
|
||||
maybeReloadPages();
|
||||
String basePath = currentPath.contains("?") ?
|
||||
currentPath.substring(0, currentPath.indexOf("?")) : currentPath;
|
||||
Map<String, String> queryParams = parseQueryParams(currentPath);
|
||||
@@ -29,12 +46,59 @@ public class PageService {
|
||||
}
|
||||
|
||||
public String getPageName(String currentPath) {
|
||||
maybeReloadPages();
|
||||
String basePath = currentPath.contains("?") ?
|
||||
currentPath.substring(0, currentPath.indexOf("?")) : currentPath;
|
||||
Map<String, String> queryParams = parseQueryParams(currentPath);
|
||||
return findPageName(pageStructure.getHome(), basePath, queryParams);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Dev-only hot reload of the `page:` tree.
|
||||
// Toggle via `portal.dev.hot-reload-pages: true` in any active profile yml.
|
||||
// Only the page tree (breadcrumb / menu names) reloads — `portal.pages` route
|
||||
// mappings are still registered at boot and require a restart.
|
||||
// -----------------------------------------------------------------------------
|
||||
private void maybeReloadPages() {
|
||||
if (!hotReloadEnabled) return;
|
||||
try {
|
||||
Path p = resolveYmlPath();
|
||||
if (p == null) return;
|
||||
long mtime = Files.getLastModifiedTime(p).toMillis();
|
||||
if (mtime == lastReloadedMtime) return;
|
||||
|
||||
try (InputStream is = Files.newInputStream(p)) {
|
||||
Yaml yaml = new Yaml();
|
||||
Map<String, Object> root = yaml.load(is);
|
||||
Object page = (root != null) ? root.get("page") : null;
|
||||
if (page instanceof Map) {
|
||||
Object home = ((Map<?, ?>) page).get("home");
|
||||
if (home instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> homeMap = (Map<String, Object>) home;
|
||||
pageStructure.setHome(homeMap);
|
||||
lastReloadedMtime = mtime;
|
||||
log.debug("[PageService] page tree reloaded from {} (mtime={})", p, mtime);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[PageService] page tree reload failed: {}", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Path resolveYmlPath() {
|
||||
if (watchedPath != null) return watchedPath;
|
||||
try {
|
||||
ClassPathResource res = new ClassPathResource("application.yml");
|
||||
if (!res.exists() || !res.isFile()) return null;
|
||||
watchedPath = res.getFile().toPath();
|
||||
return watchedPath;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> parseQueryParams(String path) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (path.contains("?")) {
|
||||
@@ -52,7 +116,6 @@ public class PageService {
|
||||
private String findPageName(Map<String, Object> currentNode, String basePath, Map<String, String> queryParams) {
|
||||
String path = (String) currentNode.get("path");
|
||||
|
||||
// 기본 경로가 일치하는지 확인
|
||||
if (path != null) {
|
||||
String nodePath = path.contains("?") ?
|
||||
path.substring(0, path.indexOf("?")) : path;
|
||||
@@ -63,7 +126,6 @@ public class PageService {
|
||||
}
|
||||
}
|
||||
|
||||
// 자식 노드들을 재귀적으로 검색
|
||||
if (currentNode.containsKey("children")) {
|
||||
Map<String, Object> children = (Map<String, Object>) currentNode.get("children");
|
||||
for (Map.Entry<String, Object> entry : children.entrySet()) {
|
||||
@@ -116,7 +178,6 @@ public class PageService {
|
||||
}
|
||||
|
||||
private boolean matchesQueryParams(Map<String, String> nodeParams, Map<String, String> requestParams) {
|
||||
// 특별히 tab 파라미터에 대해서만 체크
|
||||
String nodeTab = nodeParams.get("tab");
|
||||
String requestTab = requestParams.get("tab");
|
||||
|
||||
|
||||
@@ -41,3 +41,6 @@ gateway:
|
||||
portal:
|
||||
auth-virtual-code: 654321
|
||||
test-auth-notice-enabled: true
|
||||
dev:
|
||||
# application.yml의 `page:` 트리(브레드크럼/메뉴 이름) 라이브 반영
|
||||
hot-reload-pages: true
|
||||
@@ -25,6 +25,9 @@ portal:
|
||||
test-auth-notice-enabled: true
|
||||
# 검증 단계에선 사용하지 않음
|
||||
# auth-virtual-code: 654321
|
||||
dev:
|
||||
# application.yml의 `page:` 트리(브레드크럼/메뉴 이름) 라이브 반영
|
||||
hot-reload-pages: true
|
||||
|
||||
|
||||
ems:
|
||||
|
||||
@@ -179,10 +179,6 @@ portal:
|
||||
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
|
||||
@@ -267,11 +263,8 @@ page:
|
||||
name: "회원가입 안내"
|
||||
path: "/service/guide"
|
||||
oauth2_guide:
|
||||
name: "OAuth2 인증가이드"
|
||||
name: "OAuth2 개발가이드"
|
||||
path: "/service/oauth2-guide"
|
||||
oauth2_2legged_guide:
|
||||
name: "OAuth2 2-Legged 가이드"
|
||||
path: "/service/oauth2-2legged-guide"
|
||||
apis:
|
||||
name: "API"
|
||||
path: "#"
|
||||
|
||||
@@ -18429,566 +18429,6 @@ body.commission-print-page .btn-primary:hover {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
.oauth2-guide {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #FFFFFF;
|
||||
color: #1A1A2E;
|
||||
font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.oauth2-guide__eyebrow {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #0049b4;
|
||||
letter-spacing: 1.2px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.oauth2-guide__h2 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
color: #1A1A2E;
|
||||
}
|
||||
.oauth2-guide__hero {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 320px;
|
||||
gap: 32px;
|
||||
padding: 60px 80px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid #E2E8F0;
|
||||
background: linear-gradient(135deg, #EDF9FE 0%, #FFFFFF 100%);
|
||||
}
|
||||
.oauth2-guide__hero-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 14px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #c3dfea;
|
||||
color: #0049b4;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.oauth2-guide__hero-eyebrow-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #0049b4;
|
||||
}
|
||||
.oauth2-guide__hero-title {
|
||||
margin: 0 0 16px;
|
||||
font-size: 44px;
|
||||
font-weight: 800;
|
||||
color: #1A1A2E;
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.oauth2-guide__hero-lead {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.oauth2-guide__hero-lead + .oauth2-guide__hero-lead {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.oauth2-guide__hero-chips {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.oauth2-guide__chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 18px;
|
||||
border-radius: 15px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #E2E8F0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1A1A2E;
|
||||
}
|
||||
.oauth2-guide__hero-illust {
|
||||
align-self: center;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #c3dfea;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.oauth2-guide__hero-illust svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.oauth2-guide__definition {
|
||||
display: grid;
|
||||
grid-template-columns: 220px 1fr;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 16px;
|
||||
background: #FFFFFF;
|
||||
overflow: hidden;
|
||||
}
|
||||
.oauth2-guide__definition-icon {
|
||||
background: #F8FAFC;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
}
|
||||
.oauth2-guide__definition-icon svg {
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
}
|
||||
.oauth2-guide__definition-body {
|
||||
padding: 32px 40px;
|
||||
}
|
||||
.oauth2-guide__definition-body .oauth2-guide__eyebrow {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.oauth2-guide__definition-body p {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #64748B;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.oauth2-guide__definition-body p + p {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.oauth2-guide__parties-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.oauth2-guide__party-card {
|
||||
display: grid;
|
||||
grid-template-columns: 88px 1fr;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding: 24px 32px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.oauth2-guide__party-icon svg {
|
||||
display: block;
|
||||
width: 56px;
|
||||
height: 44px;
|
||||
}
|
||||
.oauth2-guide__party-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: #1A1A2E;
|
||||
}
|
||||
.oauth2-guide__party-body p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.oauth2-guide__party-body p + p {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.oauth2-guide__grant-sub {
|
||||
margin: 0 0 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #64748B;
|
||||
}
|
||||
.oauth2-guide__grant-sub strong {
|
||||
color: #0049b4;
|
||||
font-weight: 700;
|
||||
}
|
||||
.oauth2-guide__grant-card {
|
||||
position: relative;
|
||||
padding: 24px 32px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(90deg, #0049b4 0%, #1f6fd0 100%);
|
||||
color: #FFFFFF;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-areas: "badge endpoint" "title endpoint" "desc endpoint";
|
||||
column-gap: 32px;
|
||||
row-gap: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.oauth2-guide__grant-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: 6px;
|
||||
background: #00D4FF;
|
||||
}
|
||||
.oauth2-guide__grant-badge {
|
||||
grid-area: badge;
|
||||
align-self: start;
|
||||
justify-self: start;
|
||||
padding: 5px 14px;
|
||||
border-radius: 13px;
|
||||
background: #FFFFFF;
|
||||
color: #0049b4;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.oauth2-guide__grant-title {
|
||||
grid-area: title;
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #FFFFFF;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.oauth2-guide__grant-desc {
|
||||
grid-area: desc;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #c3dfea;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.oauth2-guide__grant-endpoint {
|
||||
grid-area: endpoint;
|
||||
align-self: center;
|
||||
padding: 14px 18px;
|
||||
background: #1A1A2E;
|
||||
border-radius: 10px;
|
||||
min-width: 320px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.oauth2-guide__endpoint-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.oauth2-guide__method {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 4px;
|
||||
background: #0049b4;
|
||||
color: #FFFFFF;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.oauth2-guide__path {
|
||||
font-family: "Fira Code", monospace;
|
||||
font-size: 13px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.oauth2-guide__endpoint-params {
|
||||
font-family: "Fira Code", monospace;
|
||||
font-size: 11px;
|
||||
color: #94A3B8;
|
||||
}
|
||||
.oauth2-guide__sequence-diagram {
|
||||
padding: 16px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.oauth2-guide__sequence-diagram svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.oauth2-guide__bearer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.oauth2-guide__code-block {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 28px 24px 28px 32px;
|
||||
border-radius: 14px;
|
||||
background: #1A1A2E;
|
||||
color: #FFFFFF;
|
||||
font-family: "Fira Code", monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.oauth2-guide__code-block::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: 6px;
|
||||
background: #0049b4;
|
||||
border-radius: 14px 0 0 14px;
|
||||
}
|
||||
.oauth2-guide__code-comment {
|
||||
display: block;
|
||||
color: #94A3B8;
|
||||
font-size: 13px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.oauth2-guide__code-line {
|
||||
display: block;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.oauth2-guide__code-line--highlight {
|
||||
color: #00D4FF;
|
||||
}
|
||||
.oauth2-guide__lifecycle {
|
||||
padding: 24px 28px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.oauth2-guide__lifecycle-title {
|
||||
margin: 0 0 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1A1A2E;
|
||||
}
|
||||
.oauth2-guide__lifecycle-steps {
|
||||
list-style: none;
|
||||
margin: 0 0 14px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.oauth2-guide__lifecycle-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
}
|
||||
.oauth2-guide__lifecycle-step:not(:last-child)::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 0;
|
||||
border-top: 1.5px dashed #94A3B8;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.oauth2-guide__lifecycle-num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: #0049b4;
|
||||
color: #FFFFFF;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.oauth2-guide__lifecycle-step--muted .oauth2-guide__lifecycle-num {
|
||||
background: #94A3B8;
|
||||
}
|
||||
.oauth2-guide__lifecycle-step--muted .oauth2-guide__lifecycle-num {
|
||||
background: #94A3B8;
|
||||
}
|
||||
.oauth2-guide__lifecycle-step--muted .oauth2-guide__lifecycle-label {
|
||||
color: #64748B;
|
||||
}
|
||||
.oauth2-guide__lifecycle-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1A1A2E;
|
||||
}
|
||||
.oauth2-guide__lifecycle-desc {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.oauth2-guide__lifecycle-desc + .oauth2-guide__lifecycle-desc {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.oauth2-guide__security-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
.oauth2-guide__security-card {
|
||||
padding: 24px 28px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.oauth2-guide__security-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.oauth2-guide__security-icon svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.oauth2-guide__security-icon--blue {
|
||||
background: #EDF9FE;
|
||||
}
|
||||
.oauth2-guide__security-icon--yellow {
|
||||
background: #FFF9E6;
|
||||
}
|
||||
.oauth2-guide__security-icon--green {
|
||||
background: #E8F8EE;
|
||||
}
|
||||
.oauth2-guide__security-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #1A1A2E;
|
||||
}
|
||||
.oauth2-guide__security-card p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.oauth2-guide__security-card p + p {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.oauth2-guide__cta {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 36px 48px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(90deg, #0049b4 0%, #1f6fd0 100%);
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
.oauth2-guide__cta:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 28px rgba(0, 73, 180, 0.25);
|
||||
}
|
||||
.oauth2-guide__cta-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.oauth2-guide__cta-eyebrow {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #c3dfea;
|
||||
letter-spacing: 1.2px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.oauth2-guide__cta-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: #FFFFFF;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.oauth2-guide__cta-desc {
|
||||
margin: 0 0 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #c3dfea;
|
||||
}
|
||||
.oauth2-guide__cta-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 10px 24px;
|
||||
border-radius: 19px;
|
||||
background: #FFFFFF;
|
||||
color: #0049b4;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.oauth2-guide__cta-deco {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.oauth2-guide__cta-deco--lg {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
right: 80px;
|
||||
bottom: 30px;
|
||||
}
|
||||
.oauth2-guide__cta-deco--sm {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
right: 20px;
|
||||
bottom: -10px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.oauth2-guide__hero {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 40px;
|
||||
}
|
||||
.oauth2-guide__hero-illust {
|
||||
max-width: 280px;
|
||||
}
|
||||
.oauth2-guide__definition {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.oauth2-guide__definition-icon {
|
||||
padding: 24px;
|
||||
}
|
||||
.oauth2-guide__bearer-grid, .oauth2-guide__parties-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.oauth2-guide__security-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.oauth2-guide__grant-card {
|
||||
grid-template-areas: "badge" "title" "desc" "endpoint";
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.oauth2-guide__grant-endpoint {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.oauth2-guide {
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
.oauth2-guide__hero {
|
||||
padding: 28px 24px;
|
||||
}
|
||||
.oauth2-guide__hero-title {
|
||||
font-size: 32px;
|
||||
}
|
||||
.oauth2-guide__hero-lead {
|
||||
font-size: 15px;
|
||||
}
|
||||
.oauth2-guide__h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
.oauth2-guide__security-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.oauth2-guide__cta {
|
||||
padding: 28px 24px;
|
||||
}
|
||||
.oauth2-guide__cta-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
.oauth2-2legged {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -660,617 +660,6 @@ $service-card-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgb
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// OAuth2 인증 가이드 (Figma 367:1054 — OAuth2 인증가이드 V2)
|
||||
// =============================================================================
|
||||
|
||||
$oguide-primary: #0049b4;
|
||||
$oguide-secondary: #c3dfea;
|
||||
$oguide-bg-soft: #EDF9FE;
|
||||
$oguide-bg-light: #F8FAFC;
|
||||
$oguide-text-dark: #1A1A2E;
|
||||
$oguide-text-gray: #64748B;
|
||||
$oguide-text-mute: #94A3B8;
|
||||
$oguide-border: #E2E8F0;
|
||||
$oguide-accent-cyan: #00D4FF;
|
||||
$oguide-accent-yellow: #FFD93D;
|
||||
$oguide-accent-green: #6BCF7F;
|
||||
|
||||
.oauth2-guide {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #FFFFFF;
|
||||
color: $oguide-text-dark;
|
||||
font-family: '원신한', 'OneShinhan', 'Spoqa Han Sans Neo', sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
box-sizing: border-box;
|
||||
|
||||
// -- common eyebrow / heading -----------------------------------------------
|
||||
&__eyebrow {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: $oguide-primary;
|
||||
letter-spacing: 1.2px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__h2 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
color: $oguide-text-dark;
|
||||
}
|
||||
|
||||
// -- Section 1: Hero --------------------------------------------------------
|
||||
&__hero {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 320px;
|
||||
gap: 32px;
|
||||
padding: 60px 80px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid $oguide-border;
|
||||
background: linear-gradient(135deg, $oguide-bg-soft 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
&__hero-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 14px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid $oguide-secondary;
|
||||
color: $oguide-primary;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__hero-eyebrow-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: $oguide-primary;
|
||||
}
|
||||
|
||||
&__hero-title {
|
||||
margin: 0 0 16px;
|
||||
font-size: 44px;
|
||||
font-weight: 800;
|
||||
color: $oguide-text-dark;
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__hero-lead {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
color: $oguide-text-gray;
|
||||
line-height: 1.6;
|
||||
|
||||
& + & { margin-top: 2px; }
|
||||
}
|
||||
|
||||
&__hero-chips {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 18px;
|
||||
border-radius: 15px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid $oguide-border;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $oguide-text-dark;
|
||||
}
|
||||
|
||||
&__hero-illust {
|
||||
align-self: center;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid $oguide-secondary;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Section 2: Definition --------------------------------------------------
|
||||
&__definition {
|
||||
display: grid;
|
||||
grid-template-columns: 220px 1fr;
|
||||
border: 1px solid $oguide-border;
|
||||
border-radius: 16px;
|
||||
background: #FFFFFF;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__definition-icon {
|
||||
background: $oguide-bg-light;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
|
||||
svg { width: 120px; height: 80px; }
|
||||
}
|
||||
|
||||
&__definition-body {
|
||||
padding: 32px 40px;
|
||||
|
||||
.oauth2-guide__eyebrow { margin-bottom: 6px; }
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: $oguide-text-gray;
|
||||
line-height: 1.7;
|
||||
|
||||
& + p { margin-top: 2px; }
|
||||
}
|
||||
}
|
||||
|
||||
// -- Section 3: Core Parties ------------------------------------------------
|
||||
&__parties-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
&__party-card {
|
||||
display: grid;
|
||||
grid-template-columns: 88px 1fr;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding: 24px 32px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid $oguide-border;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
&__party-icon svg {
|
||||
display: block;
|
||||
width: 56px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
&__party-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: $oguide-text-dark;
|
||||
}
|
||||
|
||||
&__party-body p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $oguide-text-gray;
|
||||
line-height: 1.6;
|
||||
|
||||
& + p { margin-top: 2px; }
|
||||
}
|
||||
|
||||
// -- Section 4: Grant Type --------------------------------------------------
|
||||
&__grant-sub {
|
||||
margin: 0 0 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $oguide-text-gray;
|
||||
|
||||
strong {
|
||||
color: $oguide-primary;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
&__grant-card {
|
||||
position: relative;
|
||||
padding: 24px 32px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(90deg, $oguide-primary 0%, #1f6fd0 100%);
|
||||
color: #FFFFFF;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-areas:
|
||||
"badge endpoint"
|
||||
"title endpoint"
|
||||
"desc endpoint";
|
||||
column-gap: 32px;
|
||||
row-gap: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: 6px;
|
||||
background: $oguide-accent-cyan;
|
||||
}
|
||||
}
|
||||
|
||||
&__grant-badge {
|
||||
grid-area: badge;
|
||||
align-self: start;
|
||||
justify-self: start;
|
||||
padding: 5px 14px;
|
||||
border-radius: 13px;
|
||||
background: #FFFFFF;
|
||||
color: $oguide-primary;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
&__grant-title {
|
||||
grid-area: title;
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #FFFFFF;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&__grant-desc {
|
||||
grid-area: desc;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $oguide-secondary;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
&__grant-endpoint {
|
||||
grid-area: endpoint;
|
||||
align-self: center;
|
||||
padding: 14px 18px;
|
||||
background: $oguide-text-dark;
|
||||
border-radius: 10px;
|
||||
min-width: 320px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__endpoint-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__method {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 4px;
|
||||
background: $oguide-primary;
|
||||
color: #FFFFFF;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
&__path {
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 13px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__endpoint-params {
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 11px;
|
||||
color: $oguide-text-mute;
|
||||
}
|
||||
|
||||
// -- Section 5: Sequence Diagram --------------------------------------------
|
||||
&__sequence-diagram {
|
||||
padding: 16px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid $oguide-border;
|
||||
border-radius: 14px;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Section 6: Bearer ------------------------------------------------------
|
||||
&__bearer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
&__code-block {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 28px 24px 28px 32px;
|
||||
border-radius: 14px;
|
||||
background: $oguide-text-dark;
|
||||
color: #FFFFFF;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
overflow-x: auto;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: 6px;
|
||||
background: $oguide-primary;
|
||||
border-radius: 14px 0 0 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&__code-comment {
|
||||
display: block;
|
||||
color: $oguide-text-mute;
|
||||
font-size: 13px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
&__code-line {
|
||||
display: block;
|
||||
color: #FFFFFF;
|
||||
|
||||
&--highlight { color: $oguide-accent-cyan; }
|
||||
}
|
||||
|
||||
&__lifecycle {
|
||||
padding: 24px 28px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid $oguide-border;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
&__lifecycle-title {
|
||||
margin: 0 0 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: $oguide-text-dark;
|
||||
}
|
||||
|
||||
&__lifecycle-steps {
|
||||
list-style: none;
|
||||
margin: 0 0 14px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__lifecycle-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
|
||||
&:not(:last-child)::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 0;
|
||||
border-top: 1.5px dashed $oguide-text-mute;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&__lifecycle-num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: $oguide-primary;
|
||||
color: #FFFFFF;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__lifecycle-step--muted &__lifecycle-num { background: $oguide-text-mute; }
|
||||
// ↑ doesn't work in nesting; explicit modifier below:
|
||||
&__lifecycle-step--muted .oauth2-guide__lifecycle-num { background: $oguide-text-mute; }
|
||||
&__lifecycle-step--muted .oauth2-guide__lifecycle-label { color: $oguide-text-gray; }
|
||||
|
||||
&__lifecycle-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $oguide-text-dark;
|
||||
}
|
||||
|
||||
&__lifecycle-desc {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: $oguide-text-gray;
|
||||
line-height: 1.6;
|
||||
|
||||
& + & { margin-top: 2px; }
|
||||
}
|
||||
|
||||
// -- Section 7: Security ----------------------------------------------------
|
||||
&__security-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
&__security-card {
|
||||
padding: 24px 28px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid $oguide-border;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
&__security-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 14px;
|
||||
|
||||
svg { width: 28px; height: 28px; }
|
||||
|
||||
&--blue { background: $oguide-bg-soft; }
|
||||
&--yellow { background: #FFF9E6; }
|
||||
&--green { background: #E8F8EE; }
|
||||
}
|
||||
|
||||
&__security-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: $oguide-text-dark;
|
||||
}
|
||||
|
||||
&__security-card p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: $oguide-text-gray;
|
||||
line-height: 1.6;
|
||||
|
||||
& + p { margin-top: 2px; }
|
||||
}
|
||||
|
||||
// -- Section 8: CTA ---------------------------------------------------------
|
||||
&__cta {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 36px 48px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(90deg, $oguide-primary 0%, #1f6fd0 100%);
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 28px rgba(0, 73, 180, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
&__cta-body { position: relative; z-index: 1; }
|
||||
|
||||
&__cta-eyebrow {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: $oguide-secondary;
|
||||
letter-spacing: 1.2px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__cta-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: #FFFFFF;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&__cta-desc {
|
||||
margin: 0 0 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $oguide-secondary;
|
||||
}
|
||||
|
||||
&__cta-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 10px 24px;
|
||||
border-radius: 19px;
|
||||
background: #FFFFFF;
|
||||
color: $oguide-primary;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__cta-deco {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
|
||||
&--lg {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
right: 80px;
|
||||
bottom: 30px;
|
||||
}
|
||||
|
||||
&--sm {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
right: 20px;
|
||||
bottom: -10px;
|
||||
background: rgba(255, 255, 255, 0.10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.oauth2-guide {
|
||||
&__hero {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 40px;
|
||||
}
|
||||
&__hero-illust { max-width: 280px; }
|
||||
&__definition { grid-template-columns: 1fr; }
|
||||
&__definition-icon { padding: 24px; }
|
||||
&__bearer-grid,
|
||||
&__parties-grid { grid-template-columns: 1fr; }
|
||||
&__security-grid { grid-template-columns: 1fr 1fr; }
|
||||
&__grant-card {
|
||||
grid-template-areas:
|
||||
"badge"
|
||||
"title"
|
||||
"desc"
|
||||
"endpoint";
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
&__grant-endpoint { min-width: 0; }
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.oauth2-guide {
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
|
||||
&__hero { padding: 28px 24px; }
|
||||
&__hero-title { font-size: 32px; }
|
||||
&__hero-lead { font-size: 15px; }
|
||||
&__h2 { font-size: 22px; }
|
||||
&__security-grid { grid-template-columns: 1fr; }
|
||||
&__cta { padding: 28px 24px; }
|
||||
&__cta-title { font-size: 22px; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// OAuth2 2-Legged 구현 가이드 (Figma 367:1564 - OAuth2 2-Legged 가이드 V3)
|
||||
|
||||
@@ -1,422 +0,0 @@
|
||||
<!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 <access_token></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>
|
||||
@@ -5,260 +5,410 @@
|
||||
<body>
|
||||
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<section class="oauth2-guide">
|
||||
<section class="oauth2-2legged">
|
||||
|
||||
<!-- 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
|
||||
<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-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>
|
||||
<h1 class="oauth2-2legged__hero-title">OAuth2 개발가이드</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-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">
|
||||
<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>
|
||||
<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>
|
||||
<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="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>
|
||||
<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: 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 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>
|
||||
|
||||
<!-- 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 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-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">
|
||||
<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>
|
||||
<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">
|
||||
<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="oguide-arrow-gray" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto">
|
||||
<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="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"/>
|
||||
<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="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"/>
|
||||
<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="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="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="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="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="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="216" text-anchor="middle" font-size="12" font-weight="700" fill="#0049b4">③ GET /api/v1/... · Authorization: Bearer <access_token></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="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)"/>
|
||||
<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 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>
|
||||
<!-- 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-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-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-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 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 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>
|
||||
<!-- 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-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>
|
||||
<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>
|
||||
}
|
||||
|
||||
<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>
|
||||
<span class="o2leg-g"># 응답 본문은 캐싱하여 expires_in 동안 재사용 가능</span></pre>
|
||||
</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 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>
|
||||
<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>
|
||||
<!-- 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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -36,8 +36,7 @@
|
||||
<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>
|
||||
<li><a th:href="@{/service/oauth2-guide}">OAuth2 개발가이드</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/apis" class="nav-link">오픈 API</a></li>
|
||||
|
||||
Reference in New Issue
Block a user