Skip to content

[feat] #9 - 구글 소셜 로그인 기능 구현#14

Open
Jy000n wants to merge 51 commits into
developfrom
feat/#9-google-oauth2-login
Open

[feat] #9 - 구글 소셜 로그인 기능 구현#14
Jy000n wants to merge 51 commits into
developfrom
feat/#9-google-oauth2-login

Conversation

@Jy000n

@Jy000n Jy000n commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

관련 이슈 🛠

작업 내용 요약 ✏️

Google OAuth2 소셜 로그인 기반 인증/인가 시스템 구축
JWT(AccessToken + RefreshToken)와 Redis를 활용한 Stateless 인증 구현

주요 변경 사항 🛠️

  • [Security]: SecurityConfig 설정 (CORS, CSRF 비활성화, Stateless 세션, URL 권한, AuthenticationEntryPoint)
  • [Auth]: CustomOAuth2UserService - Google 사용자 정보 기반 회원가입 / 로그인 처리
  • [Auth]: OAuthSuccessHandler - 1회성 code URL 전달 + RefreshToken HttpOnly 쿠키 설정
  • [Auth]: OAuthFailureHandler - 로그인 실패 시 ErrorDto 포맷 에러 응답
  • [JWT]: JwtTokenProvider - AccessToken(30분) / RefreshToken(7일) 생성 및 type 클레임 검증 분리
  • [JWT]: JwtAuthenticationFilter - Authorization 헤더 대신 쿠키에서 AccessToken 읽어 검증
  • [Redis]: RefreshTokenService - RefreshToken 저장 / 조회 / 삭제
  • [User]: User 엔티티 및 UserRepository 구현

트러블 슈팅 ⚽️

  • 소셜 로그인 테스트용 코드와 파일을 생성하는 도중
    • 프론트와 연결하지 않았는데 프론트의 주소로 리다이렉트하여 accessToken이 전달되지 않는 문제 수정 (테스트용)
    • 테스트용 /login-success 경로가 permitAll에 누락되어 인증 필터에 막히는 문제 수정
  • Redis TTL 단위 밀리초 → 초로 수정 (JWT보다 1000배 빨리 만료되는 버그)
  • accessToken URL 파라미터 노출 → HttpOnly 쿠키 → Body 응답 → 1회성 code 방식으로 변경
    • 토큰을 URL query parameter로 전달 시 브라우저 히스토리, 리퍼러, 프록시/서버 로그에 남을 수 있어 보안 취약점 존재
    • 1회성 code(30초 TTL)를 생성하여 URL 파라미터로 전달하고, 프론트에서 code로 /api/auth/token 호출 시 accessToken 발급
    • code는 1회 사용 후 즉시 Redis에서 삭제되어 재사용 불가
    • refreshToken은 HttpOnly 쿠키로 전달하여 JS 접근 차단

