Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Upstash Redis와 Cloudflare Workers로 안전한 API 키 생성기 만들기: 단계별 완벽 가이드

API 키는 여러분의 서비스로 통하는 '현관 열쇠'와 같습니다. 사용자가 서비스에 접근할 수 있게 해주면서 동시에 보안을 지켜주죠. 이 글에서는 빠르고 서버리스 방식의 데이터 저장을 위한 Upstash Redis와 엣지(Edge)에서 요청을 처리하는 Cloudflare Workers를 활용해, 간단하면서도 안전한 API 키 생성기를 만드는 과정을 단계별로 소개합니다. 새로운 서비스를 구축하든 기존 앱에 키 기능을 추가하든, API 키를 생성하고 저장하고 검증하는 방법을 배워 서비스가 원활하고 효율적으로 운영되도록 만들 수 있습니다.

API 키란 무엇인가?

API 키는 여러분의 API에 접근하려는 사용자나 애플리케이션을 식별하고 인증하는 고유한 코드입니다. 개인 출입증과 비슷하게 생각하면 됩니다. 누군가 서비스를 이용하려면 자신이 허가된 사용자임을 증명하는 이 '키'를 제시해야 하죠. API 키를 활용하면 리소스에 접근할 수 있는 대상을 통제할 수 있으며, 사용량 추적, 요청 한도 강제, 무단 접근 차단 등에 널리 사용됩니다. API 접근을 관리하고 데이터를 안전하게 지키는 가장 간단하면서도 효과적인 방법 중 하나입니다.

우리가 만들 것

이 가이드에서는 두 가지 핵심 기능을 제공하는 API 키 생성기를 만듭니다.

  1. 커스텀 설정 옵션으로 새로운 API 키 생성
  2. API 키 검증 및 메타데이터 조회

주요 기능은 다음과 같습니다.

  • 커스터마이징 가능한 키 접두사(prefix)
  • 만료일 설정
  • 요청 속도 제한(Rate Limiting)
  • 메타데이터 저장
  • 소유자 식별

API 키 시스템 구조 미리보기

아래 다이어그램은 클라이언트, Cloudflare Worker, Upstash Redis 간의 상호작용을 통해 API 키를 생성하고 검증하는 흐름을 보여줍니다. 전체적인 그림을 머릿속에 그려두고, 이제 시스템 구축을 시작해 보겠습니다.

Upstash Redis와 Cloudflare Workers로 안전한 API 키 생성기 만들기: 단계별 완벽 가이드

사전 준비 사항

이 가이드를 따라 하려면 다음이 필요합니다.

  • Cloudflare Workers 계정
  • Upstash 계정
  • 로컬 머신에 설치된 Node.js

프로젝트 구조

프로젝트는 아래와 같은 구조로 구성됩니다.

folder-name/
├── src/
│ ├── config/
│ │ ├── generateApiKey.ts
│ │ └── schema-validation.ts
│ ├── lib/
│ │ └── ratelimit.ts
│ ├── routes/
│ │ ├── create.ts
│ │ └── verify.ts
│ ├── types/
│ │ └── api.ts
│ └── index.ts
├── package.json
└── wrangler.toml

1단계: 프로젝트 설정

먼저 프로젝트를 세팅하고 필요한 의존성을 설치합니다.

새 프로젝트 디렉터리 만들기

터미널을 열고 아래 명령어를 실행합니다.

mkdir keyflow
cd keyflow
npm init -y

의존성 설치

프로젝트에 필요한 패키지들을 설치합니다.

npm install hono @upstash/redis @upstash/ratelimit @hono/zod-validator zod wrangler

각 패키지의 역할은 다음과 같습니다.
@upstash/redis: 서버리스 환경용 Upstash Redis 클라이언트
@upstash/ratelimit: Upstash Redis 기반 요청 속도 제한 라이브러리
@hono/zod-validator: Hono용 요청 검증 미들웨어
zod: TypeScript 우선 스키마 검증 라이브러리
wrangler: Cloudflare Workers 개발 및 배포용 CLI 도구

Upstash Redis 설정

  1. Upstash 계정에 로그인한 후 새 Redis 데이터베이스를 생성합니다.

Upstash Redis와 Cloudflare Workers로 안전한 API 키 생성기 만들기: 단계별 완벽 가이드

  1. 생성이 완료되면 'REST API' 섹션으로 이동합니다.

