먼저 JWT(JSON Web Token)에 대해 알아보자.
Spring Security는 여러 가지 인증 방법을 지원하며, JWT는 그 중 하나일 뿐이다.
- 그 외 인증 방식 : 세션 기반 인증(상태 저장 방식) 등
JWT의 구조
Header (헤더)
토큰의 타입과 서명 알고리즘을 포함한다.
Payload (페이로드)
주로 사용자 정보나 기타 데이터를 담고 있다.
Claims 포함된다.
Signature (서명)
JWT의 무결성을 검증하는 데 사용된다.
🧐 먼저 나는 왜 JWT를 선택했을까 ?
- JWT는 상태 비저장 방식의 인증을 지원하기 때문에 서버가 별도의 세션을 저장하지 않아도 되고, 토큰이 만료되지 않는 한 추가적인 인증 검증 절차가 필요없기 때문에 확장성 측면에서 유리하다.
- 리액트와 같은 SPA는 클라이언트 측에서 상태를 관리하는 특성이 있어, JWT를 localStorage에 저장해두고 이후 요청마다 인증 헤더에 포함시키는 방식이 자연스럽다
아래 설명한 파일들은 현재 PetHarmony Spring Security에서 JWT 인증을 구현하기 위해 필수적으로 필요한 파일들이다.
- CustomUserDetails
UserDetails 인터페이스를 구현하여 사용자의 세부 정보를 제공하는 클래스이다.
🧐 사용하지 않는 기본 메소드를 왜 남겨두었는지 ?
UserDetails 인터페이스에서 제공하는 기본 메소드는 Spring Security에서 필수적으로 요구하는 메소드들이고,
현재 필요가 없다고 해도 추후 확장 가능성을 고려해 남겨두었다.
UserDetails 인터페이스에서 제공하는 메소드
- getAuthorities() : 사용자의 권한 목록을 반환
- getPassword() : 사용자의 비밀번호를 반환
- getUsername() : 사용자의 고유한 사용자 이름(우리 프젝에선 이메일)을 반환 ➡️ 이름은 중복되지만, 이메일은 고유하기 때문
(⬇️ 사용하지 않는 메소드)
- isAccountNonLocked() : 계정이 잠겨있지 않았는지 확인
- isAccountNonExpired() : 계정이 만료되지 않았는지 확인
- isCredentialsNonExpired() : 자격 증명이 만료되지 않았는지 확인
- isEnabled() : 계정이 활성화되었는지 확인
직접 만든 메소드
비즈니스 로직에 맞게 사용자 정보를 더 구체적으로 관리하고자 메소드를 직접 구현했다.
예를 들어, isEnabled()는 계정이 활성화되었는지 여부만 확인할 수 있지만 PetHarmony에서는 계정 활성화 외에도 탈퇴 여부와 계정 상태를 ACTIVE와 BANNED 같은 세부적인 정보가 필요했기 때문에 커스텀 메소드를 추가하게 되었다.
- getUserName() : 사용자의 이름을 반환 (Spring Security의 getUsername()과 혼동되지 않도록 설계)
- getIsWithdrawal() : 탈퇴 여부를 반환
- getUserState() : 계정 상태를 반환
- CustomUserDetailsService
Spring Security에서 사용자 인증 작업을 처리하기 위한 서비스로, UserDetailsService 인터페이스를 구현하여 사용자 정보를 데이터베이스에서 조회하는 역할을 담당한다.
loadUserByUsername 메소드
: 사용자 인증을 시도할 때 전달받은 이메일을 파라미터로 받아 사용자를 조회하고, 해당 사용자의 세부 정보(UserDetails)를 반환한다.
- JwtTokenProvider
Spring Security를 사용한 애플리케이션에서 인증을 위한 JWT를 생성하고, 이를 검증하여 사용자 정보를 추출하는 과정을 처리한다.
JWT 토큰 생성
- 주어진 사용자 인증 정보를 바탕으로 JWT 토큰을 생성한다.
- JWT는 사용자를 인증하는 데 필요한 정보(이메일, 만료 시간)를 포함한 JSON 객체를 암호화하여 생성된다.
- 토큰 만료 시간을 현재 시간으로부터 24시간 뒤로 설정한다.
- 생성된 JWT는 HMAC-SHA512 알고리즘을 사용해 서명된다.
JWT 토큰에서 사용자 정보 추출
- JWT에서 사용자 이메일을 추출하는 역할을 한다.
- JWT는 서명이 검증된 후, Claims 객체에서 사용자 정보를 가져온다.
- setSubject로 설정된 값(username => 사용자 이메일)을 추출한다.
JWT 토큰 검증
- 주어진 JWT 토큰이 유효한지 확인한다.
- JWT의 서명을 검증하고, 토큰의 유효성을 체크한다.
- 서명이 올바르지 않거나 만료된 토큰을 예외를 발생시킨다.
- JwtAuthenticationFilter
Spring Security에서 JWT를 사용하여 사용자 인증을 처리하는 필터 역할을 한다.
OncePerRequestFilter를 상속받아, 요청마다 한 번만 실행되는 필터로 동작한다.
JWT가 요청 헤더에 포함되어 있는 경우 이를 검증하고, 사용자 정보를 SecurityContext에 설정하는 역할을 한다.
doFilterInternal 메서드
- JWT 추출
요청 헤더에서 JWT를 추출한다. 헤더에서 "Authorization" 값을 확인하고, "Bearer"로 시작하는 토큰을 가져온다.
- 토큰 검증
유효하지 않은 토큰은 인증을 처리하지 않고 요청을 다음 필터로 넘긴다.
- 사용자 정보 로드
JWT에서 사용자 이메일을 추출한 후, 사용자 정보를 조회한다.
- 인증 설정
조회된 사용자 정보를 바탕으로 인증을 설정하고, 인증 정보를 SecurityContext에 저장한다.
동작 흐름
1. 클라이언트가 서버로 HTTP 요청을 보낼 때 JWT를 Authorization 헤더에 담아 보낸다.
2. JwtAuthenticationFilter가 요청을 가로채고, 헤더에서 JWT를 추출한다.
3. 추출한 JWT가 유효한지 JwtTokenProvider로 검증한다.
4. 토큰이 유효하다면, 토큰에서 사용자 정보를 추출하고, UserDetailsService를 통해 사용자 정보를 조회한다.
5. 조회된 사용자 정보를 바탕으로 인증 객체를 생성하고, SecurityContext에 저장한다.
6. 이후 요청은 인증된 상태로 처리되며, 사용자 정보는 SecurityContext를 통해 접근할 수 있다.
- SecurityConfig
Spring Security 설정을 위한 구성 클래스이다.
이 클래스는 Spring Security와 JWT를 이용하여 애플리케이션의 보안 설정을 관리한다.
HTTP 요청에 보안 규칙 정의
- CSRF(Cross-Site Request Forgery) 공격을 방지하기 위한 보안 기능 비활성화
🧐 보호하면 좋은 거 아니야? 왜 비활성화 해 ?
🗣️ JWT를 사용하는 애플리케이션에서는 보통 CSRF 보호가 필요 없다.
- authorizeHttpRequests : 요청에 대한 권한 설정
✅ /api/public/** : 이 경로로 시작하는 요청은 인증 없이 누구나 접근할 수 있도록 설정
✅ /api/user/** : 이 경로는 USER 역할을 가진 사용자만 접근할 수 있도록 설정
✅ /api/admin/** : 이 경로는 ADMIN 역할을 가진 사용자만 접근할 수 있도록 설정
✅ anyRequest().authenticated() : 위에 지정되지 않은 모든 요청은 인증이 필요하다.
- passwordEncoder()
비밀번호를 해시 처리하기 위해 정의한다. BCypt 알고리즘을 사용하여 비밀번호를 안전하게 저장한다.
🌈 자! 이제 추가해야할 건 뭘까 ??? 🍀
JWT 토큰 만료 처리를 처리해야 한다.
이 작업은 매우 중요하다. JWT 토큰은 만료 시간이 지나면 더 이상 사용할 수 없기 때문에 클라이언트에게 이를 알려야 하고, 적절한 조치를 취하는 로직을 추가해야 한다.
간편한 구현을 원한다면 Access Token만 사용하여 구현할 수도 있지만
이번 프로젝트에선 보안과 사용자 경험(UX) 모두 중요시 하기 때문에
Access Token + Refresh Token 조합 으로 해볼 것이다.
'팀프로젝트_PetHarmony' 카테고리의 다른 글
🔍 코드 리팩트링으로 성능 최적화 개선율 분석 (0) | 2024.09.20 |
---|---|
Spring Security 기록 (끝) (4) | 2024.09.05 |
⚡️ 트러블 슈팅 - 지연 초기화 (0) | 2024.09.02 |
❓보호 경로 설정이 안된다 (해결 완료) (3) | 2024.09.02 |
🌈 예외처리 (1) | 2024.08.25 |