Back-end/Spring

[Spring] Spring Security JWT 로그인 구현 필수 개념 (Session, JWT, Access / Refresh Token)

랑 이 2026. 1. 16. 21:58
반응형

이전 글에서 Spring Security기본 구조와 인증 처리 흐름에 대해 작성해 보았습니다

이번에는 Spring Security를 이용하여 JWT 토큰 기반 인증 방식을 적용한 로그인 서비스를 구현해보려고 합니다

 

Spring Security에서 기본적으로 세션(Session) 기반 인증 방식을 제공하고 있습니다

세션 기반 인증

클라이언트가 서버에 로그인 요청을 보내고 로그인 성공 시 서버가 사용자의 인증 정보를 세션에 저장하고 세션 ID를 생성하여

클라이언트에 쿠키로 전달한다 이후 요청에서 클라이언트는 세션 ID를 함께 보내 서버에서 사용자를 식별한다

위 다이어그램은 세션 기반 인증 방식의 전체 흐름을 간단하게 나타낸 것이다 

클라이언트와 서버 간의 인증 방식은 크게 다음과 같은 순서로 진행된다

 

1. 로그인 요청 전송

클라이언트는 사용자로부터 입력받은 아이디(Eamil)와 비밀번호를 포함하여 서버에 로그인 요청을 보낸다

POST /login
{ email, password }
Spring Security의 인증 필터를 통해 처리된다

 

2. 인증 성공 및 세션 생성

서버는 전달받은 사용자 정보를 통해 사용자를 식별하고 인증에 성공하면 다음 작업을 수행한다

- 사용자 인증 정보를 서버의 세션(Session)에 저장

- 해당 세션을 식별하기 위한 Session ID 생성

 

3. Session ID를 쿠키로 클라이언트에 전달

서버는 생성된 Session ID쿠키(Cookie) 형태클라이언트에 전달한다

- 일반적으로 JSESSIONID라는 이름의 쿠키를 사용

- 클라이언트는 별도 처리 없이 쿠키를 자동 저장

 

4. 이후 서버에 요청 시 Session ID 포함 전송

로그인 이후 클라이언트가 서버에 요청을 보낼 때마다 브라우저는 저장된 Session ID 쿠키를 자동으로 함께 전송한다

Request + Session ID

 

5. 서버에서 사용자 식별

서버는 전달받은 Session ID를 기반으로 세션을 조회하고 세션에 저장된 인증 정보를 통해 사용자를 식별한다

- 추가적인 로그인 과정 없이 인증 상태를 유지

- 인증 정보를 서버가 직접 관리하는 형태

 

6. 응답 반환

사용자 인증이 완료되면 서버는 요청을 정상 처리하여 클라이언트에게 응답을 반환한다

토큰 기반 인증

서버가 사용자의 인증 정보직접 유지하지 않고 로그인 성공 시 발급한 토큰을 통해 사용자를 식별하는 방식으로 토큰은 서버에서 클라이언트를 구분하기 위한 유일한 값이다 클라이언트는 전달받은 토큰을 스토리지,쿠키에 저장하여 이후 요청마다 토큰을 헤더나 쿠키에 담아서 서버에 함께 전송하고 서버는 토큰의 유효성만 검증하여 사용자를 인증한다

1. 로그인 요청 전송

클라이언트는 사용자로부터 입력받은 아이디(Email)와 비밀번호를 포함하여 서버에 로그인 요청

POST /login 
{ 
    email, 
    password 
}
해당 요청은 Spring Security의 인증 필터(Authentication Filter)를 통해 처리된다

 

2. 사용자 정보 조회 및 인증 검증

서버는 전달받은 이메일을 기반으로 DB에서 사용자 정보를 조회하고 저장된 비밀번호와 요청으로 전달된 비밀번호를

비교하여 인증을 수행한다

 

