YoungSoo

Node_Authentication 본문

FE/Node.js

Node_Authentication

YoungSooSoo 2023. 1. 5. 12:02
    1. Authentication(인증)과 Authorization(인가)
      • 인증 : 계정 관련, 로그인 관련
      • 인가 : 권한 관련
    2. 인증을 구현하는 방법
      • 로컬 로그인 : 회원 정보를 저장하고 있다가 인증
        • 회원 정보를 저장할 때는 비밀번호는 복호화가 불가능한 방식을 사용하고 개인을 식별할 수 있는 정보를 마스킹 처리를 하거나 복호화가 가능한 방식의 암호화를 활용해야 합니다.
      • OAuth(공통된 이증 방식) 로그인 : 다른 서버(카카오나 구글)에 저장된 인증 정보를 활용해서 인증을 하는 방식
    3. Passport 모듈
      • Node에서 인증 작업을 도와주는 모듈
      • 세션이나 쿠키 처리를 직접하지 않고 이 모듈의 도움을 받으면 쉽게 구현이 가능합니다.
      • Social 로그인 작업을 쉽게 처리할 수 있도록 해줍니다.
      • https://www.passportjs.org
    4. 로컬 로그인 구현
      • 필요한 모듈 설치 passport, passport-loacl, bcrypt(암호화를 해서 비교는 가능하지만 복호화는 안 되는 암호화 모듈)
      • app.js 파일에 Passport 모듈을 사용할 수 있는 설정을 추가
      • const passport = require('passport'); const passportConfig = require('./passport');//현재 프로젝트의 passport 디렉토리에 index.js 확인 passportConfig(); app.use(passport.initialize()); //세션 기능은 passport 모듈이 알아서 사용 app.use(passport.session());
      • Passport 모듈 사용 설정
        • passport 디렉토리 생성
        • passport 디렉토리에 index.js 파일을 생성하고 작성
        const passport = require('passport');
        const local = require('./localStrategy');
        const User = require('../models/user');
        
        module.exports = () => {
            //로그인 성공했을 때 정보를 deserializeUser 함수에게 넘기는 함수
            passport.serializeUser((user, done) => {
                done(null, user.id);
            });
        
            //넘어온 id에 해당하는 데이터가 있으면 데이터베이스에서 찾아서 세션에 저장
            passport.deserializeUser((id, done) => {
                User.findOne({where:{id}})
                    .then(user => done(null, user))
                    .catch(err => done(err));
            });
            local();
        }
        
        • 이렇게 하면 로그인 여부를 request 객체의 isAuthenticated() 함수로 할 수 있게 됩니다.
      • 로그인 여부를 판단할 수 있는 함수를 routes 디렉토리의 middlewares.js 파일을 추가하고 작성
//로그인 여부 판단
exports.isLoggedIn = (req, res, next) => {
  //로그인 여부 판단 - req.session.user 가 존재하는지 확인
  if (req.isAuthenticated()) {
    next();
  } else {
    res.status(403).send("로그인 필요");
  }
};

//로그인 하지 않은 경우를 판단
exports.isNotLoggedIn = (req, res, next) => {
  if (!req.isAuthenticated()) {
    next();
  } else {
    //메시지를 생성하는 query string(parameter)로
    //사용할 것이라서 encoding을 해주어야 합니다.
    const message = encodeURIComponent("로그인 한 상태입니다.");
    //이전 request 객체의 내용을 모두 삭제하고
    //새로운 요청 흐름을 만드는 것으로 새로 고침을 하면
    //결과 화면만 새로고침 됩니다.
    res.redirect(`/?error=${message}`);
  }
};
  • routes 디렉토리의 page.js 파일을 수정
const express = require('express');
const {isLoggedIn, isNotLoggenIn} = require("./middlewares");

const router = express.Router();

//공통된 처리 - 무조건 수행
router.use((req, res, next) => {
    //로그인 한 유저 정보
    //유저정보를 res.locals.user에 저장
    res.locals.user == req.user;
    //게시글을 follow 한 개수
    res.locals.followerCount = 0;
    res.locals.followingCount = 0;
    //게시글을 follow 하고 있는 유저들의 목록
    res.locals.followerIdList = [];

    next();
});

//메인 화면
router.get('/', (req, res, next) => {
    const twits =[];
    //템플릿 엔진을 이용한 출력
    //views 디렉토리의 main.html로 출력
    res.render('main', {title:"Node Authenticaiton", twits,});
});

//회원 가입 - 로그인이 되어있지 않은 경우만 수행
router.get('/join', isNotLoggenIn, (req, res, next) => {
    res.render('join', {title:'회원 가입 - Node Authentication'});
});

