Skip to content

Latest commit

ย 

History

History
506 lines (400 loc) ยท 17.1 KB

File metadata and controls

506 lines (400 loc) ยท 17.1 KB

๋ณด์•ˆ

Altsis ์‹œ์Šคํ…œ์˜ ๋ณด์•ˆ ์ •์ฑ…, ์•”ํ˜ธํ™” ์ฒด๊ณ„, ์ ‘๊ทผ ์ œ์–ด ๋ฐ ๋ณด์•ˆ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.


๊ฐœ์š”

Altsis๋Š” ํ•™์ƒ ๊ฐœ์ธ์ •๋ณด์™€ ๊ต์œก ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ์‹œ์Šคํ…œ์œผ๋กœ, ๋‹ค์ธต ๋ณด์•ˆ ์ „๋žต์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     Altsis ๋ณด์•ˆ ์ฒด๊ณ„                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                 โ”‚
โ”‚  โ”Œโ”€ ํ†ต์‹  ๋ณด์•ˆ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚  HTTPS (ACM + CloudFront/ALB)                              โ”‚ โ”‚
โ”‚  โ”‚  CORS ๋„๋ฉ”์ธ ์ œํ•œ                                           โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚                                                                 โ”‚
โ”‚  โ”Œโ”€ ์ธ์ฆ ๋ณด์•ˆ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚  Passport.js (๋กœ์ปฌ + Google OAuth 2.0)                     โ”‚ โ”‚
โ”‚  โ”‚  Redis ๊ธฐ๋ฐ˜ ์„ธ์…˜ ๊ด€๋ฆฌ (TTL 24์‹œ๊ฐ„)                           โ”‚ โ”‚
โ”‚  โ”‚  ๋ฏธ๋“ค์›จ์–ด ๊ธฐ๋ฐ˜ ๊ถŒํ•œ ์ฒดํฌ                                      โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚                                                                 โ”‚
โ”‚  โ”Œโ”€ ๋ฐ์ดํ„ฐ ๋ณด์•ˆ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚  bcrypt ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ                                       โ”‚ โ”‚
โ”‚  โ”‚  mongoose-encryption ํ•„๋“œ ์•”ํ˜ธํ™”                             โ”‚ โ”‚
โ”‚  โ”‚  S3 Pre-signed URL (5๋ถ„ ๋งŒ๋ฃŒ)                               โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚                                                                 โ”‚
โ”‚  โ”Œโ”€ ์ธํ”„๋ผ ๋ณด์•ˆ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚  ๋ณด์•ˆ ๊ทธ๋ฃน, IAM ์ตœ์†Œ ๊ถŒํ•œ                                    โ”‚ โ”‚
โ”‚  โ”‚  ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์•”ํ˜ธํ™” ๊ด€๋ฆฌ                                       โ”‚ โ”‚
โ”‚  โ”‚  S3 ๋ฒ„ํ‚ท ์ ‘๊ทผ ์ œ์–ด                                          โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚                                                                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™”

๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ (bcrypt)

์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” bcrypt ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ํ•ด์‹ฑ๋˜์–ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์›๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์–ด๋””์—๋„ ์ €์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ ์„ค๋ช…
์•Œ๊ณ ๋ฆฌ์ฆ˜ bcrypt
Salt Rounds ํ™˜๊ฒฝ ๋ณ€์ˆ˜ saltRounts๋กœ ์„ค์ • (๊ถŒ์žฅ: 10 ์ด์ƒ)
์ ์šฉ ์‹œ์  ์‚ฌ์šฉ์ž ์ƒ์„ฑ ๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ์ ์šฉ
๋ชจ๋ธ User

ํ•ด์‹ฑ ํ”„๋กœ์„ธ์Šค

๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ
    โ”‚
    โ–ผ
bcrypt.genSalt(saltRounds)  โ† ๋žœ๋ค Salt ์ƒ์„ฑ
    โ”‚
    โ–ผ
bcrypt.hash(password, salt) โ† Salt + ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ
    โ”‚
    โ–ผ
ํ•ด์‹ฑ๋œ ๊ฐ’ DB์— ์ €์žฅ

๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ ํ”„๋กœ์„ธ์Šค

