2-Legged 가이드 삭제 완료
- 관련 HTML 제거 - 메뉴 및 경로 수정
This commit is contained in:
@@ -1,24 +1,41 @@
|
|||||||
package com.eactive.apim.portal.common.breadcrumb;
|
package com.eactive.apim.portal.common.breadcrumb;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.io.IOException;
|
||||||
import org.springframework.stereotype.Service;
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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
|
@Service
|
||||||
public class PageService {
|
public class PageService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PageService.class);
|
||||||
|
|
||||||
private final PageStructure pageStructure;
|
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) {
|
public PageService(PageStructure pageStructure) {
|
||||||
this.pageStructure = pageStructure;
|
this.pageStructure = pageStructure;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Map> getBreadcrumb(String currentPath) {
|
public List<Map> getBreadcrumb(String currentPath) {
|
||||||
// URL에서 기본 경로와 쿼리 파라미터 분리
|
maybeReloadPages();
|
||||||
String basePath = currentPath.contains("?") ?
|
String basePath = currentPath.contains("?") ?
|
||||||
currentPath.substring(0, currentPath.indexOf("?")) : currentPath;
|
currentPath.substring(0, currentPath.indexOf("?")) : currentPath;
|
||||||
Map<String, String> queryParams = parseQueryParams(currentPath);
|
Map<String, String> queryParams = parseQueryParams(currentPath);
|
||||||
@@ -29,12 +46,59 @@ public class PageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getPageName(String currentPath) {
|
public String getPageName(String currentPath) {
|
||||||
|
maybeReloadPages();
|
||||||
String basePath = currentPath.contains("?") ?
|
String basePath = currentPath.contains("?") ?
|
||||||
currentPath.substring(0, currentPath.indexOf("?")) : currentPath;
|
currentPath.substring(0, currentPath.indexOf("?")) : currentPath;
|
||||||
Map<String, String> queryParams = parseQueryParams(currentPath);
|
Map<String, String> queryParams = parseQueryParams(currentPath);
|
||||||
return findPageName(pageStructure.getHome(), basePath, queryParams);
|
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) {
|
private Map<String, String> parseQueryParams(String path) {
|
||||||
Map<String, String> params = new HashMap<>();
|
Map<String, String> params = new HashMap<>();
|
||||||
if (path.contains("?")) {
|
if (path.contains("?")) {
|
||||||
@@ -52,7 +116,6 @@ public class PageService {
|
|||||||
private String findPageName(Map<String, Object> currentNode, String basePath, Map<String, String> queryParams) {
|
private String findPageName(Map<String, Object> currentNode, String basePath, Map<String, String> queryParams) {
|
||||||
String path = (String) currentNode.get("path");
|
String path = (String) currentNode.get("path");
|
||||||
|
|
||||||
// 기본 경로가 일치하는지 확인
|
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
String nodePath = path.contains("?") ?
|
String nodePath = path.contains("?") ?
|
||||||
path.substring(0, path.indexOf("?")) : path;
|
path.substring(0, path.indexOf("?")) : path;
|
||||||
@@ -63,7 +126,6 @@ public class PageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 자식 노드들을 재귀적으로 검색
|
|
||||||
if (currentNode.containsKey("children")) {
|
if (currentNode.containsKey("children")) {
|
||||||
Map<String, Object> children = (Map<String, Object>) currentNode.get("children");
|
Map<String, Object> children = (Map<String, Object>) currentNode.get("children");
|
||||||
for (Map.Entry<String, Object> entry : children.entrySet()) {
|
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) {
|
private boolean matchesQueryParams(Map<String, String> nodeParams, Map<String, String> requestParams) {
|
||||||
// 특별히 tab 파라미터에 대해서만 체크
|
|
||||||
String nodeTab = nodeParams.get("tab");
|
String nodeTab = nodeParams.get("tab");
|
||||||
String requestTab = requestParams.get("tab");
|
String requestTab = requestParams.get("tab");
|
||||||
|
|
||||||
@@ -125,4 +186,4 @@ public class PageService {
|
|||||||
}
|
}
|
||||||
return Objects.equals(nodeTab, requestTab);
|
return Objects.equals(nodeTab, requestTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,4 +40,7 @@ gateway:
|
|||||||
|
|
||||||
portal:
|
portal:
|
||||||
auth-virtual-code: 654321
|
auth-virtual-code: 654321
|
||||||
test-auth-notice-enabled: true
|
test-auth-notice-enabled: true
|
||||||
|
dev:
|
||||||
|
# application.yml의 `page:` 트리(브레드크럼/메뉴 이름) 라이브 반영
|
||||||
|
hot-reload-pages: true
|
||||||
@@ -25,6 +25,9 @@ portal:
|
|||||||
test-auth-notice-enabled: true
|
test-auth-notice-enabled: true
|
||||||
# 검증 단계에선 사용하지 않음
|
# 검증 단계에선 사용하지 않음
|
||||||
# auth-virtual-code: 654321
|
# auth-virtual-code: 654321
|
||||||
|
dev:
|
||||||
|
# application.yml의 `page:` 트리(브레드크럼/메뉴 이름) 라이브 반영
|
||||||
|
hot-reload-pages: true
|
||||||
|
|
||||||
|
|
||||||
ems:
|
ems:
|
||||||
|
|||||||
@@ -179,10 +179,6 @@ portal:
|
|||||||
method: GET
|
method: GET
|
||||||
view-name: apps/service/oauth2-guide
|
view-name: apps/service/oauth2-guide
|
||||||
|
|
||||||
- path-pattern: /service/oauth2-2legged-guide
|
|
||||||
method: GET
|
|
||||||
view-name: apps/service/oauth2-2legged-guide
|
|
||||||
|
|
||||||
- path-pattern: /dashboard
|
- path-pattern: /dashboard
|
||||||
method: GET
|
method: GET
|
||||||
view-name: apps/mypage/dashboard
|
view-name: apps/mypage/dashboard
|
||||||
@@ -267,11 +263,8 @@ page:
|
|||||||
name: "회원가입 안내"
|
name: "회원가입 안내"
|
||||||
path: "/service/guide"
|
path: "/service/guide"
|
||||||
oauth2_guide:
|
oauth2_guide:
|
||||||
name: "OAuth2 인증가이드"
|
name: "OAuth2 개발가이드"
|
||||||
path: "/service/oauth2-guide"
|
path: "/service/oauth2-guide"
|
||||||
oauth2_2legged_guide:
|
|
||||||
name: "OAuth2 2-Legged 가이드"
|
|
||||||
path: "/service/oauth2-2legged-guide"
|
|
||||||
apis:
|
apis:
|
||||||
name: "API"
|
name: "API"
|
||||||
path: "#"
|
path: "#"
|
||||||
|
|||||||
@@ -18429,566 +18429,6 @@ body.commission-print-page .btn-primary:hover {
|
|||||||
font-size: 18px;
|
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 {
|
.oauth2-2legged {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
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)
|
// 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>
|
<body>
|
||||||
|
|
||||||
<th:block layout:fragment="contentFragment">
|
<th:block layout:fragment="contentFragment">
|
||||||
<section class="oauth2-guide">
|
<section class="oauth2-2legged">
|
||||||
|
|
||||||
<!-- Section 1: Hero -->
|
<!-- Section 1: Hero -->
|
||||||
<header class="oauth2-guide__hero">
|
<header class="oauth2-2legged__hero">
|
||||||
<div class="oauth2-guide__hero-body">
|
<div class="oauth2-2legged__hero-body">
|
||||||
<span class="oauth2-guide__hero-eyebrow">
|
<span class="oauth2-2legged__hero-eyebrow">
|
||||||
<span class="oauth2-guide__hero-eyebrow-dot"></span>
|
<span class="oauth2-2legged__hero-eyebrow-dot"></span>
|
||||||
개발자 가이드 · Authentication
|
개발 가이드 · 2-Legged · Client Credentials
|
||||||
</span>
|
</span>
|
||||||
<h1 class="oauth2-guide__hero-title">OAuth 2.0 인증 개요</h1>
|
<h1 class="oauth2-2legged__hero-title">OAuth2 개발가이드</h1>
|
||||||
<p class="oauth2-guide__hero-lead">DJBank Open API를 사용하기 위한 표준 인증 방식,</p>
|
<p class="oauth2-2legged__hero-lead">DJBank Open API를 호출하기 위한 ClientID/Secret 기반 토큰 발급과</p>
|
||||||
<p class="oauth2-guide__hero-lead">OAuth 2.0의 개념과 동작 원리를 한눈에 정리했습니다.</p>
|
<p class="oauth2-2legged__hero-lead">Bearer 인증 호출 방법을 단계별로 설명합니다.</p>
|
||||||
<div class="oauth2-guide__hero-chips">
|
|
||||||
<span class="oauth2-guide__chip">2-Legged</span>
|
<div class="oauth2-2legged__hero-chips">
|
||||||
<span class="oauth2-guide__chip">Client Credentials</span>
|
<span class="oauth2-2legged__chip oauth2-2legged__chip--primary">Server-to-Server</span>
|
||||||
<span class="oauth2-guide__chip">Bearer Token</span>
|
<span class="oauth2-2legged__chip">cURL 예제</span>
|
||||||
|
<span class="oauth2-2legged__chip">application/x-www-form</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="oauth2-guide__hero-illust" aria-hidden="true">
|
<div class="oauth2-2legged__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">
|
<svg viewBox="0 0 280 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Server-to-Server Token Flow">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="oguide-shield" x1="0" y1="0" x2="0" y2="1">
|
<marker id="o2leg-hero-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto">
|
||||||
<stop offset="0%" stop-color="#0049b4"/>
|
<path d="M0,0 L10,5 L0,10 Z" fill="#0049b4"/>
|
||||||
<stop offset="100%" stop-color="#0066d6"/>
|
</marker>
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
</defs>
|
||||||
<rect x="0" y="0" width="280" height="220" rx="24" fill="#FFFFFF"/>
|
<rect x="0" y="0" width="280" height="200" rx="20" fill="#FFFFFF"/>
|
||||||
<circle cx="40" cy="40" r="3" fill="#00D4FF"/>
|
|
||||||
<circle cx="60" cy="32" r="2" fill="#A78BFA"/>
|
<rect x="40" y="90" width="80" height="50" rx="8" fill="#EDF9FE" stroke="#0049b4"/>
|
||||||
<circle cx="240" cy="190" r="3" fill="#FFD93D"/>
|
<text x="80" y="120" text-anchor="middle" font-size="11" font-weight="700" fill="#0049b4">Client</text>
|
||||||
<circle cx="220" cy="200" r="2" fill="#6BCF7F"/>
|
|
||||||
<g transform="translate(80,40)">
|
<rect x="160" y="90" width="80" height="50" rx="8" fill="#EDF9FE" stroke="#0049b4"/>
|
||||||
<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)"/>
|
<text x="200" y="112" text-anchor="middle" font-size="10" font-weight="700" fill="#0049b4">DJBank</text>
|
||||||
<path d="M30 70 L52 92 L92 50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
<text x="200" y="126" text-anchor="middle" font-size="10" font-weight="700" fill="#0049b4">Open API</text>
|
||||||
</g>
|
|
||||||
<g transform="translate(30,170)">
|
<line x1="120" y1="115" x2="160" y2="115" stroke="#0049b4" stroke-width="2" marker-end="url(#o2leg-hero-arrow)"/>
|
||||||
<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 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>
|
</g>
|
||||||
|
|
||||||
|
<text x="140" y="170" text-anchor="middle" font-size="11" font-weight="600" fill="#64748B">Server-to-Server Token Flow</text>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Section 2: Definition -->
|
<!-- Section 2: 사전 준비 -->
|
||||||
<section class="oauth2-guide__definition" aria-labelledby="oguide-def-title">
|
<section class="oauth2-2legged__prereq" aria-labelledby="o2leg-prereq-title">
|
||||||
<div class="oauth2-guide__definition-icon" aria-hidden="true">
|
<span class="oauth2-2legged__eyebrow">PREREQUISITE</span>
|
||||||
<svg viewBox="0 0 120 80" xmlns="http://www.w3.org/2000/svg">
|
<h2 class="oauth2-2legged__h2" id="o2leg-prereq-title">시작 전 준비 사항</h2>
|
||||||
<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 -->
|
<div class="oauth2-2legged__prereq-grid">
|
||||||
<section class="oauth2-guide__parties" aria-labelledby="oguide-parties-title">
|
<article class="oauth2-2legged__prereq-card">
|
||||||
<span class="oauth2-guide__eyebrow">CORE PARTIES</span>
|
<span class="oauth2-2legged__prereq-num">1</span>
|
||||||
<h2 class="oauth2-guide__h2" id="oguide-parties-title">OAuth 2.0 주요 구성</h2>
|
<div class="oauth2-2legged__prereq-body">
|
||||||
<div class="oauth2-guide__parties-grid">
|
<h3 class="oauth2-2legged__prereq-title">앱 등록</h3>
|
||||||
<article class="oauth2-guide__party-card">
|
<p>개발자포털 [내 앱] 메뉴에서 신규</p>
|
||||||
<div class="oauth2-guide__party-icon" aria-hidden="true">
|
<p>애플리케이션을 등록합니다.</p>
|
||||||
<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>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="oauth2-guide__party-card">
|
<article class="oauth2-2legged__prereq-card">
|
||||||
<div class="oauth2-guide__party-icon" aria-hidden="true">
|
<span class="oauth2-2legged__prereq-num">2</span>
|
||||||
<svg viewBox="0 0 56 44" xmlns="http://www.w3.org/2000/svg">
|
<div class="oauth2-2legged__prereq-body">
|
||||||
<defs>
|
<h3 class="oauth2-2legged__prereq-title">ClientID / Secret 발급</h3>
|
||||||
<linearGradient id="oguide-party-shield" x1="0" y1="0" x2="0" y2="1">
|
<p>승인 완료 후 발급된 자격증명을</p>
|
||||||
<stop offset="0%" stop-color="#0049b4"/>
|
<p>서버 환경변수에 안전하게 보관.</p>
|
||||||
<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>
|
||||||
<div class="oauth2-guide__party-body">
|
</article>
|
||||||
<h3 class="oauth2-guide__party-title">DJBank Open API</h3>
|
|
||||||
<p>토큰 발급과 API 응답을 모두 담당하는</p>
|
<article class="oauth2-2legged__prereq-card">
|
||||||
<p>DJBank의 단일 진입점입니다.</p>
|
<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>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Section 4: Grant Type -->
|
<!-- Section 3: 전체 인증·호출 시퀀스 -->
|
||||||
<section class="oauth2-guide__grant" aria-labelledby="oguide-grant-title">
|
<section class="oauth2-2legged__sequence" aria-labelledby="o2leg-seq-title">
|
||||||
<span class="oauth2-guide__eyebrow">GRANT TYPE</span>
|
<span class="oauth2-2legged__eyebrow">SEQUENCE</span>
|
||||||
<h2 class="oauth2-guide__h2" id="oguide-grant-title">지원 Grant Type</h2>
|
<h2 class="oauth2-2legged__h2" id="o2leg-seq-title">전체 인증·호출 시퀀스</h2>
|
||||||
<p class="oauth2-guide__grant-sub">
|
|
||||||
DJBank는 <strong>Client Credentials (2-Legged)</strong> 방식을 지원합니다.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<article class="oauth2-guide__grant-card">
|
<div class="oauth2-2legged__sequence-diagram">
|
||||||
<span class="oauth2-guide__grant-badge">SUPPORTED</span>
|
<svg viewBox="0 0 1120 282" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OAuth 2.0 2-Legged Sequence">
|
||||||
<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>
|
<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"/>
|
<path d="M0,0 L10,5 L0,10 Z" fill="#0049b4"/>
|
||||||
</marker>
|
</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"/>
|
<path d="M0,0 L10,5 L0,10 Z" fill="#64748B"/>
|
||||||
</marker>
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<g>
|
<g>
|
||||||
<rect x="160" y="20" width="200" height="44" rx="22" fill="#EDF9FE" stroke="#0049b4"/>
|
<rect x="120" y="24" width="240" height="48" rx="24" fill="#EDF9FE" stroke="#0049b4"/>
|
||||||
<text x="260" y="48" text-anchor="middle" font-size="14" font-weight="700" fill="#0049b4">Client (앱)</text>
|
<text x="240" y="54" text-anchor="middle" font-size="14" font-weight="700" fill="#0049b4">Client Application</text>
|
||||||
<line x1="260" y1="64" x2="260" y2="200" stroke="#94A3B8" stroke-dasharray="4 4"/>
|
<line x1="240" y1="72" x2="240" y2="254" stroke="#94A3B8" stroke-dasharray="4 4"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g>
|
<g>
|
||||||
<rect x="760" y="20" width="200" height="44" rx="22" fill="#FFFFFF" stroke="#0049b4"/>
|
<rect x="760" y="24" width="240" height="48" rx="24" fill="#FFFFFF" stroke="#0049b4"/>
|
||||||
<text x="860" y="48" text-anchor="middle" font-size="14" font-weight="700" fill="#0049b4">DJBank Open API</text>
|
<text x="880" y="54" 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"/>
|
<line x1="880" y1="72" x2="880" y2="254" stroke="#94A3B8" stroke-dasharray="4 4"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<text x="560" y="90" text-anchor="middle" font-size="12" font-weight="700" fill="#0049b4">① ClientID + Secret 으로 토큰 요청</text>
|
<text x="560" y="102" text-anchor="middle" font-size="12" font-weight="700" fill="#0049b4">① POST /dj/oauth/token</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="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>
|
<text x="560" y="160" 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)"/>
|
<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>
|
<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="260" y1="172" x2="860" y2="172" stroke="#0049b4" stroke-width="2" marker-end="url(#oguide-arrow-primary)"/>
|
<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>
|
<text x="560" y="248" 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)"/>
|
<line x1="880" y1="254" x2="240" y2="254" stroke="#64748B" stroke-width="2" marker-end="url(#o2leg-arrow-gray)"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Section 6: Bearer Token -->
|
<!-- Section 4: STEP 1 — 액세스 토큰 요청 -->
|
||||||
<section class="oauth2-guide__bearer" aria-labelledby="oguide-bearer-title">
|
<section class="oauth2-2legged__step" aria-labelledby="o2leg-step1-title">
|
||||||
<span class="oauth2-guide__eyebrow">BEARER TOKEN</span>
|
<span class="oauth2-2legged__eyebrow">STEP 1</span>
|
||||||
<h2 class="oauth2-guide__h2" id="oguide-bearer-title">발급된 토큰으로 API 호출</h2>
|
<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">
|
<div class="oauth2-2legged__endpoint-box">
|
||||||
<pre class="oauth2-guide__code-block"><span class="oauth2-guide__code-comment">// HTTP Request Header</span>
|
<span class="oauth2-2legged__method">POST</span>
|
||||||
<span class="oauth2-guide__code-line">GET /api/v1/accounts HTTP/1.1</span>
|
<code class="oauth2-2legged__endpoint-path">https://openapi.djbank.co.kr/dj/oauth/token</code>
|
||||||
<span class="oauth2-guide__code-line">Host: openapi.djbank.co.kr</span>
|
<span class="oauth2-2legged__endpoint-content-type">x-www-form-urlencoded</span>
|
||||||
<span class="oauth2-guide__code-line oauth2-guide__code-line--highlight">Authorization: Bearer eyJhbGciOi...</span></pre>
|
</div>
|
||||||
|
|
||||||
<div class="oauth2-guide__lifecycle">
|
<div class="oauth2-2legged__step-grid">
|
||||||
<h3 class="oauth2-guide__lifecycle-title">토큰 라이프사이클</h3>
|
<div class="oauth2-2legged__panel">
|
||||||
<ol class="oauth2-guide__lifecycle-steps">
|
<h3 class="oauth2-2legged__panel-title">요청 파라미터</h3>
|
||||||
<li class="oauth2-guide__lifecycle-step"><span class="oauth2-guide__lifecycle-num">1</span><span class="oauth2-guide__lifecycle-label">발급</span></li>
|
<table class="oauth2-2legged__table">
|
||||||
<li class="oauth2-guide__lifecycle-step"><span class="oauth2-guide__lifecycle-num">2</span><span class="oauth2-guide__lifecycle-label">사용</span></li>
|
<thead>
|
||||||
<li class="oauth2-guide__lifecycle-step"><span class="oauth2-guide__lifecycle-num">3</span><span class="oauth2-guide__lifecycle-label">만료</span></li>
|
<tr>
|
||||||
<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>
|
<th>PARAMETER</th>
|
||||||
</ol>
|
<th>REQ</th>
|
||||||
<p class="oauth2-guide__lifecycle-desc">TTL 내에는 토큰을 재사용하고,</p>
|
<th>설명</th>
|
||||||
<p class="oauth2-guide__lifecycle-desc">만료 시 새 토큰을 발급받아 호출합니다.</p>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Section 7: Security -->
|
<!-- Section 5: STEP 2 — 액세스 토큰 응답 -->
|
||||||
<section class="oauth2-guide__security" aria-labelledby="oguide-security-title">
|
<section class="oauth2-2legged__step" aria-labelledby="o2leg-step2-title">
|
||||||
<span class="oauth2-guide__eyebrow">SECURITY</span>
|
<span class="oauth2-2legged__eyebrow">STEP 2</span>
|
||||||
<h2 class="oauth2-guide__h2" id="oguide-security-title">기본 보안 권고</h2>
|
<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">
|
<div class="oauth2-2legged__step-grid">
|
||||||
<article class="oauth2-guide__security-card">
|
<div class="oauth2-2legged__code-panel">
|
||||||
<div class="oauth2-guide__security-icon oauth2-guide__security-icon--blue" aria-hidden="true">
|
<span class="oauth2-2legged__code-tag oauth2-2legged__code-tag--ok">200 OK · JSON</span>
|
||||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
<pre class="oauth2-2legged__code-block">{
|
||||||
<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"/>
|
<span class="o2leg-c">"access_token"</span>: <span class="o2leg-y">"eyJhbGciOiJSUzI1NiJ9..."</span>,
|
||||||
</svg>
|
<span class="o2leg-c">"token_type"</span>: <span class="o2leg-y">"bearer"</span>,
|
||||||
</div>
|
<span class="o2leg-c">"expires_in"</span>: <span class="o2leg-p">86400</span>,
|
||||||
<h3 class="oauth2-guide__security-title">HTTPS 필수</h3>
|
<span class="o2leg-c">"scope"</span>: <span class="o2leg-y">"read.accounts"</span>,
|
||||||
<p>모든 토큰·API 호출은</p>
|
<span class="o2leg-c">"jti"</span>: <span class="o2leg-y">"f47ac10b-58cc-4372-..."</span>
|
||||||
<p>반드시 TLS 위에서만 수행합니다.</p>
|
}
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="oauth2-guide__security-card">
|
<span class="o2leg-g"># 응답 본문은 캐싱하여 expires_in 동안 재사용 가능</span></pre>
|
||||||
<div class="oauth2-guide__security-icon oauth2-guide__security-icon--yellow" aria-hidden="true">
|
</div>
|
||||||
<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-2legged__panel">
|
||||||
<div class="oauth2-guide__security-icon oauth2-guide__security-icon--green" aria-hidden="true">
|
<h3 class="oauth2-2legged__panel-title">응답 필드</h3>
|
||||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
<table class="oauth2-2legged__table">
|
||||||
<circle cx="16" cy="16" r="9" fill="none" stroke="#6BCF7F" stroke-width="2.5"/>
|
<thead>
|
||||||
<path d="M16 11 L16 16 L19 19" stroke="#6BCF7F" stroke-width="2.5" stroke-linecap="round"/>
|
<tr>
|
||||||
</svg>
|
<th>FIELD</th>
|
||||||
</div>
|
<th>TYPE</th>
|
||||||
<h3 class="oauth2-guide__security-title">Token TTL 활용</h3>
|
<th>설명</th>
|
||||||
<p>expires_in 만큼 재사용하고,</p>
|
</tr>
|
||||||
<p>만료 직전에 갱신을 트리거합니다.</p>
|
</thead>
|
||||||
</article>
|
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Section 8: CTA -->
|
<!-- Section 6: STEP 3 — API 호출 -->
|
||||||
<a class="oauth2-guide__cta" th:href="@{/service/oauth2-2legged-guide}">
|
<section class="oauth2-2legged__step" aria-labelledby="o2leg-step3-title">
|
||||||
<div class="oauth2-guide__cta-body">
|
<span class="oauth2-2legged__eyebrow">STEP 3</span>
|
||||||
<span class="oauth2-guide__cta-eyebrow">NEXT STEP</span>
|
<h2 class="oauth2-2legged__h2" id="o2leg-step3-title">발급 토큰으로 API 호출</h2>
|
||||||
<h2 class="oauth2-guide__cta-title">2-Legged 구현 가이드 보기</h2>
|
<p class="oauth2-2legged__desc">Authorization 헤더에 Bearer 토큰을 실어 보호 자원 API 를 호출합니다.</p>
|
||||||
<p class="oauth2-guide__cta-desc">실제 토큰 발급·API 호출 방법을 코드 샘플과 함께 확인합니다.</p>
|
|
||||||
<span class="oauth2-guide__cta-button">개발 가이드 →</span>
|
<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>
|
</div>
|
||||||
<span class="oauth2-guide__cta-deco oauth2-guide__cta-deco--lg" aria-hidden="true"></span>
|
</section>
|
||||||
<span class="oauth2-guide__cta-deco oauth2-guide__cta-deco--sm" aria-hidden="true"></span>
|
|
||||||
|
<!-- 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>
|
</a>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -36,8 +36,7 @@
|
|||||||
<ul class="sub-menu">
|
<ul class="sub-menu">
|
||||||
<li><a th:href="@{/service/intro}">API 포탈 소개</a></li>
|
<li><a th:href="@{/service/intro}">API 포탈 소개</a></li>
|
||||||
<li><a th:href="@{/service/guide}">회원가입 안내</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-guide}">OAuth2 개발가이드</a></li>
|
||||||
<li><a th:href="@{/service/oauth2-2legged-guide}">OAuth2 2-Legged 가이드</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/apis" class="nav-link">오픈 API</a></li>
|
<li><a href="/apis" class="nav-link">오픈 API</a></li>
|
||||||
|
|||||||
Reference in New Issue
Block a user