Swagger에 광주은행 OAuth 2.0 API 문서 업데이트 및 XSS 방지 처리 추가

This commit is contained in:
Rinjae
2026-02-25 19:22:47 +09:00
parent 220b4f3cfd
commit 1ed6630261
4 changed files with 76 additions and 27 deletions
-14
View File
@@ -415,18 +415,4 @@ var jsonlint = (function(){
if (typeof require !== 'undefined' && typeof exports !== 'undefined') { if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
exports.parser = jsonlint; exports.parser = jsonlint;
exports.parse = function () { return jsonlint.parse.apply(jsonlint, arguments); } exports.parse = function () { return jsonlint.parse.apply(jsonlint, arguments); }
exports.main = function commonjsMain(args) {
if (!args[1])
throw new Error('Usage: '+args[0]+' FILE');
if (typeof process !== 'undefined') {
var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
} else {
var cwd = require("file").path(require("file").cwd());
var source = cwd.join(args[1]).read({charset: "utf-8"});
}
return exports.parser.parse(source);
}
if (typeof module !== 'undefined' && require.main === module) {
exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
}
} }
@@ -1,12 +1,48 @@
const customPopups = { const customPopups = {
/**
* HTML 새니타이저 - 허용된 태그만 통과시키고 나머지는 제거
* XSS 방지를 위해 script, event handler 등 위험 요소를 제거합니다.
* @param {string} input - 새니타이즈할 문자열
* @returns {string} 새니타이즈된 HTML 문자열
*/
_sanitizeHtml: function (input) {
if (!input) return '';
var allowedTags = ['br', 'strong', 'b', 'em', 'i', 'span', 'p'];
var div = document.createElement('div');
div.innerHTML = input;
// script, style, iframe, object, embed, form 등 위험 요소 제거
var dangerousTags = div.querySelectorAll('script, style, iframe, object, embed, form, link, meta, base');
for (var i = dangerousTags.length - 1; i >= 0; i--) {
dangerousTags[i].parentNode.removeChild(dangerousTags[i]);
}
// 허용되지 않은 태그는 텍스트 노드로 대체
var allElements = div.querySelectorAll('*');
for (var j = allElements.length - 1; j >= 0; j--) {
var el = allElements[j];
if (allowedTags.indexOf(el.tagName.toLowerCase()) === -1) {
var textNode = document.createTextNode(el.textContent);
el.parentNode.replaceChild(textNode, el);
} else {
// 허용된 태그라도 모든 속성 제거 (on* 이벤트 핸들러 등 방지)
while (el.attributes.length > 0) {
el.removeAttribute(el.attributes[0].name);
}
}
}
return div.innerHTML;
},
/** /**
* 알림 팝업 표시 * 알림 팝업 표시
* @param {string} message - 알림 메시지 * @param {string} message - 알림 메시지
* @param {Function} callback - 확인 버튼 클릭 시 호출되는 콜백 (선택사항) * @param {Function} callback - 확인 버튼 클릭 시 호출되는 콜백 (선택사항)
*/ */
showAlert: function (message, callback) { showAlert: function (message, callback) {
// 메시지 설정 // 메시지 설정 (XSS 방지를 위해 새니타이즈)
$('#customAlertMessage').html(message); $('#customAlertMessage').html(customPopups._sanitizeHtml(message));
// 팝업 표시 (modal 구조 사용) // 팝업 표시 (modal 구조 사용)
$('#customAlert').show(); $('#customAlert').show();
@@ -100,9 +136,9 @@ const customPopups = {
const onConfirm = options.onConfirm; const onConfirm = options.onConfirm;
const onCancel = options.onCancel; const onCancel = options.onCancel;
// 팝업 내용 설정 // 팝업 내용 설정 (XSS 방지를 위해 새니타이즈)
$('#passwordPopupTitle').text(title); $('#passwordPopupTitle').text(title);
$('#passwordPopupMessage').html(message); $('#passwordPopupMessage').html(customPopups._sanitizeHtml(message));
// 입력 필드 및 에러 초기화 // 입력 필드 및 에러 초기화
$('#passwordPopupInput').val('').removeClass('error'); $('#passwordPopupInput').val('').removeClass('error');
@@ -215,8 +251,8 @@ const customPopups = {
* @param {Function} callback - 버튼 클릭 시 호출되는 콜백 (파라미터: boolean - true는 확인, false는 취소) * @param {Function} callback - 버튼 클릭 시 호출되는 콜백 (파라미터: boolean - true는 확인, false는 취소)
*/ */
showConfirm: function (message, callback) { showConfirm: function (message, callback) {
// 메시지 설정 // 메시지 설정 (XSS 방지를 위해 새니타이즈)
$('#customConfirmMessage').html(message); $('#customConfirmMessage').html(customPopups._sanitizeHtml(message));
// 팝업 표시 (modal 구조 사용) // 팝업 표시 (modal 구조 사용)
$('#customConfirm').show(); $('#customConfirm').show();
@@ -302,7 +338,7 @@ const customPopups = {
$(document).off('keydown.customConfirmEsc'); $(document).off('keydown.customConfirmEsc');
}, },
showEmailValidationPopup: function (message, onConvertCallback, onChangeEmailCallback) { showEmailValidationPopup: function (message, onConvertCallback, onChangeEmailCallback) {
$('#emailValidationPopupMessage').html(message); $('#emailValidationPopupMessage').html(customPopups._sanitizeHtml(message));
$('#emailValidationPopup').css('display', 'flex'); $('#emailValidationPopup').css('display', 'flex');
$('#emailConvertButton').one('click', function () { $('#emailConvertButton').one('click', function () {
@@ -320,7 +356,7 @@ const customPopups = {
}); });
}, },
showEmailResendPopup: function (message, loginId) { showEmailResendPopup: function (message, loginId) {
$('#emailResendMessage').html(message); $('#emailResendMessage').html(customPopups._sanitizeHtml(message));
$('#userLoginId').val(loginId); $('#userLoginId').val(loginId);
$('#emailResend').show(); $('#emailResend').show();
}, },
@@ -1,15 +1,40 @@
{ {
"openapi": "3.0.0", "openapi": "3.0.0",
"info": { "info": {
"title": "케이뱅크 OAuth 2.0 토큰 발급", "title": "광주은행 OAuth 2.0 토큰 발급",
"description": "OAuth 2.0 토큰 발급을 위한 API 문서입니다.", "description": "OAuth 2.0 토큰 발급을 위한 API 문서입니다.",
"version": "1.0.0" "version": "1.0.0"
}, },
"security": [
{
"clientCredentials": []
}
],
"components": {
"securitySchemes": {
"clientCredentials": {
"type": "oauth2",
"flows": {
"clientCredentials": {
"tokenUrl": "/api/v1/oauth/token",
"scopes": {
"api": "API 접근 권한"
}
}
}
}
}
},
"paths": { "paths": {
"/api/v1/oauth/token": { "/api/v1/oauth/token": {
"post": { "post": {
"summary": "케이뱅크 액세스 토큰 발급", "summary": "광주은행 액세스 토큰 발급",
"description": "client_credentials grant type을 사용하여 액세스 토큰을 발급받습니다.", "description": "client_credentials grant type을 사용하여 액세스 토큰을 발급받습니다.",
"security": [
{
"clientCredentials": ["api"]
}
],
"requestBody": { "requestBody": {
"required": true, "required": true,
"content": { "content": {
@@ -26,12 +51,12 @@
"client_id": { "client_id": {
"type": "string", "type": "string",
"maxLength": 256, "maxLength": 256,
"description": "케이뱅크에서 발급한 client id" "description": "광주은행에서 발급한 client id"
}, },
"client_secret": { "client_secret": {
"type": "string", "type": "string",
"maxLength": 512, "maxLength": 512,
"description": "케이뱅크에서 발급한 client secret" "description": "광주은행에서 발급한 client secret"
}, },
"scope": { "scope": {
"type": "string", "type": "string",
@@ -183,6 +183,8 @@
// 테스트 환경 안내 메시지 표시 // 테스트 환경 안내 메시지 표시
function displayTestAuthNotice(authNumber) { function displayTestAuthNotice(authNumber) {
// XSS 방지를 위해 authNumber를 이스케이프 처리
const safeAuthNumber = String(authNumber).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
const noticeHtml = ` const noticeHtml = `
<div class="test-env-notice" style="margin-top: 12px; padding: 12px 16px; background-color: #FFF4E6; border: 1px solid #FFB84D; border-radius: 4px;"> <div class="test-env-notice" style="margin-top: 12px; padding: 12px 16px; background-color: #FFF4E6; border: 1px solid #FFB84D; border-radius: 4px;">
<div class="test-env-notice__content" style="display: flex; align-items: flex-start; gap: 8px;"> <div class="test-env-notice__content" style="display: flex; align-items: flex-start; gap: 8px;">
@@ -193,7 +195,7 @@
<p style="margin: 0; font-size: 14px; color: #E65100; font-weight: 500;">테스트 환경 안내</p> <p style="margin: 0; font-size: 14px; color: #E65100; font-weight: 500;">테스트 환경 안내</p>
<p style="margin: 4px 0 0 0; font-size: 13px; color: #5D4037; line-height: 1.5;"> <p style="margin: 4px 0 0 0; font-size: 13px; color: #5D4037; line-height: 1.5;">
현재 테스트 환경입니다. 실제로 SMS가 발송되지 않으며, 인증번호는 현재 테스트 환경입니다. 실제로 SMS가 발송되지 않으며, 인증번호는
<strong style="color: #E65100; font-family: monospace; font-size: 14px; font-weight: bold;">${authNumber}</strong> <strong style="color: #E65100; font-family: monospace; font-size: 14px; font-weight: bold;">${safeAuthNumber}</strong>
입니다. 입니다.
</p> </p>
</div> </div>