๊ด€๋ฆฌ ๋ฉ”๋‰ด

Daily Front_Minhhk

[cookie] sprint-auth-cookie ๋ณธ๋ฌธ

Code๊ฐœ๋ฐœ์ผ์ง€

[cookie] sprint-auth-cookie

Minhhk 2023. 1. 7. 20:45
๐Ÿคช ์–ด๋–ป๊ฒŒ ์ฐพ์œผ์‹  ๋ถ„์—๊ฒ ๋„์›€์ด ๋˜๊ธธ,, ์–ด๋ ค์›Œ์„œ ๊ณ ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž‘์„ฑ ๋ถ€๋ถ„ ์ •๋„๋กœ ๊ฐ„๋žตํžˆ!!

๊ณผ์ •์€ ์ด๋ ‡๋‹ค!

 

๐Ÿ’ก ์„œ๋ฒ„์—ด๊ธฐ

 

HTTPS ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก

์„œ๋ฒ„์˜ index.js ํŒŒ์ผ์„ ํ™•์ธํ•ด๋ณด๋ฉด ์ธ์ฆ์„œ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ฝ”๋“œ๊ฐ€ ์žˆ๋Š”๋ฐ,

// ์ธ์ฆ์„œ ํŒŒ์ผ๋“ค์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ https ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
// ๋งŒ์•ฝ ์ธ์ฆ์„œ ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š”๊ฒฝ์šฐ, http ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
// ํŒŒ์ผ ์กด์žฌ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ํด๋”๋Š” package.json์ด ์œ„์น˜ํ•œ server ํด๋”์ž…๋‹ˆ๋‹ค.
let server;
if (fs.existsSync("./key.pem") && fs.existsSync("./cert.pem")) {
  const privateKey = fs.readFileSync(__dirname + "/key.pem", "utf8");
  const certificate = fs.readFileSync(__dirname + "/cert.pem", "utf8");
  const credentials = {
    key: privateKey,
    cert: certificate,
  };

ํ•ด๋‹น ํด๋”์— ์ธ์ฆ์„œ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— HTTPS ์„œ๋ฒ„๊ฐ€ ์•„๋‹Œ ๊ทธ๋ƒฅ HTTP ์„œ๋ฒ„๊ฐ€ ์—ด๋ ค์„œ

  • ์„œ๋ฒ„ ํด๋” ์•ˆ์—์„œ mkcert๋ฅผ ์‚ฌ์šฉํ•ด ์ธ์ฆ์„œ๋ฅผ ๋งŒ๋“ค์–ด์„œ HTTPS ์„œ๋ฒ„๋กœ ๋ณ€๊ฒฝ
  • mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1

 

๋‚˜๋จธ์ง€ ๊ฑด๋“ค์ง€ ๋ง๊ณ  corsOptions ์— Cors ์„ค์ •ํ•ด์ค€๋‹ค.

server/index.js

const corsOptions = {
  /* TODO: CORS ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์–ด๋–ค origin์ธ์ง€์— ๋”ฐ๋ผ ๋‹ฌ๋ฆฌ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
   * ๋ฉ”์„œ๋“œ๋Š” GET, POST, OPTIONS๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
   */
  // client๋Š” <http://localhost:3000> ์„ ์ด์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  origin: "<http://localhost:3000>",

  // cookie๋Š” ์ธ์ฆ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์œผ๋ฏ€๋กœ credentials๋„ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.
  credentials: true,

  // ํ—ˆ์šฉํ•  ๋ฉ”์†Œ๋“œ๋ฅผ ๋ฐฐ์—ด์— ๋‹ด์•„์„œ ์ž‘์„ฑํ•ด์ค๋‹ˆ๋‹ค.
  methods: ["GET", "POST", "OPTION"],
};

app.use(cors(corsOptions));

app.post("/login", controllers.login);
app.post("/logout", controllers.logout);
app.get("/userinfo", controllers.userInfo);

ํ˜น์‹œ ํฌ๋กฌ https ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค๋ฉด

ํฌ๋กฌ ์—…๋ฐ์ดํŠธ ํ•ด์ค์‹œ๋‹ค(์•ˆ๋œ ์ด์œ  ์ €๋Š”/)

์•„๋ž˜ ๋งํฌ์— ๋˜ ์ž์„ธํžˆ ์„ค๋ช…!

์ฐธ์กฐ

 

10 Ways to Fix the NET::ERR_CERT_DATE_INVALID Error

The NET::ERR_CERT_DATE_INVALID error occurs when browsers don't trust the reliability of a site's SSL certificate. Here's how to fix it.

kinsta.com

 

๐Ÿช Client

 

client/app.js

→ ์ „๋‹ฌ ํ•ด ์ค„ props ๋ฐ›์•„ ๋‚ด๋ ค์˜ค๊ธฐ

import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Login from "./pages/Login";
import Mypage from "./pages/Mypage";
import React, { useEffect, useState } from "react";
import axios from "axios";

// ๋ชจ๋“  ์š”์ฒญ์— withCredentials๊ฐ€ true๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.
axios.defaults.withCredentials = true;

function App() {
  const [isLogin, setIsLogin] = useState(false);
  const [userInfo, setUserInfo] = useState(null);

  const authHandler = () => {
    return axios
      .get("<https://localhost:4000/userinfo>")
      .then((res) => {
        setIsLogin(true);
        setUserInfo(res.data);
      })
      .catch((err) => {
        console.log(err.response.data);
      });
  };

  useEffect(() => {
    // ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ์‹œ ์•„๋ž˜ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
    authHandler();
  }, []);

  return (
    <BrowserRouter>
      <div className="main">
        <Routes>
          <Route
            path="/"
            element={
              isLogin ? (
                <Mypage
                  /*
                TODO: ๋ Œ๋”๋ง์— ํ•„์š”ํ•œ App์˜ ์ƒํƒœ์™€ ์ด๋ฅผ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก props๋ฅผ ์ „๋‹ฌํ•˜์„ธ์š”. 
                */
                  userInfo={userInfo}
                  setIsLogin={setIsLogin}
                  setUserInfo={setUserInfo}
                />
              ) : (
                <Login
                  /*
                TODO: App์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก props๋ฅผ ์ „๋‹ฌํ•˜์„ธ์š”. 
                */
                  setIsLogin={setIsLogin}
                  setUserInfo={setUserInfo}
                />
              )
            }
          />
        </Routes>
      </div>
    </BrowserRouter>
  );
}

export default App;

client/pages/Login.js

→ props๋กœ ์ „๋‹ฌ ๋ฐ›์€ ๊ฒƒ ์ ์šฉ

→ ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์— ๋„˜๊ฒจ์ค„ ํ•ธ๋“ค๋Ÿฌ

export default function Login({ setIsLogin, setUserInfo }) {
  const [loginInfo, setLoginInfo] = useState({
    userId: "",
    password: "",
  });
  const [checkedKeepLogin, setCheckedKeepLogin] = useState(false);
  const [errorMessage, setErrorMessage] = useState("");
  const handleInputValue = (key) => (e) => {
    setLoginInfo({ ...loginInfo, [key]: e.target.value });
  };
  const loginRequestHandler = () => {
    // TODO: Login ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” state๋ฅผ ์ด์šฉํ•ด ๋กœ๊ทธ์ธ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
    // ๋กœ๊ทธ์ธ์— ํ•„์š”ํ•œ ์œ ์ €์ •๋ณด๊ฐ€ ์ถฉ๋ถ„ํžˆ ์ œ๊ณต๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์—๋Ÿฌ๋ฉ”์‹œ์ง€๊ฐ€ ๋‚˜ํƒ€๋‚˜๋„๋ก ๊ตฌํ˜„ํ•˜์„ธ์š”.
    if (!loginInfo.userId || !loginInfo.password) {
      setErrorMessage("์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”");
      // ์ž…๋ ฅ๋˜์ง€ ์•Š์€ ๊ฐ’์ด ์žˆ๋Š”๊ฑฐ๋‹ˆ๊นŒ ์š”์ฒญ์„ ๋ณด๋‚ด๋ณผ ํ•„์š”๋„ ์—†์ด ๋ฐ”๋กœ ๋ฆฌํ„ดํ•ด์ค๋‹ˆ๋‹ค.
      return;
    }
    return axios
      .post("<https://localhost:4000/login>", { loginInfo, checkedKeepLogin })
      .then((res) => {
        // ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ๋‹ค๋ฉด ์‘๋‹ต์œผ๋กœ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๊ฐ€ Mypage์— ๋ Œ๋”๋ง๋˜๋„๋ก State๋ฅผ ๋ณ€๊ฒฝํ•˜์„ธ์š”.
        setIsLogin(true);
        setUserInfo(res.data);
        //์—ฌ๊ธฐ์—์„œ ์—๋Ÿฌ ์ดˆ๊ธฐํ™”
        setErrorMessage("");
      })
      .catch((err) => {
        // ๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ๋‹ค๋ฉด ๊ทธ์— ๋Œ€ํ•œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง
        if (err.response.status === 401) {
          setErrorMessage("๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
          alert('๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค!')
        }
      });
  };

client/pages/Mypage.js

→ props๋กœ ์ „๋‹ฌ ๋ฐ›์€ ๊ฒƒ ์ ์šฉ

→ ๋กœ๊ทธ์•„์›ƒ์€ ๋กœ๊ทธ์ธ๊ณผ ๋ฐ˜๋Œ€๋กœ

 โžก๏ธ setIsLogin(false); setUserInfo(null);

 

export default function Mypage({ userInfo, setIsLogin, setUserInfo }) {
  const logoutHandler = () => {
    // TODO: Logout ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ์‹œ Login ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜์„ธ์š”.
    return axios
      .post("<https://localhost:4000/logout>")
      .then((res) => {
        // ๋กœ๊ทธ์•„์›ƒ์— ์„ฑ๊ณตํ–ˆ๋‹ค๋ฉด App์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์„ธ์š”.
        setIsLogin(false);
        setUserInfo(null);
      })
      .catch((err) => {
        // ๋กœ๊ทธ์•„์›ƒ์— ์‹คํŒจํ–ˆ๋‹ค๋ฉด ๊ทธ์— ๋Œ€ํ•œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.
        console.log(err.response.data);
        alert(err);
      });
  };

npm test → ํ…Œ์ŠคํŠธ ํ†ต๊ณผใ„ฑใ„ฑ

server/db/data.js ํ™•์ธ

→ ๋กœ๊ทธ์ธ, ์•„์›ƒ ํ•  ๋•Œ ์‚ฌ์šฉ!

ID : kimcoding

PASSWORD : 1234

 

 

 ๐Ÿช Server

 

index.js ๋Š” ๋งจ ์ฒซ๋ถ€๋ถ„ ์„œ๋ฒ„ ์—ด๊ธฐ์—์„œ ํ™•์ธ!

์ž! ๋‹ค์Œ

server/controllers/users/login.js

const { USER_DATA } = require("../../db/data");

module.exports = (req, res) => {
  const { userId, password } = req.body.loginInfo;
  const { checkedKeepLogin } = req.body;
  const userInfo = {
    // ์›๋ณธ ๊ฐ์ฒด ๋ฐ์ดํ„ฐ ์œ ์ง€ ์œ„ํ•ด ๋ณต์‚ฌ๋ณธ ์‚ฌ์šฉ
    ...USER_DATA.filter(
      (user) => user.userId === userId && user.password === password
    )[0],
  };

  //! ์ฝ˜์†”,,
  // console.log(req.body);
  // console.log(userInfo);

  //! ์ฟ ํ‚ค์˜ต์…˜
  const cookiesOption = {
    domain: "localhost",
    path: "/",
    httpOnly: true,
    sameSite: "none",
    secure: true,

    //! 7์ผ ํ›„ ์†Œ๋ฉธ๋˜๋Š” Persistent Cookie
    expires: new Date(Date.now() + 24 * 3600 * 1000 * 7),
  };

  if (!userInfo.id) {

    res.status(401).send("Not Authorized");
  } else if (checkedKeepLogin) {

    res.cookie("cookieId", userInfo.id, cookiesOption);
    res.redirect("/userinfo");
  } else {
		// expires ์˜ต์…˜ ์‚ญ์ œ ํ•ด์ค€๋‹ค,, session, persistent cookie ๊ตฌ๋ณ„ ์œ„ํ•ด
    delete cookiesOption.expires;

    res.cookie("cookieId", userInfo.id, cookiesOption);
    res.redirect("/userinfo");
  }

  /*
   * TODO: ๋กœ๊ทธ์ธ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.
   *
   * userInfo์—๋Š” ์š”์ฒญ์˜ ๋ฐ”๋””๋ฅผ ์ด์šฉํ•ด db์—์„œ ์กฐํšŒํ•œ ์œ ์ €์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ˜์†”์—์„œ userInfo๋ฅผ ์ถœ๋ ฅํ•ด๋ณด์„ธ์š”.
   * ์œ ์ €์˜ ์ •๋ณด๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค๋ฉด ํ•ด๋‹น ์œ ์ €๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒƒ์ž„์œผ๋กœ ๋กœ๊ทธ์ธ ์„ฑ๊ณต์— ๋Œ€ํ•œ ์‘๋‹ต์„ ์ „์†กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
   * ๋งŒ์•ฝ undefined๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค๋ฉด ํ•ด๋‹นํ•˜๋Š” ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ž„์œผ๋กœ ๋กœ๊ทธ์ธ ์‹คํŒจ์— ๋Œ€ํ•œ ์‘๋‹ต์„ ์ „์†กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
   *
   * ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ์—๋Š” ํด๋ผ์ด์–ธํŠธ์— ์ฟ ํ‚ค๋ฅผ ์ „์†กํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์ฟ ํ‚ค์˜ cookieId์—๋Š” userInfo.id๊ฐ€ ๋‹ด๊ฒจ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
   * ํ…Œ์ŠคํŠธ์ผ€์ด์Šค์—์„œ ์š”๊ตฌํ•˜๋Š” ์ฟ ํ‚ค ์˜ต์…˜์„ ๋ชจ๋‘ ์„ค์ •ํ•˜์„ธ์š”.
   * ์˜์†์„ฑ์žˆ๋Š” ์ฟ ํ‚ค๋ฅผ ๋ณด๋‚ด๋ ค๋ฉด max-age ๋˜๋Š” expires ์˜ต์…˜์„ ์„ค์ •ํ•˜์„ธ์š”.
   *
   * ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ”๋กœ ์‘๋‹ต์„ ๋ณด๋‚ด์ง€์•Š๊ณ  ์„œ๋ฒ„์˜ /useinfo๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
   * express์˜ res.redirect ๋ฉ”์„œ๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์„œ๋ฒ„์˜ /userinfo๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜์„ธ์š”.
   */
};

server/controllers/users/logout.js

module.exports = (req, res) => {
  /*
   * TODO: ๋กœ๊ทธ์•„์›ƒ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.
   *
   * cookie-parser์˜ clearCookie('์ฟ ํ‚ค์˜ ํ‚ค', cookieOption) ๋ฉ”์„œ๋“œ๋กœ ํ•ด๋‹น ํ‚ค๋ฅผ ๊ฐ€์ง„ ์ฟ ํ‚ค๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
   * ๋งŒ์•ฝ res.clearCookie('user', cookieOption) ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค๋ฉด `user=....` ์ฟ ํ‚ค๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.
   * ๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต์— ๋Œ€ํ•œ ์ƒํƒœ ์ฝ”๋“œ๋Š” 205๊ฐ€ ๋˜์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
   */
  const cookiesOption = {
    domain: "localhost",
    path: "/",
    httpOnly: true,
    sameSite: "none",
    secure: true,
  };

  res.status(205).clearCookie("cookieId", cookiesOption).send("logout");
};

server/controllers/users/userInfo.js

const { USER_DATA } = require("../../db/data");

module.exports = (req, res) => {
  /*
   * TODO: ์ฟ ํ‚ค ๊ฒ€์ฆ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์œ ์ € ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.
   *
   * ๋กœ๊ทธ์ธ ์‹œ ์„ค์ •ํ•œ ์ฟ ํ‚ค๊ฐ€ ์กด์žฌํ•˜๋Š” ์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
   * ์•„์ง ๋กœ๊ทธ์ธ์„ ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์ฟ ํ‚ค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
   * ์ฟ ํ‚ค์— ์œ ์ €์˜ id๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ฝ˜์†”์— req.cookies๋ฅผ ์ถœ๋ ฅํ•ด๋ณด์„ธ์š”.
   */

  const cookieId = req.cookies.cookieId;
  const userInfo = {
    ...USER_DATA.filter((user) => user.id === cookieId)[0],
  };
  if (!cookieId || !userInfo.id) {
    res.status(401).send("Not Authorized");
  } else {
    // ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ผ์„œ ์‚ญ์ œ ํ›„์— ๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    delete userInfo.password;
    res.send(userInfo);
  }
};

๊ทธ๋ฆฌ๊ณ 

๊ฐœ๋ฐœ์ž ๋„๊ตฌ - ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—์„œ

์ฟ ํ‚คํƒญ์— ์ฟ ํ‚ค๋ฅผ ํ™•์ธํ•œ๋‹ค.

 

๋กœ๊ทธ์ธํ™”๋ฉด์˜

๋กœ๊ทธ์ธ ์ƒํƒœ์œ ์ง€ํ•˜๊ธฐ ์ฒดํฌ or ์ฒดํฌX

์ผ๋•Œ

์ฒดํฌX → session ์ด ๋“ค์–ด์˜ค๊ณ 

์ฒดํฌO → expires ์„ค์ •ํ•ด๋’€๋˜ ๊ฒƒ์ด ๋œฐ ๊ฒƒ์ด๋‹ค.

ํ™•์ธ!

 

 

 

npm test → ํ…Œ์ŠคํŠธ ํ†ต๊ณผใ„ฑใ„ฑ

 

'Code๊ฐœ๋ฐœ์ผ์ง€' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[session] sprint-auth-session  (0) 2023.01.07
Session  (0) 2023.01.07
Cookie  (0) 2023.01.07
HTTP/HTTPS  (0) 2023.01.07
๋„คํŠธ์›Œํฌ[TCP/UDP, OSI 7]  (0) 2023.01.06