๋กœ๊ทธ์ธ ์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ
    โ”‚
    โ–ผ
bcrypt.compare(plainPassword, hashedPassword)
    โ”‚
    โ”œโ”€ true โ†’ ์ธ์ฆ ์„ฑ๊ณต
    โ””โ”€ false โ†’ ์ธ์ฆ ์‹คํŒจ

Note

bcrypt๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ Salt๋ฅผ ํ•ด์‹œ๊ฐ’์— ํฌํ•จ์‹œํ‚ค๋ฏ€๋กœ ๋ณ„๋„์˜ Salt ์ €์žฅ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Salt Rounds ๊ฐ’์ด ๋†’์„์ˆ˜๋ก ๋ณด์•ˆ์ด ๊ฐ•ํ™”๋˜์ง€๋งŒ ์—ฐ์‚ฐ ์‹œ๊ฐ„์ด ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

ํ‰๊ฐ€ ์ •๋ณด ์•”ํ˜ธํ™” (mongoose-encryption)

์ˆ˜๊ฐ• ๋“ฑ๋ก(Enrollment) ๋ชจ๋ธ์˜ ํ‰๊ฐ€(evaluation) ํ•„๋“œ๋Š” mongoose-encryption์œผ๋กœ ์•”ํ˜ธํ™”๋ฉ๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ ์„ค๋ช…
๋ชจ๋ธ Enrollment
์•”ํ˜ธํ™” ํ•„๋“œ evaluation
์•”ํ˜ธํ™” ํ‚ค ENCKEY_E (32๋ฐ”์ดํŠธ Base64)
์„œ๋ช… ํ‚ค SIGKEY_E (64๋ฐ”์ดํŠธ Base64)
์•Œ๊ณ ๋ฆฌ์ฆ˜ AES-256-CBC (์•”ํ˜ธํ™”) + HMAC-SHA-512 (์„œ๋ช…)
// backend/src/models/Enrollment.js
enrollmentSchema.plugin(encrypt, {
  encryptionKey: process.env["ENCKEY_E"],
  signingKey: process.env["SIGKEY_E"],
  encryptedFields: ["evaluation"],
});

ํ•™์ƒ ๊ธฐ๋ก ์•”ํ˜ธํ™” (mongoose-encryption)

ํ•™์ƒ ๊ธฐ๋ก(Archive) ๋ชจ๋ธ์˜ ๋ฐ์ดํ„ฐ(data) ํ•„๋“œ๋Š” ๋ณ„๋„์˜ ํ‚ค ์Œ์œผ๋กœ ์•”ํ˜ธํ™”๋ฉ๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ ์„ค๋ช…
๋ชจ๋ธ Archive
์•”ํ˜ธํ™” ํ•„๋“œ data
์•”ํ˜ธํ™” ํ‚ค ENCKEY_A (32๋ฐ”์ดํŠธ Base64)
์„œ๋ช… ํ‚ค SIGKEY_A (64๋ฐ”์ดํŠธ Base64)
์•Œ๊ณ ๋ฆฌ์ฆ˜ AES-256-CBC (์•”ํ˜ธํ™”) + HMAC-SHA-512 (์„œ๋ช…)
// backend/src/models/Archive.js
archiveSchema.plugin(encrypt, {
  encryptionKey: process.env["ENCKEY_A"],
  signingKey: process.env["SIGKEY_A"],
  encryptedFields: ["data"],
});

Caution

์•”ํ˜ธํ™” ํ‚ค ๊ด€๋ฆฌ ์ฃผ์˜์‚ฌํ•ญ

  • ์•”ํ˜ธํ™” ํ‚ค๋ฅผ ๋ถ„์‹คํ•˜๋ฉด ์•”ํ˜ธํ™”๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต๊ตฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค
  • ํ‚ค๋Š” ๋ฐ˜๋“œ์‹œ ์•ˆ์ „ํ•œ ๊ณณ์— ๋ณ„๋„๋กœ ๋ฐฑ์—…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ํ‰๊ฐ€ ์ •๋ณด์™€ ํ•™์ƒ ๊ธฐ๋ก์— ์„œ๋กœ ๋‹ค๋ฅธ ํ‚ค ์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฉ๋ฆฌํ•ฉ๋‹ˆ๋‹ค
  • ํ‚ค ๊ต์ฒด ์‹œ์—๋Š” ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ๋ณตํ˜ธํ™”ํ•œ ํ›„ ์ƒˆ ํ‚ค๋กœ ์žฌ์•”ํ˜ธํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

