본문 바로가기

Nodejs

Authentication (2024-05-02)

Authentication(인증)

더보기


1. session & cookie

✔쿠키 
- 클라이언트 컴퓨터에 저장되는 작은 데이터 조각
- 서버로 부터 전송되어 클라이언트 웹 브라우저에 저장
- 텍스트 형식으로 주로 사용자 인증, 설정, 장바구니 등에 사용

✔세션
- 웹 서버 측에서 유지되는 상태 정보
- 사용자에 대한 고유한 세션 ID를 통해 식별
- 서버 메모리 또는 데이터베이스에 저장할 수 있음


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

{ Header | Payload | Signature}

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


3. bcrypt
- 해시 함수를 사용하여 비밀번호를 안전하게 저장하는데 사용되는 암호화 라이브러리
- 단방향 해시 함수로 한 번 해시된 값을 다시 원래 값으로 역추적하는 것이 불가능
- 솔트(salt) : 해시에 고유한 솔트 값을 추가하여 보안성을 높임. 같은 비밀번호를 가진 사용자가 있더라도 서로 다른 해시값을 가질 수 있음
1234(해시값) + 10(솔트값) - > asdf1234
1234 + 5 - > asdf1357

- 작업인자(Adaptive Work Factor) : 매개변수를 조정하여 해시 작업의 복잡성을 조절 할 수 있음. 암호 분석학적으로 한전한 해시 함수를 유지하면서도 암호화 작업에 필요한 시간을 조절할 수 있게 함

✔ 해시 함수
임의의 길이의 데이터를 받아서 고정된 길이의 고유한 값으로 변환하는 함수. 이러한 변한된 값은 해시 값 또는 해시 코드라고 함
- 동일한 입력에 대해 항상 동일한 해시 값을 생성 
- 고정된 출력 길이를 생성 (입력값에 영향을 받지 않음)
- 해시된 값을 통해 원본 값을 복구할 수 없음(단방향성이다)

 

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

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

 

 

 

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());
app.use(morgan("dev"));

app.use('/tweets', tweetsRouter);
app.use('/auth', authRouter);

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

app.listen(8080);

 

 

 

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';
import { isAuth } from '../middleware/auth.js';

const router = express.Router();

const validateLogin = [
    body('username').trim().notEmpty().withMessage('username을 입력하세요'),
    body('password').trim().isLength({min:4}).withMessage('password는 최소 4자 이상') ,validate];


const validateSignup = [
    ... validateLogin,
    body('name').trim().notEmpty().withMessage('이름을 입력하세요 '),
    body('email').trim().isEmail().withMessage('이메일 형식 확인'),
    body('url').isURL().withMessage('url형식 확인하세요'),
    validate
];

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

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

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

export default router;

 

 

router > tweets.js

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


const router = express.Router();
const validateTweet = [
    body('text').trim().isLength({min : 3}).withMessage('최소 3자 이상 입력') , validate
]


// 해당 아이디에 대한 트윗 가져오기
// GET
// http://localhost:8080/tweets?username=:username
router.get('/', isAuth, tweetcontroller.getTweets);


// 글 번호에 대한 트윗 가져오기
// GET
// http://localhost:8080/tweets/:id
router.get('/:id', isAuth, tweetcontroller.getTweet);


// 트윗하기
// POST
// http://localhost:8080/tweets
// name, username, text를 받아서 글을 등록 
// json형태로 입력 후 추가된 데이터까지 모두 json으로 출력
router.post('/', validateTweet, isAuth, tweetcontroller.createTweet);




// 트윗 수정하기
// PUT
// http://localhost:8080/tweets/:id
// id, username, text를 받아서 글을 수정
// json형태로 입력 후 변경된 데이터까지 모두 json으로 출력
router.put('/:id', validateTweet, isAuth, tweetcontroller.updateTweet);


// 트윗 삭제하기
// DELETE
// http://localhost:8080/tweets/:id
router.delete('/:id', isAuth, tweetcontroller.deleteTweet);


export default router;

 

 

middleware > auth.js

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

const AUTH_ERROR = {message : "인증에러"}

