2026.09.08

Memory Calendar 개발(4) - 로그인과 JWT Access Token 발급

1. 이번 작업의 목표

지난 작업에서는 User 도메인과 이메일 기반 회원가입을 구현했다.

이번에는 가입한 사용자의 이메일과 비밀번호를 확인하고, 로그인에 성공하면 JWT Access Token을 발급하도록 구성했다.

이번 단계는 토큰 발급까지 진행하고, 발급한 토큰을 검증해 보호된 API에 접근하도록 하는 기능은 다음 단계로 나눴다.

2. 로그인 처리

로그인 API는 POST /api/auth/signin으로 구현했다.

이메일로 사용자를 조회하고, PasswordEncoder.matches()로 입력한 비밀번호와 저장된 BCrypt 해시를 비교한다. 검증에 성공하면 Access Token과 사용자 정보를 반환한다.

로그인 과정은 Service에서 처리하고, 결과를 UserSignInResult로 묶어 Controller에 전달하도록 했다.

AuthService.signin()
→ 사용자 조회
→ 비밀번호 검증
→ Access Token 발급
→ UserSignInResult 반환

Controller는 이 결과를 응답 DTO로 변환한다. 사용자 조회와 토큰 발급 로직이 Controller에 흩어지지 않도록 역할을 나눈 것이다.

존재하지 않는 이메일과 잘못된 비밀번호는 같은 오류로 처리했다.

401 Unauthorized
AUTH_001
이메일 또는 비밀번호가 올바르지 않습니다.

로그인 실패 응답에서 이메일 존재 여부와 비밀번호 오류를 구분해 알려주지 않도록 했다.

3. JWT Access Token 구성

로그인 이후 요청에서 사용자를 식별하는 수단으로 JWT Access Token을 사용했다. 클라이언트가 발급받은 토큰을 보내면, 서버에서 검증한 뒤 사용자 ID를 확인하는 흐름으로 구성할 예정이다.

토큰은 HS256 방식으로 서명하고, 다음 정보를 포함하도록 했다.

  • sub: 사용자 ID
  • iat: 발급 시각
  • exp: 만료 시각

Access Token의 유효시간은 3,600초로 설정했다. 로그인 성공 응답에는 사용자 정보와 토큰, 유효시간을 함께 담았다.

{
  "userId": 1,
  "email": "user@example.com",
  "name": "사용자",
  "accessToken": "<access-token>",
  "expiresIn": 3600
}

이번에는 Access Token 발급에 집중하고, Refresh Token과 토큰 재발급은 이후 확장 항목으로 남겼다.

4. JWT Secret 외부 설정

JWT 서명에 사용하는 Secret은 저장소에 포함하지 않도록 .env로 분리했다.

application.yaml에서는 외부 파일을 불러오고, JWT_SECRET 값을 참조하도록 설정했다.

spring:
  config:
    import: optional:file:.env[.properties]

jwt:
  secret: ${JWT_SECRET}
  access-token-expiration: 3600

불러온 설정은 JwtProperties와 JwtConfig를 통해 토큰 발급에 사용하도록 연결했다.

실제 Secret이 들어 있는 .env는 Git 추적에서 제외하고, .env.example에는 필요한 변수 이름만 남겼다.

5. 적용 결과

정상적인 이메일과 비밀번호로 로그인했을 때 200 OK와 Access Token이 반환되고, expiresIn이 3600으로 내려오는 것을 확인했다.

존재하지 않는 이메일과 잘못된 비밀번호는 모두 401 / AUTH_001로 처리됐다. 잘못된 이메일 형식이나 빈 비밀번호는 요청 검증에서 400으로 반환됐다.

이번 작업으로 로그인과 Access Token 발급을 구현했다.

다음에는 요청의 Authorization 헤더로 전달된 토큰을 검증하고, 인증된 사용자만 보호된 API에 접근하도록 구성한다.