인증
SMSBAT ChatHub API는 회사 토큰과 운영자 토큰이 포함된 2단계 JWT 토큰 기반 인증 시스템을 사용합니다.
인증 흐름
graph TD
A[Login Credentials] --> B[Get Company Token]
B --> C[Use Company Token]
C --> D[Get Operator Token]
D --> E[Use Operator Token]
E --> F[Validate Token]
회사 토큰
회사 토큰은 ChatHub API에 대한 조직 수준 액세스를 제공합니다.
회사 토큰 받기
엔드포인트: POST /api/company/get-token
요청:
curl -X POST https://chatapi.smsbat.com/api/company/get-token \
-H "Content-Type: application/json" \
-d '{
"login": "your-company-login",
"password": "your-company-password"
}'
요청 본문:
응답:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
응답은 JWT 토큰 문자열입니다.
회사 토큰 사용
다음 두 가지 방법 중 하나를 사용하여 API 요청에 회사 토큰을 포함합니다.
방법 1: 인증 헤더(권장)
curl -X GET https://chatapi.smsbat.com/api/company/organization \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
방법 2: X-Authorization-Key 헤더
curl -X GET https://chatapi.smsbat.com/api/company/organization \
-H "X-Authorization-Key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
운영자 토큰
운영자 토큰은 조직 내의 개별 운영자에게 사용자별 액세스를 제공합니다.
운영자 토큰 받기
엔드포인트: POST /api/operator/get-token
요청:
curl -X POST https://chatapi.smsbat.com/api/operator/get-token \
-H "Authorization: Bearer {company-token}" \
-H "Content-Type: application/json" \
-d '{
"id": 123,
"expiresAt": "2025-12-31T23:59:59Z"
}'
요청 본문:
매개변수:
| 매개변수 | 유형 | 필수 | 설명 |
|---|---|---|---|
id |
정수 | 예 | 운영자 ID |
만료일 |
문자열(ISO 8601) | 예 | 토큰 만료 날짜 및 시간(지금부터 최대 24시간) |
중요: 최대 토큰 수명은 24시간입니다. expiresAt 매개변수는 향후 24시간을 초과할 수 없습니다.
응답:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyYXRvcl9pZCI6MTIzLCJleHAiOjE3Mzc0MTI3OTl9.example_signature"
운영자 토큰 사용
API 요청에 연산자 토큰을 포함합니다.
토큰 검증
토큰을 사용하기 전에 토큰이 아직 유효한지 확인하세요.
엔드포인트: POST /api/operator/validate-token
요청:
curl -X POST https://chatapi.smsbat.com/api/operator/validate-token \
-H "Authorization: Bearer {company-token}" \
-H "Content-Type: application/json" \
-d '{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}'
요청 본문:
응답(유효한 토큰):
{
"isValid": true,
"operatorId": 123,
"clientId": 0,
"expiresAt": "2025-12-31T23:59:59Z",
"error": null
}
응답(잘못된 토큰):
토큰 만료
회사 토큰
- API에 명시적인 만료가 없습니다.
- 토큰 수명주기 정책에 대해서는 계정 관리자에게 문의하세요.
- 보안을 위해 주기적으로 토큰을 순환시킵니다.
운영자 토큰
- 토큰 요청 시 만료 설정(
expiresAt매개변수) - 사용하기 전에 토큰을 검증하세요
- 만료되기 전에 새 토큰을 요청하세요.
구현 예
파이썬
import requests
from datetime import datetime, timedelta
class ChatHubAuth:
def __init__(self, base_url):
self.base_url = base_url
self.company_token = None
self.operator_tokens = {}
def get_company_token(self, login, password):
"""Get company authentication token"""
response = requests.post(
f"{self.base_url}/api/company/get-token",
json={"login": login, "password": password}
)
response.raise_for_status()
self.company_token = response.json()
return self.company_token
def get_operator_token(self, operator_id, expires_days=30):
"""Get operator token with expiration"""
expires_at = (
datetime.utcnow() + timedelta(days=expires_days)
).isoformat() + "Z"
response = requests.post(
f"{self.base_url}/api/operator/get-token",
headers={"Authorization": f"Bearer {self.company_token}"},
json={"id": operator_id, "expiresAt": expires_at}
)
response.raise_for_status()
token = response.json()
self.operator_tokens[operator_id] = token
return token
def validate_token(self, token):
"""Validate if token is still valid"""
response = requests.post(
f"{self.base_url}/api/operator/validate-token",
headers={"Authorization": f"Bearer {self.company_token}"},
json={"token": token}
)
response.raise_for_status()
return response.json()
# Usage
auth = ChatHubAuth("https://chatapi.smsbat.com")
# Get company token
company_token = auth.get_company_token("login", "password")
# Get operator token
operator_token = auth.get_operator_token(operator_id=123, expires_days=30)
# Validate token
is_valid = auth.validate_token(operator_token)
print(f"Token valid: {is_valid['valid']}")
자바스크립트(Node.js)
const axios = require('axios');
class ChatHubAuth {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.companyToken = null;
this.operatorTokens = {};
}
async getCompanyToken(login, password) {
const response = await axios.post(
`${this.baseUrl}/api/company/get-token`,
{ login, password }
);
this.companyToken = response.data;
return this.companyToken;
}
async getOperatorToken(operatorId, expiresDays = 30) {
const expiresAt = new Date(
Date.now() + expiresDays * 24 * 60 * 60 * 1000
).toISOString();
const response = await axios.post(
`${this.baseUrl}/api/operator/get-token`,
{ id: operatorId, expiresAt },
{
headers: {
Authorization: `Bearer ${this.companyToken}`
}
}
);
const token = response.data;
this.operatorTokens[operatorId] = token;
return token;
}
async validateToken(token) {
const response = await axios.post(
`${this.baseUrl}/api/operator/validate-token`,
{ token },
{
headers: {
Authorization: `Bearer ${this.companyToken}`
}
}
);
return response.data;
}
}
// Usage
const auth = new ChatHubAuth('https://chatapi.smsbat.com');
async function authenticate() {
// Get company token
const companyToken = await auth.getCompanyToken('login', 'password');
// Get operator token
const operatorToken = await auth.getOperatorToken(123, 30);
// Validate token
const validation = await auth.validateToken(operatorToken);
console.log('Token valid:', validation.isValid);
}
authenticate();
PHP
<?php
class ChatHubAuth {
private $baseUrl;
private $companyToken;
private $operatorTokens = [];
public function __construct($baseUrl) {
$this->baseUrl = $baseUrl;
}
public function getCompanyToken($login, $password) {
$ch = curl_init($this->baseUrl . '/api/company/get-token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'login' => $login,
'password' => $password
]));
$response = curl_exec($ch);
curl_close($ch);
$this->companyToken = json_decode($response);
return $this->companyToken;
}
public function getOperatorToken($operatorId, $expiresDays = 30) {
$expiresAt = date(
'Y-m-d\TH:i:s\Z',
time() + ($expiresDays * 24 * 60 * 60)
);
$ch = curl_init($this->baseUrl . '/api/operator/get-token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->companyToken
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'id' => $operatorId,
'expiresAt' => $expiresAt
]));
$response = curl_exec($ch);
curl_close($ch);
$token = json_decode($response);
$this->operatorTokens[$operatorId] = $token;
return $token;
}
public function validateToken($token) {
$ch = curl_init($this->baseUrl . '/api/operator/validate-token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->companyToken
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'token' => $token
]));
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
// Usage
$auth = new ChatHubAuth('https://chatapi.smsbat.com');
// Get company token
$companyToken = $auth->getCompanyToken('login', 'password');
// Get operator token
$operatorToken = $auth->getOperatorToken(123, 30);
// Validate token
$validation = $auth->validateToken($operatorToken);
echo "Token valid: " . ($validation['isValid'] ? 'Yes' : 'No');
모범 사례
토큰 보관
- ✅ 토큰을 안전하게 저장합니다(암호화된 데이터베이스, 비밀 관리자)
- ✅ 버전 관리에 토큰을 커밋하지 마세요.
- ✅ 자격 증명에 환경 변수를 사용하세요
- ❌ 토큰을 일반 텍스트로 저장하지 마세요.
- ❌ 클라이언트 측 코드에 토큰을 노출하지 마세요.
토큰 순환
- 회사 토큰을 주기적으로 순환(3~6개월마다)
- 운영자 토큰에 대한 합리적인 만료 설정(7~30일)
- 만료 전 자동 토큰 새로 고침 구현
- 운영자가 떠날 때 토큰을 취소합니다.
오류 처리
async function authenticateWithRetry(login, password, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await getCompanyToken(login, password);
} catch (error) {
if (error.response?.status === 401) {
throw new Error('Invalid credentials');
}
if (i === retries - 1) throw error;
// Wait before retry
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
}
}
}
토큰 검증
중요한 작업 전에는 항상 토큰의 유효성을 검사하세요.
async function performSecureOperation(token, operation) {
// Validate token first
const validation = await validateToken(token);
if (!validation.isValid) {
throw new Error('Token expired or invalid');
}
// Proceed with operation
return await operation();
}
보안 고려 사항
HTTPS 전용
인증 요청을 보낼 때 항상 HTTPS를 사용하십시오.
// ✅ Correct
const baseUrl = 'https://chatapi.smsbat.com';
// ❌ Wrong - never use HTTP for authentication
const baseUrl = 'http://api.chathub.smsbat.com';
토큰 범위
각 작업에 적절한 토큰을 사용하세요.
- 회사 토큰: 조직 관리, 운영자 생성
- 운영자 토큰: 채팅 운영, 메시지 처리
속도 제한
인증 요청에 대한 속도 제한을 구현합니다.
class RateLimitedAuth {
constructor() {
this.lastRequest = 0;
this.minInterval = 1000; // 1 second between requests
}
async getToken(login, password) {
const now = Date.now();
const timeSinceLastRequest = now - this.lastRequest;
if (timeSinceLastRequest < this.minInterval) {
await new Promise(resolve =>
setTimeout(resolve, this.minInterval - timeSinceLastRequest)
);
}
this.lastRequest = Date.now();
return await makeAuthRequest(login, password);
}
}
문제 해결
401 승인되지 않음
- 자격 증명이 올바른지 확인하세요.
- 수표 토큰이 만료되지 않았습니다.
- 요청 헤더에 토큰이 포함되어 있는지 확인하세요.
- 토큰 형식 검증
403 금지됨
- 토큰에 필수 권한이 있는지 확인하세요.
- 올바른 토큰 유형을 사용하고 있는지 확인하세요(회사 vs. 운영자)
- 토큰이 취소되지 않았는지 확인하세요.
토큰 만료됨
- 새로운 토큰 요청
- 자동 토큰 새로 고침 구현
- 적절한 만료 시간 설정