Today What I Learned
Javascript를 배우고 있습니다. 매일 배운 것을 이해한만큼 정리해봅니다.
1. 인증(authentication) 관련 개념 정리
1. 쿠키(cookie)란?
- 서버가 사용자 위치에 정보를 저장하고 불러올 수 있는 수단
- 서버와 클라이언트가 대화하는 수단이라고도 볼 수 있음
- 브라우저가 서버와 연결되면 서버는 쿠키 유무를 확인하고 → response 내 쿠키 부분에 내용을 담아서 전달 → 해당 호스트에서 보내는 요청에는 쿠키가 계속 심겨서 주고 받을 수 있다.
- 쿠키의 구성: 이름, 값, 만료날짜, 경로 정보
2. 세션(session)이란?
- 서버와 클라이언트 연결이 활성화 된 상태
- 브라우저의 첫 접속 시에 고유 값(세션ID)을 따서 서버 측에서 in-memory 형식으로 관리한다.
- 서버에서는 세션 객체에 임의 정보를 추가할 수 있다.
- 사용자 정보 중 보안상 중요한 데이터는 쿠키가 아닌 세션에서 관리한다. 출처: 코드스테이츠
3. 토큰(token)이란?
- 인증을 위해 사용되는 암호화된 문자열
- 클라이언트가 토큰을 가지고 있으면 신원 인증이 된 것으로 여기고 별도의 유저 상태 관리를 하지 않기 때문에 서버 측에서는 해당 api를 수월하게 생성할 수 있다.
- 토큰을 사용하면 세션처럼 서버에서 클라이언트의 상태 정보를 저장하지 않아도 되어서 서버 측의 무리가 덜 가고, 사용자 세션 관리를 위한 비용이 절감된다.
4. 캐시(cache)란?
- 가져오는데 비용이 드는 정보/데이터를 임시로 복사/저장하는 장소(컴퓨터나 서버 등)
5. 개념 정리 참고
2. 암호화(Encryption)
1. 해싱(hashing)이란?
- 어떤 문자열에 임의의 연산을 적용해 다른 문자열로 변환하는 것
- 해싱의 특징
- 해시로 변환 시 계산이 너무 오래 걸리면 안 된다.
- 어떤 값을 해싱했을 때 매번 고유한 값으로 변환해야 한다.
- 값에 아주 작은 단위의 변화를 주어도 해시 값은 완전히 달라야 한다.
2. crypto
- node JS 내장 암호화 모듈, 다양한 해싱 알고리즘을 적용하여 암호화를 쉽게 구현해준다.
- salt: 해시하려는 원본 값에 추가하는 임의의 값(소금을 치는 느낌?), salt를 더해 기존 해시값과 전혀 다른 해시 값을 반환되해 알고리즘이 노출 되더라도 원본 값을 보호
- 암호화 하려는 값 + salt → 해싱
- user마다 salt를 다르게 적용할 수 있음
3. 해싱 알고리즘
- hmac 사용하기
const password = crypto.createHmac('sha256', 'salt')
.update(data.password)
.digest('hex');
- createHash 사용하기
const secret = 'aaaaaa'
const password = crypto.createHmac('sha256', secret)
.update('hello world')
.digest('base64');
- pbkdf2/pbkdf2Sync 사용하기
- Sync 없이 작성 시 해싱 후 callback을 사용할 수 있다.
- Sync를 추가하면 callback 없이 동기적 처리가 가능하다.let password = 'abcdef'
const hash = crypto.pbkdf2Sync(
password,
'salt',
123456,
64,
'sha512'
);
password = hash.toString('hex');
- randomBytes 사용하기
- salting 부분을 랜덤으로 생성하고 싶다면 randomBytes를 사용하면 된다.
crypto.randomBytes(64, (err, buf)=>{
const randomlyGeneratedSalt = buf.toString('base64')
crypto.pbkdf2
('aaaaaa',
randomlyGeneratedSalt,
123456,
64,
'sha512',
(err, derivedKey)=>{
if(err) throw err;
console.log(derivedKey.toString('hex')) })
})
3. Express-session package
1. express-session 패키지란?
- 익스프레스 서버에서 세션 객체를 사용해 세션을 관리해주는 기능을 한다.
2. session 객체의 이용
- express-session가 만들어준 세션 객체는 request에 위치하고, 세션 객체 내 변수로 값을 담을 수 있다. 세션 객체 내 sessionId라는 키에는 해당 session의 고유값을 담고 있다. 서버는 인메모리 형태로 sessionId들을 저장하고 기억한다.
3. 클라이언트와의 소통
- express-session 덕분에 서버는 request.session.sessionId 값을 response의 header 부분에 set-cookie 키에 대한 값으로 담아서 보낸다. 그 결과 클라이언트는 서버로부터 sessionId를 받게 된다. (의사소통이 이루어진 부분)
- 클라이언트는 이렇게 받은 sessionId를 storage에 담고 있다가 다음번 api를 호출할 때 다시 request의 cookie에 담아서 보내준다. 서버는 api 호출 요청이 오면 request.cookie에 담긴 sessionId를 찾아 해당 하는 세션 객체에 담긴 유효한 값들을 꺼내와서 이를 기준으로 db와 소통하고 적절한 response를 보낸다.
- 만약 이 과정에서 클라이언트로부터 서버가 기억하지 못하는 sessionId가 오면 서버는 클라이언트에 권한이 없다는 응답을 보내게 된다.(unauthorized)
4. JWT(JSON Web Tocken)
1. JWT란?
- 개념: 위에서 토큰이란 인증을 위해 사용하는 암호화 된 문자열이라고 했는데, jwt는 JSON 형식의 토큰이라고 이해하면 된다.
- 구성 요소 : 헤더, 페이로드, 서명
- 헤더 : 토큰 타입과 알고리즘 형식을 지정해준다 {"type" : "JWT", "alg": "HS256" }
- 페이로드: 서버에 보낼 정보가 들어간다.일반적으로 유저별 고유값과 토큰의 유효기간이 담긴다. 각 정보의 단위는 클레임(claim)이라고 부르고 이 클레임들을 모아 페이로드를 배치한다.
- registered claims : 토큰의 발급자, 제목, 대상자 등 토큰에 대한 정보들을 담는다. 키 값은 이미 예약되어있다.
- public claims : 공개 가능한 클레임들은 충돌이 방지된 (collision-resistant) 이름으로 키 값을 URI 방식으로 작성한다.
- private claims : registered도 public도 아닌 클레임을 담는다. 보통은 서버와 클라이언트 간 합의 한 클레임들을 담는다.
- 서명: Base64 방식으로 인코딩한 헤더, 페이로드 그리고 SECRET KEY를 더해 해싱된다.
- JWT의 최종 형태 : 헤더 + "." + 페이로드 + "." + 서명
- 참고 : 벨로퍼트님 블로그 https://velopert.com/2389
2. token 인증 방식
- session 방식과 달리 token 방식 인증에서는 서버가 세션을 저장하지 않는다. 대신 클라이언트와 소통을 위해 토큰을 만들어 발행하여 response의 cookie에 값을 담아 전달한다.
- 그런 다음 서버는 클라이언트로부터 받은 request.cookie에 토큰을 decode하여 authorization 여부를 확인한다.
- 참고 글 : jwt 기반으로 사용자 인증 구현하기 https://victorydntmd.tistory.com/116
3. 클라이언트와 소통
- 서버로부터 response.cookie를 통해 토큰을 받은 클라이언트는 이 토큰을 브라우저의 cookies 등에서 가지고 있는다.
- 그 후 서버에 또 다른 api 호출을 해야할 때 이 토큰을 request.cookie에 담아 호출할 때 함께 서버로 보낸다.
4. 디코딩 방식
- 서버에서는 jwt 패키지를 사용하여 jwt.verify 방식으로 디코딩을 실시한다.
- 디코딩 된 모양 또한 JSON이므로 객체 내에 필요한 정보를 검색하여 인가 여부를 확정지을 수 있다. (이쯤되면 객체지향만세)
- jwt 인증의 장점과 단점
- 장점: 토큰 이용 시 서버가 자체 세션을 인메모리로 저장하지 않아도 되어서 유저의 세션 관리 비용 부담이 줄어들고 서버의 부하를 줄일 수 있다.
- 단점: 토큰에 중요한 내용을 많이 담고 있기 때문에 토큰이 쉽게 디코딩 되는 경우, 악의적으로 유저만 볼 수 있는 내용들을 중간에서 가로챌 수 있는 위험성이 있다.
5. Sequelize association 설정
1. 모델 간 관계 정의(association)
- SQL을 사용하여 테이블 간 관계 정의가 가능하듯, ORM인 sequelize에서도 모델링 과정에서 모델 간 관계 정의가 가능하다.
2. 관계 별 사용 구문
urls.associate = function(models) {
urls.belongsTo(models.users, {
foreignKey: 'user_id'
});
};
3. references로 직접 지정
- 모델링을 하면서 필드를 설정하면서 reference 옵션으로 키를 직접 연결할 수도 있다.
module.exports = (sequelize, DataTypes) => {
const urls = sequelize.define(
'urls',
{
url: DataTypes.STRING,
baseUrl: DataTypes.STRING,
code: DataTypes.STRING,
title: DataTypes.STRING,
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'user',
key: 'user_id'
}
}
}
4. 참고 글
6. Sequelize hooks lifecycle
1. hooks lifecycle이란?
- Sequelize가 모델링하는 전체적인 과정이 lifecycle, 각 단계마다 특정 작업을 지정할 수 있도록 hook 설정을 할 수 있다.
- 난 개인적으로 훅스를 보면서 마치 리액트의 라이프사이클 같다는 인상을 받았다.
2. 사이클 종류
3. hooks lifecycle을 이용해서 패스워드 해싱하기
- beforeCreated, beforeFind에서 비밀번호를 해싱하면 서버에서 db에 비밀번호를 생성하거나 검색할 때 해싱 처리된다. 따라서 db에는 해싱된 비밀번호만 남게 된다.