export const isAuth = async (req, res, next) =>{
    const authHeader = req.get('Authorization')
    console.log(authHeader);
    if(!(authHeader && authHeader.startsWith('Bearer '))){
        console.log('error1')
        return res.status(401).json(AUTH_ERROR);
    }
    const token = authHeader.split(' ')[1];
        jwt.verify(
        token, 'abcd1234%^&*', async(error, decoded) => {
            if(error){
                console.log('error2')
                return res.status(401).json(AUTH_ERROR);
            }
            const user = await authRepository.findById(decoded.id);
            if(!user){
                console.log('error3');
                return res.stasus(401).json(AUTH_ERROR);
            }
            req.userId = user.id;
            next();
        }
    )
}


// import { validationResult } from "express-validator";

// export const validate = (req, res, next) => {
//     const errors = validationResult(req);
//     if(errors.isEmpty()){
//         return next();
//     }
//     return res.status(400).json({message : errors.array()[0].msg});
// }

 

 

data > auth.js

let users = [
    {
        id: '1',
        username: 'apple',
        password: '$2b$10$ERrMBARDprS1Ajt9snjS2uXaCuYv/enG3nWFldAQWsnUQ2G.XwKvu',
        name: '김사과',
        email: 'apple@apple.com',
        url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTJSRyel4MCk8BAbI6gT_
        j4DBTEIcY0WW4WWfoklymsWA&s'
    },
    {
        id: '2',
        username: 'banana',
        password: '$2b$10$ERrMBARDprS1Ajt9snjS2uXaCuYv/enG3nWFldAQWsnUQ2G.XwKvu',
        name: '반하나',
        email: 'banana@banana.com',
        url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:
        ANd9GcTJSRyel4MCk8BAbI6gT_j4DBTEIcY0WW4WWfoklymsWA&s'
    }
];
// 아이디(username) 중복검사
export async function findByUsername(username){
    return users.find((user) => user.username === username);
}
// id 중복검사
export async function findById(id){
    return users.find((user) => user.id === id);
}

export async function createUser(user){
    const created = {id:'10', ...user }
    users.push(created);
    return created.id;
}
export async function login(username){
    const user = users.find((user) => user.username === username)
    return user;
}

 

 

controller > auth.js

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

const secretKey = "abcd1234%^&*";
const jwtExpiresInDays = '2d';
const bcryptSaltRounds = 10;
function createJwtToken(id){
    return jwt.sign({id}, secretKey, {expiresIn: jwtExpiresInDays});
};

export async function signup(req, res, next){
    const {username, password, name, email, url} = req.body;
    const found = await authRepository.findByUsername(username);
    if(found){
        return res.status(409).json({message:`${username}이 이미 있습니다`});
    }
    const hashed = await bcrypt.hash(password, bcryptSaltRounds);
    const userId = await authRepository.createUser({username, hashed, name, email, url});
    const token = createJwtToken(userId);
    res.status(201).json({token, username});
};

export async function login(req, res, next){
    const {username, password} = req.body;
    // const user = await authRepository.login(username);
    const user = await authRepository.findByUsername(username);
    if(!user){
        return res.status(401).json({message : '아이디를 찾을 수 없음'})
    }
    
    const isValidpassword = await bcrypt.compareSync(password, user.password);
    if(!isValidpassword){
        return res.status(401).json({message : `비밀번호가 틀렸음`});
    }
    const token = createJwtToken(user.id);
        return res.status(200).json({token, 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);
//     }
// };

export async function me(req, res, next){
const user = await authRepository.findById(req.userId);
if(!user){
    return res.status(404).json({message : '일치하는 사용자가 없음'})
}
res.status(200).json({token : req.token, username: user.username})
}

'Nodejs' 카테고리의 다른 글

MongoDB (2024-05-09)  (0) 2024.05.10
tweet, auth DB연결 (2024-05-08)  (0) 2024.05.08
과제 회원가입, 로그인 router, tweet 활용  (0) 2024.04.29
리팩토링, validation(2024-04-29)  (0) 2024.04.29
params, query  (0) 2024.04.29