기존에 했던 Spring Security 많이 까먹어서 코틀린으로 한 번 더 개발해봄.
개발 프로세스
1.
공통 모듈 정의 Enum(OAuth2 Provider, Role, Member Status), Exception(Error Code)
2.
회원, 리프레시 토큰 도메인 추가
3.
JwtProvider 구현 → 토큰 생성 및 검증
4.
JwtAuthFilter 구현 → 토큰에서 인증정보 추출
5.
OAuth2UserInfo, CustomOAuth2UserService → OAuth2 사용자 정보 파싱
6.
OAuth2SuccessHandler, OAuth2FailureHandler → 로그인 성공/실패 핸들러
7.
SecurityConfig 추가
8.
토큰 갱신, 로그아웃 API 추가
기능 수행 프로세스
클라이언트 → OAuth2 Provider → 서버 → JWT 발급 → 클라이언트
최초 로그인 (소셜 로그인)
1.
클라이언트에서 /oauth2/authorization/{provider} 로 요청
a.
이 엔드포인트는 Spring Security OAuth2 Client 의존성이 자동으로 생성해줌
b.
콜백 URL까지 자동이라 SecurityConfig에서 아래 .oauth2Login 블록만 끼워주면 전체 흐름 연결
.oauth2Login {
it.userInfoEndpoint { endpoint -> endpoint.userService(customOAuth2UserService) }
it.successHandler(oAuth2SuccessHandler)
it.failureHandler(oAuth2FailureHandler)
}
Kotlin
복사
2.
해당 {provider}로 리다이렉트 → 사용자가 로그인 수행
3.
Provider 쪽에서 Authorization Code를 서버로 전달 (서버와 직접 통신)
4.
서버가 Provider에게 Authorization Code → Access Token 교환 요청
5.
서버가 Access Token으로 사용자 정보 조회
6.
DB에서도 provider, providerId로 회원 조회
a.
없으면 → 신규 생성
b.
있으면 → 업데이트 (nickname, profileImage, lastLoginAt 등)
7.
JWT Access Token 발급
8.
Refresh Token 생성 → DB 저장(Redis 같은) → HttpOnly Cookie로 전달
9.
클라이언트 redirect URI로 리다이렉트 (?token=accessToken)
인증된 API 요청
1.
클라이언트가 Authorization: Bearer {accessToken} 헤더와 함께 API 요청
2.
JwtAuthFilter에서 토큰 꺼내서 Claims(내부 데이터) 검증(validateToken → parseClaims try)
a.
VALID → getMemberId, getRole
b.
EXPIRED → ExpiredJwtException 401
c.
INVALID → Exception 401
3.
Controller에서 SecurityContext의 memberId로 요청 처리
a.
JwtProvider 에서 val auth = UsernamePasswordAuthenticationToken(memberId, null, authorities)(여기서 principal = memberId, credential = null 임)로 설정했기 때문에, 컨트롤러에서 이런식으로 꺼낸다.
@GetMapping("/me")
fun getMyInfo(authentication: Authentication):
ApiResponse<...> {
val memberId = authentication.principal as Long
Kotlin
복사
b.
토큰 갱신 (Refresh)
1.
Access Token 만료 → 클라이언트 /auth/refresh요청 (@CookieValue("refresh_token"..))
2.
서버에서 Refresh Token 추출 (findByToken)
3.
만료되었을 경우 → EXPIRED_TOKEN + DB에서 삭제
4.
token의 memberId로 회원 조회
5.
새 Access Token 발급 후 반환.
6.
Refresh Token은 선택적으로 재발급(Refresh Token Rotation 설정은 내 기준 하는게 훨씬 괜찮은 것 같음. 보안적으로)
로그아웃
1.
클라이언트가 /auth/logout 요청
2.
서버가 DB에서 refresh_token 삭제
3.
서버가 Cookie를 만료(maxAge=0)시켜 응답
4.
클라이언트가 보유한 accessToken은 클라이언트 측에서 삭제
(서버는 stateless라 별도 블랙리스트 없이는 강제 만료 불가)
추가적인 보안 체크리스트
•
BANNED 회원 로그인 시 차단 로직 추가
•
Refresh Token Rotation 적용 여부 결정
•
Access Token 블랙리스트 필요 여부 결정 (Redis)
•
Cookie의 SameSite 속성 설정 (CSRF 방어)
•
HTTPS 환경에서만 secure=true Cookie 동작 확인