์•”ํ˜ธํ™” ํ‚ค ์ƒ์„ฑ ๋ฐฉ๋ฒ•

# ์•”ํ˜ธํ™” ํ‚ค ์ƒ์„ฑ (32๋ฐ”์ดํŠธ Base64)
openssl rand -base64 32

# ์„œ๋ช… ํ‚ค ์ƒ์„ฑ (64๋ฐ”์ดํŠธ Base64)
openssl rand -base64 64

์„ธ์…˜ ๋ณด์•ˆ

Redis ๊ธฐ๋ฐ˜ ์„ธ์…˜ ๊ด€๋ฆฌ

์‚ฌ์šฉ์ž ์„ธ์…˜์€ Redis์— ์ €์žฅ๋˜๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ณด์•ˆ ์„ค์ •์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ ์„ค์ • ์„ค๋ช…
์ €์žฅ์†Œ Redis Cloud (connect-redis) ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ ๋น ๋ฅธ ์„ธ์…˜ ์ ‘๊ทผ
TTL 24์‹œ๊ฐ„ (86400์ดˆ) ์„ธ์…˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„
์„ธ์…˜ ํ‚ค session_key ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ธ์…˜ ์ฟ ํ‚ค ์„œ๋ช…์— ์‚ฌ์šฉ
httpOnly true JavaScript์—์„œ ์ฟ ํ‚ค ์ ‘๊ทผ ์ฐจ๋‹จ
resave false ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ ์„ธ์…˜ ์žฌ์ €์žฅ ๋ฐฉ์ง€
saveUninitialized false ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์€ ์„ธ์…˜ ์ €์žฅ ๋ฐฉ์ง€
rolling true ์š”์ฒญ๋งˆ๋‹ค ์„ธ์…˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๊ฐฑ์‹ 
// backend/src/app.js
app.use(
  session({
    resave: false,
    saveUninitialized: false,
    secret: process.env["session_key"].trim(),
    cookie: {
      httpOnly: true,
      secure: false,  // ALB์—์„œ HTTPS ์ข…๋ฃŒ ํ›„ HTTP๋กœ ์ „๋‹ฌ๋˜๋ฏ€๋กœ
    },
    rolling: true,
    store: new RedisStore({
      client: redisClient,
      ttl: 24 * 60 * 60,  // 24์‹œ๊ฐ„
    }),
  })
);

์„ธ์…˜ ๋ผ์ดํ”„์‚ฌ์ดํด

๋กœ๊ทธ์ธ ์„ฑ๊ณต
    โ”‚
    โ–ผ
์„ธ์…˜ ์ƒ์„ฑ โ†’ Redis์— ์ €์žฅ (TTL: 24์‹œ๊ฐ„)
    โ”‚
    โ–ผ
์„ธ์…˜ ์ฟ ํ‚ค ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌ (httpOnly)
    โ”‚
    โ–ผ
์ดํ›„ ์š”์ฒญ๋งˆ๋‹ค ์„ธ์…˜ ํ™•์ธ + TTL ๊ฐฑ์‹  (rolling)
    โ”‚
    โ”œโ”€ 24์‹œ๊ฐ„ ๋ฏธํ™œ๋™ โ†’ ์„ธ์…˜ ์ž๋™ ๋งŒ๋ฃŒ
    โ””โ”€ ๋กœ๊ทธ์•„์›ƒ โ†’ ์„ธ์…˜ ๋ช…์‹œ์  ์‚ญ์ œ

CORS ์„ค์ •

ํ—ˆ์šฉ๋œ ๋„๋ฉ”์ธ์—์„œ๋งŒ API์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก CORS๋ฅผ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.