Spring Security에서는 비밀번호를 저장할 때 PasswordEncoder를 사용하여 단방향 해시 처리를 한 뒤 DB에 저장하기 때문에

로그인 시 입력된 비밀번호를 동일한 방식으로 해시 처리한 뒤 비교하여 인증을 수행한다

  • 사용자 존재 여부 확인
  • 비밀번호 일치 여부 검증
  • 인증 실패 시 예외 발생

3. JWT 토큰 생성 및 응답

인증에 성공하면 서버는 사용자 식별 정보와 권한 정보를 기반으로 JWT 토큰을 생성한다

생성된 JWT 토큰은 응답(Response)으로 클라이언트에 전달된다

  • Access Token(JWT) 생성
  • 토큰에는 사용자 식별 정보와 만료 시간이 포함됨
  • 서버는 세션을 생성하거나 저장하지 않음 (Stateless)

4. 클라이언트 토큰 저장

클라이언트는 서버로부터 전달받은 JWT 토큰을 저장한다

이후 서버에 요청을 보낼 때 헤더에 토큰을 함께 전송하게 된다

  • LocalStorage
  • SessionStorage
  • HttpOnly Cookie
LocalStorage 방식은 구현이 간편하지만
JavaScript를 통해 접근이 가능하기 때문에 XSS 공격에 취약하다는 단점이 있다

이러한 보안 이슈를 고려하여, HttpOnly 옵션이 적용된 Cookie 방식을 사용하는 게 더 적합하며
주로 HttpOnly Cookie 방식을 사용하는 것을 권장된다

JavaScript에서 직접 접근이 불가능하며 또한 헤더에 담아 전달하지 않아도 되기 때문에 프론트엔드에서 토큰을 직접 헤더에 추가하여 서버에 요청을 보낼 필요가 없다는 점에서도 효율적인 방식이라고 생각하고 있습니다

 

5. 토큰과 함께 요청

로그인 이후 클라이언트는 서버에 요청을 보낼 때
HTTP HeaderAuthorization 필드에 JWT 토큰을 포함하여 전송한다

Authorization: Bearer {JWT}

쿠기에 저장하고 있다면 따로 헤더에 추가하여 보내지 않고 따로 설정을 해줘야 한다

// Web
fetch(url, {
  credentials: "include"
});

// Axios
axios.get(url, { withCredentials: true });

 

6. 토큰 검증 및 인증 실패 처리

서버는 요청이 들어오면 JWT 인증 필터(JWT Authentication Filter)에서 토큰을 검증한다

  • 토큰 존재 여부 확인
  • 서명(Signature) 검증
  • 만료 시간(exp) 검증

검증에 실패한 경우:

  • 토큰이 없거나 유효하지 않으면 401 Unauthorized
  • 권한이 부족한 경우 403 Forbidden 응답 반환
    → 컨트롤러에는 진입하지 않음

7. 인증 성공 시 데이터 조회 및 처리

토큰 검증에 성공하면 서버는 토큰에 포함된 사용자 정보를 기반으로 SecurityContext에 인증 정보를 저장한다

이후 컨트롤러에서 인증된 사용자로 요청을 처리하고 필요한 데이터를 데이터베이스에서 조회한다

 

8. 인증 성공 응답 반환

모든 인증 및 인가 과정이 완료되면 서버는 요청을 정상 처리하여 200 OK 응답과 함께 결과 데이터를 클라이언트에 반환한다

JWT (JSON Web Token)

 

JSON Web Tokens - jwt.io

JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).

www.jwt.io

사용자 인증 및 인가필요한 정보 JSON 형태로 담아 서명한 토큰으로 서버가 상태를 유지하지 않는

Stateless 인증을 가능하게 하는 방식에 사용

JWT 구성 요소

헤더(Header), 내용(Payload), 서명(signature) 3가지 영역으로 이루어져 있습니다

 

헤더(Header)

토큰의 타입과 해시 알고리즘 정보를 담겨 있습니다

