Skip to content

[Feature] SpringSecurity 및 소셜로그인 구현#148

Merged
Kimgyuilli merged 48 commits intodevelopfrom
145-feature/spring-security-jwt
Jan 28, 2026
Merged

[Feature] SpringSecurity 및 소셜로그인 구현#148
Kimgyuilli merged 48 commits intodevelopfrom
145-feature/spring-security-jwt

Conversation

@Kimgyuilli
Copy link
Contributor

🛠 Related issue 🛠

✏️ Work Description ✏️

  • Spring Security + JWT 인증 시스템 구현
    • Stateless JWT 기반 인증 구성
    • Access Token (30분) / Refresh Token (14일) 발급
    • RTR (Refresh Token Rotation) 적용
  • 소셜 로그인 구현 (카카오, 애플)
    • 카카오: Access Token으로 /v2/user/me API 호출하여 사용자 정보 조회
    • 애플: Identity Token을 Apple 공개키로 RSA 검증
    • Apple 공개키 24시간 캐싱으로 성능 최적화
  • Redis 기반 토큰 관리
    • Refresh Token 저장 (refresh_token:{userId})
    • Access Token 블랙리스트 (blacklist:{token})
    • TTL 기반 자동 만료
  • Auth API 엔드포인트
    • POST /api/auth/login - 소셜 로그인
    • POST /api/auth/refresh - 토큰 재발급
    • POST /api/auth/logout - 로그아웃 (RT 삭제 + AT 블랙리스트)
  • User 엔티티 확장
    • socialProvider (KAKAO, APPLE)
    • socialId (소셜 플랫폼 고유 ID)
    • email
  • 인증 관련 글로벌 설정
    • SecurityConfig - Spring Security 필터 체인
    • JwtAuthenticationFilter - JWT 검증 필터
    • JwtAuthenticationEntryPoint - 401 처리
    • JwtAccessDeniedHandler - 403 처리
    • @CurrentUser - 현재 사용자 주입 어노테이션

📸 Screenshot 📸

설명 사진
소셜 로그인 API image
토큰 재발급 API image
로그아웃 API image

😅 Uncompleted Tasks 😅

  • 기존 컨트롤러 @RequestHeader("X-User-Id")@CurrentUser UserPrincipal 변경 (별도 PR 예정)

📢 To Reviewers 📢

  • application-auth.yamlJWT_SECRET_KEY, APPLE_CLIENT_ID 환경변수 설정 필요
  • application-redis.yamlREDIS_HOST, REDIS_PORT, REDIS_PASSWORD 환경변수 설정 필요
  • Apple 공개키 캐싱 전략: 24시간 캐싱 + kid 불일치 시 강제 갱신 + 네트워크 장애 시 기존 캐시 사용
  • Access Token 블랙리스트는 토큰 남은 만료시간만큼 TTL 설정되어 자동 정리됨

@Kimgyuilli Kimgyuilli self-assigned this Jan 26, 2026
@Kimgyuilli Kimgyuilli linked an issue Jan 26, 2026 that may be closed by this pull request
@soseoyo12
Copy link

ㅏㅏㅏㅏ 진짜 바로 소설 로그인을;;;

…TEAM-Cherrish/Cherrish-Server into 145-feature/spring-security-jwt

