Web/Node.js

2024-04-30

nomad06 2024. 4. 30. 08:52

과제
- router, data, controller에 auth.js를 추가하고 tweet.js를 참고하여 적용
- 회원가입, 로그인 (회원정보 확인, 아이디를 보내면 해당 객체의 정보만 출력)

    users = [
   {
         id: '1',
         username: 'apple',
         password: '1111',
         name: '김사과',
         email: 'apple@apple.com',
         url: 'https://www.logoyogo.com/web/wp-content/uploads/edd/2021/02/logoyogo-1-45.jpg'
    },
   {
     ...
    }
  ]

// data/auth.js

let users = [
    {
        id: '1',
        userid: "apple",
        password: "1111",
        name: "김사과",
        email: "apple@apple.com",
    },
    {
        id: '2',
        userid: "banana",
        password: "2222",
        name: "반하나",
        email: "banana@banana.com",
    }
]

export async function createUser(username, password, name, email){
    const user ={
        id: "10",
        username,
        password,
        name,
        email,
    }
    users = [user, ...users]
    return users;
}


export async function login(username){
    return users.find((users) => users.username === username);
}
// controller/auth.js

import * as authRepository from '../data/auth.js';

// 회원가입
export async function signup(req, res, next){
    const { username, password, name, email } = req.body;
    const users = await authRepository.createUser(username, password, name, email);
    if(users){
        res.status(201).json(users);
    }
}


//로그인
export async function login(req, res, next){
    const { username, password } = req.body;
    const user = await authRepository.login(username);
    if(user){
        res.status(201).json(`${username} 로그인 완료`);
    }
    else{
        res.status(404).json({ message: `${username} 님 아이디 또는 비밀번호를 확인해주세요` });
    }
}


// router/auth.js

import express from 'express';
import { body } from 'express-validator';
import * as authController from '../controller/auth.js';
import { validate } from '../middleware/validator.js';

const router = express.Router();

const validateSignup = [
    body('username').trim().isLength({min: 3}).withMessage('최소 3자 이상 입력'),
    body('password').trim().isLength({min: 4}).withMessage('최소 4자 이상 입력'),
    body('email').trim().isEmail().withMessage('이메일 형식 확인'), validate
]

router.post('/signup', validateSignup, authController.signup);

router.post('/login', authController.login);

export default router;


// http://localhost:8080/auth/signup
// {
//     "username":"orange",
//     "password":"3333",
//     "name":"오렌지",
//     "email":"orange@orange.com"
// }
 

// http://localhost:8080/auth/login
// {
//     "username":"orange",
//     "password":"3333"
// }
// app.js

import express from "express";
import morgan from "morgan";
import tweetsRouter from './router/tweets.js'
import authRouter from './router/auth.js'

const app = express();

app.use(express.json());  // json로 연결
app.use(morgan("dev"));

//tweetsRouter 미들웨어 등록
app.use('/tweets', tweetsRouter);  
//authsRouter 미들웨어 등록
app.use('/auth', authRouter);

app.use((req, res, next) => {
    res.sendStatus(404);
});

app.listen(8080);

 

깃허브에 추가

 

 

 

Authentication(인증)

1. session & cookie
쿠키
- 클라이언트 컴퓨터에 저장되는 작은 데이터 조각
- 서버로 부터 전송되어 클라이언트 웹브라우저에 저장
- 텍스트 형식으로 주로 사용자 인증, 설정, 장바구니 등에 사용
세션
- 웹 서버 측에서 유지되는 상태 정보
- 사용자에 대한 고유한 세션ID를 통해 식별
- 서버 메모리 또는 데이터베이스에 저장할 수 있음 

 

 

 

2. JWT(JSON Web Token)
- 웹 애플리케이션과 서비스 간에 정보를 안전하게 전달하기 위한 인증 및 권한 매커니즘을 구현하는데 사용하는 표준화된 방법 중 하나
- JSON 포멧을 사용하여 정보를 표현하고 서명 및 암호화를 통해 정보의 무결성 보장

    { Header | Payload | Signature }

Header: 토큰 유형 및 서명 알고리즘과 같은 메타데이터가 포함
Payload: 토큰에 포함될 데이터가 들어있는 부분
Signature: 헤더, 페이로드 및 비밀 키를 사용하여 생성된 서명으로 토큰의 무결성을 검증하는데 사용

설치

 

 

// jwt.js

