과제 - 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 } 님 아이디 또는 비밀번호를 확인해주세요` });
}
}
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 서명하기 위해 사용될 비밀 키 또는 개인 키