Upstash Redis와 Cloudflare Workers로 안전한 API 키 생성기 만들기: 단계별 완벽 가이드

  1. .env 섹션에서 UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN 값을 복사합니다.

Cloudflare Workers 설정

프로젝트 루트에 wrangler.toml 파일을 만들고 아래 내용을 작성합니다.

name = "keyflow"
main = "src/index.ts"
compatibility_date = "2023-05-18"
 
[vars]
UPSTASH_REDIS_REST_URL = "your-redis-url"
UPSTASH_REDIS_REST_TOKEN = "your-redis-token"

"your-redis-url""your-redis-token"은 Upstash에서 복사한 값으로 교체하세요.

2단계: API 타입 정의

API 요청과 응답에 사용할 TypeScript 인터페이스를 정의합니다. 이 타입들은 애플리케이션 전반의 타입 안정성을 유지하는 데 큰 도움이 됩니다. src/types/api.ts 파일을 생성합니다.

export type CreateKeyRequest = {
 apiId: string;
 prefix?: string;
 byteLength?: number;
 ownerId?: string;
 name: string;
 meta?: Record<string, unknown>;
 expires?: number;
 ratelimit?: {
 type: "fast" | "consistent";
 limit: number;
 refillRate: number;
 refillInterval: number;
 };
};
 
export type CreateKeyResponse = {
 key: string;
 keyId: string;
};
 
export type VerifyKeyRequest = {
 key: string;
};
 
export type VerifyKeyResponse = {
 valid: boolean;
 ownerId?: string;
 meta?: Record<string, unknown>;
 expires?: number;
 ratelimit?: {
 limit: number;
 remaining: number;
 reset: number;
 };
};
 
export type Env = {
 UPSTASH_REDIS_REST_URL: string;
 UPSTASH_REDIS_REST_TOKEN: string;
};
 

3단계: API 키 생성 로직 구현

이제 API 키를 생성하는 유틸리티 함수를 만들겠습니다. src/config/generateApiKey.ts 파일을 생성합니다.

