TIL - 20260531

juni·2026년 5월 31일

TIL

목록 보기
366/468

0531 풀스택 실무 기초 (4/N): 인증과 인가, 세션과 JWT


✅ 1. 인증과 인가란 무엇인가?

  • 웹서비스에서 인증(Authentication)과 인가(Authorization)는 보안의 핵심 개념입니다.
  • 두 단어가 비슷해 보이지만 의미는 다릅니다.

➕ 1-1. 인증(Authentication)

  • 인증은 “사용자가 누구인지 확인하는 과정”입니다.
  • 쉽게 말하면 로그인입니다.
  • 사용자가 아이디와 비밀번호를 입력하면, 서버는 그 정보가 맞는지 확인하고 사용자를 식별합니다.
사용자: 저는 admin@example.com입니다.
서버: 비밀번호가 맞는지 확인하겠습니다.
서버: 확인 완료. 당신은 admin@example.com 사용자입니다.
  • 실무 예시

    • 관리자 로그인
    • 회원 로그인
    • 휴대폰 본인인증
    • 소셜 로그인
    • API Key 인증

➕ 1-2. 인가(Authorization)

  • 인가는 “인증된 사용자가 특정 기능을 사용할 권한이 있는지 확인하는 과정”입니다.
  • 로그인했다고 해서 모든 기능을 사용할 수 있는 것은 아닙니다.
  • 예를 들어 일반 사용자는 관리자 페이지에 접근하면 안 됩니다.
사용자: 저는 로그인한 사용자입니다. 관리자 페이지에 들어가겠습니다.
서버: 로그인은 되어 있지만 관리자 권한은 없습니다.
서버: 접근 거부.
  • 실무 예시

    • 관리자만 상품 등록 가능
    • 슈퍼관리자만 계정 삭제 가능
    • 본인 주문만 조회 가능
    • 담당자만 상담 상태 변경 가능
    • 로그인 사용자만 마이페이지 접근 가능

✅ 2. 인증과 인가를 구분해야 하는 이유

  • 인증과 인가를 구분하지 못하면 보안 설계가 흔들립니다.
  • 실무에서는 “로그인 여부 확인”과 “권한 확인”을 분리해서 생각해야 합니다.

➕ 2-1. 잘못된 예시

로그인만 되어 있으면 관리자 API 접근 허용
  • 이 방식은 위험합니다.
  • 일반 사용자도 로그인만 되어 있으면 관리자 기능을 사용할 수 있기 때문입니다.

➕ 2-2. 올바른 예시

1. 사용자가 로그인되어 있는지 확인
2. 사용자의 role이 ADMIN인지 확인
3. ADMIN이면 관리자 API 접근 허용
4. 아니면 403 Forbidden 반환
  • 인증 실패와 인가 실패는 상태 코드도 다르게 처리하는 것이 좋습니다.
상황상태 코드의미
로그인하지 않음401 Unauthorized인증 필요
로그인했지만 권한 없음403 Forbidden접근 권한 없음

✅ 3. 로그인 처리 기본 흐름

  • 로그인은 단순히 아이디와 비밀번호를 비교하는 작업이 아닙니다.
  • 비밀번호 암호화, 토큰 발급, 쿠키 저장, 세션 관리, 권한 확인까지 연결됩니다.

➕ 3-1. 기본 로그인 흐름

1. 사용자가 이메일/비밀번호 입력
2. React에서 POST /api/auth/login 요청
3. NestJS 서버가 이메일로 사용자 조회
4. 입력한 비밀번호와 DB의 암호화된 비밀번호 비교
5. 일치하면 로그인 성공
6. 서버가 세션 또는 JWT 발급
7. 클라이언트가 이후 요청에 인증 정보를 함께 전송
8. 서버가 인증 정보를 확인하고 API 처리

➕ 3-2. 로그인 API 요청 예시

POST /api/auth/login
Content-Type: application/json

{
  "email": "admin@example.com",
  "password": "password1234"
}

➕ 3-3. 로그인 성공 응답 예시

{
  "success": true,
  "message": "로그인에 성공했습니다.",
  "data": {
    "user": {
      "id": 1,
      "email": "admin@example.com",
      "role": "ADMIN"
    },
    "accessToken": "jwt-access-token"
  }
}
  • 단, 실제 서비스에서는 보안상 토큰을 응답 body로 줄지, HttpOnly Cookie로 줄지 신중히 결정해야 합니다.