// backend/src/app.js
app.use(
  cors({
    origin: process.env["URL"].trim(),  // ํ—ˆ์šฉํ•  ํด๋ผ์ด์–ธํŠธ URL
    credentials: true,                   // ์ฟ ํ‚ค ์ „์†ก ํ—ˆ์šฉ
  })
);
ํ•ญ๋ชฉ ์„ค๋ช…
origin ํ™˜๊ฒฝ ๋ณ€์ˆ˜ URL์— ์„ค์ •๋œ ํด๋ผ์ด์–ธํŠธ ๋„๋ฉ”์ธ๋งŒ ํ—ˆ์šฉ
credentials true - ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ ํฌํ•จํ•œ ์š”์ฒญ ํ—ˆ์šฉ

Important

CORS origin์—๋Š” ์ •ํ™•ํ•œ ํด๋ผ์ด์–ธํŠธ URL์„ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์™€์ผ๋“œ์นด๋“œ(*)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด credentials: true์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋ณด์•ˆ์ƒ์œผ๋กœ๋„ ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค.


ํ†ต์‹  ๋ณด์•ˆ

HTTPS ๊ตฌ์„ฑ

๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ-์„œ๋ฒ„ ํ†ต์‹ ์€ HTTPS๋กœ ์•”ํ˜ธํ™”๋ฉ๋‹ˆ๋‹ค.

๊ตฌ๊ฐ„ HTTPS ์ฒ˜๋ฆฌ ์ธ์ฆ์„œ
ํด๋ผ์ด์–ธํŠธ โ†’ CloudFront CloudFront์—์„œ HTTPS ์ข…๋ฃŒ ACM (us-east-1)
ํด๋ผ์ด์–ธํŠธ โ†’ ALB ALB์—์„œ HTTPS ์ข…๋ฃŒ ACM (์„œ๋น„์Šค ๋ฆฌ์ „)
ALB โ†’ EC2 HTTP (๋‚ด๋ถ€ ๋„คํŠธ์›Œํฌ) -
[ํด๋ผ์ด์–ธํŠธ] โ”€โ”€โ”€ HTTPS โ”€โ”€โ”€โ†’ [CloudFront/ALB] โ”€โ”€โ”€ HTTP โ”€โ”€โ”€โ†’ [EC2]
                                    โ”‚
                              ACM ์ธ์ฆ์„œ๋กœ
                              TLS ์ข…๋ฃŒ

S3 Pre-signed URL

S3์— ์ €์žฅ๋œ ํŒŒ์ผ์€ ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์—†์œผ๋ฉฐ, Pre-signed URL์„ ํ†ตํ•ด์„œ๋งŒ ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ ์„ค์ •
๋งŒ๋ฃŒ ์‹œ๊ฐ„ 5๋ถ„ (300์ดˆ)
์„œ๋ช… ๋ฐฉ์‹ AWS Signature V4
์šฉ๋„ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ URL ์ƒ์„ฑ
// backend/src/_s3/fileBucket.js
const signedUrlExpireSeconds = 60 * 5; // 5๋ถ„

export const signUrl = (key, filename, seconds = signedUrlExpireSeconds) => {
  const preSignedUrl = fileS3.getSignedUrl("getObject", {
    Bucket: fileBucket,
    Key: key,
    Expires: seconds,
    ResponseContentDisposition: `attachment; filename="${encodeURI(filename)}"`,
  });
  // ...
};

Pre-signed URL์˜ ๋™์ž‘ ๋ฐฉ์‹:

1. ์‚ฌ์šฉ์ž๊ฐ€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์š”์ฒญ
2. ์„œ๋ฒ„์—์„œ S3 Pre-signed URL ์ƒ์„ฑ (5๋ถ„ ์œ ํšจ)
3. ํด๋ผ์ด์–ธํŠธ์— URL ์ „๋‹ฌ
4. ํด๋ผ์ด์–ธํŠธ๊ฐ€ URL๋กœ S3์— ์ง์ ‘ ๋‹ค์šด๋กœ๋“œ
5. 5๋ถ„ ํ›„ URL ์ž๋™ ๋งŒ๋ฃŒ

์ธ์ฆ ๋ณด์•ˆ

Passport.js ๊ธฐ๋ฐ˜ ์ธ์ฆ

