scale-up
: 컴퓨터의 성능( CPU, Memory 등)을 올려주는 것.
scale-out
: 똑같은 성능의 컴퓨터를 추가하는 것.
stateful
: session에 로그인한 유저 정보를 저장하여 가지고 있는 상태
stateless
: session에 로그인한 유저 정보가 없는 상태
Redis
: 메모리에 저장해두는 임시 데이터 베이스 ( 속도 문제 해결)
session을 scale-out 해오지 못하는 문제점을 보완한 방법이며, 현재 많이 쓰이고 있는 방법 중 하나
로그인 정보를 백엔드 session에 저장하는 것이 아니라 DB에 저장하는 방법 => DB의 부하 초래
DB에 저장할 때는 메모리 기반의 session 방식
에서 디스크 기반의 token(DB에 저장되어 있는 유저의 ID) 방식
으로 바뀜
- 수직으로 나누는 수직파티셔닝
- 수평으로 나누는 수평파티셔닝(샤딩)
JWT 토큰은 유저정보를 담은 객체를 암호화를 통해 문자열로 만들어 암호화된 키(accessToken
)를 브라우저로 전달
브라우저는 받아온 암호화된 키를 브라우저 저장소에 저장해두었다가 유저의 정보가 필요한 API를 사용할 때 API 요청과 함께 보내주게 되면, 해당 키를 백엔드에서 복호화해서 사용자를 식별한 후 접근이 가능하도록함
JWT 토큰에는 해당 토큰이 **발급 받아온 서버에서 정상적으로 발급을 받았다는 증명을하는 signature를 가지고 있음 → 사용자의 정보를 DB를 열어보지 않고도 식별할 수 있음
📍JWT 토큰의 구성
1. header : 토큰의 타입, 암호화시 사용한 알고리즘 정보
2. payload : 토큰 발행정보(누구인지, 언제 발행되었는지, 언제 만료될 것 인지)
3. signature : 토큰의 비밀번호
❌ JWT토큰은 암호화 했지만 누구든 열어볼 수 있다는 것 -> 중요한 데이터는 JWT 토큰에 저장해서는 안됨
로그인을 해서 accessToken을 받아오는 과정
accessToken을 통해 복호화하여 유정정보를 검증하는 과정
양방향 암호화(Encryption)
: 암호화와 복호화과정을 통해 송 ・ 수신 간 주고받는 메시지를 안전하게 암호화하고 평문으로 복호화하는 과정.단방향 암호화
: 해싱(Hashing)을 이용한 암호화 방식으로 양방향과는 다른 개념으로, 평문을 암호문으로 암호화는 가능하지만 암호문을 평문으로 복호화 하는 것은 불가능. 즉, 암호를 푸는 것은 불가
해시에 의해 암호화된 데이터를 다이제스트(digest)
// users.resolver.ts
import { Args, Int, Mutation, Resolver } from '@nestjs/graphql';
import { User } from './entities/user.entity';
import { UserService } from './user.service';
@Resolver()
export class UsersResolver {
constructor(
private readonly userService: UserService, //
) {}
@Mutation(() => User)
createUser(
@Args('email') email: string,
@Args('password') password: string,
@Args('name') name: string,
@Args({ name: 'age', type: () => Int }) age: number,
//@Args 를 통해 createUser 시 필요한 정보들을 받아오고, 받아온 데이터를 users.service.ts 파일로 넘겨줌
) {
return this.userService.create({ email, password, name, age });
//return 을 통해 프론트로 유저 정보 객체 결과 값을 보내줌
}
}
// users.service.ts
import { ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async create({ email, password, name, age }) {
const user = await this.userRepository.findOne({ where: { email } });
if (user) throw new ConflictException('이미 등록된 이메일입니다.');
//중복 이메일 가입 방지
// if(user) throw new HttpException("이미 등록된 이메일입니다.", HttpStatus.CONFLICT) // 이렇게도 가능
return this.userRepository.save({ email, password, name, age });
// 넘겨받은 유저정보를 DB에 저장하고 `return`을 통해 `users.resolver.ts`
}
}
// user.entity.ts
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
@ObjectType()
export class User {
@PrimaryGeneratedColumn('uuid')
@Field(() => String)
id: string;
@Column()
@Field(() => String)
email: string;
@Column()
// @Field(() => String) 비밀번호 노출 금지!!(graphql)
password: string;
@Column()
@Field(() => String)
name: string;
@Column()
@Field(() => Int)
age: number;
}
해싱의 대표적인 라이브러리
// users.resolver.ts
import { Args, Int, Mutation, Resolver } from '@nestjs/graphql';
import { User } from './entities/user.entity';
import { UsersService } from './user.service';
import * as bcrypt from 'bcrypt';
// as를 사용해 bcrypt 모듈의 모든 메서드를 사용
@Resolver()
export class UserResolver {
constructor(
private readonly userService: UserService, //
) {}
@Mutation(() => User)
async createUser(
@Args('email') email: string,
@Args('password') password: string,
@Args('name') name: string,
@Args({ name: 'age', type: () => Int }) age: number,
) {
const hashedPassword = await bcrypt.hash(password, 10);
//hash 메서드의 두 번째 인자는 salt . 원본 password를 10회 salt 시켜 주는 것
console.log(hashedPassword);
return this.userService.create({ email, hashedPassword, name, age });
}
}
// users.service.ts
import { ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
) {}
async create({ email, hashedPassword: password, name, age }) {
//resolver 파일에서 받아온 hashedPassword를 create 함수내에서 password로 바꿔서 사용하기 위해
//`hashedPassword: password` 를 인자값
const user = await this.userRepository.findOne({ where: { email } });
if (user) throw new ConflictException('이미 등록된 이메일입니다.');
// if(user) throw new HttpException("이미 등록된 이메일입니다.", HttpStatus.CONFLICT) // 이렇게도 가능
return this.userRepository.save({ email, password, name, age });
} //short hand property
}