๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๋…์„œ

[ ๋…ํ›„๊ฐ ] ํ•œ ๊ถŒ์œผ๋กœ ๋๋‚ด๋Š” Node & Express 2ํŒ์„ ์ฝ๊ณ ...

by YWTechIT 2022. 2. 28.
728x90

๐Ÿ“ [ ๋…ํ›„๊ฐ ] Web Development with Node & Express 2ํŒ์„ ์ฝ๊ณ ...


โœ๏ธ ์„œ๋ก 

์ด ์ฑ…์„ ๋ณด๊ฒŒ ๋œ ๊ณ„๊ธฐ๋Š” ์—˜๋ฆฌ์Šค ํŒ€ ํ”„๋กœ์ ํŠธ์—์„œ ํ”„๋ก ํŠธ์—”๋“œ๋ฅผ ์ค€๋น„ํ•˜๊ณ  ์žˆ๋˜ ๋‚˜์—๊ฒŒ ๋ฌด์ง€ํ–ˆ๋˜ ๋ฐฑ์—”๋“œ ํŒŒํŠธ๋ฅผ ๋‹ด๋‹นํ•˜๊ฒŒ ๋˜์—ˆ๊ณ , ๋งจ๋•…์— ํ—ค๋”ฉํ•˜๋Š” ๊ฒฉ์œผ๋กœ ๋ฐฑ์—”๋“œ์— ๊ด€ํ•œ ์ •๋ณด๋ฅผ ์ด๋ฆฌ์ €๋ฆฌ ์ฐพ์•„๋ณด๋˜ ์ค‘ ๋น„๊ต์  ์ตœ๊ทผ(2021.5.21.)์— ์ถœํŒํ–ˆ๊ณ  Spring์„ ๋‹ค๋ฃจ๋Š” ์ฑ…์ด ์•„๋‹ˆ๋ผ Express.js๋ฅผ ๋‹ค๋ฃจ๋Š” ์„œ์ ์ด์–ด์„œ ๊ตฌ๋งคํ–ˆ๋‹ค. ์ฑ…์€ ์•Œ๋ผ๋”˜์—์„œ ์ฃผ๋ฌธํ–ˆ๊ณ , ์ฑ…์€ ์•ฝ 450ํŽ˜์ด์ง€๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ์œผ๋ฉฐ ๋…์„œ๊ธฐ๊ฐ„์€ 2022.01.25 ~ 2022.02.19์ด๋‹ค.


728x90

โœ๏ธ ๋ณธ๋ก 