Altsis๋Š” Passport.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‘ ๊ฐ€์ง€ ์ธ์ฆ ์ „๋žต์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์ „๋žต ํŒŒ์ผ ์„ค๋ช…
Local Strategy _passport/localStrategy2.js ์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ ๋กœ๊ทธ์ธ
Google Strategy _passport/googleStrategy2.js Google OAuth 2.0 ๋กœ๊ทธ์ธ

์ธ์ฆ ํ๋ฆ„

๋กœ์ปฌ ์ธ์ฆ

์‚ฌ์šฉ์ž โ†’ ์•„์ด๋”” + ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ
    โ”‚
    โ–ผ
Passport Local Strategy
    โ”‚
    โ”œโ”€ User ๋ชจ๋ธ์—์„œ ์‚ฌ์šฉ์ž ์กฐํšŒ
    โ”œโ”€ bcrypt.compare()๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ
    โ”‚
    โ”œโ”€ ์„ฑ๊ณต โ†’ serializeUser โ†’ ์„ธ์…˜ ์ƒ์„ฑ
    โ””โ”€ ์‹คํŒจ โ†’ ์—๋Ÿฌ ์‘๋‹ต

Google OAuth 2.0 ์ธ์ฆ

์‚ฌ์šฉ์ž โ†’ Google ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ํด๋ฆญ
    โ”‚
    โ–ผ
Google OAuth ์ธ์ฆ ํŽ˜์ด์ง€ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
    โ”‚
    โ–ผ
์‚ฌ์šฉ์ž โ†’ Google ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ
    โ”‚
    โ–ผ
Google โ†’ ์ฝœ๋ฐฑ URL๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ (์ธ์ฆ ์ฝ”๋“œ ํฌํ•จ)
    โ”‚
    โ–ผ
Passport Google Strategy
    โ”‚
    โ”œโ”€ ์ธ์ฆ ์ฝ”๋“œ๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ
    โ”œโ”€ ๊ธฐ์กด ์‚ฌ์šฉ์ž์™€ ๋งค์นญ
    โ”‚
    โ”œโ”€ ์„ฑ๊ณต โ†’ serializeUser โ†’ ์„ธ์…˜ ์ƒ์„ฑ
    โ””โ”€ ์‹คํŒจ โ†’ ์—๋Ÿฌ ์‘๋‹ต

์„ธ์…˜ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”

// ์ง๋ ฌํ™”: ์„ธ์…˜์— ์ตœ์†Œ ์ •๋ณด๋งŒ ์ €์žฅ
passport.serializeUser(({ user, academyId }, done) => {
  done(null, { _id: user._id, academyId });
});

// ์—ญ์ง๋ ฌํ™”: ๋งค ์š”์ฒญ๋งˆ๋‹ค ์„ธ์…˜์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ณต์›
passport.deserializeUser(({ _id, academyId }, done) => {
  User(academyId).findOne({ _id }, (err, user) => {
    user["academyId"] = academyId;
    done(null, user);
  });
});

๋ฏธ๋“ค์›จ์–ด ๊ธฐ๋ฐ˜ ๊ถŒํ•œ ์ฒดํฌ

API ์š”์ฒญ์€ ์‚ฌ์šฉ์ž ์—ญํ• ์— ๋”ฐ๋ผ ์ ‘๊ทผ์ด ์ œํ•œ๋ฉ๋‹ˆ๋‹ค.

์—ญํ•  ์ฝ”๋“œ ๊ถŒํ•œ ๋ฒ”์œ„
Owner owner ์‹œ์Šคํ…œ ์ „์ฒด ๊ด€๋ฆฌ
Admin admin ์•„์นด๋ฐ๋ฏธ ๊ด€๋ฆฌ
Manager manager ํ•™๊ต ๊ด€๋ฆฌ
Member member ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž ๊ธฐ๋Šฅ
Guest (๋ฏธ์ธ์ฆ) ๊ณต๊ฐœ API๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
HTTP ์š”์ฒญ
    โ”‚
    โ–ผ