✅ 4. 비밀번호 저장 방식

  • 비밀번호는 절대 원문 그대로 DB에 저장하면 안 됩니다.
  • DB가 유출되었을 때 사용자의 실제 비밀번호가 그대로 노출되기 때문입니다.

➕ 4-1. 잘못된 방식

email: admin@example.com
password: password1234
  • 이 방식은 매우 위험합니다.
  • 운영 서비스에서 비밀번호를 평문으로 저장하는 것은 절대 피해야 합니다.

➕ 4-2. 올바른 방식

  • 비밀번호는 해시 함수와 salt를 사용해 암호화된 값으로 저장합니다.
  • 실무에서는 bcrypt, argon2 같은 라이브러리를 많이 사용합니다.
email: admin@example.com
passwordHash: $2b$10$X8Z...
  • 로그인할 때는 사용자가 입력한 비밀번호를 다시 해시해서 비교하는 것이 아니라, bcrypt의 비교 함수를 사용합니다.
import * as bcrypt from 'bcrypt';

const isValidPassword = await bcrypt.compare(
  loginDto.password,
  user.passwordHash,
);

✅ 5. 세션 기반 인증

  • 세션 기반 인증은 서버가 로그인 상태를 직접 기억하는 방식입니다.
  • 사용자가 로그인하면 서버는 세션 저장소에 로그인 정보를 저장하고, 브라우저에는 세션 ID가 담긴 쿠키를 내려줍니다.

➕ 5-1. 세션 인증 흐름

1. 사용자가 로그인
2. 서버가 세션 저장소에 사용자 정보 저장
3. 서버가 브라우저에 sessionId 쿠키 전달
4. 브라우저는 이후 요청마다 sessionId 쿠키 자동 전송
5. 서버는 sessionId로 세션 저장소 조회
6. 사용자 인증 여부 확인

➕ 5-2. 세션 방식의 장점

  • 서버에서 로그인 상태를 제어하기 쉽습니다.
  • 강제 로그아웃 처리가 쉽습니다.
  • 세션을 삭제하면 즉시 인증을 무효화할 수 있습니다.
  • 브라우저 기반 서비스와 잘 어울립니다.

➕ 5-3. 세션 방식의 단점

  • 서버가 세션 상태를 저장해야 합니다.
  • 서버가 여러 대로 늘어나면 세션 공유 문제가 생깁니다.
  • Redis 같은 별도 세션 저장소가 필요할 수 있습니다.
  • 모바일 앱이나 외부 API 연동에서는 JWT 방식이 더 편할 수 있습니다.

✅ 6. JWT 기반 인증

  • JWT(JSON Web Token)는 사용자 인증 정보를 토큰 형태로 만들어 클라이언트가 들고 다니는 방식입니다.
  • 서버는 토큰을 검증해서 사용자가 누구인지 확인합니다.

➕ 6-1. JWT 구조

  • JWT는 보통 세 부분으로 구성됩니다.
Header.Payload.Signature
구성 요소설명
Header토큰 타입과 서명 알고리즘 정보
Payload사용자 ID, 권한, 만료시간 같은 데이터
Signature토큰이 변조되지 않았는지 검증하는 서명

➕ 6-2. JWT Payload 예시

{
  "sub": 1,
  "email": "admin@example.com",
  "role": "ADMIN",
  "iat": 1717142400,
  "exp": 1717146000
}
필드의미
sub사용자 ID
email사용자 이메일
role사용자 권한
iat토큰 발급 시간
exp토큰 만료 시간
  • JWT Payload는 암호화된 것이 아니라 인코딩된 값입니다.
  • 따라서 비밀번호, 주민등록번호, API Key 같은 민감한 정보는 절대 넣으면 안 됩니다.

✅ 7. Access Token과 Refresh Token

  • JWT 인증에서는 보통 Access Token과 Refresh Token을 함께 사용합니다.

➕ 7-1. Access Token

  • API 요청 시 인증에 사용하는 토큰입니다.
  • 보통 짧은 만료 시간을 가집니다.
  • 예를 들어 15분, 30분, 1시간 등으로 설정할 수 있습니다.
Authorization: Bearer access-token

➕ 7-2. Refresh Token

  • Access Token이 만료되었을 때 새 Access Token을 발급받기 위한 토큰입니다.
  • Access Token보다 긴 만료 시간을 가집니다.
  • 예를 들어 7일, 14일, 30일 등으로 설정할 수 있습니다.

