Auth Guide
보안 정보
모든 API 요청은 OAuth를 사용하여 인증되어야 합니다. 이를 통해 콘텐츠 피드 및 보호된 리소스에 대한 안전하고 토큰 기반의 접근을 보장합니다.
OAuth 요청하기
* 참고
이 튜토리얼(및 관련 문서) 전반에 걸쳐 코드 예제에서 요소, 속성 및 값을 나타내기 위해 {placeholders}를 사용합니다. 따라서 요청 및 코드에서는 이러한 placeholders를 중괄호 {} 없이 바꿔야 합니다.
1. API 접근을 위한 인증 자격 증명
OAuth를 사용하여 당사 API에 액세스하려면 다음 자격 증명이 필요합니다. 이러한 자격 증명은 온보딩 과정의 일부로 안전하게 공유됩니다.
apikey
client_id
client_secret2. OAuth API 호출
다음 URL로 API를 요청합니다.
* 참고
이 POST 요청으로 생성된 베어러 토큰은 30분간 유효합니다.
다음 단계를 참고하세요.
- POST 요청에 대한 Header 설정:
Content Type 설정
Content-Type: application/x-www-form-urlencoded
- POST 요청에 대한 Body 값 설정:
Body 값 설정
| KEY | VALUE | EXAMPLE |
|---|---|---|
| client_id | {ClientID} | 71428704-24e5-15a2-6dc9-efe759a80743 |
| client_secret | {ClientSecret} | AC5tfkn291434f9kJzY8 |
| grant_type | {client_credentials} | client_credentials |
- OAuth API 호출:
Call the OAUTH API
요청의 실제 출력(예: access_token, expires_in, token_type)을 확인하는 곳에서 액세스 토큰을 변수에 저장하여 이후 GET 요청에서 Bearer Token 인증으로 사용합니다.
3. STATS API 호출
STATS API에 GET 요청을 보내고 인증 유형을 " Bearer Token "으로 설정하세요.
요청을 보내면 인증 헤더가 자동으로 생성됩니다.
예를 들어, 위의 OAuth 응답 예시에서 BearerToken의 값은 다음과 같습니다.
{
"access_token": "eyJhbGciOiJFUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJHdUd1ZVZIaFJ1cHI3OGRmM3F1RklHQ2c0TWd0STFxSlYzMG5vNjB2VW5rIn0.eyJleHAiOjE3NTM3Nzk0MzEsImlhdCI6MTc1Mzc3NzYzMSwianRpIjoib25ydHJvOjZlMDVjZjY1LTZkYzItNDE0ZS04OThmLTFhNWEyOTc4NjE3ZSIsImlzcyI6Imh0dHBzOi8vYXBpLnN0YXRzcGVyZm9ybS5jb20vcmVhbG1zL2FwaWd3IiwiYXVkIjoiYXBpZ3ciLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmMzYzZmQ0Zi04NGNjLTRjNzYtOWJmZi0zNjNlOTk0OWJhNjUtY2xpZW50Iiwic2lkIjoiNjg1NTBjMDYtNDQxOC00ZTU4LWIzYzQtNzcyN2U0NDUyYjUzIiwic2NvcGUiOiJ1c2VyY29uZmlnIiwidXNlcmNvbmZpZyI6ImYzNjNmZDRmLTg0Y2MtNGM3Ni05YmZmLTM2M2U5OTQ5YmE2NS1vYXV0aCJ9.AZ96uHzVFs9W5tTn0AAsEgoVrP_L_b5kgh44-M5Zi0zYRMAVuQLT5ZAjt2UY-SryNm0A2aY_0mbDX0C6q0ArCTVMAJ7W1ZED1kVq3tiWypafMV4tvNVggeh3fIXcY1H5VLCnxajLImNZV79sgdIyFgnx68JA2TM9SNuVFCN9jh4b4be0",
"expires_in": 1800,
"refresh_expires_in": 0,
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "68550c06-4418-4e58-b3c4-7727e4452b53",
"scope": "userconfig"
}요청 URL Format:
https://{requestDomain}/{feedResource}/{feedName}/{assetId}?accept={response type}* 참고
· {requestDomain} 에 사용되는 STATS API 는 api.statsperform.com 입니다.
· {feedResource} 에는 soccer, basketball, football 등으로 적절히 바꿔주세요.
· {feedName} 에는 events 등으로 적절히 바꿔주세요.
· {assetId} 에는 호출하려는 피드에 맞게 적절히 바꿔주세요. 이는 {EventID}와 같은 스포츠 관련 개체의 UUID일 수 있습니다. 지원되는 호출에 대한 자세한 내용은 피드 문서를 참조하십시오.
요청 예시:
이 예시에서 요청은 야구 API의 이벤트 피드에서 특정 경기 일정을 가져오는 것이므로 다음과 같은 요소들을 포함합니다.
· https://api.statsperform.com/statsapi/v1/ ({requestDomain}을 대체)
· /baseball (야구API : {feedResource}를 대체)
· /mlb (리그약어: {league}를 대체)
· /events (피드 엔드포인트: {feedName}를 대체)
· /2718451 (이벤트 ID: {assetId}를 대체)
· ?accept=json (Json 포맷으로 받기 위한 매개변수)
위 내용으로 조합한 요청 URL 은 다음과 같습니다.
위 URL 에 요청한 응답은 아래와 같습니다.
{
"status": "OK",
"recordCount": 1,
"startTimestamp": "2025-09-02T10:58:46.6929001Z",
"endTimestamp": "2025-09-02T10:58:46.7397767Z",
"timeTaken": 0.0468766,
"apiResults": [
{
"sportId": 2,
"name": "Baseball",
"league": {
"leagueId": 7,
"name": "Major League Baseball",
"abbreviation": "MLB",
"displayName": "Major League Baseball",
"season": {
"season": 2024,
"name": "2024 MLB Season",
"isActive": false,
"eventType": [
{
"eventTypeId": 0,
"name": "Spring Training",
"events": [
{
"eventId": 2619963,
"startDate": [
{
"year": 2024,
"month": 2,
"date": 22,
"hour": 13,
"minute": 10,
"full": "2024-02-22T13:10:00",
"dateType": "Local"
},
{
"year": 2024,
"month": 2,
"date": 22,
"hour": 20,
"minute": 10,
"full": "2024-02-22T20:10:00",
"dateType": "UTC"
}
],
"isTba": false,
"isDataConfirmed": {
"score": true
},
"eventStatus": {
"eventStatusId": 4,
"balls": null,
"strikes": null,
"outs": null,
"inning": 9,
"inningDivision": null,
"isActive": false,
"firstPitchDateTime": null,
"dateType": null,
"name": "Final",
"currentBatter": null,
"runnersOnBase": []
},
"isDoubleheader": false,
"venue": {
"venueId": 2782,
"name": "Peoria Sports Complex",
"city": "Peoria",
"attendanceInfo": {
"reasonId": 0,
"reasonDesc": "Full Audience"
},
"state": {
"stateId": 3,
"name": "Arizona",
"abbreviation": "AZ"
},
"country": {
"countryId": 1,
"name": "United States",
"abbreviation": "USA"
},
"zipCode": "85382"
},
"tvStations": [
{
"tvStationId": 581,
"name": "ESPN",
"callLetters": "ESPN",
"networkType": {
"networkTypeId": 1,
"name": "National"
},
"country": {
"countryId": 1,
"name": "United States",
"abbreviation": "USA"
}
},
{
"tvStationId": 879,
"name": "Time Warner Cable SportsNet LA",
"callLetters": "SNLA",
"networkType": {
"networkTypeId": 2,
"name": "Regional"
},
"country": {
"countryId": 1,
"name": "United States",
"abbreviation": "USA"
}
},
{
"tvStationId": 864,
"name": "MLB Network",
"callLetters": "MLBN",
"networkType": {
"networkTypeId": 1,
"name": "National"
},
"country": {
"countryId": 1,
"name": "United States",
"abbreviation": "USA"
}
}
],
"teams": [
{
"teamId": 249,
"location": "San Diego",
"nickname": "Padres",
"abbreviation": "SD",
"teamLocationType": {
"teamLocationTypeId": 1,
"name": "home"
},
"isSplitSquad": false,
"pitchers": [
{
"type": {
"typeId": 3,
"name": "lose"
},
"player": {
"playerId": 597766,
"firstName": "Joe",
"lastName": "Musgrove"
},
"wins": 0,
"losses": 1
},
{
"type": {
"typeId": 1,
"name": "starter"
},
"player": {
"playerId": 597766,
"firstName": "Joe",
"lastName": "Musgrove"
},
"wins": 0,
"losses": 1
}
],
"conference": {
"conferenceId": 6,
"name": "National League",
"abbreviation": "NL"
},
"linescoreTotals": {
"runs": 1,
"hits": 7,
"errors": 0
},
"liveState": {
"currentPitcher": null,
"nextUpBatters": []
},
"record": {
"wins": 0,
"losses": 1,
"percentage": ".000"
},
"isWinner": false
},
{
"teamId": 243,
"location": "Los Angeles",
"nickname": "Dodgers",
"abbreviation": "LAD",
"teamLocationType": {
"teamLocationTypeId": 2,
"name": "away"
},
"isSplitSquad": false,
"pitchers": [
{
"type": {
"typeId": 2,
"name": "win"
},
"player": {
"playerId": 1218087,
"firstName": "Gavin",
"lastName": "Stone"
},
"wins": 1,
"losses": 0,
"earnedRunAverage": "0.00"
},
{
"type": {
"typeId": 1,
"name": "starter"
},
"player": {
"playerId": 1218087,
"firstName": "Gavin",
"lastName": "Stone"
},
"wins": 1,
"losses": 0,
"earnedRunAverage": "0.00"
}
],
"conference": {
"conferenceId": 6,
"name": "National League",
"abbreviation": "NL"
},
"linescoreTotals": {
"runs": 14,
"hits": 12,
"errors": 0
},
"liveState": {
"currentPitcher": null,
"nextUpBatters": []
},
"record": {
"wins": 1,
"losses": 0,
"percentage": "1.000"
},
"isWinner": true
}
],
"eventConference": {
"conferenceId": 1,
"name": "Cactus League",
"abbreviation": "Cactus League",
"isInterleague": false
},
"coverageLevel": {
"coverageLevelId": 93,
"details": "Spring Training - Scores only, one update per inning",
"name": "Level 3"
}
}
]
}
]
}
}
}
]
}OAuth 예시
Javascript
// ====================================
// 설정 및 라이브러리 임포트
// ====================================
// Node 18+에서는 fetch가 내장되어 있습니다.
// (Node 18 미만이면 node-fetch 설치가 필요합니다.)
// ====================================
// OAUTH 토큰 생성기
// Bearer Token을 얻기 위한 POST 함수
// ====================================
async function getOauthToken(hostname, apikey, clientId, clientSecret) {
const requestUrl = `https://${hostname}/realms/apigw/protocol/openid-connect/token?apikey=${apikey}`;
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
};
const data = new URLSearchParams({
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
});
try {
const response = await fetch(requestUrl, {
method: "POST",
headers,
body: data,
});
// HTTP 오류(4xx 또는 5xx)가 발생하면 예외처럼 처리
if (!response.ok) {
const text = await response.text();
throw new Error(`HTTP ${response.status} - ${text}`);
}
const responseJson = await response.json();
const accessToken = responseJson.access_token;
if (accessToken) {
console.log("OAuth 토큰을 성공적으로 가져왔습니다.");
console.log(`Access Token: ${accessToken}`);
console.log(`Expires In: ${responseJson.expires_in} 초`);
return accessToken;
} else {
console.log("오류: 응답에 'access_token'이 없습니다.");
console.log("전체 응답:", responseJson);
return null;
}
} catch (e) {
console.log(`요청 중 오류 발생: ${e.message}`);
return null;
}
}
// ====================================
// (JWT) Base64URL 디코딩 유틸
// ====================================
function base64UrlDecode(str) {
str = str.replace(/-/g, "+").replace(/_/g, "/");
const pad = str.length % 4;
if (pad) str += "=".repeat(4 - pad);
return Buffer.from(str, "base64").toString("utf8");
}
// ====================================
// 서명 검증 없이 JWT 페이로드만 디코딩
// ====================================
function decodeJwtPayload(accessToken) {
const parts = accessToken.split(".");
if (parts.length < 2) throw new Error("잘못된 JWT 형식입니다.");
return JSON.parse(base64UrlDecode(parts[1]));
}
// ====================================
// 토큰 남은 유효 시간
// Bearer Token의 남은 시간을 조회하는 함수
// ====================================
function getTokenTimeRemaining(accessToken) {
try {
// 서명 검증 없이 토큰 페이로드를 디코딩합니다.
// 만료 시간(exp)만 빠르게 확인하기 위함입니다.
const decodedToken = decodeJwtPayload(accessToken);
// 'exp' 클레임은 Unix 타임스탬프(에폭 이후 초 단위)입니다.
const expirationTimestamp = decodedToken.exp;
if (expirationTimestamp == null) {
// 토큰에 'exp' 클레임이 없으면 유효성 판단이 어렵습니다.
// 안전하게 invalid로 처리합니다.
console.log("경고: 토큰에 'exp' 클레임이 없습니다.");
return null;
}
// 현재 시간(UTC 기준으로 계산하려면 Date.now()로 충분)
const currentTimeMs = Date.now();
// 만료 시간을 ms로 변환
const expirationTimeMs = expirationTimestamp * 1000;
// 남은 시간 계산(ms)
const timeRemainingMs = expirationTimeMs - currentTimeMs;
// 남은 시간이 양수일 때만 반환
return timeRemainingMs > 0 ? timeRemainingMs : null;
} catch (e) {
// 토큰이 잘못된 형식인 경우 처리
console.log(`오류: 유효하지 않은 토큰 - ${e.message}`);
return null;
}
}
// ====================================
// 토큰 생성 시간
// Bearer Token의 생성 시간을 조회하는 함수
// ====================================
function getTokenCreationTime(accessToken) {
try {
// 서명 검증 없이 토큰 페이로드를 디코딩합니다.
// 생성 시간(iat) 확인용입니다.
const decodedToken = decodeJwtPayload(accessToken);
// 'iat' 클레임은 Unix 타임스탬프(에폭 이후 초 단위)입니다.
const creationTimestamp = decodedToken.iat;
if (creationTimestamp == null) {
// 토큰에 'iat' 클레임이 없으면 생성 시간 확인 불가
console.log("경고: 토큰에 'iat' 클레임이 없습니다.");
return null;
}
// 생성 시간을 Date 객체로 변환(UTC)
const creationTime = new Date(creationTimestamp * 1000);
return creationTime;
} catch (e) {
// 토큰이 잘못된 형식인 경우 처리
console.log(`오류: 유효하지 않은 토큰 - ${e.message}`);
return null;
}
}
// ====================================
// 메인 실행부
// ====================================
(async () => {
// --- 토큰 발급을 위한 설정 ---
const hostname = "api.statsperform.com";
const apikey = ""; // 실제 dev apikey로 교체
const clientId = ""; // 실제 dev client_id로 교체
const clientSecret = ""; // 실제 dev client_secret로 교체
// --- OAuth 토큰 발급 ---
console.log("--- OAuth 토큰 발급 시도 ---");
const accessToken = await getOauthToken(hostname, apikey, clientId, clientSecret);
if (!accessToken) {
console.log("토큰 발급 실패");
return;
}
console.log("--- 토큰 생성 시간 확인 ---");
const creationTime = getTokenCreationTime(accessToken);
console.log("토큰 생성 시간(UTC):", creationTime ? creationTime.toISOString() : null);
console.log("--- 토큰 유효 시간 확인 ---");
const timeLeftValidMs = getTokenTimeRemaining(accessToken);
if (timeLeftValidMs != null) {
console.log(`남은 유효 시간: ${Math.floor(timeLeftValidMs / 1000)} 초`);
} else {
console.log("토큰이 만료되었거나 유효하지 않습니다.");
}
})();Python
# ====================================
# 설정 및 라이브러리 임포트
# ====================================
import requests
import json
import time
import jwt
from datetime import datetime, timedelta
!pip install pyjwt
# ====================================
# OAUTH 토큰 생성기
# Bearer Token을 얻기 위한 POST 함수
# ====================================
def get_oauth_token(hostname, apikey, client_id, client_secret):
request_url = f"https://{hostname}/realms/apigw/protocol/openid-connect/token?apikey={apikey}"
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
try:
response = requests.post(request_url, headers=headers, data=data)
response.raise_for_status() # HTTP 오류(4xx 또는 5xx)가 발생하면 예외 발생
response_json = response.json()
access_token = response_json.get('access_token')
if access_token:
print("Successfully retrieved OAuth token!")
print(f"Access Token: {access_token}")
print(f"Expires In: {response_json.get('expires_in')} seconds")
return access_token
else:
print("Error: 'access_token' not found in the response.")
print(f"Full response: {response_json}")
return None
except requests.exceptions.RequestException as e:
print(f"An error occurred during the request: {e}")
return None
# ====================================
# 토큰 남은 유효 시간
# Bearer Token의 남은 시간을 조회하는 함수
# ====================================
def get_token_time_remaining(access_token: str) -> timedelta | None:
"""
JWT가 만료되기까지 남은 시간을 계산합니다.
Args:
access_token: JWT 문자열
Returns:
토큰이 유효하면 남은 시간을 timedelta 객체로 반환,
만료되었거나 유효하지 않거나 만료 정보가 없으면 None 반환
"""
try:
# 서명 검증 없이 토큰 페이로드를 디코딩합니다.
# 만료 시간(exp)만 빠르게 확인하기 위함입니다.
decoded_token = jwt.decode(access_token, options={"verify_signature": False})
# 'exp' 클레임은 Unix 타임스탬프(에폭 이후 초 단위)입니다.
expiration_timestamp = decoded_token.get("exp")
if expiration_timestamp is None:
# 토큰에 'exp' 클레임이 없으면 유효성 판단이 어렵습니다.
# 안전하게 invalid로 처리합니다.
print("Warning: Token has no 'exp' claim.")
return None
# 현재 UTC 시간 가져오기
current_time = datetime.utcnow()
# 만료 타임스탬프를 datetime 객체로 변환
expiration_time = datetime.utcfromtimestamp(expiration_timestamp)
# 남은 시간 계산
time_remaining = expiration_time - current_time
# 남은 시간이 양수일 때만 반환
if time_remaining.total_seconds() > 0:
return time_remaining
else:
return None
except jwt.InvalidTokenError as e:
# 토큰이 잘못된 형식인 경우 처리
print(f"Error: Invalid token - {e}")
return None
except Exception as e:
# 기타 예외 처리
print(f"An unexpected error occurred: {e}")
return None
# ====================================
# 토큰 생성 시간
# Bearer Token의 생성 시간을 조회하는 함수
# ====================================
def get_token_creation_time(access_token: str) -> timedelta | None:
"""
토큰이 생성된 시간을 계산합니다.
Args:
access_token: JWT 문자열
Returns:
유효한 토큰일 경우 생성 시간을 반환
"""
try:
# 서명 검증 없이 토큰 페이로드를 디코딩합니다.
# 생성 시간(iat) 확인용입니다.
decoded_token = jwt.decode(access_token, options={"verify_signature": False})
# 'iat' 클레임은 Unix 타임스탬프(에폭 이후 초 단위)입니다.
creation_timestamp = decoded_token.get("iat")
if creation_timestamp is None:
# 토큰에 'iat' 클레임이 없으면 생성 시간 확인 불가
print("Warning: Token has no 'iat' claim.")
return None
# 현재 UTC 시간
current_time = datetime.utcnow()
# 생성 타임스탬프를 datetime 객체로 변환
creation_time = datetime.utcfromtimestamp(creation_timestamp)
except jwt.InvalidTokenError as e:
# 토큰 형식 오류 처리
print(f"Error: Invalid token - {e}")
return None
except Exception as e:
# 기타 예외 처리
print(f"An unexpected error occurred: {e}")
return None
return creation_time
# ====================================
# 메인 실행부
# ====================================
if __name__ == "__main__":
# --- 토큰 발급을 위한 설정 ---
hostname = "api.statsperform.com"
apikey = "" # 실제 dev apikey로 교체
client_id = "" # 실제 dev client_id로 교체
client_secret = "" # 실제 dev client_secret로 교체
# --- OAuth 토큰 발급 ---
print("--- Attempting to get OAuth token ---")
access_token = get_oauth_token(
hostname,
apikey,
client_id,
client_secret,
)
print("--- Checking Oauth token creation time ---")
creation_time = get_token_creation_time(access_token)
print(f"Token Creation Time: {creation_time}")
print("--- Checking Oauth token validity ---")
# 토큰 유효 시간 확인
time_left_valid = get_token_time_remaining(access_token)
if time_left_valid:
print(f"Time remaining for valid token: {time_left_valid}")
else:
print("Valid token has expired or is invalid.")C#
// ====================================
// 설정 및 라이브러리 임포트
// ====================================
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Text.Json;
using System.IdentityModel.Tokens.Jwt;
// ====================================
// OAUTH 토큰 생성기
// Bearer Token을 얻기 위한 POST 함수
// ====================================
static async Task<string?> GetOAuthToken(string hostname, string apikey, string clientId, string clientSecret)
{
var requestUrl = $"https://{hostname}/realms/apigw/protocol/openid-connect/token?apikey={apikey}";
using var http = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = clientId,
["client_secret"] = clientSecret
});
try
{
var response = await http.PostAsync(requestUrl, content);
response.EnsureSuccessStatusCode(); // HTTP 오류(4xx 또는 5xx)가 발생하면 예외 발생
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("access_token", out var tokenEl))
{
var accessToken = tokenEl.GetString();
Console.WriteLine("OAuth 토큰을 성공적으로 가져왔습니다.");
Console.WriteLine($"Access Token: {accessToken}");
if (doc.RootElement.TryGetProperty("expires_in", out var expEl))
Console.WriteLine($"Expires In: {expEl.GetInt32()} 초");
return accessToken;
}
else
{
Console.WriteLine("오류: 응답에 'access_token'이 없습니다.");
Console.WriteLine($"전체 응답: {json}");
return null;
}
}
catch (Exception e)
{
Console.WriteLine($"요청 중 오류 발생: {e.Message}");
return null;
}
}
// ====================================
// 토큰 남은 유효 시간
// Bearer Token의 남은 시간을 조회하는 함수
// ====================================
static TimeSpan? GetTokenTimeRemaining(string accessToken)
{
try
{
// 서명 검증 없이 토큰 페이로드를 디코딩합니다.
// 만료 시간(exp)만 빠르게 확인하기 위함입니다.
var handler = new JwtSecurityTokenHandler();
var jwt = handler.ReadJwtToken(accessToken);
// 'exp' 클레임은 Unix 타임스탬프(에폭 이후 초 단위)입니다.
if (!jwt.Payload.TryGetValue("exp", out var expObj))
{
Console.WriteLine("경고: 토큰에 'exp' 클레임이 없습니다.");
return null;
}
var expirationTime = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(expObj)).UtcDateTime;
var currentTime = DateTime.UtcNow;
var timeRemaining = expirationTime - currentTime;
// 남은 시간이 양수일 때만 반환
return timeRemaining.TotalSeconds > 0 ? timeRemaining : null;
}
catch (Exception e)
{
Console.WriteLine($"토큰 처리 오류: {e.Message}");
return null;
}
}
// ====================================
// 토큰 생성 시간
// Bearer Token의 생성 시간을 조회하는 함수
// ====================================
static DateTime? GetTokenCreationTime(string accessToken)
{
try
{
// 서명 검증 없이 토큰 페이로드를 디코딩합니다.
// 생성 시간(iat) 확인용입니다.
var handler = new JwtSecurityTokenHandler();
var jwt = handler.ReadJwtToken(accessToken);
// 'iat' 클레임은 Unix 타임스탬프(에폭 이후 초 단위)입니다.
if (!jwt.Payload.TryGetValue("iat", out var iatObj))
{
Console.WriteLine("경고: 토큰에 'iat' 클레임이 없습니다.");
return null;
}
return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(iatObj)).UtcDateTime;
}
catch (Exception e)
{
Console.WriteLine($"토큰 처리 오류: {e.Message}");
return null;
}
}
// ====================================
// 메인 실행부
// ====================================
static async Task Main()
{
var hostname = "api.statsperform.com";
var apikey = ""; // 실제 apikey로 교체
var clientId = ""; // 실제 client_id로 교체
var clientSecret = ""; // 실제 client_secret로 교체
Console.WriteLine("--- OAuth 토큰 발급 시도 ---");
var accessToken = await GetOAuthToken(hostname, apikey, clientId, clientSecret);
if (string.IsNullOrWhiteSpace(accessToken))
{
Console.WriteLine("토큰 발급 실패");
return;
}
Console.WriteLine("--- 토큰 생성 시간 확인 ---");
var creationTime = GetTokenCreationTime(accessToken);
Console.WriteLine($"토큰 생성 시간(UTC): {creationTime}");
Console.WriteLine("--- 토큰 유효 시간 확인 ---");
var remaining = GetTokenTimeRemaining(accessToken);
if (remaining != null)
Console.WriteLine($"남은 유효 시간: {remaining}");
else
Console.WriteLine("토큰이 만료되었거나 유효하지 않습니다.");
}PHP
<?php
// ====================================
// 설정 및 라이브러리 임포트
// ====================================
// PHP 기본 함수(curl, json_decode 등)만 사용합니다.
// ====================================
// OAUTH 토큰 생성기
// Bearer Token을 얻기 위한 POST 함수
// ====================================
function get_oauth_token($hostname, $apikey, $client_id, $client_secret) {
$request_url = "https://{$hostname}/realms/apigw/protocol/openid-connect/token?apikey={$apikey}";
$headers = ["Content-Type: application/x-www-form-urlencoded"];
$data = http_build_query([
"grant_type" => "client_credentials",
"client_id" => $client_id,
"client_secret" => $client_secret
]);
$ch = curl_init($request_url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($response === false) {
echo "요청 중 오류 발생: {$err}\n";
return null;
}
// HTTP 오류(4xx 또는 5xx)가 발생하면 예외처럼 처리
if ($http_code < 200 || $http_code >= 300) {
echo "HTTP 오류 발생: {$http_code}\n";
echo "응답: {$response}\n";
return null;
}
$response_json = json_decode($response, true);
$access_token = $response_json["access_token"] ?? null;
if ($access_token) {
echo "OAuth 토큰을 성공적으로 가져왔습니다.\n";
echo "Access Token: {$access_token}\n";
echo "Expires In: " . ($response_json["expires_in"] ?? "unknown") . " 초\n";
return $access_token;
} else {
echo "오류: 응답에 'access_token'이 없습니다.\n";
echo "전체 응답: {$response}\n";
return null;
}
}
// ====================================
// (JWT) Base64URL 디코딩 유틸
// ====================================
function base64url_decode($data) {
$data = strtr($data, '-_', '+/');
$pad = strlen($data) % 4;
if ($pad) $data .= str_repeat('=', 4 - $pad);
return base64_decode($data);
}
// ====================================
// 서명 검증 없이 JWT 페이로드만 디코딩
// ====================================
function decode_jwt_payload($access_token) {
$parts = explode('.', $access_token);
if (count($parts) < 2) {
throw new Exception("잘못된 JWT 형식입니다.");
}
$payload_json = base64url_decode($parts[1]);
$payload = json_decode($payload_json, true);
if (!is_array($payload)) {
throw new Exception("JWT 페이로드 디코딩 실패");
}
return $payload;
}
// ====================================
// 토큰 남은 유효 시간
// Bearer Token의 남은 시간을 조회하는 함수
// ====================================
function get_token_time_remaining($access_token) {
try {
// 서명 검증 없이 토큰 페이로드를 디코딩합니다.
// 만료 시간(exp)만 빠르게 확인하기 위함입니다.
$decoded_token = decode_jwt_payload($access_token);
// 'exp' 클레임은 Unix 타임스탬프(에폭 이후 초 단위)입니다.
$expiration_timestamp = $decoded_token["exp"] ?? null;
if ($expiration_timestamp === null) {
// 토큰에 'exp' 클레임이 없으면 유효성 판단이 어렵습니다.
// 안전하게 invalid로 처리합니다.
echo "경고: 토큰에 'exp' 클레임이 없습니다.\n";
return null;
}
// 현재 시간(Unix seconds)
$current_time = time();
// 남은 시간 계산(초)
$time_remaining = $expiration_timestamp - $current_time;
// 남은 시간이 양수일 때만 반환
return ($time_remaining > 0) ? $time_remaining : null;
} catch (Exception $e) {
// 토큰이 잘못된 형식인 경우 처리
echo "오류: 유효하지 않은 토큰 - {$e->getMessage()}\n";
return null;
}
}
// ====================================
// 토큰 생성 시간
// Bearer Token의 생성 시간을 조회하는 함수
// ====================================
function get_token_creation_time($access_token) {
try {
// 서명 검증 없이 토큰 페이로드를 디코딩합니다.
// 생성 시간(iat) 확인용입니다.
$decoded_token = decode_jwt_payload($access_token);
// 'iat' 클레임은 Unix 타임스탬프(에폭 이후 초 단위)입니다.
$creation_timestamp = $decoded_token["iat"] ?? null;
if ($creation_timestamp === null) {
// 토큰에 'iat' 클레임이 없으면 생성 시간 확인 불가
echo "경고: 토큰에 'iat' 클레임이 없습니다.\n";
return null;
}
// 생성 시간을 DateTimeImmutable(UTC)로 변환
$creation_time = (new DateTimeImmutable("@{$creation_timestamp}"))->setTimezone(new DateTimeZone("UTC"));
return $creation_time;
} catch (Exception $e) {
// 토큰이 잘못된 형식인 경우 처리
echo "오류: 유효하지 않은 토큰 - {$e->getMessage()}\n";
return null;
}
}
// ====================================
// 메인 실행부
// ====================================
// --- 토큰 발급을 위한 설정 ---
$hostname = "api.statsperform.com";
$apikey = ""; // 실제 dev apikey로 교체
$client_id = ""; // 실제 dev client_id로 교체
$client_secret = ""; // 실제 dev client_secret로 교체
// --- OAuth 토큰 발급 ---
echo "--- OAuth 토큰 발급 시도 ---\n";
$access_token = get_oauth_token($hostname, $apikey, $client_id, $client_secret);
if (!$access_token) {
echo "토큰 발급 실패\n";
exit(1);
}
echo "--- 토큰 생성 시간 확인 ---\n";
$creation_time = get_token_creation_time($access_token);
echo "토큰 생성 시간(UTC): " . ($creation_time ? $creation_time->format(DateTimeInterface::ATOM) : "null") . "\n";
echo "--- 토큰 유효 시간 확인 ---\n";
$time_left_valid = get_token_time_remaining($access_token);
if ($time_left_valid !== null) {
echo "남은 유효 시간: {$time_left_valid} 초\n";
} else {
echo "토큰이 만료되었거나 유효하지 않습니다.\n";
}에러 코드
| HTTP 상태 코드 | 설명 |
|---|---|
| 200 | OK |
| 304 | Not modified |
| 401 | Developer inactive 세션이 종료되었을 수 있습니다. 예를 들어 서명이 더 이상 유효하지 않아 새 서명이 필요하거나, I/O 문서를 사용 중인 경우 로그인 후 다시 시도해야 합니다. 모든 경우에 새 서명을 생성하고 새 호출을 하기 전에 API 키가 올바른지 확인하십시오. |
| 401 | Unauthorized/Not Authorized 권한이 부족합니다. 구독 범위 외의 리소스에 액세스하려고 시도했거나, API 키 또는 시크릿이 잘못되었거나, 고유 서명이 더 이상 유효하지 않아 새 서명이 필요할 수 있습니다. API 호출에는 반드시 고유 서명을 포함해야 합니다. 서명을 하드코딩하면 만료되므로 사용하지 마십시오. 인증 가이드에 설명된 대로 애플리케이션은 API 호출에 사용할 새 서명을 정기적으로 생성해야 합니다. 모든 경우에 새 서명을 생성하고 새 API 호출을 하기 전에 API 키가 올바른지 확인하십시오. 서명은 SHA256 해시로 생성해야 합니다. |
| 401 | Waiting 계정이 설정되었지만 아직 활성화되지 않았습니다. 이 오류가 계속 발생하는 경우 계정 관리자에게 문의하여 계정을 활성화하세요. |
| 403 | Forbidden 접근이 금지되었습니다. |
| 403 | Client exceeded queries limit 클라이언트에게 허용된 쿼리수가 초과되었습니다. |
| 403 | Timestamp is invalid 요청한 타임스탬프가 잘 못 되었습니다. |
| 403 | Service requires SSL 서비스 요청시 SSL이 필요합니다. |
| 403 | Invalid API key CID pair 잘못된 API 키 CID 쌍입니다. |
| 403 | Invalid API plan 잘못된 API 계획입니다. |
| 403 | CORS origin deniedCORS 위반 에러입니다. |
| 403 | Developer Over QPS 각 API 계정에는 초당 호출 횟수 제한이 있습니다. 이 제한을 초과하면 이후 API 호출은 실패하고 오류가 반환됩니다. 다음 초에 다시 호출하면 정상적으로 처리됩니다. 이 문제를 방지하는 가장 쉬운 방법은 이전 호출이 완료된 후에 다음 호출이 실행되도록 설계하는 것입니다. |
| 403 | Developer Over Limit 각 API 계정에는 일일/월간 API 호출 횟수 제한이 있습니다. 제한 횟수가 초과되면 해당 제한이 해제될 때까지 API 호출이 계속 실패하고 이 오류가 발생합니다. 담당 계정 관리자에게 문의하여 도움을 받으세요. |
| 404 | Data not found / Not found 사용 가능한 데이터가 없습니다(데이터가 실제로 존재하지 않을 수 있습니다). 이러한 현상은 여러 가지 이유로 발생할 수 있으며, 몇 가지 예는 다음과 같습니다.
|
| 405 | Mehtod Not Allowed 허락되지 않은 HTTP 메서드 입니다. |
| 500 | Internal server error |
| 501 | Not implemented |
| 502 | Bad gateway |
| 503 | Service unavailable |
| 504 | Gateway timeout |
| 596 | Invalid preflight CORS request Origin 헤더가 누락되었거나 요청의 HTTP 메서드가 OPTIONS가 아닙니다. |
| 596 | Service Not Found 다음과 같은 상황에서 이 오류 메시지가 표시됩니다.
|