์„ธ์…˜ ํ™•์ธ (req.isAuthenticated())
    โ”‚
    โ”œโ”€ ๋ฏธ์ธ์ฆ โ†’ Guest ์ฒ˜๋ฆฌ ๋˜๋Š” 403 ์‘๋‹ต
    โ”‚
    โ””โ”€ ์ธ์ฆ๋จ โ†’ ์—ญํ•  ํ™•์ธ (req.user.auth)
        โ”‚
        โ”œโ”€ ๊ถŒํ•œ ์ถฉ๋ถ„ โ†’ ์š”์ฒญ ์ฒ˜๋ฆฌ
        โ””โ”€ ๊ถŒํ•œ ๋ถ€์กฑ โ†’ 403 Forbidden

ํŒŒ์ผ ๋ณด์•ˆ

S3 ๋ฒ„ํ‚ท ์ ‘๊ทผ ์ œ์–ด

๋ฒ„ํ‚ท ์ ‘๊ทผ ๋ฐฉ์‹ ์„ค๋ช…
ํด๋ผ์ด์–ธํŠธ ๋ฒ„ํ‚ท Public Read ์ •์  ํŒŒ์ผ ์„œ๋น™ (CloudFront ๊ฒฝ์œ )
ํŒŒ์ผ ๋ฒ„ํ‚ท Private Pre-signed URL์„ ํ†ตํ•œ ์ธ์ฆ๋œ ์ ‘๊ทผ๋งŒ ํ—ˆ์šฉ
๋กœ๊ทธ ๋ฒ„ํ‚ท Private ์„œ๋ฒ„์—์„œ๋งŒ ์“ฐ๊ธฐ, ๊ด€๋ฆฌ์ž๋งŒ ์ฝ๊ธฐ

IAM ์ตœ์†Œ ๊ถŒํ•œ ์›์น™

๊ฐ ์„œ๋น„์Šค์— ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.

CI/CD์šฉ IAM ์‚ฌ์šฉ์ž

์„œ๋น„์Šค ํ—ˆ์šฉ ์ž‘์—… ๋Œ€์ƒ ๋ฆฌ์†Œ์Šค
S3 s3:PutObject, s3:DeleteObject, s3:ListBucket ํด๋ผ์ด์–ธํŠธ ๋ฒ„ํ‚ท

๋ฐฑ์—”๋“œ ์„œ๋ฒ„์šฉ IAM ์‚ฌ์šฉ์ž

์„œ๋น„์Šค ํ—ˆ์šฉ ์ž‘์—… ๋Œ€์ƒ ๋ฆฌ์†Œ์Šค
S3 s3:GetObject, s3:PutObject, s3:DeleteObject, s3:ListBucket ํŒŒ์ผ ๋ฒ„ํ‚ท
S3 s3:PutObject ๋กœ๊ทธ ๋ฒ„ํ‚ท

ํŒŒ์ผ ์—…๋กœ๋“œ ๋ณด์•ˆ

ํ•ญ๋ชฉ ์„ค์ •
ํŒŒ์ผ ํฌ๊ธฐ ์ œํ•œ Multer ์„ค์ •์— ๋”ฐ๋ฆ„
ํŒŒ์ผ ํƒ€์ž… ๊ฒ€์ฆ MIME ํƒ€์ž… ์ฒดํฌ
์ €์žฅ ๊ฒฝ๋กœ ์•„์นด๋ฐ๋ฏธ ID๋กœ ๊ฒฉ๋ฆฌ ({academyId}/archive/...)

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ด€๋ฆฌ

.env ํŒŒ์ผ ๋ณด์•ˆ

๊ทœ์น™ ์„ค๋ช…
.gitignore ํฌํ•จ .env ํŒŒ์ผ์ด Git์— ์ปค๋ฐ‹๋˜์ง€ ์•Š๋„๋ก ๋ณด์žฅ
๋กœ์ปฌ ๊ด€๋ฆฌ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋กœ์ปฌ์—์„œ ์ง์ ‘ ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ
๊ณต์œ  ๊ธˆ์ง€ ์Šฌ๋ž™, ์ด๋ฉ”์ผ ๋“ฑ์œผ๋กœ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ณต์œ  ๊ธˆ์ง€

GitHub Secrets ํ™œ์šฉ