export function generateApiKey(
 prefix: string | undefined,
 byteLength: number,
): string {
 const randomBytes = crypto.getRandomValues(new Uint8Array(byteLength));
 const key = btoa(String.fromCharCode(...new Uint8Array(randomBytes)))
 .replace(/\+/g, "-")
 .replace(/\//g, "_")
 .replace(/=/g, "");
 return prefix ? `${prefix}_${key}` : key;
}
 

이 함수는 암호학적으로 안전한 난수 바이트를 사용해 API 키를 생성하고, base64로 인코딩한 뒤 URL에서 안전하게 사용할 수 있도록 변환합니다.

4단계: 요청 속도 제한(Rate Limiting) 구현

src/lib/ratelimit.ts 파일을 생성합니다.

import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis/cloudflare";
import type { Context, Next } from "hono";
import { env } from "hono/adapter";
import type { Env } from "../types/api";
 
// Rate limiting middleware
export async function rateLimitMiddleware(c: Context, next: Next) {
 const { UPSTASH_REDIS_REST_TOKEN, UPSTASH_REDIS_REST_URL } = env<Env>(c);
 
 const redis = new Redis({
 url: UPSTASH_REDIS_REST_URL,
 token: UPSTASH_REDIS_REST_TOKEN,
 });
 
 const ratelimit = new Ratelimit({
 redis: redis,
 limiter: Ratelimit.slidingWindow(5, "30 s"),
 });
 
 const ip = c.req.header("CF-Connecting-IP") || "127.0.0.1";
 const { success, limit, remaining, reset } = await ratelimit.limit(ip);
 
 if (!success) {
 return c.json({ error: "Rate limit exceeded" }, 429);
 }
 
 c.header("X-RateLimit-Limit", limit.toString());
 c.header("X-RateLimit-Remaining", remaining.toString());
 c.header("X-RateLimit-Reset", reset.toString());
 
 await next();
}

이 미들웨어는 30초 동안 IP당 최대 5개의 요청만 허용하는 슬라이딩 윈도우 방식의 속도 제한을 적용합니다.

5단계: API 라우트 생성

메인 애플리케이션 파일을 구성하고 API 라우트를 만듭니다. 이 파일은 Hono 애플리케이션에 두 개의 메인 라우트를 설정합니다. 새 API 키를 생성하는 /keys/create와 기존 키를 검증하는 /keys/verify이며, 세 개의 파일로 나누어 구현합니다.

1. API 키 생성 라우트 구현

src/routes/create.ts 파일을 생성합니다.

import { zValidator } from "@hono/zod-validator"
import { Redis } from "@upstash/redis/cloudflare"
import { Hono } from "hono"
import { generateApiKey } from "../config/generateApiKey"
import { createApiKeySchema } from "../config/schema-validation"
import type { CreateKeyRequest, CreateKeyResponse, Env } from "../types/api"
 
const create = new Hono<{
 Bindings: Env
}>()
 
create.post(
 "/create",
 zValidator("json", createApiKeySchema, (result, c) => {
 if (!result.success) {
 return c.text("Invalid!", 400)
 }
 }),
 async (c) => {
 // Redis 클라이언트 초기화
 const { UPSTASH_REDIS_REST_TOKEN, UPSTASH_REDIS_REST_URL } = c.env
 const redis = new Redis({
 url: UPSTASH_REDIS_REST_URL,
 token: UPSTASH_REDIS_REST_TOKEN,
 })
 
 const body = await c.req.json<CreateKeyRequest>()
 
 // 고유 식별자와 API 키 생성
 const keyId = crypto.randomUUID()
 const key = generateApiKey(body.prefix, body.byteLength || 16)
 
 const keyData = {
 ...body,
 key,
 keyId,
 createdAt: Date.now(),
 }
 
 const encodedKey = encodeURIComponent(key)
 
 try {
 // 키 데이터와 조회용 참조를 Redis에 저장
 await redis.set(`key:${keyId}`, JSON.stringify(keyData))
 await redis.set(`lookup:${encodedKey}`, keyId)
 
 return c.json<CreateKeyResponse>({ key, keyId })
 } catch (error) {
 console.error("Error in /keys/create:", error)
 return c.json({ error: "Internal Server Error" }, 500)
 }
 }
)
 
export default create

2. API 키 검증 라우트 구현

src/routes/verify.ts 파일을 생성합니다.

import { zValidator } from "@hono/zod-validator";
import { Redis } from "@upstash/redis/cloudflare";
import { Hono } from "hono";
import { verifyApiKeySchema } from "../config/schema-validation";
import type {
 CreateKeyRequest,
 Env,
 VerifyKeyRequest,
 VerifyKeyResponse,
} from "../types/api";
 
// 환경 변수 바인딩과 함께 Hono 앱 초기화
const verify = new Hono<{ Bindings: Env }>();
 
// API 키 검증용 POST 라우트 정의
verify.post(
 "/verify",
 // 요청 본문을 스키마로 검증
 zValidator("json", verifyApiKeySchema, (result, c) => {
 if (!result.success) {
 return c.text("Invalid!", 400); // 검증 실패 시 400 반환
 }
 }),
 async (c) => {
 // 환경 변수로 Redis 설정
 const { UPSTASH_REDIS_REST_TOKEN, UPSTASH_REDIS_REST_URL } = c.env;
 const redis = new Redis({
 url: UPSTASH_REDIS_REST_URL,
 token: UPSTASH_REDIS_REST_TOKEN,
 });
 
 const body = await c.req.json<VerifyKeyRequest>();
 if (!body.key) {
 return c.json({ error: "key is required" }, 400); // 요청 본문에 키 필수
 }
 
 const encodedKey = encodeURIComponent(body.key);
 const keyId = await redis.get<string>(`lookup:${encodedKey}`); // 인코딩된 키로 키 ID 조회
 
 if (!keyId) {
 return c.json<VerifyKeyResponse>({ valid: false }); // 키를 찾을 수 없음
 }
 
 const keyDataString = await redis.get<string>(`key:${keyId}`); // 키 ID로 키 데이터 조회
 
 if (!keyDataString || typeof keyDataString !== "string") {
 return c.json<VerifyKeyResponse>({ valid: false }); // 키 데이터 없음 또는 유효하지 않음
 }
 
 let keyData: CreateKeyRequest & {
 key: string;
 keyId: string;
 createdAt: number;
 };
 
 try {
 keyData = JSON.parse(keyDataString); // 키 데이터 파싱
 } catch (parseError) {
 // 파싱 오류 발생 시 잘못된 데이터 삭제 처리
 console.error("Key data parse error:", parseError);
 await Promise.all([
 redis.del(`key:${keyId}`),
 redis.del(`lookup:${encodedKey}`),
 ]);
 return c.json(
 {
 error: "Invalid key data in storage",
 details: parseError instanceof Error ? parseError.message : "Unknown parse error",
 valid: false,
 },
 500,
 );
 }
 
 // 키 만료 여부 확인
 if (keyData.expires && keyData.expires < Date.now()) {
 await Promise.all([
 redis.del(`key:${keyId}`),
 redis.del(`lookup:${encodedKey}`),
 ]);
 return c.json<VerifyKeyResponse>({ valid: false });
 }
 
 // 검증 결과와 메타데이터로 응답 구성
 const response: VerifyKeyResponse = {
 valid: true,
 ownerId: keyData.ownerId,
 meta: keyData.meta,
 expires: keyData.expires,
 };
 
 if (keyData.ratelimit) {
 response.ratelimit = {
 limit: keyData.ratelimit.limit,
 remaining: keyData.ratelimit.limit,
 reset: Date.now() + keyData.ratelimit.refillInterval,
 };
 }
 
 return c.json(response); // 검증 응답 반환
 },
);
 
export default verify;
 

3. 메인 파일 index.ts 구현

메인 파일 src/index.ts에서 create.ts, verify.ts, rateLimitMiddleWare를 임포트합니다.

import { Hono } from "hono";
import { rateLimitMiddleware } from "./lib/ratelimit";
import create from "./routes/create";
import verify from "./routes/verify";
import type { Env } from "./types/api";
 
const app = new Hono<{
 Bindings: Env;
}>().basePath("/keys");
 
app.use("*", rateLimitMiddleware);
 
// create 라우트와 verify 라우트 추가
app.route("/", create);
app.route("/", verify);
 
export default app;
 

6단계: 배포

완성된 애플리케이션을 Cloudflare Workers에 배포합니다.

  1. Wrangler CLI가 설치되어 있는지 확인합니다.

    npm install -g wrangler
    
  2. Cloudflare 계정으로 인증합니다.

    wrangler login
    
  3. Worker를 배포합니다.

    wrangler deploy
    

7단계: API 테스트하기

배포가 완료되었으니 새로 만든 API를 테스트해 보겠습니다.

새 API 키 생성하기

curl -X POST https://keyflow.<your-subdomain>.workers.dev/keys/create \
 -H "Content-Type: application/json" \
 -d '{
 "apiId": "my-api",
 "prefix": "prod",
 "name": "Production API Key",
 "expires": 1735689600000,
 "meta": {
 "environment": "production",
 "team": "backend"
 }
 }'

