next()
next() 메서드는 다음 할 일을 하러 가라고 명령하는 기능을 한다. 여기서 다음 할 일이라는 것은 미들웨어나 콜백 함수 같은 것들을 말한다.
아래 코드는 유효성 검사를 위해 추가할 미들웨어이다.
const validate = (req, res, next) => {
const err = validationResult(req);
if (err.isEmpty()) {
return next();
} else {
return res.status(400).json(err.array());
}
}
만약 if문안에 return next()를 적지 않았다고 해보자. validate 미들웨어를 실행한 후 다음 할 일이 있다고 말해주지 않은 것과 같기 때문에 콜백 함수를 실행하지 않게 되고, 결과적으로 콜백 함수 안에 있는 응답을 보내주는 코드가 실행되지 않게 된다. 따라서 아래와 같이 무한 로딩되는 상황이 벌어진다. 이게 next 메서드를 사용하는 이유다.
인증과 인가
인증(Authentication)
인증은 사용자를 식별하는 행위를 말한다.
쇼핑몰 사이트에서 마이페이지에 들어가려고 해보자. 마이페이지에 들어가려면 내가 이 사이트에 가입되어 있다는 증명이 필요하고, 그 증명 방법이 로그인이다.
웹 개발에서의 인증은 보통 사용자의 신원을 증명하는, 즉 로그인이라고 생각하면 된다.
인가(Authorization)
인가는 접근 권한을 확인하는 프로세스를 말한다.
같은 쇼핑몰 사이트에서 관리자인지 고객인지에 따라 접근할 수 있는 페이지가 다르다고 가정해보자. 관리자든 고객이든 일단 이 사이트에 가입된 사용자라는 것을 증명(인증)해야 하고, 그 후에 이 사용자가 이 페이지에 접근할 수 있는 권한이 있는가(인가)에 대해 판단한다.
결론적으로 인가는 인증 이후에 발생하는 프로세스라고 할 수 있다.
쿠키와 세션
쿠키
웹 서버가 생성하여 웹 브라우저에게 전송하는 작은 정보 파일이다.
- 사용자가 로그인을 한다.
- 서버가 로그인에 필요한 정보를 쿠키에 적어서 사용자에게 준다.
- 사용자는 쿠키를 웹 브라우저에 저장해놓고 로그인이 필요할 때 서버에게 이 쿠키를 준다.
- 자동 로그인 성공
장점
로그인에 필요한 정보가 서버에 저장되는게 아니기 때문에 서버 저장 공간을 절약할 수 있다. 또한, HTTP는 서버가 상태를 저장하지 않는 stateless를 지향한다. 로그인에 필요한 정보를 서버에 저장해 놓은 게 아니기 때문에 stateless 하다고 할 수 있고, 서버가 stateless이므로 RESTful 하다고 할 수 있다.
단점
로그인이 필요할 일이 생기면 쿠키가 서버로 전송된다. 이때 쿠키가 인터넷 공간을 통해 날아가기 때문에 보안이 취약하다는 단점이 있다.
세션
사용자와 서버 사이의 연결을 확인하기 위한 정보를 말한다.
- 사용자가 로그인을 한다.
- 서버가 데이터를 저장한 후 쿠키에 세션 아이디를 적어서 사용자에게 준다.
- 사용자는 쿠키를 웹 브라우저에 저장해놓고, 로그인이 필요할 때 서버에게 쿠키를 준다.
- 서버는 쿠키에 적힌 세션 아이디로 사용자 데이터를 찾아서 자동 로그인 시켜준다.
장점
쿠키에 주요 정보는 없고, 세션 아이디만 적혀있으므로 비교적 보안이 좋다.
단점
서버가 정보를 저장하고있기 때문에 stateless가 아니다.
JWT
JSON Web Token의 약자로, JSON 형태의 데이터를 안전하게 전송하기 위한 웹에서 사용하는 토큰이다. 토큰은 토큰을 가진 사용자가 증명을 하기 위해 사용된다.
장점
서버가 상태를 저장하지 않기 때문에 두 번째와 세 번째의 장점을 가진다.
또한, 토큰을 발행하는 서버를 따로 만들 수도 있다.
- 보안에 강하며, 암호화가 되어있다.
- stateless
- 서버 부담을 줄여줄 수 있다.
구조
Decoded에 적혀있는 내용들을 암호화해서 Encoded 내용을 만든다.
구조 | 내용 |
Header | 토큰을 암호화할 때 사용한 알고리즘, 토큰 타입(jwt) |
Payload | 사용자의 정보(이름, 주소 등, 비밀번호는 담지 않음)가 JSON 형태로 적힌다. iat는 issued at으로, 토큰이 발행된 시간을 초 단위로 나타낸다. |
Verify Signature | Header, Payload가 이 데이터 값으로 적혀있다는 것을 보증하는 서명이다. Payload 값이 바뀌면 서명 값도 바뀌기 때문에 서명이 달라졌다면 서버에서 받아주지 않는다. |
인증 - 인가 절차
- 사용자가 로그인을 시도한다.
- 서버는 내부 로직을 확인하고 토큰(jwt)을 발행한다. 토큰 발행 시점은 언제 로그인했는가와 일치하며, 이 정보는 토큰의 payload에 적혀있다.
- 서버는 당분간 사용자의 로그인을 유지시켜주며, 쿠키에 토큰(jwt)을 담아 함께 보내준다. 사용자는 다음 요청부터 이 토큰을 서버에게 전달하면 서버가 서명을 확인하고 자동으로 로그인시켜준다.
- 사용자가 서버에게 다른 요청을 보내면서 Header에 토큰을 넣어 보낸다.
- 서버가 서명을 확인한다.
- 서명이 맞으면 사용자를 로그인시켜준다.
실습
모듈 설치
터미널에서 아래 명령어를 입력한다.
npm i jsonwebtoken
실습
sign 메서드는 토큰을 생성(jwt 서명)해준다. 첫 번째 파라미터는 payload, 두 번째 파라미터는 암호화 알고리즘(기본 설정은 SHA256) 외에 추가로 암호화하는 데 사용할 비밀키이다. 세 번째 파라미터는 암호화 알고리즘이다. 아래 코드에서는 암호화 알고리즘을 별도로 지정하지 않았다.
const jwt = require('jsonwebtoken');
// 토큰 발행
const token = jwt.sign({ id: 'ncherryu' }, 'thisissecret');
console.log(token);
// 검증
const decoded = jwt.verify(token, privateKey);
console.log(decoded);
.env
environment의 앞 글자로, 환경 변수의 설정 값을 의미한다.
포트 번호, DB 계정, 비밀키 등 외부에 유출되면 안 되는 중요한 변수들을 따로 관리하기 위한 숨겨진 파일이다.
.env 파일은 환경 변수 파일이므로 프로젝트의 최상위 패키지에 위치해야 한다. 변수는 모두 대문자, snake_case를 사용한다. 주석은 #으로 작성한다.
JWT 실습 코드에는 비밀키가 노출되어 있다. 이 비밀키를 환경 변수 파일로 옮겨보자. 아래 변수를 .env 파일에 저장한다.
PRIVATE_KEY = 'thisissecret' # JWT 암호키
모듈 설치
환경 변수를 파일에 저장해 놓고 다른 파일에서 환경 변수에 접근할 수 있게 해주는 모듈을 설치한다.
npm i dotenv
실습
config 메서드를 호출한 이후부터 환경 변수에 접근할 수 있다.
실행 결과는 비밀키를 코드에 노출시켰던 결과와 동일하다.
const jwt = require('jsonwebtoken');
const dotenv = require('dotenv');
dotenv.config(); // 이 파일에서 dotenv를 사용하겠다.
const privateKey = process.env.PRIVATE_KEY;
// 토큰 발행
const token = jwt.sign({ foo: 'bar' }, privateKey);
console.log('발행된 토큰: ', token);
// 검증
const decoded = jwt.verify(token, privateKey);
console.log('토큰 복호화 결과: ', decoded);
Youtube에 JWT 적용
모듈 설치
npm i jsonwebtoken dotenv
모듈 불러오기 & 설정
users.js에 아래 코드를 추가한다.
// jwt 모듈
const jwt = require('jsonwebtoken');
// dotenv 모듈
const dotenv = require('dotenv');
dotenv.config();
환경 변수 저장
.env 파일을 생성하고, 아래 변수를 저장한다.
PRIVATE_KEY = 'thisissecret' # jwt 토큰 암호키
사용자 로그인에 적용
아래 코드는 users.js에서 로그인하는 부분이다.
토큰은 원래 쿠키에 작성해야 하지만, 지금은 정상적으로 동작하는지 확인하기 위해 body에 적었다.
로그인에 성공했을 경우 환경 변수로 저장되어 있는 비밀키로 jwt를 발행(=서명)한다.
router.post(
'/login',
[
body('email').notEmpty().isEmail().withMessage('이메일 확인 필요'),
body('password').notEmpty().isString().withMessage('비밀번호 확인 필요'),
validate
],
(req, res) => {
const { email, password } = req.body;
const sql = `SELECT * FROM users WHERE email = ?`;
conn.query(sql, email,
function (err, results) {
if (err) {
console.log(err);
return res.status(400).end();
}
const loginUser = results[0];
if (loginUser && loginUser.password === password) {
// token 발행
const token = jwt.sign({
email: loginUser.email,
name: loginUser.name
}, process.env.PRIVATE_KEY);
res.status(200).json({
message: `${loginUser.name}님, 로그인 되었습니다.`,
token: token
});
} else {
res.status(404).json({
message: '이메일 또는 비밀번호가 틀렸습니다.'
});
}
}
);
})
'데브코스' 카테고리의 다른 글
[6주차 복습 발표] JWT를 이용한 인증 및 인가 (0) | 2024.04.04 |
---|---|
[6주차 - DAY4] cookie, jwt 설정 (0) | 2024.04.04 |
[6주차 - DAY2] 유효성 검사 (1) | 2024.04.02 |
[6주차 - DAY1] RESTful API 개발 실습 (0) | 2024.04.01 |
[5주차 - DAY5] Workbench 사용 및 DB 연동 (0) | 2024.03.29 |