ํŠน์„ฑ ์„ค๋ช…
์•”ํ˜ธํ™” ์ €์žฅ GitHub์ด Secrets์„ ์•”ํ˜ธํ™”ํ•˜์—ฌ ์ €์žฅ
์›Œํฌํ”Œ๋กœ์šฐ์—์„œ๋งŒ ์ ‘๊ทผ GitHub Actions ์‹คํ–‰ ์‹œ์—๋งŒ ๋ณตํ˜ธํ™”
๋กœ๊ทธ ๋งˆ์Šคํ‚น ์›Œํฌํ”Œ๋กœ์šฐ ๋กœ๊ทธ์—์„œ ์ž๋™์œผ๋กœ ๋งˆ์Šคํ‚น ์ฒ˜๋ฆฌ
์ ‘๊ทผ ์ œํ•œ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๊ด€๋ฆฌ์ž๋งŒ ์„ค์ • ๊ฐ€๋Šฅ

๋ฏผ๊ฐ ์ •๋ณด ๋ถ„๋ฅ˜

๋“ฑ๊ธ‰ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์œ ์ถœ ์‹œ ์˜ํ–ฅ
์ตœ๊ณ  ENCKEY_*, SIGKEY_* ์•”ํ˜ธํ™”๋œ ํ•™์ƒ ๋ฐ์ดํ„ฐ ๋ณตํ˜ธํ™” ๊ฐ€๋Šฅ
๋†’์Œ DB_URL, REDIS_URL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ง์ ‘ ์ ‘๊ทผ ๊ฐ€๋Šฅ
๋†’์Œ s3_secretAccessKey* S3 ๋ฒ„ํ‚ท ์ ‘๊ทผ ๊ฐ€๋Šฅ
์ค‘๊ฐ„ session_key ์„ธ์…˜ ์œ„์กฐ ๊ฐ€๋Šฅ์„ฑ
์ค‘๊ฐ„ GOOGLE_CLIENT_ID OAuth ์ธ์ฆ ์•…์šฉ ๊ฐ€๋Šฅ์„ฑ
๋‚ฎ์Œ SERVER_PORT, S3_REGION ์‹œ์Šคํ…œ ๊ตฌ์„ฑ ์ •๋ณด ๋…ธ์ถœ

๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๋ฐฐํฌ ์ „ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • ๋ชจ๋“  ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ GitHub Secrets์— ๋“ฑ๋ก๋˜์–ด ์žˆ๋Š”๊ฐ€
  • .env ํŒŒ์ผ์ด .gitignore์— ํฌํ•จ๋˜์–ด ์žˆ๋Š”๊ฐ€
  • HTTPS๊ฐ€ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘์— ์ ์šฉ๋˜์–ด ์žˆ๋Š”๊ฐ€
  • CORS origin์ด ์ •ํ™•ํ•œ ํด๋ผ์ด์–ธํŠธ URL๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”๊ฐ€
  • MongoDB Atlas ๋„คํŠธ์›Œํฌ ์ ‘๊ทผ์ด EC2 IP๋กœ ์ œํ•œ๋˜์–ด ์žˆ๋Š”๊ฐ€
  • S3 ๋ฒ„ํ‚ท ์ •์ฑ…์ด ์ตœ์†Œ ๊ถŒํ•œ์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”๊ฐ€
  • IAM ์‚ฌ์šฉ์ž์— ์ตœ์†Œ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌ๋˜์–ด ์žˆ๋Š”๊ฐ€
  • bcrypt salt rounds๊ฐ€ 10 ์ด์ƒ์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”๊ฐ€
  • ์•”ํ˜ธํ™” ํ‚ค๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐฑ์—…๋˜์–ด ์žˆ๋Š”๊ฐ€