API 키 검증하기

curl -X POST https://keyflow.<your-subdomain>.workers.dev/keys/verify \
 -H "Content-Type: application/json" \
 -d '{
 "key": "prod_AbC123XyZ..."
 }'

<your-subdomain>은 본인의 Cloudflare Workers 서브도메인으로, prod_AbC123XyZ...는 create 엔드포인트에서 실제로 발급받은 키 값으로 교체하세요.

마무리

API 키 생성기를 구축하는 것은 애플리케이션의 API를 보호하고 서비스 접근 권한을 관리하는 데 필수적인 단계입니다. API 키를 생성, 검증, 관리할 수 있는 시스템을 갖추면 강력한 접근 제어 계층이 생겨 데이터를 안전하고 체계적으로 보호할 수 있습니다.

견고한 API 키 생성기의 핵심 요소를 정리하면 다음과 같습니다.

  1. 안전한 키 생성: 커스텀 접두사나 지정된 길이 같은 옵션을 포함한 고유하고 안전한 키는 각 키를 구별 가능하게 만들고 추측 공격도 어렵게 합니다.
  2. 검증과 만료 처리: 검증 로직과 만료일 설정을 추가하면 각 키가 정해진 기간 동안만 유효하게 되어, 필요에 따라 접근을 쉽게 통제하고 제한할 수 있습니다.
  3. 메타데이터와 속도 제한: 키마다 부가 정보를 저장하고 속도 제한을 설정하면 키 사용 현황을 모니터링하고 활동을 추적하며 API 남용을 방지할 수 있습니다.

Upstash Redis와 Cloudflare Workers 같은 도구를 활용하면 확장성이 뛰어나고 효율적으로 작동하는 서버리스 기반의 글로벌 분산형 키 관리 시스템을 손쉽게 구축할 수 있습니다.

이 기반 위에서 API 접근을 안전하고 관리하기 쉽게 유지할 수 있으며, 리소스가 잘 보호되고 모니터링하기 쉽다는 안정감을 얻게 됩니다.