테스트 결과 📄

  • http://localhost:8080/oauth2/authorization/google 접속 후 Google 계정 선택
  • 로그인 성공 시 `http://localhost:8080/login-success 리다이렉트 확인
  • 로그인 성공 시 쿠키에 refreshToken 들어간 것 확인
  • accessToken 발급은 다음 PR에서 AuthController 구현 후 테스트 예정

스크린샷 📷

http://localhost:8080/oauth2/authorization/google 접속 시 로그인>구글 계정 선택 화면
image

로그인 성공 시 테스트용 /login-success 페이지로 리다이렉트 확인
image

로그인 성공 시 개발자 도구 > 애플리케이션 탭에서 쿠키 내 refreshToken 들어간 것 확인
image

로그인 성공 시 accesToken 발급을 위한 1회용 코드 생성 확인
image

1회용 코드로 accessToken 발급 확인
image

리뷰 요구사항 📢

  • 죄송합니다,, 커밋 메시지들이 예쁘게 안 들어간게 좀 있네요... 신경 쓰겠습니다ㅠㅠ

📎 참고 자료 (선택)

Summary by CodeRabbit

  • New Features

    • OAuth 로그인과 JWT 기반 인증 흐름이 추가되었습니다.
    • 로그인 성공 시 리다이렉트와 함께 인증 코드가 전달되며, 이를 통해 액세스 토큰을 발급받을 수 있습니다.
    • 리프레시 토큰 저장 및 관리가 지원됩니다.
    • 구글 소셜 로그인 사용자 정보가 저장·갱신됩니다.
  • Bug Fixes

    • 인증 실패 및 토큰 검증 실패 시 더 일관된 오류 응답이 제공됩니다.
    • 로그인 처리 중 사용자 정보가 없거나 유효하지 않을 때 적절한 실패 응답을 반환합니다.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Google OAuth2 소셜 로그인과 JWT 기반 인증 시스템이 추가되었다. User 엔티티/저장소, CustomOAuth2UserService, JwtTokenProvider, RefreshTokenService, AuthCodeService, 인증 성공/실패 핸들러, JwtAuthenticationFilter, RedisConfig, SecurityConfig 갱신, 토큰 교환용 AuthController가 포함된다.

Changes

OAuth2 + JWT 인증 시스템

Layer / File(s) Summary
User 엔티티 및 저장소
domain/user/entity/User.java, domain/user/repository/UserRepository.java, domain/user/enums/Provider.java, global/auth/dto/CustomUserDetails.java
users 테이블에 매핑되는 User 엔티티, 이메일 조회 저장소, Provider enum, OAuth2/Spring Security 사용자 브리지(CustomUserDetails)가 추가된다.
JWT 및 Redis 토큰 인프라
build.gradle, global/config/RedisConfig.java, global/jwt/provider/JwtTokenProvider.java, global/auth/service/RefreshTokenService.java, global/auth/service/AuthCodeService.java, global/exception/code/ErrorCode.java
OAuth2/Redis/JWT 의존성, RedisTemplate 빈, HMAC 기반 액세스/리프레시 토큰 발급·검증, Redis 기반 refresh 토큰 및 인증 코드 저장·조회·삭제, OAUTH2_INVALID_USER_INFO 오류 코드가 추가된다.
OAuth 사용자 처리와 성공·실패 응답
global/auth/service/CustomOAuth2UserService.java, global/auth/handler/OAuthSuccessHandler.java, global/auth/handler/OAuthFailureHandler.java, global/auth/handler/AuthErrorResponseWriter.java, global/auth/handler/JwtAuthenticationEntryPoint.java, global/auth/controller/AuthController.java
OAuth2 attributes로 사용자를 upsert하고, 성공 시 refreshToken 쿠키와 인증 코드 발급 후 리다이렉트하며, 실패/미인증 시 공통 오류 응답을 작성하고, 코드로 accessToken을 발급하는 엔드포인트가 추가된다.
JWT 필터 및 SecurityConfig
global/jwt/filter/JwtAuthenticationFilter.java, global/config/SecurityConfig.java
Bearer 토큰 검증 및 인증 컨텍스트 설정 필터, CORS/인가 규칙/OAuth2 로그인 연결/JWT 필터 배치/인증 진입점 구성이 SecurityConfig에 추가된다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant CustomOAuth2UserService
  participant OAuthSuccessHandler
  participant RefreshTokenService
  participant AuthCodeService
  participant AuthController
  participant JwtTokenProvider

  Browser->>CustomOAuth2UserService: OAuth2 사용자 정보 로드
  CustomOAuth2UserService->>OAuthSuccessHandler: CustomUserDetails 반환
  OAuthSuccessHandler->>JwtTokenProvider: generateRefreshToken(userId)
  OAuthSuccessHandler->>RefreshTokenService: save(userId, refreshToken)
  OAuthSuccessHandler->>AuthCodeService: generateAndSave(userId)
  OAuthSuccessHandler-->>Browser: redirectUri?code=...
  Browser->>AuthController: GET /api/auth/token?code=...
  AuthController->>AuthCodeService: getAndDelete(code)
  AuthController->>JwtTokenProvider: generateAccessToken(userId)
  AuthController-->>Browser: accessToken JSON
Loading
sequenceDiagram
  participant Client
  participant JwtAuthenticationFilter
  participant JwtTokenProvider
  participant UserRepository
  participant SecurityContextHolder

  Client->>JwtAuthenticationFilter: Authorization: Bearer <token>
  JwtAuthenticationFilter->>JwtTokenProvider: validateAccessToken(token)
  JwtTokenProvider-->>JwtAuthenticationFilter: true/false
  JwtAuthenticationFilter->>JwtTokenProvider: getUserId(token)
  JwtAuthenticationFilter->>UserRepository: findById(userId)
  UserRepository-->>JwtAuthenticationFilter: User(optional)
  JwtAuthenticationFilter->>SecurityContextHolder: setAuthentication(CustomUserDetails)
Loading

Possibly related PRs

  • Team-Timo/Timo-Server#6: 두 PR 모두 SecurityConfig.javaSecurityFilterChain 인가 규칙과 필터 체인 구성을 수정한다.

Suggested reviewers: laura-jung

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive 대부분의 OAuth2/JWT/Redis 요구사항은 구현됐지만 application.yml 설정 반영 여부가 보여서 전체 충족은 확정하기 어렵습니다. Google OAuth2/Redis/JWT 설정이 반영된 구성 파일 변경을 포함해 요구사항 충족 여부를 명확히 보여 주세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 구글 소셜 로그인 구현이라는 핵심 변경을 잘 요약합니다.
Out of Scope Changes check ✅ Passed 변경된 파일들은 모두 소셜 로그인, JWT, Redis, 보안 설정과 직접 관련되어 보이며 별도 무관한 변경은 보이지 않습니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#9-google-oauth2-login

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (1)
src/main/java/com/Timo/Timo/global/config/SecurityConfig.java (1)

65-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

CORS Origin을 프로퍼티로 분리해 주세요.

Line 65는 credentials CORS에서 localhost Origin을 코드에 고정하고 있습니다. 배포 도메인 반영을 TODO에 의존하면 환경별 로그인 호출이 쉽게 깨지므로 application-*.yml 또는 @ConfigurationProperties로 분리하는 편이 좋습니다. 필요하면 설정 클래스까지 포함한 패치를 만들어 드릴 수 있습니다. As per path instructions, src/main/java/**/global/**/*.java: Spring Boot 설정이 과도하게 복잡하거나 전역 상태에 의존하지 않는지 확인해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/Timo/Timo/global/config/SecurityConfig.java` at line 65,
The CORS origins in SecurityConfig are hardcoded to localhost, which should be
moved out of the code and into configuration. Update the CORS setup in
SecurityConfig to read allowed origins from application-*.yml or a dedicated
`@ConfigurationProperties` class, and wire that property into the existing
corsConfigurationSource/CorsConfiguration usage instead of calling
setAllowedOrigins with fixed values. Keep the security configuration simple and
environment-driven so login CORS works correctly across local and deployed
environments.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java`:
- Around line 55-57: The OAuth success flow in OAuthSuccessHandler currently
appends accessToken to the redirect URL as a query parameter, which can expose
sensitive data. Update the redirect handling to avoid passing the token in the
URL, preferably by setting it in an HttpOnly Secure cookie or switching to a
one-time authorization code exchange. Keep the fix localized around the
redirectUrl construction in OAuthSuccessHandler and ensure no token is written
to browser history, referrers, or logs.
- Line 50: The refresh-token cookie lifetime in OAuthSuccessHandler is hardcoded
to 7 days, which can drift from the Redis/JWT expiry settings. Update the cookie
maxAge to use the same refresh token expiry source as
JwtTokenProvider#getRefreshTokenExpiry() so the browser cookie, Redis TTL, and
JWT refresh token all stay aligned when the expiration changes.
- Line 48: refresh token cookie의 Secure 설정이 OAuthSuccessHandler에서 하드코딩되어 있어 배포
환경에서 실수로 비활성화될 수 있습니다. OAuthSuccessHandler의 쿠키 생성 로직에서 secure(false)를 제거하고, 기본값은
true로 두되 로컬/개발 프로필에서만 끌 수 있도록 설정값이나 환경변수로 분리해 주세요. 관련해서 refresh token 쿠키를 만드는
메서드와 secure 옵션을 적용하는 부분을 함께 수정해 설정이 안전하게 주입되도록 정리해 주세요.
- Line 3: Use the configured redirect URI value instead of the OAuth2
REDIRECT_URI constant when building the post-login redirect in
OAuthSuccessHandler. Update the success flow that appends the access token so it
reads the actual configured redirectUri and redirects to that target rather than
constructing a redirect_uri?accessToken=... URL from the constant.

In
`@src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java`:
- Around line 26-31: In CustomOAuth2UserService, validate that the OAuth2User
attributes contain a non-null email before calling
userRepository.findByEmail(email). If the email is missing, convert the failure
into an OAuth2AuthenticationException with a clear error so the OAuth failure
handler can respond consistently; keep the check close to the attribute
extraction for email, name, and picture in the same loadUser flow.
- Around line 22-35: The existing-user profile update path in
CustomOAuth2UserService.loadUser is modifying the entity returned from
userRepository.findByEmail() without a transaction boundary, so the change may
not be persisted. Add a transactional boundary to loadUser or explicitly persist
the updated User after existing.update(name, picture), and keep the fix anchored
around loadUser, userRepository.findByEmail, and existing.update.

In `@src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java`:
- Around line 18-24: The RefreshTokenService.save method is storing the Redis
entry with the wrong TTL unit, causing the key to expire much earlier than the
JWT refresh token. Update the redisTemplate.opsForValue().set call to use the
same time unit expected by JwtTokenProvider.getRefreshTokenExpiry, and align the
Redis expiration with the token’s actual lifetime so the refresh token remains
available until JWT expiry.
- Around line 35-36: The refresh token validation in RefreshTokenService.isValid
currently calls equals on refreshToken directly, which can throw a
NullPointerException when the token is missing. Update this method to use a
null-safe comparison, such as comparing refreshToken against get(email) with
Objects.equals, so missing cookie/parameter cases return false instead of
causing a 500. Keep the fix localized to isValid and ensure null handling stays
safe.

In `@src/main/java/com/Timo/Timo/global/config/SecurityConfig.java`:
- Around line 41-48: The security matcher list in SecurityConfig does not
explicitly allow /login-success, so OAuth callback flow may still be blocked.
Update the permitAll requestMatchers in SecurityConfig to include the
/login-success endpoint alongside the existing /login/** and /oauth2/** entries,
using the same matcher setup so the success redirect can be accessed without
authentication.
- Around line 49-56: The current SecurityConfig.oauth2Login setup is causing
`/api/**` authentication failures to be handled like browser login redirects
instead of API 401 responses. Update the security configuration in
SecurityConfig to distinguish API requests from OAuth2 login flow, and add an
AuthenticationEntryPoint for API paths so missing or expired JWTs return a 401
JSON response. Keep the OAuth2 login success/failure handlers for the web login
flow, but route API authentication failures through the new entry point.

In `@src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java`:
- Around line 36-37: AccessToken 전용 검증이 없어서 JwtAuthenticationFilter의 인증 분기에서
RefreshToken까지 통과할 수 있습니다. JwtTokenProvider에 token type claim을 구분하는
validateAccessToken(...)을 추가하고, JwtAuthenticationFilter의 doFilterInternal()에서는
validateToken() 대신 access 전용 검증만 호출하도록 바꾸세요. 또한 getEmail(token) 전에 토큰이 access인지
먼저 확인해 인증 컨텍스트가 RefreshToken으로 설정되지 않게 해 주세요.

In `@src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java`:
- Around line 39-67: `JwtTokenProvider`에서 리프레시 토큰이 인증에 그대로 사용되는 문제를 막아야 합니다.
`validateToken()`과 `getEmail()`이 단순 서명 검증만 하지 말고 토큰의 `type` 클레임을 확인하도록 수정하고,
`JwtAuthenticationFilter`는 인증 시 `"access"`만 허용하도록 호출 흐름을 맞추세요. 재발급용 경로에서는
`"refresh"`를 기대하도록 분리해 `generateRefreshToken()`, `getClaims()`,
`validateToken()`, `getEmail()`의 역할이 명확히 구분되게 하세요.

---

Nitpick comments:
In `@src/main/java/com/Timo/Timo/global/config/SecurityConfig.java`:
- Line 65: The CORS origins in SecurityConfig are hardcoded to localhost, which
should be moved out of the code and into configuration. Update the CORS setup in
SecurityConfig to read allowed origins from application-*.yml or a dedicated
`@ConfigurationProperties` class, and wire that property into the existing
corsConfigurationSource/CorsConfiguration usage instead of calling
setAllowedOrigins with fixed values. Keep the security configuration simple and
environment-driven so login CORS works correctly across local and deployed
environments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 465d4783-1636-40ab-ab14-a346d674e55f

📥 Commits

Reviewing files that changed from the base of the PR and between a3b014a and bc564f4.

📒 Files selected for processing (13)
  • .coderabbit.yaml
  • build.gradle
  • src/main/java/com/Timo/Timo/domain/user/entity/User.java
  • src/main/java/com/Timo/Timo/domain/user/repository/UserRepository.java
  • src/main/java/com/Timo/Timo/global/auth/dto/CustomUserDetails.java
  • src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java
  • src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java
  • src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java
  • src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java
  • src/main/java/com/Timo/Timo/global/config/RedisConfig.java
  • src/main/java/com/Timo/Timo/global/config/SecurityConfig.java
  • src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java
  • src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java

Comment thread src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java Outdated
Comment thread src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java Outdated
Comment thread src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java Outdated
Comment thread src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java Outdated
Comment thread src/main/java/com/Timo/Timo/global/config/SecurityConfig.java
Comment thread src/main/java/com/Timo/Timo/global/config/SecurityConfig.java
Comment thread src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java Outdated
Comment thread src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java`:
- Line 50: The accessToken cookie is using the refresh token expiry, so update
OAuthSuccessHandler to set its maxAge from the access token lifetime instead.
Add a dedicated access token expiry getter to JwtTokenProvider, then replace the
existing refreshTokenExpiry usage in the cookie builder with that new
access-token-specific value so the cookie expires together with the access
token.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d92c60b5-e247-403c-8f89-8e8f4b110fa4

📥 Commits

Reviewing files that changed from the base of the PR and between 9d40673 and 5de9d41.

📒 Files selected for processing (3)
  • src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java
  • src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java
  • src/main/java/com/Timo/Timo/global/config/SecurityConfig.java

Comment thread src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java Outdated

@laura-jung laura-jung left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

현재 스크린샷으로 보았을 때는 accessToken이 url로 들어오는 것 같아보이는데 맞을까요...?
accessToken도 일종의 개인정보라 응답 body로 내려주는게 좋을 것 같다고 생각합니다! body로도 오고 있는 것 같아서 redirect 경로만 수정하면 될 것 같아요! 이외에도 코멘트 달아두었으니 확인 부탁드립니다.
oauth 로그인 쉽지 않은데 너무 잘 해준 것 같아서 복복복해주고 싶습니다. (실은 전 예전에 실패했었어요) 파일링도 깔끔하고 아주그냥 굳입니다

private String email;

private String name;
private String picture;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p3) picture이 아마도 이미지 사진일 것 같은데 이미지의 경우에는 imageUrl과 같은 네임으로 많이 사용합니다! 아마 oauth에서는 필드명으로 picture를 사용하는 것으로 알고 있는데 도메인 엔티티에서는 수정해주는게 좋을 것 같습니다

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

아하 OAuth의 필드명을 그대로 사용했었는데, 말씀해주신 것처럼 도메인 엔티티에서는 imageUrl이 의미도 더 명확하고 직관적인 것 같네요! imageUrl로 변경해서 반영했습니다. 감사합니다!

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@AllArgsConstructor

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p3) @AllArgsConstructor의 경우 외부에서 생성자를 만들 위험이 있기 때문에 조심해서 사용을 해야하는데요 저희 서비스의 경우 기본적으로는 구글의 소셜로그인을 통해 사용자가 생성되기 때문에 임의로 사용자가 만들어질 수 있는 해당 어노테이션을 사용하는건 위험하다고 생각합니다! 실제 생성은 @builder가 붙은 private 생성자로 제한해도 좋을 것 같아요. 그러면 User가 어떤 값으로 생성되어야 하는지 더 명확해지고, 잘못된 상태의 엔티티 생성을 줄일 수 있을 것 같습니다.

@Builder
  private User(String email, String name, String profileImageUrl, Provider provider) {
    this.email = email;
    this.name = name;
    this.ImageUrl = ImageUrl;
    this.provider = provider;
  }

이런식으로요!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

옹 예시 코드까지 감사합니다:) @AllArgsConstructor를 사용하면 외부에서 임의의 상태로 User를 생성할 수 있다는 점을 놓쳤던 것 같습니다. 말씀해주신 방향대로 @AllArgsConstructor 대신 @Builder가 적용된 private 생성자를 통해서만 생성할 수 있도록 수정였습니다-!!

this.picture = picture;
}

public enum Provider { GOOGLE }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p4) 이 enum의 경우 따로 파일을 분리하는게 어떨까요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

안 그래도 고민했던 부분이었는데, 현재는 User 엔티티 내에서만 한 번 사용되어 내부 enum으로 두었습니다. 다만 위의 다른 리뷰에서 말씀해주신 것처럼 추후 Provider가 추가되거나 다른 곳에서 재사용될 가능성도 고려해 별도 enum 파일로 분리하여 반영했습니다. 감사합니다!

Comment on lines +44 to +47
@Override
public String getUsername(){
return user.getEmail();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

getUsername과 getName모두 getEmail을 가져오는 것 같은데 두개가 뭐가 다른건가요?

@Jy000n Jy000n Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

저도 이 부분 정말 헷갈렸는데요,, 둘 다 사용자를 식별하는 용도이긴 하지만, 역할이 조금 다르다고 합니다!

현재 해당 클래스는 구글 로그인으로 받아온 사용자를 Spring Security가 이해할 수 있는 로그인 객체로 변환해 주는 어댑터 역할을 하며, OAuth2UserUserDetails인터페이스를 모두 구현하고 있습니다.

  • getName() -> OAuth2User 인터페이스 구현 (OAuth2 로그인할 때 제공자(구글)이 사용하는 사용자 식별 = '이 OAuth2 사용자의 식별자가 뭐야?')
  • getUsername() -> UserDetails 인터페이스 구현 (Spring Security가 내부적으로 사용자를 식별할 때 사용하는 값 = '현재 로그인한 사람이 누구야?')

이었는데 아래 코드리뷰와 관련해서 식별자 값을 email->userId로 변경 했으니 참고해주시면 감사하겠습니다:)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

완전 이해되었습니다 꼼꼼한 설명 감사합니다.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p3) 지금 보았을때 위의 jwtAuthenticationEntryPoint와 oauthFailureHandler의 코드가 겹치는 부분이 많은 것 같아요! 따로 writer로 분리해서 작성하면 중복코드 사용을 줄일 수 있지 않을까하는 생각입니다!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

오홍 인지하지 못하고 있었는데 ErrorDto 생성 → status 설정 → contentType 설정 → write 하는 로직이 동일하게 반복되고 있었네요! AuthErrorResponseWriter로 분리하여 중복을 제거하고 이후 에러 응답 형식이 바뀌더라도 한 곳만 수정하면 되도록 개선해야겠습니다-!!

Comment on lines +44 to +45
String accessToken = jwtTokenProvider.generateAccessToken(email);
String refreshToken = jwtTokenProvider.generateRefreshToken(email);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

사용자 식별용으로 email을 사용하신 이유가 있나요? 현재는 구글 로그인 밖에 없지만 후에 다른 소셜 로그인이 추가된다면 email이 중복될 수도 있을 것 같아서요!

p2)저의 경우에는 userId를 많이 사용하는 것 같습니다.

email을 사용하신 특별한 이유가 있다면 수정하지 않으셔도 됩니다

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

구글만 사용할 때로 한정해서 생각했던 것 같습니다. 현재는 구글 로그인만 있어서 email 중복 문제가 없었는데 말씀 주신대로 추후 다른 소셜 로그인 추가 시 같은 email로 가입된 경우 충돌이 생길 수 있을 것 같아 userId로 수정하도록 하겠습니다!!

.email(email)
.name(name)
.picture(picture)
.provider(User.Provider.GOOGLE)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p3) 여기서도 google로 박아두기 보다는 후에 확장성을 위해 받아올 수 있게 하는게 좋지 않을까요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

넵 좋습니다! registrationId를 통해 동적으로 받아오도록 변경했습니다! 확장성을 많이 고려하지 못했던 것 같습니다😓

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:5173")); // TODO: 프론트 배포 시 도메인으로 변경

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

TODO 너무 좋습니다

Comment on lines +49 to +55
ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", refreshToken)
.httpOnly(true)
.secure(cookieSecure)
.path("/")
.maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry()))
.sameSite("Strict")
.build();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p2) refreshCookie를 생성하고 있지만 응답 헤더에 추가하는 코드가 없어 실제 클라이언트로 쿠키가 내려가지 않을 것 같습니다.
response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()) 처리가 필요한 것 같습니다. 현재 상태라면 refresh token가 redis에는 저장되지만 브라우저 쿠키에는 저장되지 않아 재발급이 실패할 수 있을 것 같습니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

헉 맞습니다! 마지막에 accessToken 전달 방식을 쿠키로 잘못 전달하고 있었어서 Body 응답으로 변경하는 과정에서 refreshToken 쿠키 헤더 추가 코드가 누락되었던 것 같습니다.

꼼꼼하게 봐주셔서 감사합니다! 바로 수정하겠습니다~

private final ObjectMapper objectMapper;

@Value("${app.oauth2.redirect-uri}")
private String redirectUri;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이부분은 아직 구현되지 않아서 따로 사용되지 않는거겠죠...?

@Jy000n Jy000n Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

redirectUri 선언만 해두고 실제로 사용하는 코드가 누락되어 있었네요.. 테스트용 코드 작성하느라 수정하면서 실수로 지운 것 같습니다.. getRedirectStrategy().sendRedirect()로 리다이렉트 처리 다시 추가해놓겠습니다!

@Jy000n

Jy000n commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

테스트 결과는 이전에 제가 accessToken을 URL 파라미터로 전달하는 방식으로 잘못 설계했을 때 진행한 내용이었습니다. 현재는 토큰 전달 방식을 수정하여 테스트 결과도 다시 업데이트해 두었으니 참고 부탁드립니다!

수정하다보니 body 방식의 문제점 알게 되어서 이거를 해결하기 위해 1회성 authorization code 방식으로 변경했습니다!

Body 방식의 문제점
→ 리다이렉트하면서 동시에 Body 전달 불가
→ sendRedirect() 호출하면 Body가 날아감
→ 프론트가 accessToken을 받을 수가 없음

로그인 성공 시 30초 TTL의 1회성 code를 생성하여 URL 파라미터로 전달하고,
프론트에서 해당 code로 /api/auth/token을 호출하면 accessToken을 발급합니다.
code는 1회 사용 후 즉시 Redis에서 삭제되어 재사용이 불가능합니다.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java (1)

31-32: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

cookieSecure 기본값을 true로 변경해 주세요.

설정으로 분리한 점은 좋으나, 기본값이 false라 배포 시 app.auth.cookie-secure 값을 명시적으로 설정하지 않으면 refreshToken 쿠키가 평문 HTTP로도 전송될 수 있습니다. 과거 리뷰에서도 기본값을 true로 제안했던 부분입니다.

🛡️ 제안 수정
-  `@Value`("${app.auth.cookie-secure:false}")
+  `@Value`("${app.auth.cookie-secure:true}")
   private boolean cookieSecure;

As per past review comments, @src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java의 secure 값은 "기본값 true인 설정값으로 분리해 주세요"라는 제안이 있었습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java`
around lines 31 - 32, The OAuthSuccessHandler cookieSecure setting is still
defaulting to false, which leaves refreshToken cookies non-secure unless
explicitly configured. Update the `@Value` default on cookieSecure in
OAuthSuccessHandler to true so the secure-cookie behavior is enabled by default,
and keep the property override support intact for app.auth.cookie-secure.
🧹 Nitpick comments (1)
src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java (1)

22-30: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

토큰 교환 엔드포인트를 POST로 바꾸세요.
RFC 6749는 token endpoint에 POST + application/x-www-form-urlencoded를 요구합니다. 지금처럼 GET으로 1회용 code를 소비하면 규격을 벗어나고, 쿼리 문자열이 접근 로그에 남을 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java`
around lines 22 - 30, The AuthController token exchange endpoint currently uses
GET with a query parameter, which should be changed to the OAuth2 token-style
POST flow. Update the token(...) method in AuthController to accept POST
requests and read the code from form-encoded request data rather than
`@RequestParam`, keeping the existing authCodeService.getAndDelete(code) and
unauthorized handling intact. Make sure the endpoint still returns the same
ResponseEntity<Map<String, String>> response while using POST and
application/x-www-form-urlencoded semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java`:
- Around line 28-30: AuthController의 인증 실패 응답이 다른 흐름과 달리 빈 401 바디를 반환하고 있습니다.
userId null 처리 분기에서 단순히 status만 내리지 말고, OAuthFailureHandler와
JwtAuthenticationEntryPoint가 사용하는 AuthErrorResponseWriter를 재사용해 표준 ErrorDto 형식의
401 응답을 반환하도록 맞춰 주세요. AuthController의 해당 엔드포인트 응답 생성 로직을 공통 인증 실패 응답 경로로 통일하면
됩니다.

In `@src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java`:
- Around line 29-36: The AuthCodeService.getAndDelete method currently does a
separate get and delete on the Redis key, which can allow the same code to be
consumed more than once under concurrent requests. Update this method to perform
the lookup and removal atomically by using Redis
ValueOperations.getAndDelete(key) if available, or by executing a Lua script
against redisTemplate so the KEY_PREFIX-based key is read and deleted in one
operation.

---

Duplicate comments:
In `@src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java`:
- Around line 31-32: The OAuthSuccessHandler cookieSecure setting is still
defaulting to false, which leaves refreshToken cookies non-secure unless
explicitly configured. Update the `@Value` default on cookieSecure in
OAuthSuccessHandler to true so the secure-cookie behavior is enabled by default,
and keep the property override support intact for app.auth.cookie-secure.

---

Nitpick comments:
In `@src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java`:
- Around line 22-30: The AuthController token exchange endpoint currently uses
GET with a query parameter, which should be changed to the OAuth2 token-style
POST flow. Update the token(...) method in AuthController to accept POST
requests and read the code from form-encoded request data rather than
`@RequestParam`, keeping the existing authCodeService.getAndDelete(code) and
unauthorized handling intact. Make sure the endpoint still returns the same
ResponseEntity<Map<String, String>> response while using POST and
application/x-www-form-urlencoded semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 84ce08d6-fb55-4fce-a2ec-c5bceaecf005

📥 Commits

Reviewing files that changed from the base of the PR and between c1db021 and 18d23a3.

📒 Files selected for processing (14)
  • src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java
  • src/main/java/com/Timo/Timo/domain/user/entity/User.java
  • src/main/java/com/Timo/Timo/domain/user/enums/Provider.java
  • src/main/java/com/Timo/Timo/global/auth/handler/AuthErrorResponseWriter.java
  • src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java
  • src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java
  • src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java
  • src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java
  • src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java
  • src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java
  • src/main/java/com/Timo/Timo/global/config/SecurityConfig.java
  • src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java
  • src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java
  • src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java
  • src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java
  • src/main/java/com/Timo/Timo/global/config/SecurityConfig.java
  • src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java

Comment thread src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java`:
- Around line 14-16: The token exchange endpoint in AuthController is currently
exposed as a GET, which can leak the authorization code and accessToken via
history/cache/prefetch/logs; change the exchange handler to use POST instead of
GET, and update the response handling to set Cache-Control: no-store (and
Pragma: no-cache if needed). Locate the endpoint mapped in AuthController and
adjust the request mapping/response building so the token exchange path no
longer returns sensitive data through a cacheable GET response.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2878ccf4-4141-4d91-9559-46a8a1aac2fb

📥 Commits

Reviewing files that changed from the base of the PR and between 18d23a3 and f2adfd0.

📒 Files selected for processing (3)
  • src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java
  • src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java
  • src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java
  • src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java

Comment on lines +14 to +16
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@coderabbitai coderabbitai Bot Jul 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify token endpoint callers and security matcher coverage before switching GET to POST.
rg -nP -C3 '(/api/auth/token|requestMatchers\s*\(|csrf|cors)' src/main/java

Repository: Team-Timo/Timo-Server

Length of output: 4087


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== AuthController outline ==\n'
ast-grep outline src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java --view expanded || true

printf '\n== SecurityConfig outline ==\n'
ast-grep outline src/main/java/com/Timo/Timo/global/config/SecurityConfig.java --view expanded || true

printf '\n== AuthController relevant lines ==\n'
sed -n '1,220p' src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java | cat -n

printf '\n== SecurityConfig relevant lines ==\n'
sed -n '1,220p' src/main/java/com/Timo/Timo/global/config/SecurityConfig.java | cat -n

printf '\n== Token endpoint callers / references ==\n'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' '/api/auth/token|getAndDelete\(|accessToken|Cache-Control|Pragma' .

Repository: Team-Timo/Timo-Server

Length of output: 7970


토큰 교환 엔드포인트는 POST로 바꾸고 응답 캐시를 막아 주세요.
현재 GET으로 인증 코드를 소모한 뒤 accessToken을 내려서, 브라우저 히스토리/캐시/프리페치/접근 로그에 값이 남을 수 있습니다. 응답에는 Cache-Control: no-store(필요하면 Pragma: no-cache도) 를 추가해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java`
around lines 14 - 16, The token exchange endpoint in AuthController is currently
exposed as a GET, which can leak the authorization code and accessToken via
history/cache/prefetch/logs; change the exchange handler to use POST instead of
GET, and update the response handling to set Cache-Control: no-store (and
Pragma: no-cache if needed). Locate the endpoint mapped in AuthController and
adjust the request mapping/response building so the token exchange path no
longer returns sensitive data through a cacheable GET response.

Source: Path instructions

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p2) accessToken을 발급하는 엔드포인트라서 코드래빗 리뷰처럼 post 메소드가 더 어울릴 것 같네요.
get은 히스토리가 남아서 보안상 위험할 수도 있을 것 같아요!!

그리고 swagger 처리 해주셔야 합니다!!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for existing Swagger/OpenAPI usage =="
rg -nP -C2 '`@Tag`\(|`@Operation`\(|springdoc|SpringDoc|swagger' src/main/java --iglob '*.java' || true

echo "== build.gradle swagger deps =="
rg -n 'springdoc|swagger' build.gradle || true

echo "== AuthController full content =="
cat -n src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java

echo "== SecurityConfig permitAll rules =="
rg -n 'permitAll|requestMatchers' src/main/java/com/Timo/Timo/global/config/SecurityConfig.java -A1 -B1

echo "== Other controllers for style reference =="
fd -e java -i 'controller' src/main/java

Length of output: 4615


@laura-jung 맞습니다! 아래처럼 반영하는 걸 제안드립니다.

  • GETPOST로 변경 (히스토리/캐시 노출 방지)
  • 응답에 Cache-Control: no-store 헤더 추가
  • springdoc-openapi가 이미 의존성에 포함되어 있으니 (build.gradle) @Tag, @Operation, @Parameter 애노테이션으로 Swagger 문서화 추가

SecurityConfigrequestMatchers("/api/auth/token")은 경로 기준이라 메서드를 바꿔도 별도 설정 변경은 필요 없습니다.

Task: AuthController /api/auth/token을 POST로 변경 + 캐시 방지 헤더 + Swagger 문서화


The requested changes have been implemented and a pull request has been created: View PR

@aneykrap aneykrap left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

자윤님 완전 고생하셨습니당 잘 작성해주셔서 공부하는 느낌으로 다시 읽어보았습니당

ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", refreshToken)
.httpOnly(true)
.secure(cookieSecure)
.path("/")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p4) Refresh Token 쿠키가 path("/")로 설정되어 모든 API 요청에 함께 전송되는것 같아요. 실제 사용되는 재발급·로그아웃 API로 경로를 제한하면 토큰의 불필요한 전송과 노출 범위를 줄일 수 있을 것 같습니다. /api/auth 정도로 제한하는 것은 어떨까요?


public void save(String userId, String refreshToken){
redisTemplate.opsForValue().set(
KEY_PREFIX + userId,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p3) Redis key가 refresh:{userId} 하나라서 같은 사용자가 다른 기기에서 로그인하면 기존 Refresh Token이 덮어써지는것 같아요. (=> 다중기기 지원 X) 기존 기기는 Access Token 만료 후 재발급에 실패하게 되는데 세션 식별자를 key에 포함하는 건 어떨까요?

@laura-jung

Copy link
Copy Markdown
Collaborator

테스트 결과는 이전에 제가 accessToken을 URL 파라미터로 전달하는 방식으로 잘못 설계했을 때 진행한 내용이었습니다. 현재는 토큰 전달 방식을 수정하여 테스트 결과도 다시 업데이트해 두었으니 참고 부탁드립니다!

수정하다보니 body 방식의 문제점 알게 되어서 이거를 해결하기 위해 1회성 authorization code 방식으로 변경했습니다!

Body 방식의 문제점
→ 리다이렉트하면서 동시에 Body 전달 불가
→ sendRedirect() 호출하면 Body가 날아감
→ 프론트가 accessToken을 받을 수가 없음

로그인 성공 시 30초 TTL의 1회성 code를 생성하여 URL 파라미터로 전달하고, 프론트에서 해당 code로 /api/auth/token을 호출하면 accessToken을 발급합니다. code는 1회 사용 후 즉시 Redis에서 삭제되어 재사용이 불가능합니다.

저도 리다이렉트하면서 동시에 body 전달 불가 문제로 소셜 로그인에 실패한 경험이 있습니다......
그럼 한가지 질문이 더 생기는데 추가로 isNewUser 여부를 보내기로 했었는데 이부분도 body로 보내지 못하는걸까요 아니면 따로 보내는 방법이 있는걸까요...?

Comment on lines +14 to +16
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p2) accessToken을 발급하는 엔드포인트라서 코드래빗 리뷰처럼 post 메소드가 더 어울릴 것 같네요.
get은 히스토리가 남아서 보안상 위험할 수도 있을 것 같아요!!

그리고 swagger 처리 해주셔야 합니다!!

Comment on lines +28 to +30
public String get(String email){
return redisTemplate.opsForValue().get(KEY_PREFIX + email);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p4) 현재 refresh token key는 userId 기준으로 저장하는 것 같은데, get(String email) 파라미터명이 이전 의미로 남아 있는 것 같아요! 동작에는 영향이 없지만 get(String userId)로 맞추면 의미가 일관돼서 더 좋을 것 같습니다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Google OAuth2 소셜 로그인 및 JWT 인증 구현

3 participants