{
  "alg": "HS256",
  "typ": "JWT"
}
이름  설명
alg 서명 암호화 알고리즘 (HS256) 
typ 토큰 타입을 지정

 

내용(Payload)

토큰에서 사용할 정보 클레임(claim)이 담겨 있습니다

내용의 한 덩어리를 클레임(claim)이라고 부르며 key-value 형식으로 이루어진 한 쌍의 정보를 의미합니다

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

클레임은 등록된 클레임,공개 클레임,비공개 클레임으로 3가지로 나눌 수 있습니다

 

등록된 클레임(Registered claims)은 토큰에 대한 정보를 담는다

이름 설명
iss 토큰 발급자 (issuer)
sub 토큰 제목 (subject)
aud 토큰 대상자 (audience)
exp 토큰의 만료 시간 (NumericDate 형식)
nbf 토큰 사용 가능 시간 (Not Before)
iat 토큰 발급 시간 (issued at)
jti 토큰 고유 ID

 

공개 클레임(Public claims)은 사용자 정의 클레임으로 그대로 공개되어도 무방한 정보를 담고 있습니다

이름은 충돌을 방지해야 하고 주로 URI로 이름을 짓습니다

{
  "https://example.com/role": "USER"
}

 

비공개 클레임(Priavte claims)은 서버와 클라이언트 사이에서 정보 공유를 위한 커스텀 클레임으로 유저를 특정하는 정보를 담고 있다 하지만 JWT 토큰은 인코딩 되어 있기 때문에 디코딩하여 원본을 확인이 가능하기 때문에 외부에 공개되면 안 되는 정보는 담아서는 안된다

{
  "sub": "user-1",
  "role": "ROLE_USER",
  "nickname": "tester"
}

 

서명(signature)

토큰이 중간에 위 ·변조되지 않았음검증하기 위한 용도로 사용되는 값

 

Header와 Payload 비밀 키로 서명하여 생성합니다 이를 통해 토큰이 중간에 위 ·변조되었는지 여부를 검증할 수 있습니다

Spring에서 JWT 서명에 사용할 비밀키(JWT_SECRET)를 단순 문자열로 사용하지 않고 256bit 이상의 랜덤 키를 생성하여
환경변수에 저장하여 관리하는 게 권장됩니다

Access Token / Refresh Token

JWT 토큰을 탈취당하게 된다면 사용자를 인증하는 티켓을 노출된 상태이기 때문에 해당 유저가 아닌 다른 유저가 접근할 수 있기 때문에 Access Token / Refresh Token 2가지 토큰을 사용하여 관리하는 방식을 많이 사용하고 있습니다

 

Access Token / Refresh Token 2가지 모두 JWT 토큰으로 동일하지만 저장 및 유효기간의 차이가 존재합니다

  • Access Token: 클라이언트에 저장하는 토큰으로 인증 이후 서버에 요청할 때 함께 포함하여 전달하는 토큰을 의미한다
  • Refresh Token: DB에 저장되는 토큰으로 만료된 Access Token을 재발급하기 위해 사용되는 토큰을 의미한다 
Access Token은 API 요청마다 서버에 전달되는 토큰으로 탈취될 경우를 대비하여 유효기간을 짧게 설정하는 것이 일반적이다
하지만 유효기간을 짧게 설정하게 되면 유저는 인증을 위해 로그인을 자주 해야 하는 문제가 있다

이러한 문제를 해결하기 위해 Refresh Token을 도입하는 편이다
만료된 Access Token을 재발급하기 위한 용도로 유효기간을 Access Token 보다 길게 설정하며 DB에 저장한다

사용자는 Refresh Token을 통해 Access Token을 재발급받아 사용하면 다시 로그인하지 않고 인증 상태를 유지할 수 있다
이런 구조는 Access Token을 탈취 시 발생할 수 있는 보안 위험을 최소화하는 구조이다

 

반응형