import jwt from 'jsonwebtoken';
// npm i jsonwebtoken

const secret = "abcdefg1234%^&*";
const token = jwt.sign(
    {
        id: 'apple',
        isAdmin: false
    },
    secret,
    { expiresIn: "1h" }
    // { expiresIn: 2 }  // 0.02초 유지 => 3초후 찍으니까 실행되었다가 에러 뜸
)

setTimeout(() => {
    jwt.verify(token, secret, (error, decode) => {
        console.log(error, decode);
    });
}, 3000); // 3초후 찍어줌

console.log(token);

 

 

3. bcrypt
- 해시 함수를 사용하여 비밀번호를 안전하게 저장하는 데 사용되는 암호화 라이브러리
- 단방향 해시 함수로 한 번 해시된 값을 다시 원래 값으로 역추적하는 것이 불가능
- 솔트(salt): 해시에 고유한 솔트 값을 추가하여 보안성을 높임. 같은 비밀번호를 가진 사용자가 있더라도 서로 다른 해시값을 가짐
- 작업인자(Adaptive Work Factor): 매개변수를 조정하여 해시 작업의 복잡성을 조절. 암호 분석학적으로 안전한 해시 함수를 유지하면서도 암호화 작업에 필요한 시간을 조절할 수 있게 함 

✔ 해시 함수
임의의 길이의 데이터를 받아서 고정된 길이의 고유한 값으로 변환하는 함수. 이러한 변환된 값은 해시 값 또는 해시 코드라고 함
- 동일한 입력에 대해 항상 동일한 해시 값을 생성\
    예)
    1234 -> asdfihlkj12
    1234 -> asdfihlkj12
- 고정된 출력 길이를 생성 
- 해시된 값을 통해 원본 값을 복수할 수 없음
    예)
    1234 + 10 -> 1111aaaabbbb
    1234 + 5 -> 1111aaaacccc

// bcrypt.js

import * as bcrypt from "bcrypt";
// npm i bcrypt

const password = 'abcd1234';
const hashed = bcrypt.hashSync(password, 10);
console.log(`password: ${password}, hash: ${hashed}`)

// abcd1234
// $2b$10$HXQADTXhaA./QmbQhm.y1eheiK2YV7NPmuZ58UxexTlVoEbP7oSvS

const result = bcrypt.compareSync('abcd1234', hashed);
console.log(result);

 

 

문제
controller/auth.js 에서 login()를 bcrypt를 적용하여 로그인 프로세스를 만들어보자 

// data/auth.js

let users = [
    {
        id: '1',
        // userid: "apple",
        username: "apple",
        password: "$2b$10$TvIhNWYtvCNzOA0ZkmNg6.YU3EG49msPLzxphLYahMT63DVWRWZTe",
        name: "김사과",
        email: "apple@apple.com",
    },
    {
        id: '2',
        // userid: "banana",
        username: "banana",
        password: "$2b$10$TvIhNWYtvCNzOA0ZkmNg6.YU3EG49msPLzxphLYahMT63DVWRWZTe",
        name: "반하나",
        email: "banana@banana.com",
    }
]

export async function createUser(username, password, name, email){
    const user ={
        id: "10",
        username,
        password,
        name,
        email,
    }
    users = [user, ...users]
    return users;
}


export async function login(username){
    return users.find((users) => users.username === username);
}
// controller/auth.js

import * as authRepository from '../data/auth.js';
import bcrypt from 'bcrypt';

// 회원가입
export async function signup(req, res, next){
    const { username, password, name, email } = req.body;
    const users = await authRepository.createUser(username, password, name, email);
    if(users){
        res.status(201).json(users);
    }
}


//로그인
export async function login(req, res, next){
    const { username, password } = req.body;
    const user = await authRepository.login(username);
    if(user){
        if(bcrypt.compareSync(password, user.password)){
        res.status(201).json(`${username} 로그인 완료`);
        }
        else{
            res.status(404).json({ message: `${username} 님 아이디 또는 비밀번호를 확인해주세요` });
        }
    }
}
// router/auth.js

import express from 'express';
import { body } from 'express-validator';
import * as authController from '../controller/auth.js';
import { validate } from '../middleware/validator.js';

const router = express.Router();

const validateSignup = [
    body('username').trim().isLength({min: 3}).withMessage('최소 3자 이상 입력'),
    body('password').trim().isLength({min: 4}).withMessage('최소 4자 이상 입력'),
    body('email').trim().isEmail().withMessage('이메일 형식 확인'), validate
]