➕ 7-3. 토큰 재발급 흐름

1. 사용자가 API 요청
2. Access Token 만료로 401 발생
3. 클라이언트가 Refresh Token으로 재발급 요청
4. 서버가 Refresh Token 검증
5. 새 Access Token 발급
6. 기존 API 요청 재시도
  • Refresh Token은 탈취되면 위험하므로 저장 위치와 만료 정책을 신중히 설계해야 합니다.

✅ 8. 토큰 저장 위치

  • 프론트엔드에서 JWT를 어디에 저장할지는 보안과 사용성에 큰 영향을 줍니다.
  • 대표적으로 localStorage, sessionStorage, Cookie를 사용할 수 있습니다.

➕ 8-1. localStorage

  • 브라우저에 데이터를 영구적으로 저장합니다.

  • 새로고침하거나 브라우저를 닫았다 열어도 유지됩니다.

  • 장점

    • 사용하기 쉽습니다.
    • React에서 토큰을 꺼내 API 헤더에 넣기 편합니다.
  • 단점

    • JavaScript로 접근 가능하므로 XSS 공격에 취약합니다.
    • 악성 스크립트가 실행되면 토큰이 탈취될 수 있습니다.
localStorage.setItem('accessToken', token);

➕ 8-2. sessionStorage

  • 탭 또는 브라우저 세션이 유지되는 동안만 데이터를 저장합니다.

  • 장점

    • localStorage보다 유지 시간이 짧습니다.
    • 탭을 닫으면 데이터가 사라집니다.
  • 단점

    • JavaScript 접근이 가능하므로 XSS 위험은 여전히 있습니다.
    • 새 탭이나 브라우저를 다시 열면 로그인 상태 유지가 어렵습니다.

  • JavaScript에서 접근할 수 없는 쿠키입니다.
  • 서버가 Set-Cookie 헤더로 내려주고, 브라우저가 이후 요청에 자동으로 포함합니다.
Set-Cookie: refreshToken=abc123; HttpOnly; Secure; SameSite=Lax
  • 장점

    • JavaScript로 토큰을 읽을 수 없어 XSS에 상대적으로 강합니다.
    • 브라우저가 자동으로 쿠키를 전송합니다.
  • 단점

    • CSRF 공격을 고려해야 합니다.
    • CORS와 쿠키 설정이 복잡해질 수 있습니다.
    • 프론트엔드와 백엔드 도메인이 다르면 설정을 더 신중히 해야 합니다.

✅ 9. 쿠키 보안 옵션

  • 쿠키를 사용할 때는 보안 옵션을 반드시 이해해야 합니다.

➕ 9-1. HttpOnly

Set-Cookie: token=abc; HttpOnly
  • JavaScript에서 쿠키를 읽지 못하게 합니다.
  • XSS로 인한 토큰 탈취 위험을 줄일 수 있습니다.

➕ 9-2. Secure

Set-Cookie: token=abc; Secure
  • HTTPS 연결에서만 쿠키가 전송되도록 합니다.
  • 운영 환경에서는 반드시 사용하는 것이 좋습니다.

➕ 9-3. SameSite

Set-Cookie: token=abc; SameSite=Lax
  • 다른 사이트에서 요청할 때 쿠키가 전송되는 방식을 제어합니다.
  • CSRF 공격을 줄이는 데 도움이 됩니다.
값설명
Strict같은 사이트 요청에만 쿠키 전송
Lax일반적인 링크 이동에는 허용, 위험한 크로스 사이트 요청은 제한
None크로스 사이트 요청에도 쿠키 전송 가능. Secure 필수

✅ 10. NestJS에서 JWT 인증 구조

  • NestJS에서는 보통 AuthModule, AuthService, JwtStrategy, AuthGuard를 사용해 인증 구조를 만듭니다.

➕ 10-1. 역할 구분

AuthController: 로그인, 회원가입, 토큰 재발급 API 담당
AuthService: 사용자 검증, 비밀번호 비교, 토큰 발급 담당
JwtStrategy: JWT 토큰 검증 방식 정의
JwtAuthGuard: 인증이 필요한 API 보호
RolesGuard: 권한이 필요한 API 보호

➕ 10-2. 로그인 Controller 예시

import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  login(@Body() body: { email: string; password: string }) {
    return this.authService.login(body.email, body.password);
  }
}

➕ 10-3. 인증이 필요한 API 예시

