로그인 구현 어케해요?에 대한 답을 정리해봤습니다.
1. JWT가 뭔디
Json Web Token
json 객체에 인증에 필요한 정보들을 담은 후 비밀키로 서명한 토큰이다.
- stateless 환경에서 사용자 데이터를 주고받을 수 있게 한다. (HTTP 프로토콜의 무상태성)
- 로그인 후 JWT를 헤더에 넣어서 보내야 하는데, 이는 매번 인증과정을 거쳐야 하는 HTTP의 특성때문이다.
- 공식적으로 인증(Authentication), 권한허가(Authorization) 방식으로 사용된다.
- 해커는 클라이언트-서버간 네트워크 트래픽을 가로채 JWT 토큰을 가져올 수 있는데, 이는 HTTPS를 사용함으로써 방지할 수 있다.
cf. HTTP 프로토콜의 무상태성
HTTP는 각 요청이 독립적으로 처리되기 때문에, 서버는 이전의 요청상태를 기억하지 못한다.
따라서 클라이언트는 매번 서버에 요청할 때마다 인증 정보를 포함해야 한다. (JWT를 Authorization 헤더에 포함시킴)
1-1 JWT 토큰 구조
Header
- kid: 서명 시 사용하는 키(Public/Private Key)를 식별하는 값
- typ: 사용할 타입(토큰 유형)
- alg: 서명 암호화 할고리즘
Payload
사용자 정보와 같은 클레임(claims)을 포함하며, 이 데이터는 서버에서 인증된 후 사용자에게 반환된다. 중요한 데이터는 포함하면 안됨
- sub (주제): JWT가 발급된 사용자 또는 개체를 식별하는 데 사용된다. 일반적으로 사용자ID로 사용됨
- name (이름): 사용자 이름을 나타내며, 추가 식별 정보를 제공한다.
- admin (관리자): 사용자가 관리자 권한을 가지고 있는지 여부를 나타내는 커스텀 클레임이다. (true/false)
- iat (발급 시간): JWT가 발급된 시점의 타임스탬프이다.
Signature
헤더/페이로드와 서버가 가진 유일한 key값을 합친 것을 헤더에서 정의한 alg로 암호화한다.
헤더와 페이로드는 단순 인코딩된 값으로, 제3자가 조작하거나 복호화할 수 있다.
Signiture는 서버 측에서 관리하는 비밀키가 있어야만 복호화가 가능하다. 따라서, 이는 토큰의 위변조 여부를 확인할 때 사용한다.
토큰 정보는 https://jwt.io/ 에서 확인할 수 있다.
2. JWT 인증 플로우
1.1 회원가입 (Sign-Up)
- 사용자 요청: 사용자는 이메일, 비밀번호 등의 회원가입에 필요한 정보를 입력하여 회원가입을 요청한다.
- 서버 응답: 서버가 사용자 정보를 저장하고, 필요 시 JWT를 생성하여 반환한다.
프론트: 사용자로부터 필수 정보를 입력받고, 이를 JSON 형태로 서버에 전송한다. (+ 비밀번호 형식 등 입력값 검증 필요)
1.2 로그인 (Login)
- 사용자 요청: 사용자가 id와 비밀번호를 입력하여 로그인한다,
- 서버 응답: 서버는 자격 증명을 확인하고, 성공 시 JWT를 발급하여 클라이언트에 반환합니다.
프론트: 사용자가 입력한 id/pw를 받아 POST 요청으로 서버에 전송한다. 요청 성공 시 JWT 토큰을 받아 클라이언트에 저장한다. (→2.)
2. 토큰 저장 (Token Storage)
프론트: 받은 JWT를 로컬 스토리지, 세션 스토리지, 또는 쿠키에 저장한다.
- 로컬 스토리지: 브라우저를 닫아도 유지
- 세션 스토리지: 브라우저 탭을 닫을 때까지 유지
- 쿠키: 보안 및 HttpOnly 옵션을 사용시 보안강화
- xss 공격 방지
4. 인증된 요청 (Authenticated Requests)
- 사용자 요청: 클라이언트가 인증이 필요한 자원에 접근할 때, 저장된 JWT를 Authorization 헤더에 포함하여 요청을 전송한다.
- 서버 검증: 서버는 JWT의 유효성을 검증하고, 요청을 처리한다.
프론트: Authorization 헤더에 Bearer 토큰을 포함하여 요청을 보낸다. (클라이언트가 요청을 보낼 때마다 헤더에 JWT를 포함)
5. 토큰 만료 및 갱신 (Token Refresh)
- JWT 토큰은 만료 시간이 존재해서, 만료시 새로운 jwt를 요청해야한다.
- refresh token으로 새로운 jwt를 요청하며, refresh token이 만료되면 사용자는 다시 로그인해야한다.
프론트: refresh token을 사용하여 새로운 JWT를 요청한 후, 새로운 JWT를 저장한다.
이 경우, 백그라운드에서 자동으로 처리되도록 구현해야 한다.
6. 로그아웃 (Logout)
- 사용자가 로그아웃
프론트: 저장된 JWT를 제거하여 더 이상 인증된 요청을 보내지 않게 하고, 상태를 로그아웃 상태로 변경한다.
3. 코드로 볼까요
회원가입
// 회원가입 API 요청
const signUp = async (userData) => {
const response = await fetch('https://api.example.com/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (response.ok) {
console.log('User signed up successfully');
} else {
console.error('Sign-up failed');
}
};
로그인 & 로컬에 저장하기
// 로그인 API 요청
const login = async (credentials) => {
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
if (response.ok) {
const data = await response.json();
const token = data.token; // 서버에서 받은 JWT
localStorage.setItem('jwt', token); // JWT를 로컬 저장소에 저장
console.log('User logged in successfully');
} else {
console.error('Login failed');
}
};
인증된 요청 보내기
사용자의 정보를 조회하거나, 유저 데이터에 접근할 때, 로그인할 때 주로 사용
// 인증된 API 요청
const fetchProtectedData = async () => {
const token = localStorage.getItem('jwt'); // 저장된 JWT 가져오기
const response = await fetch('https://api.example.com/protected', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`, // JWT를 Authorization 헤더에 추가
},
});
if (response.ok) {
const data = await response.json();
console.log('Protected data:', data);
} else {
console.error('Failed to fetch protected data');
}
};
토큰 만료 및 갱신 (Token Refresh)
import axios from 'axios';
// Axios 응답 인터셉터 설정
axios.interceptors.response.use(
response => response, // 응답이 성공적인 경우 그대로 반환
async error => {
const originalRequest = error.config;
// 401 에러(인증 실패) && 재시도가 없는 경우
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true; // 재시도 플래그를 설정
// 로컬 스토리지에서 리프레시 토큰 가져오기
const refreshToken = localStorage.getItem('refreshToken');
// 서버에 새로운 JWT를 요청
const { data } = await axios.post('/refresh-token', { token: refreshToken });
// 새 JWT 로컬 스토리지에 저장
localStorage.setItem('jwt', data.newToken);
// 원래 요청의 Authorization 헤더를 새로운 토큰으로 업데이트
originalRequest.headers['Authorization'] = 'Bearer ' + data.newToken;
// 원래 요청을 재시도
return axios(originalRequest);
}
return Promise.reject(error);
}
);
로그아웃
// 로그아웃
const logout = () => {
localStorage.removeItem('jwt'); // 로컬 스토리지에서 JWT 제거
console.log('User logged out');
};
예시 코드에서는 api 주소를 전부 노출했으나, 보통 .env파일에 넣어두고 사용한다. (for 환경별 주소 설정)
// .env
REAL_API_URL=https://api.example.com
// react 코드
import React from 'react';
const fetchData = async () => {
const response = await fetch(`${process.env.REAL_API_URL}/data`);
const data = await response.json();
console.log(data);
};
const App = () => {
React.useEffect(() => {
fetchData();
}, []);
return (
<div>
<h1>Data Fetch Example</h1>
</div>
);
};
export default App;
reference
JWT (Json Web Token)의 구조와 사용하기 – BizSpring BLOG
이번 포스팅에서는 마이크로서비스 아키텍처(Microservices Architecture, MSA) 프로젝트를 진행하면서 사용한 JWT(Json Web Token)에 대해 소개하려고 합니다. 프로젝트를 진행하면서 스프링 시큐리티(Spring S
blog.bizspring.co.kr
Using JWT (JSON Web Tokens) to authorize users and protect API routes
So your backend has a few API routes that need protectin’ and some user’s that need authorizin’. Much like myself at one point, you’re…
medium.com
[JWT인증] 프론트엔드에서 JWT 인증 구현하기
테이브 연합프로젝트에서 로그인 방식으로 JWT인증을 사용하기로 하여, JWT 방식에 대해 배우고 중요한 것들에 대해 정리해보려 한다. 일단, JWT 인증에 대한 개념을 익히기 전, 인증과 인가 개념
velog.io
'RUN' 카테고리의 다른 글
오픈그래프(og) 로컬에서 테스트하기 (0) | 2025.03.19 |
---|---|
Next.js Build Error: bcrypt 모듈 문제 해결하기 (0) | 2025.02.12 |
Next.js App Router에서 다국어 지원하기 (2) i18n 적용편 (next-intl) (0) | 2025.02.07 |
Next.js에서 다국어 지원하기 (1) google spreadsheet 연동편 (0) | 2025.01.22 |