//프로필 화면 처리
router.get('/profile', isLoggedIn, (req, res, next) => {
    res.render('profile', {title:'나의 정보 - Node Authentication'});
});

module.exports = router;
  • 회원 가입, 로그인 ,로그아웃 처리를 위한 내용을 routes 디렉토리에 auth.js 파일을 만들고 작성
    • 이 내용은 page.js에 작성해도 됩니다.
    • page.js 는 화면을 보여주는 역할을 하고 auth.js는 처리하는 역할을 하도록 분리한 것입니다.
    const express = require('express');
    
    //로그인 및 로그아웃 처리를 위해서 가져오기
    const passport = require('passport');
    //회원 가입을 위해서 가져오기
    const bcrypt = require('bcrypt');
    
    const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
    const User = require('../models/user');
    
    const router = express.Router();
    
    //회원 가입 처리 - /auth/join 인데 라우팅 할 때 /auth 추가
    router.post('/join', isNotLoggedIn, async (req, res, next) => {
        //데이터 찾아오기
        //req.body에서 email, nick, password를 찾아서 대입
        const { email, nick, password } = req.body;
        try {
            //email 존재 여부 확인
          const exUser = await User.findOne({ where: { email } });
          if (exUser) {
            //회원 가입 페이지로 리다이렉트하는데 
            //error 키에 메시지를 가지고 이동
            return res.redirect('/join?error=exist');
          }else{
            //비밀번호를 해싱
            const hash = await bcrypt.hash(password, 12);
            //저장
            await User.create({
              email, nick, password: hash,
            });
            return res.redirect('/');
          }
        } catch (error) {
          console.error(error);
          return next(error);
        }
    });
    
    //로그인 처리
    router.post('/login', isNotLoggedIn, (req, res, next) => {
        //passport 모듈을 이용해서 로그인
        passport.authenticate('local', (authError, user, info) => {
          if (authError) {
            console.error(authError);
            return next(authError);
          }
          //일치하는 User가 없을 때
          if (!user) {
            return res.redirect(`/?loginError=${info.message}`);
          }
          return req.login(user, (loginError) => {
            if (loginError) {
              console.error(loginError);
              return next(loginError);
            }
            //로그인 성공하면 메인 페이지로 이동
            return res.redirect('/');
          });
        })(req, res, next); // 미들웨어 내의 미들웨어에는 (req, res, next)를 붙입니다.
    });
    
    //로그아웃 처리
    router.get('/logout', isLoggedIn, (req, res) => {
        req.logout(function(err) {
            if (err) { return next(err); }
            //세션을 초기화
            req.session.destroy();
            res.redirect('/');
          });
    });
    
    module.exports = router;
    
  • passport 디렉토리에 로컬 로그인을 위한 localStrategy.js를 작성
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');

const User = require('../models/user');

module.exports = () => {
    passport.use(new LocalStrategy({
      usernameField: 'email',
      passwordField: 'password',
    }, async (email, password, done) => {
      try {
        //로그인 처리를 위해서 email에 해당하는 데이터 찾기
        const exUser = await User.findOne({ where: { email } });
        if (exUser) {
          const result = await bcrypt.compare(password, exUser.password);
          if (result) {
            done(null, exUser);
          } else {
            done(null, false, { message: '비밀번호가 일치하지 않습니다.' });
          }
        } else {
          done(null, false, { message: '가입되지 않은 회원입니다.' });
        }
      } catch (error) {
        console.error(error);
        done(error);
      }
    }));
};
  • app.js 파일에 로그인 관련 라우터 등록
const authRouter =require('./routes/auth');
app.use ('/auth',authRouter);
  1. 카카오 로그인 구현
    • 사용하는 모듈 : passport-kakao
    • 카카오 로그인 사용을 위한 설정
      • developers.kakao.com 에 접속해서 로그인
      • 애플리케이션이 없으면 추가
      • Rest API 키를 복사 : *****
      • 플랫폼 등록 : Web에 자신의 도메인과 포트 번호를 추가
      • 로그인 활성화
        • 왼쪽 메뉴에서 카카오 로그인을 클릭하고 활성화 설정 ON으로 설정하고 하단의 Redirect URI를 설정 - http://localhost/auth/kakao/callback
        • 왼쪽 메뉴에서 카카오 로그인 안의 동의 항목을 클릭하고 수집할 항목을 설정
        • .env 파일에 복사한 키를 저장
    • passport 디렉토리의 index.js 수정
const passport = require("passport");
//로컬 로그인 구현
const local = require("./localStrategy"); //현재 디렉토리에 localStrategy 파일을 가져오기
//카카오 로그인
const kakao = require("./kakaoStrategy");
const User = require("../models/user"); //상위 디렉토리에서 model 디렉토리의 user 가져오기