ํ‹ˆํ‹ˆ์ด ์ฑ…์„ ์ฝ์œผ๋ฉฐ ๋‹น์žฅ์— ๋ฐฑ์—”๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผํ•˜๋Š”ํ„ฐ๋ผ ๋‚ด๊ฐ€ ํ•„์š”ํ•œ ์ฃผ์ œ๋งŒ ๋จผ์ € ์‚ดํŽด๋ดค๋‹ค. ์ต์Šคํ”„๋ ˆ์Šค ์†Œ๊ฐœ, ํ’ˆ์งˆ๋ณด์ฆ, ์ฟ ํ‚ค์™€ ์„ธ์…˜, ๋ฏธ๋“ค์›จ์–ด, ์ง€์†์„ฑ, ๋ผ์šฐํŒ…, REST API์™€ JSON, SPA, ๋ณด์•ˆ ํŒŒํŠธ์˜€๋‹ค. ์ด ์ค‘์—์„œ ๊ฐ€์žฅ ๊ธฐ์–ต์— ๋‚จ๋Š” ๋ถ€๋ถ„์€ ๋ฐ”๋กœ ๋ฏธ๋“ค์›จ์–ด, ์ฟ ํ‚ค์™€ ์„ธ์…˜์ธ๋ฐ, ๋‚˜๋Š” JWT๋ฅผ ์ด์šฉํ•˜์—ฌ ์ธ์ฆ์„ ๊ตฌํ˜„ํ–ˆ์ง€๋งŒ ์„œ๋ฒ„์—์„œ ์ฟ ํ‚ค๋ฅผ ์ „ํ˜€ ์ธ์‹ํ•˜์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ ๊ฒฐ๊ตญ cookie-parser ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„์„œ ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ์˜€๋‹ค.. (์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์•ฝ 3์‹œ๊ฐ„์„ ์‚ฝ์งˆํ–ˆ๋‹ค.) ๋•๋ถ„์— ๋ฏธ๋“ค์›จ์–ด์˜ ์ค‘์š”์„ฑ์„ ๊นจ๋‹ฌ์•˜๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฟ ํ‚ค์— ๋Œ€ํ•ด ๊ธฐ์กด์— ์•Œ๊ณ  ์žˆ๋˜ ์ง€์‹๊ณผ ๋”๋ถˆ์–ด ๋ช‡ ๊ฐ€์ง€ ์ƒˆ๋กœ์šด ์‚ฌ์‹ค์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ, ์ฟ ํ‚ค์˜ ๊ด€ํ•ด ์•Œ์•„๋‘ฌ์•ผ ํ•  ์ค‘์š”ํ•œ ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1. ์‚ฌ์šฉ์ž๊ฐ€ ์ฟ ํ‚ค ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
  - ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ์— ๋ณด๋‚ด๋Š” ์ฟ ํ‚ค๋Š” ๋ชจ๋‘ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ฟ ํ‚ค๋ฅผ ์•”ํ˜ธํ™”ํ•ด์„œ ์ฝ˜ํ…์ธ ๋ฅผ ๋ณดํ˜ธํ•  ์ˆ˜ ์žˆ๊ธด ํ•˜์ง€๋งŒ, ๋–ณ๋–ณํ•œ ๋ชฉ์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๊ตณ์ด ์•”ํ˜ธํ™”ํ•  ์ด์œ ๊ฐ€ ์—†๋‹ค. ์„œ๋ช…๋œ (Signed)์ฟ ํ‚ค๋Š” ์ฝ˜ํ…์ธ ๋ฅผ ์ฝ๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค๊ธด ํ•˜๊ฒ ์ง€๋งŒ, ์•”ํ˜ธํ™”๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์ด ์•„๋‹ˆ๋‹ค.

2. ์‚ฌ์šฉ์ž๊ฐ€ ์ฟ ํ‚ค๋ฅผ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜ ๊ธˆ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  - ์‚ฌ์šฉ์ž๋Š” ์ฟ ํ‚ค ์ „์ฒด์— ๋Œ€ํ•œ ๊ถŒํ•œ์ด ์žˆ๊ณ , ๋ธŒ๋ผ์šฐ์ €์—๋Š” ์ฟ ํ‚ค๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ, ๋˜๋Š” ํ•œ๊บผ๋ฒˆ์— ์‚ญ์ œํ•˜๋Š” ๊ธฐ๋Šฅ์ด ์žˆ๋‹ค. ๋˜ํ•œ, ์‚ฌ์šฉ์ž๊ฐ€ ์ฟ ํ‚ค๋ฅผ ๊ธˆ์ง€ํ•  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ ์ด๋Ÿด ๋• ์ฟ ํ‚ค ์—†์ด๋„ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ •๋ง ๋‹จ์ˆœํ•œ ๊ธฐ๋Šฅ ๋ง๊ณ ๋Š” ์ˆ˜ํ–‰ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

3. ์ฟ ํ‚ค๋Š” ๊ณต๊ฒฉ์— ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค.
  - XSS(cross-site-scripting attack) ๊ณต๊ฒฉ ์ค‘ ์•…์˜์ ์ธ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๋กœ ์ฟ ํ‚ค ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐ”๊พธ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค. ์„œ๋ฒ„์— ์ „์†๋˜๋Š” ์ฟ ํ‚ค๋ฅผ ๋งน์‹ ํ•ด์„œ๋Š” ์•ˆ ๋˜๋Š” ์ด์œ  ์ค‘ ํ•˜๋‚˜๋‹ค. `์„œ๋ช…๋œ ์ฟ ํ‚ค(Signed cookie)`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž๋‚˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๋กœ ์ฟ ํ‚ค๋ฅผ ๋ณ€์กฐํ–ˆ์„ ๊ฒฝ์šฐ ๊ทธ ํ”์ ์ด ๋ช…๋ฐฑํžˆ ๋“œ๋Ÿฌ๋‚˜๋ฏ€๋กœ ๊ณต๊ฒฉ์„ ๋ง‰๋Š”๋ฐ ๋„์›€์ด ๋œ๋‹ค. (์„œ๋ช…๋œ ์ฟ ํ‚ค๋Š” ์ผ๋ฐ˜์ ์ธ ์ฟ ํ‚ค๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’๋‹ค. ์„œ๋ช…๋œ ์ฟ ํ‚ค์—์„œ ์‚ฌ์šฉ๋œ ์ด๋ฆ„์„ ์ผ๋ฐ˜ ์ฟ ํ‚ค์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.) ๋˜๋Š” `httpOnly` ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ฟ ํ‚ค๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ๋ชปํ•˜๊ณ  ์„œ๋ฒ„์—์„œ๋งŒ ๊ฐ€๋Šฅํ•˜๊ฒŒ๋” ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ ํ™œ์šฉ๋„๊ฐ€ ์กฐ๊ธˆ ๋–จ์–ด์ง€์ง€๋งŒ ํ›จ์”ฌ ์•ˆ์ „ํ•˜๋‹ค.