router.post('/signup', validateSignup, authController.signup);

router.post('/login', authController.login);

export default router;
 
// {
//     "username":"apple",
//     "password":"1234"
// }

// {
//     "username":"apple",
//     "password":"abcd1234"
// }

 

 

문제
jwt.js를 참고하여 controller/auth.js 에 토큰을 발행하고 login()에 로그인 완료되면 클라이언트에 토큰을 출력하는 프로세스를 만들어보자. 

// controller/auth.js

import * as authRepository from '../data/auth.js';
import bcrypt from 'bcrypt';
import jsonwebtoken from 'jsonwebtoken';

/*
문제
jwt.js를 참고하여 controller/auth.js 에 토큰을 발행하고 login()에 로그인 완료되면 클라이언트에 토큰을 출력하는 프로세스를 만들어보자
*/

const secret = 'abcd1234%^&*'

async function makeToken(id){
    const token = jsonwebtoken.sign({
        id: id,
        isAdmin: false
    }, secret, {expiresIn: '1h'})
    return token;
}

// 회원가입
export async function signup(req, res, next){
    const {username, password, name, email} = req.body;
    const hashed = bcrypt.hashSync(password, 10);
    const users = await authRepository.createUser(username, hashed, name, email);
    if(users){
        res.status(201).json(users);
    }
}

/*
문제
controller/auth.js 에서 login()를 bcrypt를 적용하여 로그인 프로세스를 만들어보자
*/

//로그인
export async function login(req, res, next){
    const {username, password} = req.body;
    const user = await authRepository.login(username);
    if(user){
        if(bcrypt.compareSync(password, user.password)){
            res.status(201).header('Token', makeToken(username)).json(`${username} 로그인 완료`);
        }else{
            res.status(404).json({message: `${username}님 아이디 또는 비밀번호 확인하세요`})
        }
    }else{
        res.status(404).json({message: `${username}님 아이디 또는 비밀번호 확인하세요`})
    }
}

export async function verify(req, res, next){
    const token = req.header['Token'];
    if(token){
        res.status(200).json(token);
    }
}

 

// router/auth.js

import express from 'express';
import { body } from 'express-validator';
import * as authController from '../controller/auth.js';
import { validate } from '../middleware/validator.js';

const router = express.Router();

const validateSignup = [
    body('username').trim().isLength({min: 3}).withMessage('최소 3자 이상 입력'),
    body('password').trim().isLength({min: 4}).withMessage('최소 4자 이상 입력'),
    body('email').trim().isEmail().withMessage('이메일 형식 확인'), validate
]

router.post('/signup', validateSignup, authController.signup);

router.post('/login', authController.login);

router.get('/me', authController.verify);

export default router;
 
// http://localhost:8080/auth/signup
// {
//     "username":"melon",
//     "password":"abcd1234",
//     "name":"이메론",
//     "email":"melon@melon.com"
// }


// http://localhost:8080/auth/login
// {
//     "username":"melon",
//     "password":"abcd1234"
// }

깃 추가

 

 

4. jasonwebtoken
- 웹 애플리케이션에서 인증 및 정보 교환을 위한 토큰 기반의 인증 방식 중 하나
- Base64로 인코딩된 JSON 객체이며 사용자 정보 및 기타 테이러를 안전하게 전송하기 위해 사용 
- Header: JWT의 유형과 해싱 알고리즘이 포함
    {
        "alg" : "HS256",
        "typ" : "JWT"
    }
- Payload: 토큰에 담길 정보가 포함
    {   
        id: 'apple',
        isAdmin: false
    }
- Signature: 헤더와 페이로드를 인코딩하고 비밀 키를 사용하여 서명된 문자열. 서명은 토큰이 변조되지 않았음을 확인하는 데 사용 

 

sing()
    jsonwebtoken.sing(payload, secretOrPrivateKey, [option, callback])
    payload: JWT에 포함될 페이로드 데이터
    secretOrPrivateKey: JWT 서명하기 위해 사용될 비밀 키 또는 개인 키

 

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

2024-05-02  (0) 2024.05.02
API 명세서  (0) 2024.04.30
2024-04-29 리팩토링  (0) 2024.04.29
2024-04-26 Project tweets  (0) 2024.04.26
2024-04-25 git 설치, github 간단 연동  (0) 2024.04.25