import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';

@Controller('admin')
export class AdminController {
  @UseGuards(JwtAuthGuard)
  @Get('me')
  getMe() {
    return {
      message: '인증된 사용자만 접근 가능합니다.',
    };
  }
}

✅ 11. 인가와 Role 관리

  • 관리자 페이지에서는 단순 로그인 여부보다 권한 관리가 중요합니다.
  • 모든 관리자가 같은 권한을 가지면 위험할 수 있습니다.

➕ 11-1. Role 예시

SUPER_ADMIN: 모든 권한
ADMIN: 일반 관리자 권한
MANAGER: 상담/주문 관리 권한
VIEWER: 조회만 가능
USER: 일반 사용자

➕ 11-2. Role 기반 접근 제어 예시

상품 삭제 API: SUPER_ADMIN, ADMIN만 가능
상담 상태 변경 API: SUPER_ADMIN, ADMIN, MANAGER 가능
상담 목록 조회 API: SUPER_ADMIN, ADMIN, MANAGER, VIEWER 가능
관리자 계정 생성 API: SUPER_ADMIN만 가능

➕ 11-3. 실무 주의점

  • 프론트엔드에서 버튼을 숨기는 것만으로는 보안이 되지 않습니다.
  • 사용자는 브라우저 개발자 도구나 직접 API 호출로 요청을 보낼 수 있습니다.
  • 권한 검사는 반드시 백엔드에서 해야 합니다.
프론트엔드: 권한 없는 버튼 숨김 → 사용자 경험 개선
백엔드: 권한 없는 API 차단 → 실제 보안

✅ 12. 인증 관련 상태 코드

  • 인증/인가에서는 상태 코드를 일관되게 사용하는 것이 중요합니다.
상태 코드상황예시
400 Bad Request요청 형식 오류이메일 형식이 잘못됨
401 Unauthorized인증 실패로그인하지 않음, 토큰 만료
403 Forbidden인가 실패권한 없는 관리자 API 접근
404 Not Found사용자 없음존재하지 않는 계정
409 Conflict중복 충돌이미 가입된 이메일
429 Too Many Requests요청 과다로그인 시도 횟수 초과
  • 로그인 실패 메시지는 너무 자세히 알려주지 않는 것이 좋습니다.
위험한 메시지:
존재하지 않는 이메일입니다.
비밀번호가 틀렸습니다.

더 안전한 메시지:
이메일 또는 비밀번호가 올바르지 않습니다.
  • 공격자가 어떤 이메일이 가입되어 있는지 추측하는 것을 어렵게 만들 수 있습니다.

✅ 13. 인증 보안에서 자주 하는 실수

➕ 13-1. 비밀번호 평문 저장

  • 가장 위험한 실수입니다.
  • 비밀번호는 반드시 bcrypt, argon2 같은 방식으로 해시 처리해야 합니다.

➕ 13-2. JWT에 민감한 정보 넣기

  • JWT Payload는 누구나 디코딩해서 볼 수 있습니다.
  • 주민등록번호, 비밀번호, API Key, 내부 권한 정보 전체를 넣으면 안 됩니다.

➕ 13-3. 토큰 만료 시간을 너무 길게 설정

  • Access Token 만료 시간이 너무 길면 탈취되었을 때 피해가 커집니다.
  • Access Token은 짧게, Refresh Token은 더 안전하게 관리하는 것이 좋습니다.

➕ 13-4. 백엔드 권한 체크 누락

  • 프론트엔드에서 메뉴를 숨겼다고 해서 API가 보호되는 것은 아닙니다.
  • 중요한 API는 반드시 서버에서 Role을 검사해야 합니다.

➕ 13-5. 로그에 토큰이나 비밀번호 출력

  • 서버 로그, 브라우저 콘솔, 에러 로그에 토큰이나 비밀번호가 남으면 안 됩니다.
console.log(password)
console.log(accessToken)
console.log(refreshToken)
  • 이런 로그는 개발 중에도 습관적으로 피해야 합니다.

✅ 14. 실무 인증 체크리스트