์ •๊ธฐ ๋ณด์•ˆ ์ ๊ฒ€ ํ•ญ๋ชฉ

  • GitHub Personal Access Token ๋งŒ๋ฃŒ ์—ฌ๋ถ€ ํ™•์ธ
  • AWS IAM ์ ‘๊ทผ ํ‚ค ๊ต์ฒด (90์ผ ์ฃผ๊ธฐ ๊ถŒ์žฅ)
  • S3 ๋ฒ„ํ‚ท ํผ๋ธ”๋ฆญ ์ ‘๊ทผ ์„ค์ • ํ™•์ธ
  • MongoDB Atlas ๋„คํŠธ์›Œํฌ ์ ‘๊ทผ ๋ชฉ๋ก ๊ฒ€ํ† 
  • ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž ๊ณ„์ • ๋น„ํ™œ์„ฑํ™”
  • ์„œ๋ฒ„ ๋กœ๊ทธ์—์„œ ๋น„์ •์ƒ ์ ‘๊ทผ ํŒจํ„ด ํ™•์ธ
  • Node.js ์˜์กด์„ฑ ๋ณด์•ˆ ์ทจ์•ฝ์  ์Šค์บ” (npm audit)
  • Docker ์ด๋ฏธ์ง€ ๋ณด์•ˆ ์Šค์บ”

์ธ์‹œ๋˜ํŠธ ๋Œ€์‘ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋‚˜ ํ‚ค๊ฐ€ ์œ ์ถœ๋œ ๊ฒฝ์šฐ:

    • ์œ ์ถœ๋œ ํ‚ค/ํ† ํฐ ์ฆ‰์‹œ ๋ฌดํšจํ™”
    • ์ƒˆ ํ‚ค/ํ† ํฐ ์ƒ์„ฑ ๋ฐ ๊ต์ฒด
    • GitHub Secrets ์—…๋ฐ์ดํŠธ
    • ์„œ๋ฒ„ ์žฌ๋ฐฐํฌ
    • ์ ‘๊ทผ ๋กœ๊ทธ ๋ถ„์„ (์œ ์ถœ ๊ธฐ๊ฐ„ ๋™์•ˆ์˜ ๋น„์ •์ƒ ์ ‘๊ทผ ํ™•์ธ)
    • ์˜ํ–ฅ๋ฐ›์€ ๋ฐ์ดํ„ฐ ๋ฒ”์œ„ ํŒŒ์•…
    • ํ•„์š” ์‹œ ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ•์ œ ๋ณ€๊ฒฝ
    • ์ธ์‹œ๋˜ํŠธ ๋ณด๊ณ ์„œ ์ž‘์„ฑ

๋ณด์•ˆ ์ทจ์•ฝ์  ๋ณด๊ณ 

Altsis์˜ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ๋ฐœ๊ฒฌํ•œ ๊ฒฝ์šฐ, ๋‹ค์Œ ์ฑ„๋„๋กœ ๋ณด๊ณ ํ•ด ์ฃผ์„ธ์š”.

ํ•ญ๋ชฉ ์—ฐ๋ฝ์ฒ˜
GitHub @devgoodway
๋ณด๊ณ  ๋ฐฉ์‹ GitHub Issue (Private) ๋˜๋Š” ์ง์ ‘ ์—ฐ๋ฝ

๋ณด๊ณ  ์‹œ ํฌํ•จํ•  ๋‚ด์šฉ

  • ์ทจ์•ฝ์  ์œ ํ˜• (์˜ˆ: XSS, CSRF, ์ธ์ฆ ์šฐํšŒ ๋“ฑ)
  • ์žฌํ˜„ ์ ˆ์ฐจ (๋‹จ๊ณ„๋ณ„)
  • ์˜ํ–ฅ ๋ฒ”์œ„
  • ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ, ๊ฐœ์„  ๋ฐฉ์•ˆ ์ œ์•ˆ

Warning

๋ณด์•ˆ ์ทจ์•ฝ์ ์€ ๊ณต๊ฐœ Issue๋กœ ๋“ฑ๋กํ•˜์ง€ ๋งˆ์„ธ์š”. ๋ฐ˜๋“œ์‹œ ๋น„๊ณต๊ฐœ ์ฑ„๋„์„ ํ†ตํ•ด ๋ณด๊ณ ํ•ด ์ฃผ์„ธ์š”. ์ทจ์•ฝ์ ์ด ์ˆ˜์ •๋  ๋•Œ๊นŒ์ง€ ๊ณต๊ฐœ๋ฅผ ์ž์ œํ•ด ์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.