module.exports = () => {
  //로그인 성공했을 때 정보를 deserializeUser 함수에게 넘기는 함수
  passport.serializeUser((user, done) => {
    done(null, user.id);
  });

  //넘어온 id에 해당하는 데이터가 있으면 데이터베이스에서 찾아서
  //세션에 저장
  passport.deserializeUser((id, done) => {
    User.findOne({ where: { id } })
      .then((user) => done(null, user))
      .catch((err) => done(err));
  });
  local();
  kakao();
};
  • passport 디렉토리의 kakaoStrategy.js 파일을 생성하고 작성
const passport = require("passport");
const KakaoStrategy = require("passport-kakao").Strategy;

//유저 정보
const User = require("../models/user");

//카카오 로그인
module.exports = () => {
  passport.use(
    new KakaoStrategy(
      {
        clientID: process.env.KAKAO_ID,
        callbackURL: "/auth/kakao/callback",
      },
      async (accessToken, refreshToken, profile, done) => {
        //로그인 성공했을 때 정보를 출력
        console.log("kakao profile", profile);
        try {
          //이전에 로그인한 적이 있는지 찾기 위해서
          //카카오 아이디와 provider가 kakao로 되어있는
          //데이터가 있는지 조회
          const exUser = await User.findOne({
            where: { snsId: profile.id, provider: "kakao" },
          });
          //이전에 로그인 한 적이 있으면
          if (exUser) {
            done(null, exUser);
          } else {
            const newUser = await User.create({
              email: profile._json.kakao_account.email,
              nick: profile.displayName,
              snsId: profile.id,
              provider: "kakao",
            });
            done(null, newUser);
          }
        } catch (error) {
          console.error(error);
          done(error);
        }
      }
    )
  );
};
  • routes 디렉토리의 auth.js 파일에 카카오 로그인 라우팅 처리 코드를 추가
const express = require("express");

//로그인 및 로그아웃 처리를 위해서 가져오기
const passport = require("passport");
//회원 가입을 위해서 가져오기
const bcrypt = require("bcrypt");

const { isLoggedIn, isNotLoggedIn } = require("./middlewares");
const User = require("../models/user");

const router = express.Router();

//회원 가입 처리 - /auth/join 인데 라우팅 할 때 /auth 추가
router.post("/join", isNotLoggedIn, async (req, res, next) => {
  //데이터 찾아오기
  //req.body에서 email, nick, password를 찾아서 대입
  const { email, nick, password } = req.body;
  try {
    //email 존재 여부 확인
    const exUser = await User.findOne({ where: { email } });
    if (exUser) {
      //회원 가입 페이지로 리다이렉트하는데
      //error 키에 메시지를 가지고 이동
      return res.redirect("/join?error=exist");
    } else {
      //비밀번호를 해싱
      const hash = await bcrypt.hash(password, 12);
      //저장
      await User.create({
        email,
        nick,
        password: hash,
      });
      return res.redirect("/");
    }
  } catch (error) {
    console.error(error);
    return next(error);
  }
});

//로그인 처리
router.post("/login", isNotLoggedIn, (req, res, next) => {
  //passport 모듈을 이용해서 로그인
  passport.authenticate("local", (authError, user, info) => {
    if (authError) {
      console.error(authError);
      return next(authError);
    }
    //일치하는 User가 없을 때
    if (!user) {
      return res.redirect(`/?loginError=${info.message}`);
    }
    return req.login(user, (loginError) => {
      if (loginError) {
        console.error(loginError);
        return next(loginError);
      }
      //로그인 성공하면 메인 페이지로 이동
      return res.redirect("/");
    });
  })(req, res, next); // 미들웨어 내의 미들웨어에는 (req, res, next)를 붙입니다.
});

//로그아웃 처리
router.get("/logout", isLoggedIn, (req, res) => {
  req.logout(function (err) {
    if (err) {
      return next(err);
    }
    //세션을 초기화
    req.session.destroy();
    res.redirect("/");
  });
});

//카카오 로그인을 눌렀을 때
router.get("/kakao", passport.authenticate("kakao"));

//카카오 로그인 실패했을 때
router.get(
  "/kakao/callback",
  passport.authenticate("kakao", {
    failureRedirect: "/",
  }),
  (req, res) => {
    res.redirect("/");
  }
);

module.exports = router;

'FE > Node.js' 카테고리의 다른 글

Node_API_Server  (0) 2023.01.06
Node DB  (0) 2023.01.04
Node  (0) 2023.01.03