➕ 14-1. 로그인 기능 구현 체크리스트

  1. 이메일 또는 아이디 형식 검증이 있는가?
  2. 비밀번호를 평문이 아닌 해시로 저장하는가?
  3. 로그인 실패 메시지가 과도하게 자세하지 않은가?
  4. Access Token 만료 시간이 적절한가?
  5. Refresh Token 저장 방식이 안전한가?
  6. 로그아웃 시 토큰 또는 세션을 무효화할 수 있는가?
  7. 로그인 실패 횟수 제한이 필요한가?
  8. 관리자 계정에는 더 강한 비밀번호 정책이 필요한가?
  9. HTTPS 환경에서만 인증 정보를 주고받는가?
  10. 인증 관련 로그에 민감정보가 남지 않는가?

➕ 14-2. 관리자 권한 체크리스트

  1. 관리자와 일반 사용자가 구분되어 있는가?
  2. Role 또는 Permission 구조가 정의되어 있는가?
  3. 관리자 API에 인증 Guard가 적용되어 있는가?
  4. 권한별로 접근 가능한 API가 구분되어 있는가?
  5. 프론트엔드 버튼 숨김과 별개로 백엔드 권한 검사가 있는가?
  6. 관리자 계정 생성/삭제는 최고 권한자만 가능한가?
  7. 중요한 변경 작업은 로그로 남기는가?
  8. 퇴사자 또는 외부 작업자 계정 회수 절차가 있는가?

➕ 14-3. 토큰 관리 체크리스트

  1. JWT secret이 Git에 올라가 있지 않은가?
  2. Access Token에 민감한 정보가 들어가지 않았는가?
  3. Access Token 만료 시간이 너무 길지 않은가?
  4. Refresh Token 탈취 시 대응 방법이 있는가?
  5. HttpOnly, Secure, SameSite 옵션을 이해하고 적용했는가?
  6. CORS 설정과 쿠키 설정이 충돌하지 않는가?
  7. 인증 실패 시 프론트엔드에서 재로그인 처리가 되는가?

✅ 15. AI를 활용해 인증 기능을 만들 때 주의할 점

  • 인증 기능은 보안과 직접 연결되므로 AI가 작성한 코드를 그대로 믿으면 안 됩니다.
  • AI는 동작하는 예제 코드는 잘 만들어주지만, 프로젝트의 실제 보안 요구사항까지 자동으로 책임져주지는 않습니다.

➕ 15-1. AI에게 요청할 때 좋은 질문

NestJS에서 JWT 로그인 기능을 만들고 싶어.
조건은 다음과 같아.

1. 비밀번호는 bcrypt로 비교
2. Access Token 만료 30분
3. Refresh Token은 HttpOnly Cookie로 저장
4. 관리자 role 기반 Guard 필요
5. Prisma User 모델 기준으로 작성
6. 민감정보는 응답에서 제외
7. 각 파일 역할을 설명해줘

➕ 15-2. AI 답변 검증 기준

  1. 비밀번호를 평문으로 비교하고 있지 않은가?
  2. JWT secret을 코드에 하드코딩하지 않았는가?
  3. 토큰에 민감한 정보를 넣지 않았는가?
  4. 인증 Guard와 권한 Guard가 분리되어 있는가?
  5. 백엔드에서 권한 체크가 실제로 이루어지는가?
  6. 에러 메시지가 과도하게 자세하지 않은가?
  7. 로그에 비밀번호나 토큰이 출력되지 않는가?

📌 요약

  • 인증은 사용자가 누구인지 확인하는 과정이고, 인가는 사용자가 특정 기능을 사용할 권한이 있는지 확인하는 과정입니다.
  • 로그인 여부와 권한 여부는 반드시 분리해서 생각해야 합니다.
  • 비밀번호는 절대 평문으로 저장하면 안 되며, bcrypt나 argon2 같은 방식으로 해시 처리해야 합니다.
  • 세션 기반 인증은 서버가 로그인 상태를 기억하는 방식이고, JWT 기반 인증은 클라이언트가 토큰을 가지고 다니는 방식입니다.
  • JWT Payload에는 민감한 정보를 넣으면 안 되며, Access Token과 Refresh Token의 역할을 구분해야 합니다.
  • 토큰 저장 위치는 localStorage, sessionStorage, HttpOnly Cookie 각각 장단점이 있으므로 보안 요구사항에 맞게 선택해야 합니다.
  • 관리자 페이지에서는 Role 기반 인가 처리가 중요하며, 프론트엔드 버튼 숨김만으로는 보안이 되지 않습니다.
  • 인증 기능은 AI가 작성한 코드라도 반드시 비밀번호 처리, 토큰 만료, 권한 체크, 민감정보 노출 여부를 직접 검증해야 합니다.

0개의 댓글