4. ์ฟ ํ‚ค๋ฅผ ๋‚จ์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆˆ์น˜์ฑˆ๋‹ค.
  - ์‚ฌ์šฉ์ž์˜ ์ปดํ“จํ„ฐ์— ์ฟ ํ‚ค๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ˆ๋ฌด ๋งŽ์ด ์ €์žฅํ•˜๋ฉด ์‚ฌ์šฉ์ž๋ฅผ ์งœ์ฆ๋‚˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ”ผํ•ด์•ผ ํ•œ๋‹ค. ์ฟ ํ‚ค๋Š” ์ตœ์†Œํ•œ์œผ๋กœ ์œ ์ง€ํ•ด๋ผ.

5. ์ฟ ํ‚ค์—์„œ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋Š” option
  - domain: ์ฟ ํ‚ค์— ๋„๋ฉ”์ธ์„ ์—ฐ๊ฒฐํ•œ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์žฌ ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰ ์ค‘์ธ ๋„๋ฉ”์ธ์ด ์•„๋‹Œ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ๊ณผ ์—ฐ๊ฒฐํ•  ์ˆ˜๋Š” ์—†๋‹ค.
  - path: ์ฟ ํ‚ค๊ฐ€ ์ ์šฉ๋  ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•œ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์ธ `/`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์ดํŠธ์˜ ๋ชจ๋“  ํŽ˜์ด์ง€์— ์ฟ ํ‚ค๊ฐ€ ์ ์šฉ๋œ๋‹ค. ๊ฒฝ๋กœ๋ฅผ `/foo`๋กœ ์„ค์ •ํ•˜๋ฉด `/foo`, `/foo/bar` ๋“ฑ์— ์ ์šฉ๋œ๋‹ค.
  - maxAge: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฟ ํ‚ค๋ฅผ ์–ผ๋งˆ๋‚˜ ์˜ค๋ž˜ ๋ณด๊ด€ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•˜๋Š”์ง€ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋กœ ์ง€์ •ํ•œ๋‹ค. ์ด ์˜ต์…˜์„ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ฟ ํ‚ค๋Š” ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ์„ ๋•Œ ์‚ญ์ œ๋œ๋‹ค. `expired` ์˜ต์…˜์—์„œ ๋งŒ๋ฃŒ ๋‚ ์งœ๋ฅผ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ ๋ฌธ๋ฒ•์ด ๋ณต์žกํ•˜๊ธฐ ๋•Œ๋ฌธ์— `maxAge`๋ฅผ ๊ถŒํ•œ๋‹ค.
  - secure: `HTTPS` ์—ฐ๊ฒฐ์„ ํ†ตํ•ด์„œ๋งŒ ์ฟ ํ‚ค๋ฅผ ์ „์†กํ•œ๋‹ค.
  - httpOnly: ์„œ๋ฒ„์—์„œ๋งŒ ์ฟ ํ‚ค๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค. ์ฆ‰, client์—์„œ๋Š” ์ฟ ๋ฆฌ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค. XSS๊ณต๊ฒฉ์„ ๋ง‰๋Š”๋ฐ ๋„์›€์ด ๋œ๋‹ค.
  - signed: ์ฟ ํ‚ค์— ์„œ๋ช…์„ ํ•ด์„œ `res.cookie`๊ฐ€ ์•„๋‹ˆ๋ผ `res.signedCookies`์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค. ์„œ๋ช…๋œ ์ฟ ํ‚ค๊ฐ€ ๋ณ€์กฐ๋˜๋ฉด ์„œ๋ฒ„๋Š” ๊ทธ ์ฟ ํ‚ค๋ฅผ ๊ฑฐ๋ถ€ํ•˜๊ณ  ์›๋ž˜ ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™” ํ•œ๋‹ค.

