TypeScriptμ μ μ νμ΄νμ νμ©νμ¬ κ²¬κ³ νκ³ μμ ν λμ§νΈ μλͺ μμ€ν μ ꡬμΆνλ λ°©λ²μ λν΄ μ¬λ μκ² λ€λ£Ήλλ€. νμ μμ ν¨ν΄μ μ¬μ©νμ¬ μ·¨μ½μ μ λ°©μ§νκ³ μΈμ¦μ κ°ννλ λ°©λ²μ λ°°μλλ€.
TypeScript λμ§νΈ μλͺ : μΈμ¦ νμ μμ μ±μ λν μ’ ν© κ°μ΄λ
μ΄μ°κ²°λ μΈκ³ κ²½μ μμ λμ§νΈ μ λ’°λ κΆκ·Ήμ μΈ ν΅νμ λλ€. κΈμ΅ κ±°λμμλΆν° 보μ ν΅μ , λ²μ ν¨λ ₯μ΄ μλ κ³μ½μ μ΄λ₯΄κΈ°κΉμ§, κ²μ¦ κ°λ₯νκ³ λ³μ‘° λ°©μ§ κΈ°λ₯μ΄ μλ λμ§νΈ μ μμ νμμ±μ΄ κ·Έ μ΄λ λλ³΄λ€ μ€μν΄μ‘μ΅λλ€. μ΄λ¬ν λμ§νΈ μ λ’°μ μ€μ¬μλ μΈμ¦, 무결μ±, λΆμΈ λ°©μ§λ₯Ό μ 곡νλ μνΈνμ κ²½μ΄λ‘μμΈ λμ§νΈ μλͺ μ΄ μμ΅λλ€. κ·Έλ¬λ μ΄λ¬ν 볡μ‘ν μνΈν κΈ°λ³Έ μμλ₯Ό ꡬννλ κ²μ μνμΌλ‘ κ°λ μ°¨ μμ΅λλ€. λ¨ νλμ μλͺ»λ λ³μ, μλͺ»λ λ°μ΄ν° νμ , λ―Έλ¬ν λ Όλ¦¬ μ€λ₯λ§μΌλ‘λ μ 체 보μ λͺ¨λΈμ΄ 침묡리μ μ½νλμ΄ μΉλͺ μ μΈ μ·¨μ½μ μ λ§λ€ μ μμ΅λλ€.
JavaScript μνκ³μμ μμ νλ κ°λ°μμκ²λ μ΄λ¬ν λμ μ΄ λμ± μ¦νλ©λλ€. μ΄ μΈμ΄μ λμ μ΄κ³ λμ¨ν νμ΄νμ μμ²λ μ μ°μ±μ μ 곡νμ§λ§, 보μ λ§₯λ½μμ νΉν μνν λ²κ·Έ κ³μΈ΅μ μ΄μ΄μ€λλ€. μνΈν ν€λ λ°μ΄ν° λ²νΌμ κ°μ΄ λ―Όκ°ν λ°μ΄ν°λ₯Ό μ λ¬ν λ, λ¨μν νμ κ°μ λ§μΌλ‘λ μμ ν μλͺ κ³Ό μΈλͺ¨μλ μλͺ μ ꡬλΆν μ μμ΅λλ€. λ°λ‘ μ¬κΈ°μ TypeScriptλ λ¨μν κ°λ° νΈμμ±μ λμ΄ νμμ μΈ λ³΄μ λκ΅¬λ‘ λ±μ₯ν©λλ€.
μ΄ μ’ ν© κ°μ΄λμμλ μΈμ¦ νμ μμ μ±μ΄λΌλ κ°λ μ νꡬν©λλ€. TypeScriptμ μ μ νμ μμ€ν μ μ¬μ©νμ¬ λμ§νΈ μλͺ ꡬνμ κ°ννκ³ , μ μ¬μ μΈ λ°νμ μ€λ₯μ μ§λ’°λ°μ΄μλ μ½λλ₯Ό μ»΄νμΌ νμ 보μ 보μ₯μ μμλ‘ λ³ννλ λ°©λ²μ μμΈν μ΄ν΄λ³΄κ² μ΅λλ€. κΈ°μ΄ κ°λ λΆν° μ€μ μ½λ μμ κΉμ§ μ΄λνλ©΄μ μ μΈκ³ μ¬μ©μλ₯Ό μν λμ± κ²¬κ³ νκ³ μ μ§ κ΄λ¦¬ κ°λ₯νλ©° λͺ λ°±ν μμ ν μΈμ¦ μμ€ν μ ꡬμΆνλ λ°©λ²μ μμ°ν κ²μ λλ€.
κΈ°μ΄: λμ§νΈ μλͺ μ λν λΉ λ₯Έ 볡μ΅
TypeScriptμ μν μ μμΈν μ΄ν΄λ³΄κΈ° μ μ, λμ§νΈ μλͺ μ΄ λ¬΄μμ΄λ©° μ΄λ»κ² μλνλμ§μ λν λͺ ννκ³ κ³΅μ λ μ΄ν΄λ₯Ό νλ¦½ν΄ λ΄ μλ€. μ€μΊν μκΈμ¨ μλͺ μ΄λ―Έμ§ κ·Έ μ΄μμ λλ€. μΈ κ°μ§ ν΅μ¬ κΈ°λ₯μ κΈ°λ°ν κ°λ ₯ν μνΈν λ©μ»€λμ¦μ λλ€.
κΈ°λ₯ 1: λ°μ΄ν° 무결μ±μ μν ν΄μ±
λ¬Έμκ° μλ€κ³ μμν΄ λ³΄μΈμ. λκ΅°κ° ν κΈμλΌλ λͺ°λ λ³κ²½νλ€λ©΄ μκ³ μΆμ΅λλ€. μ΄λ₯Ό μν΄ ν΄μ± μκ³ λ¦¬μ¦(SHA-256 λ±)μ ν΅κ³Όμν΅λλ€. μ΄ μκ³ λ¦¬μ¦μ ν΄μ λλ λ©μμ§ λ€μ΄μ μ€νΈλΌκ³ νλ κ³ μ νκ³ κ³ μ λ ν¬κΈ°μ λ¬Έμμ΄μ μμ±ν©λλ€. μ΄λ λ¨λ°©ν₯ νλ‘μΈμ€μ΄λ©°, ν΄μμμ μλ³Έ λ¬Έμλ₯Ό 볡ꡬν μ μμ΅λλ€. κ°μ₯ μ€μν κ²μ, μλ³Έ λ¬Έμμ λΉνΈ νλλΌλ λ³κ²½λλ©΄ κ²°κ³Ό ν΄μλ μμ ν λ¬λΌμ§λ€λ κ²μ λλ€. μ΄κ²μ΄ λ°μ΄ν° 무결μ±μ μ 곡ν©λλ€.
κΈ°λ₯ 2: μΈμ¦ λ° λΆμΈ λ°©μ§λ₯Ό μν λΉλμΉ μνΈν
λ§λ²μ΄ μΌμ΄λλ κ³³μ λλ€. κ³΅κ° ν€ μνΈνλΌκ³ λ μλ €μ§ λΉλμΉ μνΈνλ κ° μ¬μ©μλ§λ€ μνμ μΌλ‘ μ°κ²°λ ν€ μμ ν¬ν¨ν©λλ€.
- κ°μΈ ν€: μμ μκ° μ λ λΉλ°λ‘ μ μ§ν©λλ€. μλͺ μ μ¬μ©λ©λλ€.
- κ³΅κ° ν€: μ μΈκ³μ μμ λ‘κ² κ³΅μ λ©λλ€. κ²μ¦μ μ¬μ©λ©λλ€.
κ°μΈ ν€λ‘ μνΈνλ λͺ¨λ κ²μ ν΄λΉ κ³΅κ° ν€λ‘λ§ λ³΅νΈνν μ μμ΅λλ€. μ΄ κ΄κ³κ° μ λ’°μ κΈ°λ°μ λλ€.
μλͺ λ° κ²μ¦ νλ‘μΈμ€
κ°λ¨ν μν¬νλ‘μμ λͺ¨λ μ°κ²°ν΄ λ΄ μλ€.
- μλͺ
:
- Aliceκ° Bobμκ² μλͺ λ κ³μ½μ 보λ΄κ³ μΆμ΄ ν©λλ€.
- λ¨Όμ κ³μ½ λ¬Έμμ ν΄μλ₯Ό μμ±ν©λλ€.
- κ·Έλ° λ€μ κ°μΈ ν€λ₯Ό μ¬μ©νμ¬ μ΄ ν΄μλ₯Ό μνΈνν©λλ€. μ΄ μνΈνλ ν΄μκ° λμ§νΈ μλͺ μ λλ€.
- Aliceλ μλ³Έ κ³μ½ λ¬Έμμ λμ§νΈ μλͺ μ Bobμκ² λ³΄λ λλ€.
- κ²μ¦:
- Bobμ κ³μ½κ³Ό μλͺ μ λ°μ΅λλ€.
- κ·Έλ λ°μ κ³μ½ λ¬Έμλ₯Ό κ°μ§κ³ Aliceκ° μ¬μ©ν κ²κ³Ό λμΌν ν΄μ± μκ³ λ¦¬μ¦μ μ¬μ©νμ¬ ν΄μλ₯Ό κ³μ°ν©λλ€.
- κ·Έλ° λ€μ Aliceμ κ³΅κ° ν€(μ λ’°ν μ μλ μΆμ²μμ μ»μ μ μμ)λ₯Ό μ¬μ©νμ¬ κ·Έλ κ° λ³΄λΈ μλͺ μ 볡νΈνν©λλ€. μ΄λ κ² νλ©΄ κ·Έλ κ° κ³μ°ν μλ³Έ ν΄μκ° λλ¬λ©λλ€.
- Bobμ λ ν΄μλ₯Ό λΉκ΅ν©λλ€. μμ μ΄ μ§μ κ³μ°ν ν΄μμ μλͺ μμ 볡νΈνν ν΄μμ λλ€.
λ ν΄μκ° μΌμΉνλ©΄ Bobμ λ€μ μΈ κ°μ§λ₯Ό νμ ν μ μμ΅λλ€.
- μΈμ¦: κ³΅κ° ν€λ‘ 볡νΈνν μ μλ μλͺ μ μμ±ν μ μμλ μ¬λμ κ°μΈ ν€ μμ μμΈ AliceλΏμ λλ€.
- 무결μ±: μμ μ κ³μ° ν΄μκ° μλͺ μ ν΄μμ μΌμΉνλ―λ‘ μ μ‘ μ€μ λ¬Έμκ° λ³κ²½λμ§ μμμ΅λλ€.
- λΆμΈ λ°©μ§: μλͺ μμ±μ νμν κ°μΈ ν€λ₯Ό Aliceλ§ κ°μ§κ³ μμΌλ―λ‘ Aliceλ λμ€μ λ¬Έμμ μλͺ νμμ λΆμΈν μ μμ΅λλ€.
JavaScriptμ κ³Όμ : νμ κ΄λ ¨ μ·¨μ½μ μ΄ μ¨μ΄ μλ κ³³
μλ²½ν μΈμμμλ μμ νλ‘μΈμ€κ° μ€λ₯ μμ΄ μλν©λλ€. μννΈμ¨μ΄ κ°λ°μ νμ€, νΉν μΌλ° JavaScriptμμλ λ―Έλ¬ν μ€μκ° μ¬κ°ν 보μ ꡬλ©μ λ§λ€ μ μμ΅λλ€.
Node.jsμ μΌλ°μ μΈ μνΈν λΌμ΄λΈλ¬λ¦¬ ν¨μλ₯Ό μκ°ν΄ 보μΈμ.
// κ°μμ μΌλ° JavaScript μλͺ
ν¨μ
function createSignature(data, privateKey, algorithm) {
const sign = crypto.createSign(algorithm);
sign.update(data);
sign.end();
const signature = sign.sign(privateKey, 'base64');
return signature;
}
보기μλ κ°λ¨νμ§λ§ 무μμ΄ μλͺ»λ μ μμκΉμ?
- `data`μ λν μλͺ»λ λ°μ΄ν° νμ : `sign.update()` λ©μλλ μ’ μ’ `string` λλ `Buffer`λ₯Ό μμν©λλ€. κ°λ°μκ° μ€μλ‘ μ«μ(`12345`) λλ κ°μ²΄(`{ id: 12345 }`)λ₯Ό μ λ¬νλ©΄ JavaScriptλ μμμ μΌλ‘ λ¬Έμμ΄(`"12345"` λλ `"[object Object]"`)λ‘ λ³νν μ μμ΅λλ€. μ€λ₯ μμ΄ μλͺ μ΄ μμ±λμ§λ§, μ΄λ μλͺ»λ κΈ°λ³Έ λ°μ΄ν°μ λν μλͺ μ λλ€. κ·Έλ° λ€μ κ²μ¦μ μ€ν¨νμ¬ μ’μ μ€λ½κ³ μ§λ¨νκΈ° μ΄λ €μ΄ λ²κ·Έλ‘ μ΄μ΄μ§λλ€.
- ν€ νμ μλͺ» μ²λ¦¬: `sign.sign()` λ©μλλ `privateKey` νμμ κΉλ€λ‘μ΅λλ€. PEM νμμ λ¬Έμμ΄, `KeyObject` λλ `Buffer`μΌ μ μμ΅λλ€. μλͺ»λ νμμ 보λ΄λ©΄ λ°νμ μΆ©λμ΄ λ°μνκ±°λ, λ λμκ²λ μλͺ»λ μλͺ μ΄ μμ±λλ μ‘°μ©ν μ€λ₯κ° λ°μν μ μμ΅λλ€.
- `null` λλ `undefined` κ°: λ°μ΄ν°λ² μ΄μ€ μ‘°ν μ€ν¨λ‘ `privateKey`κ° `undefined`μΈ κ²½μ° μ΄λ»κ² λ κΉμ? μ ν리μΌμ΄μ μ΄ λ°νμμ μΆ©λν μ μμΌλ©°, μ μ¬μ μΌλ‘ λ΄λΆ μμ€ν μνλ₯Ό λ ΈμΆνκ±°λ μλΉμ€ κ±°λΆ μ·¨μ½μ μ λ§λ€ μ μμ΅λλ€.
- μκ³ λ¦¬μ¦ λΆμΌμΉ: μλͺ ν¨μκ° `'sha256'`μ μ¬μ©νμ§λ§ κ²μ¦μκ° `'sha512'`λ‘ μμ±λ μλͺ μ μμνλ κ²½μ° κ²μ¦μ νμ μ€ν¨ν©λλ€. νμ μμ€ν κ°μ κ° μμΌλ©΄ μ΄λ μ μ μΌλ‘ κ°λ°μ κ·μ¨κ³Ό λ¬Έμμ μμ‘΄ν©λλ€.
μ΄κ²μ λ¨μν νλ‘κ·Έλλ° μ€λ₯κ° μλλΌ λ³΄μ κ²°ν¨μ λλ€. μλͺ» μμ±λ μλͺ μ μ ν¨ν νΈλμμ μ΄ κ±°λΆλκ±°λ, λ 볡μ‘ν μλ리μ€μμλ μλͺ μ‘°μμ λν 곡격 벑ν°λ₯Ό μ΄μ΄μ€ μ μμ΅λλ€.
TypeScriptλ₯Ό νμ©ν ν΄κ²°: μΈμ¦ νμ μμ μ± κ΅¬ν
TypeScriptλ μ½λκ° μ€νλκΈ° μ μ μ΄λ¬ν λͺ¨λ μ’ λ₯μ λ²κ·Έλ₯Ό μ κ±°νλ λꡬλ₯Ό μ 곡ν©λλ€. λ°μ΄ν° ꡬ쑰μ ν¨μμ λν κ°λ ₯ν κ³μ½μ μμ±ν¨μΌλ‘μ¨ μ€λ₯ νμ§λ₯Ό λ°νμμμ μ»΄νμΌ νμμΌλ‘ μ΄λν©λλ€.
1λ¨κ³: ν΅μ¬ μνΈν νμ μ μ
첫 λ²μ§Έ λ¨κ³λ λͺ μμ νμ μΌλ‘ μνΈν κΈ°λ³Έ μμλ₯Ό λͺ¨λΈλ§νλ κ²μ λλ€. μΌλ° `string` λλ `any`λ₯Ό μ λ¬νλ λμ μ νν μΈν°νμ΄μ€ λλ νμ λ³μΉμ μ μν©λλ€.
μ¬κΈ°μ κ°λ ₯ν κΈ°μ μ λΈλλ©λ νμ (λλ λͺ λͺ©μ νμ΄ν)μ μ¬μ©νλ κ²μ λλ€. μ΄λ₯Ό ν΅ν΄ ꡬ쑰μ μΌλ‘ `string`κ³Ό λμΌνμ§λ§ μνΈ κ΅νν μ μλ κ³ μ ν νμ μ μμ±ν μ μμΌλ©°, μ΄λ ν€μ μλͺ μ μλ²½ν©λλ€.
// types.ts
export type Brand
// ν€λ μΌλ° λ¬Έμμ΄λ‘ μ·¨κΈν΄μλ μ λ©λλ€.
export type PrivateKey = Brand
// μλͺ
λ νΉμ μ νμ λ¬Έμμ΄μ
λλ€(μ: base64).
export type Signature = Brand
// μ€ν λ° μ€μ©μ λ°©μ§νκΈ° μν΄ νμ©λ μκ³ λ¦¬μ¦ λͺ©λ‘μ μ μν©λλ€.
export enum SignatureAlgorithm {
RS256 = 'RSA-SHA256',
ES256 = 'ECDSA-SHA256',
// μ§μλλ λ€λ₯Έ μκ³ λ¦¬μ¦μ μ¬κΈ°μ μΆκ°νμΈμ.
}
// μλͺ
ν λͺ¨λ λ°μ΄ν°μ λν κΈ°λ³Έ μΈν°νμ΄μ€ μ μ
export interface Signable {
// μλͺ
κ°λ₯ν νμ΄λ‘λμλ μ§λ ¬ν κ°λ₯ν΄μΌ ν¨μ κ°μ ν μ μμ΅λλ€.
// λ¨μνλ₯Ό μν΄ μ¬κΈ°μλ λͺ¨λ κ°μ²΄λ₯Ό νμ©νμ§λ§, νλ‘λμ
μμλ
// { [key: string]: string | number | boolean; }μ κ°μ ꡬ쑰λ₯Ό κ°μ ν μ μμ΅λλ€.
[key: string]: any;
}
μ΄λ¬ν νμ μ μ¬μ©νλ©΄ μ»΄νμΌλ¬λ `PrivateKey`κ° νμν κ³³μ `PublicKey`λ₯Ό μ¬μ©νλ €κ³ νλ©΄ μ€λ₯λ₯Ό λ°μμν΅λλ€. 무μμ λ¬Έμμ΄μ κ·Έλ₯ μ λ¬ν μ μμΌλ©°, λͺ νν μλλ₯Ό λνλ΄λ λΈλλ©λ νμ μΌλ‘ λͺ μμ μΌλ‘ μΊμ€ν ν΄μΌ ν©λλ€.
2λ¨κ³: νμ μμ μλͺ λ° κ²μ¦ ν¨μ ꡬμΆ
μ΄μ μ΄λ¬ν κ°λ ₯ν νμ μ μ¬μ©νμ¬ ν¨μλ₯Ό λ€μ μμ±ν΄ λ³΄κ² μ΅λλ€. μ΄ μμ μμλ Node.jsμ λ΄μ₯ `crypto` λͺ¨λμ μ¬μ©ν©λλ€.
// crypto.service.ts
import * as crypto from 'crypto';
import { PrivateKey, PublicKey, Signature, SignatureAlgorithm, Signable } from './types';
export class DigitalSignatureService {
public sign
ν¨μ μλͺ μ μ°¨μ΄μ μ μ΄ν΄λ³΄μΈμ.
- `sign(payload: T, privateKey: PrivateKey, ...)`: μ΄μ μ€μλ‘ `privateKey`λ‘ κ³΅κ° ν€λ μΌλ° λ¬Έμμ΄μ μ λ¬ν μ μμ΅λλ€. νμ΄λ‘λλ `Signable` μΈν°νμ΄μ€μ μν΄ μ νλλ©°, νμ΄λ‘λμ νΉμ νμ μ μ μ§νκΈ° μν΄ μ λ€λ¦(`<T extends Signable>`)μ μ¬μ©ν©λλ€.
- `verify(..., signature: Signature, publicKey: PublicKey, ...)`: μΈμκ° λͺ ννκ² μ μλ©λλ€. μλͺ κ³Ό κ³΅κ° ν€λ₯Ό νΌλν μ μμ΅λλ€.
- `algorithm: SignatureAlgorithm`: μ΄κ±°νμ μ¬μ©νμ¬ μ€ν(`'RSA-SHA256'` λ `'RSA-sha256'`)λ₯Ό λ°©μ§νκ³ κ°λ°μλ₯Ό μ¬μ μΉμΈλ μμ ν μκ³ λ¦¬μ¦ λͺ©λ‘μΌλ‘ μ ννμ¬ μ»΄νμΌ νμμ μνΈν λ€μ΄κ·Έλ μ΄λ 곡격μ λ°©μ§ν©λλ€.
3λ¨κ³: JSON μΉ ν ν°(JWT)μ μ¬μ©ν μ€μ©μ μΈ μμ
λμ§νΈ μλͺ μ μΌλ°μ μΌλ‘ JSON μΉ ν ν°(JWT)μ λ§λλ λ° μ¬μ©λλ JSON μΉ μλͺ (JWS)μ κΈ°λ°μ λλ€. μ΄λ¬ν μ ν μμ ν¨ν΄μ μ΄ λ³΄νΈμ μΈ μΈμ¦ λ©μ»€λμ¦μ μ μ©ν΄ λ΄ μλ€.
λ¨Όμ JWT νμ΄λ‘λμ λν μ격ν νμ μ μ μν©λλ€. μΌλ° κ°μ²΄ λμ κ° μμ ν΄λ μκ³Ό ν΄λΉ νμ μ μ§μ ν©λλ€.
// types.ts (νμ₯λ¨)
export interface UserTokenPayload extends Signable {
iss: string; // λ°νμ
sub: string; // μ£Όμ (μ: μ¬μ©μ ID)
aud: string; // λμ
exp: number; // λ§λ£ μκ° (Unix νμμ€ν¬ν)
iat: number; // λ°ν μκ° (Unix νμμ€ν¬ν)
jti: string; // JWT ID
roles: string[]; // μ¬μ©μ μ§μ ν΄λ μ
}
μ΄μ ν ν° μμ± λ° μ ν¨μ± κ²μ¬ μλΉμ€λ μ΄ νΉμ νμ΄λ‘λμ λν΄ κ°λ ₯νκ² νμ΄νλ μ μμ΅λλ€.
// auth.service.ts
import { DigitalSignatureService } from './crypto.service';
import { PrivateKey, PublicKey, SignatureAlgorithm, UserTokenPayload } from './types';
class AuthService {
private signatureService = new DigitalSignatureService();
private privateKey: PrivateKey; // μμ νκ² λ‘λλ¨
private publicKey: PublicKey; // 곡κ°μ μΌλ‘ μ¬μ© κ°λ₯
constructor(pk: PrivateKey, pub: PublicKey) {
this.privateKey = pk;
this.publicKey = pub;
}
// ν¨μκ° μ΄μ μ¬μ©μ ν ν° μμ±μ νΉνλμμ΅λλ€.
public generateUserToken(userId: string, roles: string[]): string {
const now = Math.floor(Date.now() / 1000);
const payload: UserTokenPayload = {
iss: 'https://api.my-global-app.com',
aud: 'my-global-app-clients',
sub: userId,
roles: roles,
iat: now,
exp: now + (60 * 15), // 15λΆ μ ν¨μ±
jti: crypto.randomBytes(16).toString('hex'),
};
// JWS νμ€μ base64λ§ μλ base64url μΈμ½λ©μ μ¬μ©ν©λλ€.
const header = { alg: 'RS256', typ: 'JWT' }; // μκ³ λ¦¬μ¦μ ν€ νμ
κ³Ό μΌμΉν΄μΌ ν©λλ€.
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
// μ°λ¦¬μ νμ
μμ€ν
μ JWS ꡬ쑰λ₯Ό μ΄ν΄νμ§ λͺ»νλ―λ‘ κ΅¬μ±ν΄μΌ ν©λλ€.
// μ€μ ꡬνμμλ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νκ² μ§λ§, μμΉμ 보μ¬μ£ΌκΈ° μν΄.
// μ°Έκ³ : μλͺ
μ 'encodedHeader.encodedPayload' λ¬Έμμ΄μ μμ΄μΌ ν©λλ€.
// λ¨μνλ₯Ό μν΄ μ¬κΈ°μλ μλΉμ€μ μ§μ νμ΄λ‘λ κ°μ²΄λ₯Ό μλͺ
ν©λλ€.
const signature = this.signatureService.sign(
payload,
this.privateKey,
SignatureAlgorithm.RS256
);
// μ¬λ°λ₯Έ JWT λΌμ΄λΈλ¬λ¦¬λ μλͺ
μ base64url λ³νμ μ²λ¦¬ν κ²μ
λλ€.
// μ΄κ²μ νμ΄λ‘λμ λν νμ
μμ μ±μ 보μ¬μ£ΌκΈ° μν λ¨μνλ μμ μ
λλ€.
return `${encodedHeader}.${encodedPayload}.${signature}`;
}
public validateAndDecodeToken(token: string): UserTokenPayload | null {
// μ€μ μ±μμλ 'jose' λλ 'jsonwebtoken'κ³Ό κ°μ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©ν κ²μ
λλ€.
// μ΄λ ꡬ문 λΆμ λ° κ²μ¦μ μ²λ¦¬ν©λλ€.
const [header, payload, signature] = token.split('.');
if (!header || !payload || !signature) {
return null; // μλͺ»λ νμ
}
try {
const decodedPayload: unknown = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
// μ΄μ νμ
κ°λλ₯Ό μ¬μ©νμ¬ λμ½λ©λ κ°μ²΄λ₯Ό κ²μ¦ν©λλ€.
if (!this.isUserTokenPayload(decodedPayload)) {
console.error('λμ½λ©λ νμ΄λ‘λκ° μμ ꡬ쑰μ μΌμΉνμ§ μμ΅λλ€.');
return null;
}
// μ΄μ decodedPayloadλ₯Ό UserTokenPayloadλ‘ μμ νκ² μ¬μ©ν μ μμ΅λλ€.
const isValid = this.signatureService.verify(
decodedPayload,
signature as Signature, // μ¬κΈ°μ λ¬Έμμ΄μμ μΊμ€ν
ν΄μΌ ν©λλ€.
this.publicKey,
SignatureAlgorithm.RS256
);
if (!isValid) {
console.error('μλͺ
κ²μ¦ μ€ν¨.');
return null;
}
if (decodedPayload.exp * 1000 < Date.now()) {
console.error('ν ν°μ΄ λ§λ£λμμ΅λλ€.');
return null;
}
return decodedPayload;
} catch (error) {
console.error('ν ν° μ ν¨μ± κ²μ¬ μ€ μ€λ₯:', error);
return null;
}
}
// μ΄κ²μ μ€μν νμ
κ°λ ν¨μμ
λλ€.
private isUserTokenPayload(payload: unknown): payload is UserTokenPayload {
if (typeof payload !== 'object' || payload === null) return false;
const p = payload as { [key: string]: unknown };
return (
typeof p.iss === 'string' &&
typeof p.sub === 'string' &&
typeof p.aud === 'string' &&
typeof p.exp === 'number' &&
typeof p.iat === 'number' &&
typeof p.jti === 'string' &&
Array.isArray(p.roles) &&
p.roles.every(r => typeof r === 'string')
);
}
}
`isUserTokenPayload` νμ κ°λλ μ λλ‘ νμ΄νλμ§ μκ³ μ λ’°ν μ μλ μΈλΆ μΈκ³(λ€μ΄μ€λ ν ν° λ¬Έμμ΄)μ μμ νκ³ νμ΄νλ λ΄λΆ μμ€ν μ¬μ΄μ λ€λ¦¬ μν μ ν©λλ€. μ΄ ν¨μκ° `true`λ₯Ό λ°νν ν TypeScriptλ `decodedPayload` λ³μκ° `UserTokenPayload` μΈν°νμ΄μ€λ₯Ό μ€μν¨μ μκ³ `any` μΊμ€ν μ΄λ `undefined` μ€λ₯μ λν λλ €μ μμ΄ `decodedPayload.sub` λ° `decodedPayload.exp`μ κ°μ μμ±μ μμ νκ² μ‘μΈμ€ν μ μμ΅λλ€.
νμ₯ κ°λ₯ν νμ μμ μΈμ¦μ μν μν€ν μ² ν¨ν΄
νμ μμ μ±μ μ μ©νλ κ²μ κ°λ³ ν¨μμ κ΄ν κ²μ΄ μλλΌ μ»΄νμΌλ¬κ° 보μ κ³μ½μ κ°μ νλ μ 체 μμ€ν μ ꡬμΆνλ κ²μ λλ€. μ΄λ¬ν μ΄μ μ νμ₯νλ λͺ κ°μ§ μν€ν μ² ν¨ν΄μ΄ μμ΅λλ€.
νμ μμ ν€ μ μ₯μ
λ§μ μμ€ν μμ μνΈν ν€λ ν€ κ΄λ¦¬ μλΉμ€(KMS)μ μν΄ κ΄λ¦¬λκ±°λ 보μ μ μ₯μμ μ μ₯λ©λλ€. ν€λ₯Ό κ°μ Έμ¬ λ μ¬λ°λ₯Έ νμ μΌλ‘ λ°νλλμ§ νμΈν΄μΌ ν©λλ€.
`getKey(keyId: string): Promise<string>`μ κ°μ ν¨μ λμ κ°λ ₯νκ² νμ΄νλ ν€λ₯Ό λ°ννλ μλΉμ€λ₯Ό μ€κ³νμΈμ.
// key.repository.ts
import { PublicKey, PrivateKey } from './types';
interface KeyRepository {
getPublicKey(keyId: string): Promise<PublicKey | null>;
getPrivateKey(keyId: string): Promise<PrivateKey | null>;
}
// μμ ꡬν (μ: AWS KMS λλ Azure Key Vaultμμ κ°μ Έμ€κΈ°)
class KmsRepository implements KeyRepository {
public async getPublicKey(keyId: string): Promise<PublicKey | null> {
// ... KMS νΈμΆ λ° κ³΅κ° ν€ λ¬Έμμ΄ κ°μ Έμ€κΈ° λ‘μ§ ...
const keyFromKms: string | undefined = await someKmsSdk.getPublic(keyId);
if (!keyFromKms) return null;
return keyFromKms as PublicKey; // λΈλλ©λ νμ
μΌλ‘ μΊμ€ν
}
public async getPrivateKey(keyId: string): Promise<PrivateKey | null> {
// ... KMS νΈμΆ λ‘μ§ (μλͺ
μ κ°μΈ ν€ μ¬μ©) ...
// λ§μ KMS μμ€ν
μμλ κ°μΈ ν€ μ체λ₯Ό κ°μ Έμ€μ§ μκ³ μλͺ
ν λ°μ΄ν°λ§ μ λ¬ν©λλ€.
// μ΄ ν¨ν΄μ λ°νλ μλͺ
μλ μ μ©λ©λλ€.
return '... μμ νκ² κ²μλ ν€ ...' as PrivateKey;
}
}
μ΄ μΈν°νμ΄μ€ λ€μ ν€ κ²μμ μΆμνν¨μΌλ‘μ¨ μ ν리μΌμ΄μ μ λλ¨Έμ§ λΆλΆμ KMS APIμ λ¬Έμμ΄ νμ΄ν νΉμ±μ λν΄ κ±±μ ν νμκ° μμ΅λλ€. `PublicKey` λλ `PrivateKey`λ₯Ό λ°λ κ²μ μ λ’°ν μ μμΌλ©°, μΈμ¦ μ€ν μ 체μ νμ μμ μ±μ΄ νλ₯΄λλ‘ λ³΄μ₯ν©λλ€.
μ λ ₯ μ ν¨μ± κ²μ¬λ₯Ό μν μ΄μ€μ ν¨μ
νμ κ°λλ νλ₯νμ§λ§, μ ν¨μ± κ²μ¬κ° μ€ν¨νλ©΄ μ¦μ μ€λ₯λ₯Ό λ°μμν€κ³ μΆμ λκ° μμ΅λλ€. TypeScriptμ `asserts` ν€μλλ μ΄λ₯Ό μν΄ μλ²½ν©λλ€.
// νμ
κ°λ μμ
function assertIsUserTokenPayload(payload: unknown): asserts payload is UserTokenPayload {
if (!isUserTokenPayload(payload)) {
throw new Error('μλͺ»λ ν ν° νμ΄λ‘λ ꡬ쑰.');
}
}
μ΄μ μ ν¨μ± κ²μ¬ λ‘μ§μμ λ€μκ³Ό κ°μ΄ ν μ μμ΅λλ€.
const decodedPayload: unknown = JSON.parse(...); assertIsUserTokenPayload(decodedPayload); // μ΄ μμ λΆν° TypeScriptλ decodedPayloadκ° UserTokenPayload νμ μμ μκ³ μμ΅λλ€. console.log(decodedPayload.sub); // μ΄μ 100% νμ μμ ν©λλ€.
μ΄ ν¨ν΄μ μ ν¨μ± κ²μ¬ λ‘μ§κ³Ό κ·Έ λ€μ μ€λ λΉμ¦λμ€ λ‘μ§μ λΆλ¦¬νμ¬ λ κΉ¨λνκ³ μ½κΈ° μ¬μ΄ μ ν¨μ± κ²μ¬ μ½λλ₯Ό μμ±ν©λλ€.
κΈλ‘λ² μν₯ λ° μΈκ°μ μμ
μμ ν μμ€ν μ ꡬμΆνλ κ²μ μ½λ μ΄μμ κ²μ ν¬ν¨νλ κΈλ‘λ² κ³Όμ μ λλ€. μ¬λ, νλ‘μΈμ€, κ·Έλ¦¬κ³ κ΅κ²½κ³Ό μκ°λλ₯Ό λλλλ νμ μ΄ ν¬ν¨λ©λλ€. μΈμ¦ νμ μμ μ±μ μ΄λ¬ν κΈλ‘λ² λ§₯λ½μμ μλΉν μ΄μ μ μ 곡ν©λλ€.
- μ΄μμλ λ¬Έμ μν : λΆμ° νμκ² μ νμ΄νλ μ½λλ² μ΄μ€λ μ ννκ³ λͺ¨νΈν¨ μλ λ¬Έμμ ν ννμ λλ€. λ€λ₯Έ λλΌμ μ κ· κ°λ°μλ νμ μ μλ§ μ½κ³ λ μΈμ¦ μμ€ν μ λ°μ΄ν° ꡬ쑰μ κ³μ½μ μ¦μ μ΄ν΄ν μ μμ΅λλ€. μ΄λ μ€ν΄λ₯Ό μ€μ΄κ³ μ¨λ³΄λ©μ κ°μνν©λλ€.
- 보μ κ°μ¬ λ¨μν: 보μ κ°μ¬μκ° μ½λλ₯Ό κ²ν ν λ νμ μμ ꡬνμ μμ€ν μ μλλ₯Ό λͺ ννκ² ν©λλ€. μ¬λ°λ₯Έ ν€κ° μ¬λ°λ₯Έ μμ μ μ¬μ©λκ³ λ°μ΄ν° κ΅¬μ‘°κ° μΌκ΄λκ² μ²λ¦¬λλμ§ νμΈνκΈ° μ½μ΅λλ€. μ΄λ SOC 2 λλ GDPRκ³Ό κ°μ κ΅μ νμ€ μ€μλ₯Ό λ¬μ±νλ λ° μ€μν μ μμ΅λλ€.
- μνΈ μ΄μ©μ± ν₯μ: TypeScriptλ μ»΄νμΌ νμ 보μ₯μ μ 곡νμ§λ§, λ°μ΄ν°μ μ¨λμμ΄μ΄ νμμ λ³κ²½νμ§ μμ΅λλ€. νμ μμ TypeScript λ°±μλμμ μμ±λ JWTλ Swiftλ‘ μμ±λ λͺ¨λ°μΌ ν΄λΌμ΄μΈνΈλ Goλ‘ μμ±λ ννΈλ μλΉμ€μμ μλΉν μ μλ νμ€ JWTμ λλ€. νμ μμ μ±μ μ μ νμ€μ μ¬λ°λ₯΄κ² ꡬννκ³ μμμ 보μ₯νλ κ°λ° μκ° λ³΄νΈ μ₯μΉμ λλ€.
- μΈμ§ λΆν κ°μ: μνΈνλ μ΄λ ΅μ΅λλ€. κ°λ°μλ μμ€ν μ μ 체 λ°μ΄ν° νλ¦κ³Ό νμ κ·μΉμ λ¨Έλ¦Ώμμ λ΄κ³ μμ νμκ° μμ΅λλ€. μ΄λ¬ν μ± μμ TypeScript μ»΄νμΌλ¬λ‘ μ€νλ‘λν¨μΌλ‘μ¨ κ°λ°μλ `TypeError: cannot read property 'sign' of undefined`λ₯Ό κ±±μ νλ λμ , μ¬λ°λ₯Έ λ§λ£ κ²μ¬ λ° κ°λ ₯ν μ€λ₯ μ²λ¦¬μ κ°μ λ λμ μμ€μ 보μ λ‘μ§μ μ§μ€ν μ μμ΅λλ€.
κ²°λ‘ : νμ μΌλ‘ μ λ’° ꡬμΆ
λμ§νΈ μλͺ μ νλ λμ§νΈ 보μμ μ΄μμ΄μ§λ§, JavaScriptμ κ°μ λμ νμ΄ν μΈμ΄μμμ ꡬνμ κ°μ₯ μμ μ€λ₯λ μ¬κ°ν κ²°κ³Όλ₯Ό μ΄λν μ μλ μ¬μΈν νλ‘μΈμ€μ λλ€. TypeScriptλ₯Ό μ±νν¨μΌλ‘μ¨ μ°λ¦¬λ λ¨μν νμ μ μΆκ°νλ κ²μ΄ μλλΌ μμ ν μ½λλ₯Ό μμ±νλ μ κ·Ό λ°©μμ κ·Όλ³Έμ μΌλ‘ λ³νμν€κ³ μμ΅λλ€.
λͺ μμ νμ , λΈλλ©λ κΈ°λ³Έ μμ, νμ κ°λ λ° μ¬λ € κΉμ μν€ν μ²λ₯Ό ν΅ν΄ λ¬μ±λλ μΈμ¦ νμ μμ μ±μ κ°λ ₯ν μ»΄νμΌ νμ μμ λ§μ μ 곡ν©λλ€. μ΄λ λ κ²¬κ³ νκ³ μΌλ°μ μΈ μ·¨μ½μ μ λ μ·¨μ½ν μμ€ν μ ꡬμΆν λΏλ§ μλλΌ κΈλ‘λ² νμκ² λ μ΄ν΄νκΈ° μ½κ³ μ μ§ κ΄λ¦¬ κ°λ₯νλ©° κ°μ¬ κ°λ₯ν μμ€ν μ ꡬμΆν μ μλλ‘ ν©λλ€.
κΆκ·Ήμ μΌλ‘ μμ ν μ½λλ₯Ό μμ±νλ κ²μ 볡μ‘μ±μ κ΄λ¦¬νκ³ λΆνμ€μ±μ μ΅μννλ κ²μ λλ€. TypeScriptλ μ΄λ₯Ό μ νν μνν μ μλ κ°λ ₯ν λꡬ μΈνΈλ₯Ό μ 곡νμ¬, μνΈ μ°κ²°λ μΈμμ΄ μμ‘΄νλ λμ§νΈ μ λ’°λ₯Ό, νμ μμ ν¨μ νλνλμ© κ΅¬μΆν μ μλλ‘ ν©λλ€.