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 |