๋˜ํ•œ, passport๋ฅผ ์ด์šฉํ•ด ์ธ์ฆํŒŒํŠธ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ, GitHub Strategy๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ธ์ฆ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” passport ํ™ˆํŽ˜์ด์ง€์˜ docs์™€ ๋น„์Šทํ•˜๊ฒŒ ์ž‘์„ฑํ–ˆ์ง€๋งŒ, ๋‹ค์Œ ์ฝ”๋“œ๋Š” ๋‚ด๊ฐ€ GitFarm ํ”„๋กœ์ ํŠธ์—์„œ ์ž‘์„ฑํ•œ ๋ฐฑ์—”๋“œ ์ฝ”๋“œ์ด๋‹ค. ์ฝ”๋“œ๋Š” ์งง์•„ ๋ณด์ด์ง€๋งŒ ๋‚˜์˜ ์„œ๋น„์Šค์— ํ•„์š”ํ•œ ์ •๋ณด์— ๋งž๊ฒŒ ์ˆ˜์ •ํ•˜๋Š”๋ฐ ๊ทธ ์ž‘์—…์ด ์ƒ๊ฐ๋ณด๋‹ค ์˜ค๋ž˜ ๊ฑธ๋ ธ๋‹ค.

// passport/strategy/GitHub.js
import GitHub from "passport-github2";
import keys from "../../config/keys.js";
import { User } from "../../model/index.js";

const GitHubStrategy = GitHub.Strategy;
const { clientID, clientSecret, callbackURL } = keys.GitHub;

const config = {
  clientID,
  clientSecret,
  callbackURL,
};

export default new GitHubStrategy(
  config,
  async (accessToken, refreshToken, profile, done) => {
    const { username } = profile;
    const { id, avatar_url: avatarUrl } = profile._json;

    const findUser = await User.findOne({ id });
    if (!findUser) {
      const newUser = new User({
        id,
        username,
        avatarUrl,
        accessToken,
      });

      newUser.save();
      return done(null, newUser);
    }
    return done(null, findUser);
  },
);

// routes/api/auth.js
router.get(
  "/github",
  passport.authenticate("github", {
    scope: ["repo", "profile", "user"],
    session: false,
  }),
);

router.get(
  "/github/callback",
  passport.authenticate("github", {
     session: false,
    failureRedirect: "/api/auth/login/failed",
  }),
  async (req, res) => {
    const { id, username } = req.user;
    const payload = { id, username };
    const token = await createToken(payload);
    res.cookie("token", token, cookieConfig);
    res.redirect("/loading");
  },
);

 


โœ๏ธ ๊ฒฐ๋ก 

์ด ์ฑ…์„ ์ฝ์œผ๋ฉฐ ์ตœ์†Œํ•œ์˜ ๋ฐฑ์—”๋“œ์—์„œ ํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š”๋ฐ ๋„์›€์„ ๋งŽ์ด ๋ฐ›์•˜๋‹ค. ์•„์ง๊นŒ์ง€ ์šฐ๋ฆฌ๋‚˜๋ผ์—์„œ๋Š” Express.js๋ณด๋‹ค Spring์„ ๋” ๋งŽ์ด ์‚ฌ์šฉํ•˜์ง€๋งŒ, ๋ฐฑ์—”๋“œ์˜ ํฐ ํ‹€์€ ๋ฒ—์–ด๋‚˜์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค. JAVA๋ณด๋‹ค JS๋ฅผ ๋” ์ž˜ ์•Œ๋ฉด์„œ ๋ฐฑ์—”๋“œ๋ฅผ ๋ง›๋ณด๊ณ  ์‹ถ์€ ๋ถ„๋“ค์—๊ฒŒ ์ถ”์ฒœํ•ด์ฃผ๊ณ  ์‹ถ์€ ์ฑ…์ด๋‹ค.

 

reference

  1. ํ•œ ๊ถŒ์œผ๋กœ ๋๋‚ด๋Š” Node & Express : ๋ชจ๋˜ ์›น์„ ์œ„ํ•œ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๋ชจ๋“  ๊ฒƒ
  2. Passport-GitHub2
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€