이번에 팀프로젝트를 하며 내가 맡은 부분은 댓글 CRUD, 좋아요 부분이었기에 인증 부분에 대한 이해가 조금 부족하게 느껴졌다. 팀원의 코드를 분석해가며 인증 부분의 기초인 쿠키와 세션에 대해 복습을 진행해보려한다.
** 인증 및 권한 부여
// Access Token을 생성하는 함수
function createAccessToken(id) {
const accessToken = jwt.sign(
{ id: id }, // JWT 데이터
ACCESS_TOKEN_SECRET, // Access Token의 비밀 키
{ expiresIn: TOKEN_EXPIREDIN }, // Access Token이 10초 뒤에 만료되도록 설정합니다.
);
return accessToken;
}
// Refresh Token을 생성하는 함수
function createRefreshToken(id) {
const refreshToken = jwt.sign(
{ id: id }, // JWT 데이터
REFRESH_TOKEN_SECRET, // Refresh Token의 비밀 키
{ expiresIn: REFRESH_TOKEN_EXPIREDIN }, // Refresh Token이 7일 뒤에 만료되도록 설정합니다.
);
return refreshToken;
}
createAccessToken 함수:
- 입력: 사용자의 ID (id)
- 동작:
jwt.sign() 함수를 사용하여 JWT(access token)를 생성
JWT 데이터로는 사용자의 ID를 포함 - 이 데이터를 통해 서버에서 특정 사용자를 식별할 수 있음
Access Token의 비밀 키로는 ACCESS_TOKEN_SECRET를 사용 - 이는 서버에서 토큰을 검증할 때 사용되는 비밀 키
토큰의 만료 시간은 TOKEN_EXPIREDIN 변수에 지정된 만료 시간으로 설정(10초)로 설정
- 출력: 생성된 Access Token
createRefreshToken 함수
-입력: 사용자의 ID (id)
-동작:
jwt.sign() 함수를 사용하여 JWT(refresh token)을 생성
JWT 데이터로는 사용자의 ID를 포함하고 있습니다. 이 데이터를 통해 서버에서 특정 사용자를 식별
Refresh Token의 비밀 키로는 REFRESH_TOKEN_SECRET를 사용 - 서버에서 토큰을 검증할 때 사용되는 비밀 키
토큰의 만료 시간은 REFRESH_TOKEN_EXPIREDIN 변수에 지정된 만료 시간으로 설정( 7일)로 설정
-출력: 생성된 Refresh Token
** 로그인
//로그인
/** Access Token, Refresh Token 발급 API **/
const { id } = user;
const accessToken = createAccessToken(id);
const refreshToken = createRefreshToken(id);
// Refresh Token을 가지고 해당 유저의 정보를 서버에 저장합니다.
tokenStorage[refreshToken] = {
id: id, // 사용자에게 전달받은 ID를 저장합니다.
ip: req.ip, // 사용자의 IP 정보를 저장합니다.
userAgent: req.headers["user-agent"], // 사용자의 User Agent 정보를 저장합니다.
};
res.cookie("accessToken", `Bearer ` + accessToken); // Access Token을 Cookie에 전달한다.
res.cookie("refreshToken", `Bearer ` + refreshToken); // Refresh Token을 Cookie에 전달한다.
- Access Token과 Refresh Token 생성:
사용자의 ID를 이용하여 createAccessToken 함수와 createRefreshToken 함수를 호출하여 액세스 토큰과 리프레시 토큰 생성
- Refresh Token을 저장:
사용자의 리프레시 토큰을 서버의 특정 위치에 저장
tokenStorage 객체를 사용하여 리프레시 토큰과 사용자 정보(IP 주소 및 User Agent)를 저장 - 사용자의 리프레시 토큰을 검증하고 사용자 정보를 확인할 수 있음
- 클라이언트에게 토큰 전달:
생성된 액세스 토큰과 리프레시 토큰을 클라이언트에게 전달
res.cookie() 함수를 사용하여 쿠키에 토큰을 저장 - 클라이언트는 이를 받아서 이후 요청에서 토큰을 사용할 수 있음
Access Token은 accessToken이라는 이름의 쿠키에, Refresh Token은 refreshToken이라는 이름의 쿠키에 저장
각 토큰 앞에는 "Bearer "를 붙여서 클라이언트에게 전달
**authMiddleware
//authMiddleware
const authorization = req.cookies.accessToken;
// **Authorization** 또는 **AccessToken이 없는 경우** - “인증 정보가 없습니다.”
if (!authorization) throw new Error(`인증 정보가 없습니다.`);
// - **JWT 표준 인증 형태와 일치하지 않는 경우** - “지원하지 않는 인증 방식입니다.”
const [tokenType, token] = authorization.split(" "); // 토큰타입 Bearer와 payload를 분리
if (tokenType !== "Bearer")
throw new Error("지원하지 않는 인증 방식입니다.");
const decodedToken = jwt.verify(token, ACCESS_TOKEN_SECRET); // 토큰의payload와 SECRETKEY가 동일하면 해당 데이터를 해석하여 변수로할
const userId = decodedToken.id; // 해석한 데이터객체 내 userId키의 값을 userId 변수에 할당 / 해당변수는 숫자로 된 문자열
// userId 변수가 데이터베이스 users테이블 내 userId 키의 일치한 값이 있는지 확인
// 없다면 쿠키 삭제 후 에러 메세지 반환
// - **Payload에 담긴 사용자 ID와 일치하는 사용자가 없는 경우** - “인증 정보와 일치하는 사용자가 없습니다.”
const user = await prisma.user.findFirst({
where: { id: userId },
});
if (!user) {
res.clearCookie("authorization");
throw new Error("인증 정보와 일치하는 사용자가 없습니다.");
}
- 클라이언트로부터 전송된 HTTP 요청에서 쿠키를 추출하여 accessToken 변수에 저장 - 사용자의 Access Token을 포함
- accessToken이 없다면(클라이언트가 요청에 Access Token을 제공하지 않은 경우) 오류를 발생시킴
- JWT의 경우, 토큰은 "Bearer [토큰값]"의 형태를 가짐 - accessToken을 공백 문자로 분리하여 토큰의 종류와 실제 토큰 값을 추출
- 추출된 토큰의 종류가 "Bearer"가 아닌 경우(다른 형태의 인증 방식을 사용하는 경우) 오류를 발생
- jsonwebtoken 패키지의 verify 함수를 사용하여 토큰을 해독 - 토큰의 유효성을 검사, 토큰에 포함된 데이터를 반환(사용자 ID)
- 해독된 토큰에서 사용자 ID를 추출하여 userId 변수에 저장합니다.
- 추출된 사용자 ID를 사용하여 데이터베이스에서 사용자를 찾음
- 사용자가 존재하지 않는 경우 (데이터베이스에서 일치하는 사용자를 찾지 못한 경우) 쿠키를 삭제하고 오류를 발생
쿠키 (Cookie)
- 브라우저가 서버로부터 응답으로 Set-Cookie 헤더를 받은 경우 해당 데이터를 저장한 뒤 모든 요청에 포함하여 보냄
- 주로 웹 브라우저에 저장되며, 클라이언트와 서버 간의 상호 작용에서 사용
- 이름, 값, 만료 날짜 등의 정보로 구성
- 클라이언트 측에서 수정할 수 있으므로 보안에 취약할 수 있음
- 주로 사용자의 로그인 상태, 언어 설정, 사용자 환경 설정 등을 유지하기 위해 사용
- cookie-parser
이전에는 req.headers.cookie와 같이 여러 프로퍼티를 넘어서야만 쿠키를 사용할 수 있었지만 cookie-parser 미들웨어를 이용하면 더욱 간편하게 쿠키를 관리할 수 있음
ex)
- 쿠키 조회 부분을 req.cookies로 변경
- 쿠키의 형태가 name=sparta에서 { name: 'sparta' } 형태의 객체로 변환
세션 (Session):
- 쿠키를 기반으로 구성된 기술 단, 클라이언트가 마음대로 데이터를 확인 할 수 있던 쿠키와는 다르게 세션은 데이터를 서버에만 저장
- 세션은 서버 측에서 사용자의 상태를 유지하는 메커니즘
- 각 사용자는 고유한 세션을 가지며, 클라이언트와 서버 간의 연결을 식별하는 세션 ID를 사용하여 식별
- 주로 서버의 메모리, 데이터베이스 또는 파일 시스템에 저장
- 클라이언트 측에서 수정할 수 없으므로 쿠키보다 보안에 더 우수
- 주로 사용자의 로그인 상태, 장바구니 내용, 사용자 프로필 등을 유지하고 관리할 때 사용
/set-session API를 호출했을 때 name=sparta 의 정보를 서버에 저장하고, 저장한 시간 정보를 쿠키로 반환.
/get-session API를 호출했을 때 쿠키의 시간 정보를 이용하여 서버에 저장된 name 정보를 출력
이런식으로 쿠키에 시간정보를 넘겨줌으로써 식별하고 보안 유지
JWT (JSON Web Token):
- JWT는 정보를 안전하게 전송하기 위한 인터넷 표준으로, JSON 객체를 사용하여 토큰을 표현
- 사용자의 정보와 서명된 토큰을 포함하여 페이로드로 구성
- 서버에서 사용자를 인증하는 데 사용되며, 클라이언트 측에서 상태를 유지할 필요가 없음
- 토큰은 Base64로 인코딩되어 있으며, 서명되어 클라이언트와 서버 간의 통신에서 안전하게 전송
- 토큰의 만료 시간을 지정하여 보안을 강화할 수 있음
- 주로 사용자의 로그인 상태, 권한 부여 등을 관리할 때 사용
- 데이터를 교환하고 관리하는 방식인 쿠키/세션과 달리, JWT는 단순히 데이터를 표현하는 형식
** JWT와 쿠키, 세션 다른 점
- JWT로 만든 데이터는 변조가 어렵고, 서버에 별도의 상태 정보를 저장하지 않기 때문에, 서버를 Stateless(무상태)로 관리할 수 있음
- 쿠키와 세션은 사용자의 로그인 정보나 세션 데이터를 서버에 저장하므로 상태를 유지 - Stateful(상태 보존)하게 데이터가 관리됨
**JWT를 써야하는 이유
- JWT가 인증 서버에서 발급되었는지 위변조 여부를 확인할 수 있음
- 누구든지 JWT 내부에 들어있는 정보를 확인할 수 있음 (복호화)
아직 refresh token에 대한 부분에 대해 복습을 진행하지 못하기도 했고, 눈에 보이지 않는 이 인증 개념이 명확하게 이해가 가지 않는다. 개념으로만 이해하는 것은 한계가 있는 것 같으니, 최대한 많은 코드를 접하고 직접 만들어보며 이해해나가보려한다.
'개발 기초 다지기' 카테고리의 다른 글
Prisma에서 include와 select의 차이점 (0) | 2024.06.12 |
---|---|
알고리즘 문제 정리 (최대공약수와 최소공배수) (0) | 2024.06.11 |
RESTful API에 대하여 (0) | 2024.06.07 |
Prisma로 데이터베이스 스키마 변경하기 (0) | 2024.06.06 |
알고리즘 문제 풀이 (부족한 금액 계산하기, 문자열 다루기 기본, 행렬의 덧셈) (0) | 2024.06.04 |
댓글