t # Please enter a commit message to explain why this merge is necessary,
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @.env.example:
- Line 1: Remove the corrupted characters at the start of the first line so the
header reads a clean ASCII comment (replace "프롲# Database Configuration" with "#
Database Configuration"); locate the exact malformed string "프롲# Database
Configuration" and trim the non-ASCII prefix so dotenv parsers won't
misinterpret the line or produce key-value parsing errors.

In
`@src/main/java/com/sopt/cherrish/domain/auth/application/service/AuthService.java`:
- Around line 131-140: In logout(Long userId, String authorizationHeader) you
currently add the access token to the blacklist regardless of TTL; change the
logic to only call accessTokenBlacklistRepository.add(accessToken,
remainingExpiration) when accessToken != null AND remainingExpiration > 0 (use
jwtTokenProvider.getRemainingExpiration(accessToken) to compute
remainingExpiration) so expired tokens (remainingExpiration <= 0) are not added
after refreshTokenRepository.deleteByUserId(userId).
- Around line 69-77: AuthService currently saves a new User (in the isNewUser
branch using User.builder and userRepository.save) without handling the unique
email constraint, which can throw DataIntegrityViolationException when
socialUserInfo.email() already exists; update the logic to first check
userRepository.existsByEmail(socialUserInfo.email()) and, if true, handle
according to business rules (e.g., link accounts, return a clear error
response), or wrap the userRepository.save(...) call in a try/catch for
DataIntegrityViolationException to return a controlled error, ensuring the fix
references the isNewUser branch, User.builder, socialUserInfo.email(), and
userRepository.save.

In
`@src/main/java/com/sopt/cherrish/domain/auth/infrastructure/jwt/JwtTokenProvider.java`:
- Around line 154-163: The getRemainingExpiration method in JwtTokenProvider
currently swallows all exceptions and returns 0; change it to catch only
expected JWT parsing exceptions (e.g., io.jsonwebtoken.JwtException and
IllegalArgumentException thrown by parseClaims) and return 0 for those, while
logging and rethrowing or returning a conservative value for unexpected
exceptions so they aren't silently ignored; reference the getRemainingExpiration
and parseClaims methods and ensure unexpected exceptions are logged via your
logger (or rethrown) instead of being swallowed.
- Around line 41-45: In JwtTokenProvider.init(), validate the decoded secret key
length before creating the SecretKey: decode jwtProperties.getSecretKey() into
keyBytes, check keyBytes.length >= 32 (256 bits) and if not throw a clear
IllegalStateException (or log and exit) describing that the configured secret
key is too short for HMAC-SHA (include the actual byte length and guidance to
use a 256-bit+ Base64 key); only then call Keys.hmacShaKeyFor(keyBytes) to
assign this.secretKey, and optionally catch and rethrow any
Decoders.BASE64.decode errors with a descriptive message so startup failures are
explicit.

In `@src/test/java/com/sopt/cherrish/domain/user/fixture/UserFixture.java`:
- Around line 17-19: Add a public reset hook to UserFixture that clears the
static counters to avoid test order dependence: provide a method like
resetCounters() (or two methods resetIdCounter() and resetSocialIdCounter())
that sets ID_COUNTER and SOCIAL_ID_COUNTER back to 1 (or new AtomicLong(1)), and
call this from tests that need a clean state; update references to ID_COUNTER
and SOCIAL_ID_COUNTER in UserFixture factory methods to keep behavior unchanged.
♻️ Duplicate comments (1)
src/main/java/com/sopt/cherrish/global/config/SecurityConfig.java (1)

73-79: CORS에서 * + credentials 조합은 여전히 위험합니다.
allowedOriginPatterns("*")allowCredentials(true)를 함께 쓰면 모든 출처가 인증 정보를 포함한 요청을 보낼 수 있습니다. 프로덕션에서는 허용 도메인 목록을 명시하거나 환경별 설정으로 주입해 제한해 주세요. (이전 리뷰와 동일 이슈입니다.)

#!/bin/bash
# CORS 허용 도메인 설정이 이미 존재하는지 확인
rg -n --glob 'application*.yml' --glob 'application*.yaml' 'cors|allowedOrigin|allowedOrigins|allowedOriginPatterns'

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @.env.example:
- Around line 8-17: The example JWT_SECRET_KEY in .env.example doesn't meet the
JwtTokenProvider's Base64 decode and minimum 32-byte requirement; update the
.env.example to either provide a valid Base64-encoded key of at least 32 bytes
(so JwtTokenProvider can successfully Base64.decode and validate) or add a clear
comment next to JWT_SECRET_KEY describing the requirement (Base64 format and
minimum length) so users supply a correct secret.

In
`@src/main/java/com/sopt/cherrish/domain/auth/infrastructure/jwt/JwtTokenProvider.java`:
- Around line 138-152: isAccessToken()와 isRefreshToken()가 parseClaims(token) 호출
시 발생할 수 있는 ExpiredJwtException, UnsupportedJwtException, MalformedJwtException 등
JWT 관련 예외를 처리하지 않으므로, 각 메서드에서 parseClaims 호출을 try-catch로 감싸고 Jwt 예외들을 모두 잡아
AuthException(INVALID_TOKEN)으로 명시적으로 변환해 던지도록 수정하세요; 구체적으로 JwtTokenProvider의
isAccessToken 및 isRefreshToken 내부에서 parseClaims(token) 호출을 try { Claims claims =
parseClaims(token); ... } catch (JwtException | IllegalArgumentException e) {
throw new AuthException(INVALID_TOKEN); } 형태로 처리해 암묵적 계약에 의존하지 않도록 만드세요.

In `@src/main/java/com/sopt/cherrish/domain/user/domain/model/User.java`:
- Around line 39-40: The User entity currently marks socialId as globally
unique; change this to a composite unique constraint on (socialProvider,
socialId) instead: remove unique = true from the socialId `@Column`, add a
`@Table`(uniqueConstraints = `@UniqueConstraint`(columnNames = {"social_provider",
"social_id"})) on the User class (or equivalent names used in your mapping), and
ensure the field names/socialProvider and socialId match the column names in the
constraint; update any DB migration/schema generation to reflect the new
composite unique constraint.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.env.example:
- Around line 16-17: Add a short explanatory comment above the APPLE_CLIENT_ID
placeholder that explains what value to provide and where to obtain it (e.g.,
the Apple Developer account / Services ID or Bundle ID used for Sign in with
Apple), and note the expected format (reverse-DNS bundle identifier) so
less-experienced developers understand how to populate APPLE_CLIENT_ID.

Copy link
Contributor

@ssyoung02 ssyoung02 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와우... 고생하셨습니다

@Kimgyuilli Kimgyuilli merged commit adc45e4 into develop Jan 28, 2026
2 checks passed
@Kimgyuilli Kimgyuilli added 우선순위: P1 현재 스프린트 내 처리, 주요 기능 개발 및 중요 버그 도메인: 인증🔐 로그인, 회원가입, 토큰 관리 and removed 우선순위: P0 즉시 대응 필요, 서비스 장애 또는 핵심 기능 불가 labels Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

담당: 규일🍊 규일 담당 작업 도메인: 인증🔐 로그인, 회원가입, 토큰 관리 우선순위: P1 현재 스프린트 내 처리, 주요 기능 개발 및 중요 버그

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spring security, jwt 적용

3 participants