Conversation
…o OT-43-feature/oauth-login-api
📝 WalkthroughWalkthroughJWT 기반 인증과 Kakao OAuth2 로그인 기능을 구현했습니다. 관리자용 인증 API, 사용자 인증 API, 공통 보안 모듈(필터, 핸들러, JWT 제공자)을 추가하고 Spring Security를 설정했으며, Docker Compose와 데이터베이스 포트를 변경했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Client as 클라이언트
participant AdminAPI as 관리자<br/>인증 API
participant AdminService as 관리자<br/>인증 서비스
participant DB as 데이터베이스
participant JwtProvider as JWT<br/>제공자
User->>Client: 관리자 로그인<br/>(이메일, 비밀번호)
Client->>AdminAPI: POST /admin/login
AdminAPI->>AdminService: login(AdminLoginRequest)
AdminService->>DB: 이메일 및 LOCAL<br/>provider로 조회
DB-->>AdminService: Member
AdminService->>AdminService: 역할 확인<br/>(ADMIN/EDITOR)
AdminService->>JwtProvider: createAccessToken<br/>createRefreshToken
JwtProvider-->>AdminService: accessToken,<br/>refreshToken
AdminService->>DB: refreshToken 저장
AdminService-->>AdminAPI: AdminLoginResponse
AdminAPI->>AdminAPI: HttpOnly 쿠키 설정<br/>(accessToken, refreshToken)
AdminAPI-->>Client: 200 OK +<br/>쿠키
Client-->>User: 로그인 성공
sequenceDiagram
participant User
participant Client as 클라이언트
participant AuthAPI as 사용자<br/>인증 API
participant OAuth2 as Kakao<br/>OAuth2
participant CustomService as CustomOAuth2<br/>UserService
participant KakaoService as Kakao<br/>인증 서비스
participant DB as 데이터베이스
participant Handler as OAuth2<br/>SuccessHandler
participant JwtProvider as JWT<br/>제공자
User->>Client: Kakao 로그인 클릭
Client->>OAuth2: 인증 요청
OAuth2-->>Client: 인증 코드
Client->>AuthAPI: 인증 코드 전달
AuthAPI->>OAuth2: 액세스 토큰 교환
OAuth2-->>AuthAPI: 액세스 토큰
AuthAPI->>CustomService: loadUser(OAuth2UserRequest)
CustomService->>OAuth2: 사용자 정보 조회
OAuth2-->>CustomService: Kakao 사용자 정보
CustomService->>KakaoService: findOrCreateMember<br/>(KakaoUserInfo)
KakaoService->>DB: providerId로 조회
alt 기존 멤버
DB-->>KakaoService: Member
KakaoService->>DB: 프로필 업데이트
else 신규 멤버
KakaoService->>DB: 새 멤버 생성
DB-->>KakaoService: Member
end
KakaoService-->>CustomService: Member
CustomService->>CustomService: OAuth2User 생성<br/>(memberId, isNewMember)
CustomService-->>AuthAPI: OAuth2User
AuthAPI->>Handler: onAuthenticationSuccess
Handler->>JwtProvider: createAccessToken<br/>createRefreshToken
JwtProvider-->>Handler: 토큰
Handler->>KakaoService: saveRefreshToken
KakaoService->>DB: refreshToken 저장
Handler->>Handler: HttpOnly 쿠키 설정
Handler-->>Client: 리다이렉트<br/>(프론트엔드 + isNewMember)
Client-->>User: 로그인 완료
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60분 Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
modules/common-web/src/main/java/com/ott/common/web/config/WebMvcConfig.java (1)
16-22:⚠️ Potential issue | 🟠 Major프로덕션 프론트 도메인 차단 가능성
공통 WebMvc CORS 설정이 localhost/127.0.0.1만 허용하고 있어, 배포 환경의 실제 FE 도메인이 이 설정에 의해 차단될 수 있습니다. 운영/스테이징 도메인이 존재한다면 환경별 프로필 또는 외부 설정(예: properties/yaml)로 주입해 주세요.
💡 예시: 외부 설정 주입 방식
+ `@Value`("${app.cors.allowed-origin-patterns:http://localhost:*,http://127.0.0.1:*}") + private String[] allowedOriginPatterns; + `@Override` public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOriginPatterns("http://localhost:*", "http://127.0.0.1:*") + .allowedOriginPatterns(allowedOriginPatterns) .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(MAX_AGE_SECS); }
🧹 Nitpick comments (15)
apps/api-user/src/main/java/com/ott/api_user/auth/service/SocialAuthService.java (1)
3-5: 빈 인터페이스는 혼란을 줄 수 있어 계약 정의 후 도입을 권장합니다.현재는 메서드가 전혀 없어 구현·사용처 기준이 모호합니다. 실제 공통 동작(예:
authenticate,fetchUserInfo등)이 정해지면 그때 계약을 추가하거나, 아직 필요 없으면 파일 자체를 미루는 쪽이 유지보수에 유리합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/java/com/ott/api_user/auth/service/SocialAuthService.java` around lines 3 - 5, The empty SocialAuthService interface currently provides no contract and should either be removed or turned into a meaningful abstraction: add clearly named method signatures such as authenticate(String provider, String accessToken) returning a domain/DTO (e.g., SocialUser or AuthenticationResult) and/or fetchUserInfo(String provider, String accessToken) returning a SocialUser, so implementations (e.g., GoogleSocialAuthService) have a defined API; if no common behaviour is required yet, delete the empty SocialAuthService to avoid confusion and reintroduce it later when you add methods.apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/handler/OAuth2FailureHandler.java (2)
20-21: 변수명 오타:frontedUrl→frontendUrl
OAuth2SuccessHandler에서도 동일한 오타가 있습니다. 일관성을 위해 수정하는 것이 좋습니다.♻️ 변수명 수정
`@Value`("${app.frontend-url}") -private String frontedUrl; +private String frontendUrl;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/handler/OAuth2FailureHandler.java` around lines 20 - 21, Rename the misspelled field frontedUrl to frontendUrl in OAuth2FailureHandler (and make the same change in OAuth2SuccessHandler) so the variable name is consistent; update the `@Value-annotated` field declaration in OAuth2FailureHandler (currently `@Value`("${app.frontend-url}") private String frontedUrl;) to private String frontendUrl and then update all usages of frontedUrl within the class to frontendUrl to avoid compilation errors and maintain consistency across both handlers.
28-28: 인증 실패는log.warn또는log.error가 더 적절합니다.
log.info는 정상적인 정보성 로그에 사용됩니다. 인증 실패는 잠재적인 보안 이벤트이므로 더 높은 로그 레벨을 사용하는 것이 좋습니다.♻️ 로그 레벨 변경
-log.info("OAuth2 로그인 실패: {}", exception.getMessage()); +log.warn("OAuth2 로그인 실패: {}", exception.getMessage());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/handler/OAuth2FailureHandler.java` at line 28, Replace the info-level log in OAuth2FailureHandler with a higher severity and include the exception object; specifically, in the OAuth2FailureHandler (onAuthenticationFailure method) change the call currently using log.info("OAuth2 로그인 실패: {}", exception.getMessage()) to use log.warn or log.error and pass the exception itself so the stack trace is recorded (e.g., log.warn("OAuth2 로그인 실패", exception) or log.error(...)). This updates the log level for authentication failures and ensures the full exception details are logged for investigation.apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/userinfo/UserInfo.java (1)
3-5: 인터페이스에 공통 메서드를 정의하거나 KakaoUserInfo에서 구현하는 것을 권장합니다.전략 패턴을 위한 인터페이스라면,
getEmail(),getNickname(),getProviderId()등의 공통 메서드를 정의하고KakaoUserInfo에서 이를 구현하는 것이 좋습니다. 현재KakaoUserInfo는 이 인터페이스를 구현하지 않고 있습니다.♻️ 제안하는 변경
// 다중 소셜 로그인 도입 시 전략 패턴으로 구성 예정 public interface UserInfo { + String getProviderId(); + String getEmail(); + String getNickname(); }그리고
KakaoUserInfo에서:public class KakaoUserInfo implements UserInfo { // ... existing code }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/userinfo/UserInfo.java` around lines 3 - 5, The UserInfo interface is empty but should define common methods used by social providers; add method signatures like getEmail(), getNickname(), and getProviderId() to the UserInfo interface and then have KakaoUserInfo implement UserInfo by adding "implements UserInfo" to the KakaoUserInfo class and implementing those methods (mapping existing KakaoUserInfo fields to the new interface methods).apps/api-user/src/main/resources/application.yml (1)
48-50: 프로덕션 환경에서는show_sql을 비활성화하는 것을 권장합니다.
show_sql: true는 개발 중에는 유용하지만, 프로덕션 환경에서는 로그 양이 많아지고 성능에 영향을 줄 수 있습니다. 환경별 프로파일을 사용하거나 환경 변수로 제어하는 것을 고려해 주세요.♻️ 환경 변수를 통한 제어 제안
properties: hibernate: - show_sql: true - format_sql: true + show_sql: ${HIBERNATE_SHOW_SQL:false} + format_sql: ${HIBERNATE_FORMAT_SQL:false}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/resources/application.yml` around lines 48 - 50, 현재 application.yml의 hibernate.show_sql이 true로 되어 있어 프로덕션에서 로그 과다 및 성능 문제를 야기할 수 있으니, hibernate.show_sql 설정을 프로파일별 또는 환경변수로 분리하여 프로덕션에서는 false로 설정하도록 변경하세요; 예를 들어 Spring 프로파일(application-prod.yml)이나 환경변수(예: HIBERNATE_SHOW_SQL)를 이용해 기본값을 false로 두고 개발환경에서만 true로 오버라이드하도록 구성하면 됩니다 (참조 설정 키: hibernate.show_sql).apps/api-user/src/main/java/com/ott/api_user/auth/service/KakaoAuthService.java (1)
50-53: 읽기 전용 메서드에@Transactional(readOnly = true)적용을 권장합니다.
isNewMember는 읽기 전용 쿼리만 수행합니다. 클래스 레벨의@Transactional이 적용되어 있어 불필요하게 쓰기 트랜잭션이 사용됩니다. 메서드 레벨에서readOnly = true를 지정하면 성능 최적화에 도움이 됩니다.♻️ 제안된 변경사항
// 신규 회원 판별 -> 태그 소유 유무로 판단 + `@Transactional`(readOnly = true) public boolean isNewMember(Long memberId) { return !preferredTagRepository.existsByMemberId(memberId); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/java/com/ott/api_user/auth/service/KakaoAuthService.java` around lines 50 - 53, The isNewMember method performs only a read query (preferredTagRepository.existsByMemberId) but the class has a broader `@Transactional`, so annotate the isNewMember method with `@Transactional`(readOnly = true) to ensure the call runs in a read-only transaction for performance; locate the isNewMember(Long memberId) method in KakaoAuthService and add the method-level `@Transactional`(readOnly = true) annotation to override the class-level transaction settings.modules/common-security/src/main/java/com/ott/common/security/jwt/JwtTokenProvider.java (1)
89-99: 검증 실패 시 로깅 추가를 고려해 주세요.현재 토큰 검증 실패 시 에러 코드만 반환하고 로깅하지 않습니다. 만료되거나 유효하지 않은 토큰 시도에 대한 로그는 보안 모니터링에 유용할 수 있습니다.
♻️ 로깅 추가 제안
클래스에
@Slf4j어노테이션을 추가하고:+import lombok.extern.slf4j.Slf4j; +@Slf4j `@Component` public class JwtTokenProvider {검증 메서드에 로깅 추가:
public ErrorCode validateAndGetErrorCode(String token) { try { getClaims(token); return null; } catch (ExpiredJwtException e) { + log.debug("Token expired: {}", e.getMessage()); return ErrorCode.EXPIRED_TOKEN; // A003 } catch (JwtException | IllegalArgumentException e) { + log.debug("Invalid token: {}", e.getMessage()); return ErrorCode.INVALID_TOKEN; // A002 } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@modules/common-security/src/main/java/com/ott/common/security/jwt/JwtTokenProvider.java` around lines 89 - 99, Add logging to JwtTokenProvider: annotate the class with `@Slf4j` (or create a private static final Logger) and in validateAndGetErrorCode(String token) log failures inside each catch block (ExpiredJwtException and JwtException | IllegalArgumentException). Include contextual info (exception message and a non-sensitive token identifier, e.g., first/last chars or masked token) and the caught exception in the log call so you capture stack/stack message while avoiding full token leakage; then return the existing ErrorCode values unchanged.modules/domain/src/main/java/com/ott/domain/member/domain/Member.java (1)
63-66: 메서드 이름을 더 범용적으로 변경하는 것을 고려해 주세요.
updateKakaoProfile메서드는 실제로 Kakao에 특화된 로직이 없으며, email과 nickname만 업데이트합니다. 향후 다른 OAuth 프로바이더(Google, Naver 등)를 추가할 경우 재사용성을 위해updateProfile로 이름을 변경하는 것을 권장합니다.♻️ 제안된 변경사항
- public void updateKakaoProfile(String email, String nickname) { + public void updateProfile(String email, String nickname) { this.email = email; this.nickname = nickname; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@modules/domain/src/main/java/com/ott/domain/member/domain/Member.java` around lines 63 - 66, Rename Member.updateKakaoProfile to a generic name like updateProfile since it only sets email and nickname; update the method signature in the Member class (change method name from updateKakaoProfile to updateProfile), update all call sites/usages, imports and any tests or Javadoc referencing updateKakaoProfile to the new name, and run compilation/tests to ensure there are no remaining references.modules/common-security/src/main/java/com/ott/common/security/handler/JwtAccessDeniedHandler.java (1)
26-40: 디버깅을 위한 로깅 추가를 권장합니다.
@Slf4j어노테이션이 있지만 실제 로깅이 사용되지 않습니다. 403 에러 발생 시 요청 URI와 에러 메시지를 로깅하면 운영 환경에서 문제 추적에 도움이 됩니다.♻️ 로깅 추가 제안
`@Override` public void handle( HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException ) throws IOException { + log.warn("Access denied: {} - {}", request.getRequestURI(), accessDeniedException.getMessage()); ErrorResponse errorResponse = ErrorResponse.of(ErrorCode.FORBIDDEN, accessDeniedException.getMessage());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@modules/common-security/src/main/java/com/ott/common/security/handler/JwtAccessDeniedHandler.java` around lines 26 - 40, The JwtAccessDeniedHandler.handle method lacks runtime logging despite `@Slf4j`; add a log entry that records the request URI and the AccessDeniedException message when a 403 occurs. Inside handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) before writing the ErrorResponse, call log.warn or log.error (choose warn) with a clear message including request.getRequestURI() and accessDeniedException.getMessage() (and optionally the exception itself) so the class JwtAccessDeniedHandler emits useful context for debugging. Ensure logging does not alter the response flow or status.modules/common-security/src/main/java/com/ott/common/security/handler/JwtAuthenticationEntryPoint.java (1)
28-44: 디버깅을 위한 로깅 추가를 권장합니다.인증 실패 시 로그를 남기면 운영 환경에서 문제 추적에 유용합니다.
@Slf4j가 선언되어 있으나 사용되지 않고 있습니다.♻️ 로깅 추가 제안
`@Override` public void commence( HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException { // Filter에서 받아온 002, 003 에러일 경우 해당 에러 사용 Object attribute = request.getAttribute(ERROR_CODE); ErrorCode errorCode = (attribute instanceof ErrorCode) ? (ErrorCode) attribute : ErrorCode.UNAUTHORIZED; + log.warn("Authentication failed: {} - {} ({})", request.getRequestURI(), errorCode, authException.getMessage()); ErrorResponse errorResponse = ErrorResponse.of(errorCode, authException.getMessage());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@modules/common-security/src/main/java/com/ott/common/security/handler/JwtAuthenticationEntryPoint.java` around lines 28 - 44, Add logging to JwtAuthenticationEntryPoint.commence to record authentication failures: log the resolved ErrorCode (from request.getAttribute(ERROR_CODE)), the authException message and stacktrace, and minimal request context (e.g., request.getRequestURI()) before writing the ErrorResponse; use the existing `@Slf4j` logger and choose appropriate level (warn/error) so operational traces include both the errorCode and the exception stack for debugging while preserving the current response behavior that uses ErrorResponse, response status, and objectMapper to write the body.apps/api-admin/src/main/java/com/ott/api_admin/auth/dto/request/AdminLoginRequest.java (1)
19-21: 비밀번호 필드에 추가 유효성 검사 고려관리자 로그인의 경우,
@NotBlank만으로는 충분하지 않을 수 있습니다. 최소 길이 제약을 추가하면 기본적인 입력 검증이 강화됩니다.♻️ 제안된 수정
+import jakarta.validation.constraints.Size; + `@NotBlank` + `@Size`(min = 8, message = "비밀번호는 최소 8자 이상이어야 합니다") `@Schema`(description = "비밀번호", example = "password123") private String password;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-admin/src/main/java/com/ott/api_admin/auth/dto/request/AdminLoginRequest.java` around lines 19 - 21, The password field in AdminLoginRequest only has `@NotBlank` which is weak; add a minimum-length validation (e.g., `@Size`(min = 8) or `@Length`(min = 8)) on the private String password field so admin passwords must meet a basic length requirement, and update the Swagger `@Schema` description/example if needed to reflect the minimum length; locate the password field in the AdminLoginRequest DTO and apply the validation annotation.apps/api-user/src/main/java/com/ott/api_user/auth/controller/AuthApi.java (2)
19-29: 응답 코드 일관성 확인 필요
reissue엔드포인트가200응답 코드를 사용하는데, 관리자 API(AdminAuthApi)는204를 사용합니다. PR 설명과 스크린샷에서도204 No Content가 표시되어 있습니다. 의도적인 차이인지 확인이 필요합니다.♻️ 관리자 API와 일관성을 맞추려면
`@ApiResponses`(value = { - `@ApiResponse`(responseCode = "200", description = "재발급 성공"), + `@ApiResponse`(responseCode = "204", description = "재발급 성공"),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/java/com/ott/api_user/auth/controller/AuthApi.java` around lines 19 - 29, The OpenAPI annotation for the reissue endpoint in AuthApi currently documents a 200 response but AdminAuthApi uses 204 and the PR/screenshot show 204; update the documentation to be consistent by changing the `@ApiResponse` for successful reissue to responseCode = "204" (No Content) for the reissue(HttpServletRequest, HttpServletResponse) operation, or if 200 is intended, change AdminAuthApi to match—ensure the `@Operation/`@ApiResponses for reissue reflect the agreed success status code consistently across AuthApi and AdminAuthApi.
35-43: 로그아웃 응답 코드 일관성로그아웃 엔드포인트도 마찬가지로
200을 문서화하고 있으나, 관리자 API는204를 사용합니다. 일관성을 위해 검토가 필요합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/java/com/ott/api_user/auth/controller/AuthApi.java` around lines 35 - 43, The AuthApi controller's logout endpoint documents a 200 response while the admin API uses 204; update the ApiResponses on the logout handler in AuthApi (the method handling logout) to use responseCode "204" and adjust the description to indicate no content (e.g., "로그아웃 성공(콘텐츠 없음)"); also remove any response body schema/content for the 204 response so the OpenAPI spec correctly reflects a no-content response and stays consistent with the admin API.apps/api-user/src/main/java/com/ott/api_user/auth/service/AuthService.java (1)
54-56:clearRefreshToken()호출이 불필요할 수 있습니다.
updateRefreshToken(newRefreshToken)이 새 토큰으로 덮어쓴다면, 바로 전에clearRefreshToken()을 호출할 필요가 없습니다. Member 엔티티의 구현에 따라 다르지만, 일반적으로 update 메서드가 값을 대체하므로 clear가 중복될 수 있습니다.♻️ 제안된 수정
// refreshToken 갱신 및 이전 토큰 폐기 - member.clearRefreshToken(); member.updateRefreshToken(newRefreshToken);Member 엔티티의
clearRefreshToken()과updateRefreshToken()구현을 확인하여 clear가 필요한지 검증하세요:#!/bin/bash # Description: Check Member entity methods for refresh token handling ast-grep --pattern $'class Member { $$$ clearRefreshToken($$$) { $$$ } $$$ }' ast-grep --pattern $'class Member { $$$ updateRefreshToken($$$) { $$$ } $$$ }'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/java/com/ott/api_user/auth/service/AuthService.java` around lines 54 - 56, Review Member.clearRefreshToken() and Member.updateRefreshToken(newRefreshToken) implementations to determine if clearRefreshToken() is redundant; if updateRefreshToken replaces the stored token atomically, remove the prior call to member.clearRefreshToken() from AuthService (the two lines in the refresh-token flow) and leave only member.updateRefreshToken(newRefreshToken); if updateRefreshToken relies on clearRefreshToken() for side effects (e.g., nulling other fields or triggering events), keep both but add a comment explaining the dependency and ensure updateRefreshToken behaves idempotently.apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/CustomOAuth2UserService.java (1)
34-36: 필드명은 lowerCamelCase로 통일 권장.
타입명과 동일한 대문자 시작 필드는 혼동을 유발합니다.✏️ 네이밍 개선 예시
- private final CustomOAuth2UserService CustomOAuth2UserService; // 카카오에서 받은 사용자 프로필 조회 후 DB에 적재 + private final CustomOAuth2UserService customOAuth2UserService; // 카카오에서 받은 사용자 프로필 조회 후 DB에 적재 ... - userInfo.userService(CustomOAuth2UserService)) + userInfo.userService(customOAuth2UserService))Also applies to: 80-82
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/CustomOAuth2UserService.java` around lines 34 - 36, Rename any fields/variables that start with an uppercase sequence matching their type to follow lowerCamelCase to avoid confusion; specifically in CustomOAuth2UserService change the local/field name oAuth2User to a lowerCamelCase variant (e.g., oauth2User) and update all usages, and apply the same rename for the similar variable(s) referenced around the other occurrence(s) (lines ~80-82) so declarations and references remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/api-admin/src/main/java/com/ott/api_admin/auth/controller/AdminAuthController.java`:
- Around line 97-112: The addCookie and deleteCookie methods currently create
Cookie objects without SameSite and with setSecure(false); update these to build
cookies with ResponseCookie (or set the SameSite header manually) inside
addCookie and deleteCookie, ensure HttpOnly is true, set SameSite to "Lax" or
"Strict" (e.g., Lax for auth), set Secure based on environment (true in
production, false in local/dev), keep Path="/" and Max-Age behavior (use maxAge
for addCookie and maxAge=0 for deleteCookie), and use
response.addHeader("Set-Cookie", responseCookie.toString()) so SameSite and
Secure are included.
In
`@apps/api-admin/src/main/java/com/ott/api_admin/auth/service/AdminAuthService.java`:
- Around line 38-42: The password verification in AdminAuthService has been
commented out, allowing logins with just an email; restore the check by
re-enabling or re-adding the logic that reads encodedPassword =
member.getPassword() and calls passwordEncoder.matches(request.getPassword(),
encodedPassword), and if it fails throw new
BusinessException(ErrorCode.UNAUTHORIZED, "이메일 또는 비밀번호가 올바르지 않습니다."); ensure the
null/empty encodedPassword guard remains and that passwordEncoder is the
injected bean used by the class so the method that handles admin login enforces
correct password validation.
In `@apps/api-admin/src/main/java/com/ott/api_admin/config/SecurityConfig.java`:
- Around line 7-12: Security 레벨에서 CORS가 비활성화되어 관리자 프론트의 다른 Origin 요청(프리플라이트, 쿠키
전송 등)이 차단되고 있으므로 SecurityConfig 내 HttpSecurity 설정에서 CORS를 활성화하여 WebMvcConfig 또는
등록된 CorsConfigurationSource 설정이 적용되도록 변경하세요; 구체적으로 SecurityConfig의 HttpSecurity
구성(예: 메서드명 configure 또는 securityFilterChain 생성 부분, 참조: HttpSecurity,
AbstractHttpConfigurer)에서 현재 cors().disable() 또는 cors 설정 누락 부분을 제거하고 http.cors()
호출을 추가한 뒤 애플리케이션에 이미 정의된 CorsConfigurationSource/CorsMappings(예: WebMvcConfig)
또는 별도 CorsConfigurationSource 빈이 사용되도록 보장하세요.
In `@apps/api-admin/src/main/resources/application.yml`:
- Line 42: access-token-expiry 설정의 값(3200000)과 주석("60분")이 불일치합니다; 의도한 만료시간이
60분이면 application.yml의 access-token-expiry 값을 3600000으로 변경하고, 만약 현재 숫자(약 53분)를
유지하려면 주석을 "약 53분 (3200000ms)" 등으로 수정하세요; 변경할 위치는 application.yml의
access-token-expiry 항목을 찾아 업데이트하면 됩니다.
In `@apps/api-user/build.gradle`:
- Around line 19-24: Update the JJWT dependency versions from 0.12.6 to 0.13.0
for all three coordinates: io.jsonwebtoken:jjwt-api, io.jsonwebtoken:jjwt-impl,
and io.jsonwebtoken:jjwt-jackson while keeping jjwt-api declared as
implementation and jjwt-impl and jjwt-jackson declared as runtimeOnly; this
ensures you use the latest stable 0.13.0 release for security without changing
the dependency scopes.
In
`@apps/api-user/src/main/java/com/ott/api_user/auth/controller/AuthController.java`:
- Around line 76-90: logincheck currently returns accessToken and refreshToken
in the response body (via extractCookie) which exposes tokens; remove these
tokens from the response body and either (a) restrict the endpoint to
non-production (e.g., annotate the method/class with `@Profile`("dev") or gate
behind a feature flag) or (b) if you need to send tokens to the client for
testing, set them as HttpOnly, Secure cookies instead of including them in the
JSON response; update the logincheck method and any callers that rely on
accessToken/refreshToken in the response to read tokens only from cookies and
remove Map entries "accessToken" and "refreshToken".
In
`@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/handler/OAuth2FailureHandler.java`:
- Around line 30-31: The code in OAuth2FailureHandler builds targetUrl by
embedding exception.getMessage() into the redirect which can leak sensitive
info; instead, put a generic error code (e.g., "AUTH_FAILURE" or a short enum
value) into the URL and URL-encode that, and log the full exception
message/stacktrace to the server logger (e.g., logger.error("OAuth2 failure",
exception)) so details remain in logs only; update the construction of targetUrl
to use the generic code and remove exception.getMessage() from any client-facing
output.
In
`@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/handler/OAuth2SuccessHandler.java`:
- Around line 38-44: The accessTokenExpiry and refreshTokenExpiry fields in
OAuth2SuccessHandler are milliseconds (used by JwtTokenProvider) but
Cookie.setMaxAge expects seconds, so update the places in OAuth2SuccessHandler
where you call cookie.setMaxAge(...) (and any helper that builds cookies) to
pass (int)(accessTokenExpiry / 1000) and (int)(refreshTokenExpiry / 1000)
respectively (ensure to handle rounding and a minimum of 1 second if needed);
alternatively introduce separate cookieExpirySeconds fields derived from the
millisecond values and use those for Cookie.setMaxAge to keep intent clear.
- Around line 76-87: The current OAuth2SuccessHandler reads redirect_uri from
the request and redirects there directly, creating an open-redirect risk; update
the logic in OAuth2SuccessHandler to validate redirectUri before using
getRedirectStrategy().sendRedirect: parse the redirectUri (the redirectUri
variable), ensure it is either a safe relative path or its host and scheme are
in a configured allowlist of trusted front-end domains (or exactly matches
frontedUrl), and if validation fails fall back to the default internal URL
(frontedUrl + "/auth/logincheck"); only after this validation build the
targetUrl with queryParam("isNewMember", isNewMember) and call
getRedirectStrategy().sendRedirect(request, response, targetUrl).
- Around line 91-98: The addCookie method currently creates a
jakarta.servlet.http.Cookie with setSecure(false) and no SameSite attribute;
update addCookie(HttpServletResponse response, String name, String value, int
maxAge) to (1) set secure to true for production (e.g., derive from
request.isSecure() or a config flag) instead of hardcoding false, (2) include
SameSite (Lax or Strict) by using ResponseCookie.from(name,
value).path("/").httpOnly(true).secure(true).sameSite("Lax").maxAge(maxAge).build()
and add it via response.addHeader("Set-Cookie", responseCookie.toString()) or
construct the Set-Cookie header string manually, and (3) preserve existing path,
HttpOnly and maxAge behavior; replace or remove the old Cookie creation so
SameSite is applied.
In
`@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/userinfo/KakaoUserInfo.java`:
- Around line 14-23: The KakaoUserInfo constructor currently assumes nested maps
exist and will throw NullPointerException if "kakao_account" or "profile" are
missing or if "email" is absent; update the KakaoUserInfo(Map<String, Object>
attributes) constructor to defensively null-check and cast the nested maps
(attributes.get("kakao_account") and kakaoAccount.get("profile")) before
accessing them, assign providerId from attributes.get("id") safely, and set
nickname and email to null (or a safe default) when their map entries are
missing or not strings so the class tolerates missing permissions or absent
fields.
In `@apps/api-user/src/main/java/com/ott/api_user/auth/service/AuthService.java`:
- Line 71: Fix the typo in the inline comment that currently reads "Optipnal 처리를
위해 사용" by changing "Optipnal" to "Optional" so it reads "Optional 처리를 위해 사용";
locate the comment near the AuthService class where that annotation appears and
update only the comment text.
- Line 35: There's an extra double space between 'throw' and 'new' in the
AuthService class; update the throw statement (throw new
BusinessException(errorCode);) to use a single space (throw new
BusinessException(errorCode);) so the code follows normal formatting
conventions.
In `@apps/api-user/src/main/java/com/ott/api_user/config/SecurityConfig.java`:
- Around line 10-17: The SecurityConfig disables CORS so your
corsConfigurationSource bean never takes effect; update the SecurityFilterChain
configuration (the method that configures HttpSecurity / bean named
SecurityFilterChain) to enable CORS by calling http.cors(...) (e.g.,
http.cors(Customizer.withDefaults()) or http.cors().and()) and remove or stop
calling any lines that disable CORS in AbstractHttpConfigurer; ensure your
existing corsConfigurationSource bean remains present so Spring will pick it up
and be applied at the security layer.
- Around line 61-69: The SecurityConfig currently uses
requestMatchers("/auth/**", ...) which unintentionally exposes authenticated
endpoints like /auth/me and /auth/logout; change the requestMatchers call in
SecurityConfig to remove the broad "/auth/**" and instead whitelist only
explicit public auth endpoints such as the login/registration/token reissue
endpoints you intend to expose (e.g., "/auth/login", "/auth/register",
"/auth/reissue" — adjust to your actual public endpoints), leaving /auth/me and
/auth/logout protected by authentication; update any related tests/config to
reflect the narrowed allowlist.
- Around line 53-84: Add an AuthorizationRequestRepository to the oauth2Login
flow so OAuth2 requests are persisted with a stateless session policy: update
the oauth2Login(...) call in SecurityConfig to call
authorizationRequestRepository(...) and pass a bean instance of a cookie-based
or custom repository (e.g., HttpCookieOAuth2AuthorizationRequestRepository), and
register that repository as a `@Bean` in your configuration so the oauth2 callback
can match the original request; keep existing
userInfo.userService(CustomOAuth2UserService), oAuth2SuccessHandler and
oAuth2FailureHandler intact.
In `@modules/common-security/build.gradle`:
- Around line 1-15: Update the jjwt dependencies in
modules/common-security/build.gradle to use version 0.13.0: replace
'io.jsonwebtoken:jjwt-api:0.12.3', 'io.jsonwebtoken:jjwt-impl:0.12.3', and
'io.jsonwebtoken:jjwt-jackson:0.12.3' with the same coordinates at 0.13.0 (the
lines referencing jjwt-api, jjwt-impl, and jjwt-jackson). After changing the
versions, refresh/sync the Gradle project to ensure the new artifacts are
resolved.
---
Nitpick comments:
In
`@apps/api-admin/src/main/java/com/ott/api_admin/auth/dto/request/AdminLoginRequest.java`:
- Around line 19-21: The password field in AdminLoginRequest only has `@NotBlank`
which is weak; add a minimum-length validation (e.g., `@Size`(min = 8) or
`@Length`(min = 8)) on the private String password field so admin passwords must
meet a basic length requirement, and update the Swagger `@Schema`
description/example if needed to reflect the minimum length; locate the password
field in the AdminLoginRequest DTO and apply the validation annotation.
In `@apps/api-user/src/main/java/com/ott/api_user/auth/controller/AuthApi.java`:
- Around line 19-29: The OpenAPI annotation for the reissue endpoint in AuthApi
currently documents a 200 response but AdminAuthApi uses 204 and the
PR/screenshot show 204; update the documentation to be consistent by changing
the `@ApiResponse` for successful reissue to responseCode = "204" (No Content) for
the reissue(HttpServletRequest, HttpServletResponse) operation, or if 200 is
intended, change AdminAuthApi to match—ensure the `@Operation/`@ApiResponses for
reissue reflect the agreed success status code consistently across AuthApi and
AdminAuthApi.
- Around line 35-43: The AuthApi controller's logout endpoint documents a 200
response while the admin API uses 204; update the ApiResponses on the logout
handler in AuthApi (the method handling logout) to use responseCode "204" and
adjust the description to indicate no content (e.g., "로그아웃 성공(콘텐츠 없음)"); also
remove any response body schema/content for the 204 response so the OpenAPI spec
correctly reflects a no-content response and stays consistent with the admin
API.
In
`@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/CustomOAuth2UserService.java`:
- Around line 34-36: Rename any fields/variables that start with an uppercase
sequence matching their type to follow lowerCamelCase to avoid confusion;
specifically in CustomOAuth2UserService change the local/field name oAuth2User
to a lowerCamelCase variant (e.g., oauth2User) and update all usages, and apply
the same rename for the similar variable(s) referenced around the other
occurrence(s) (lines ~80-82) so declarations and references remain consistent.
In
`@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/handler/OAuth2FailureHandler.java`:
- Around line 20-21: Rename the misspelled field frontedUrl to frontendUrl in
OAuth2FailureHandler (and make the same change in OAuth2SuccessHandler) so the
variable name is consistent; update the `@Value-annotated` field declaration in
OAuth2FailureHandler (currently `@Value`("${app.frontend-url}") private String
frontedUrl;) to private String frontendUrl and then update all usages of
frontedUrl within the class to frontendUrl to avoid compilation errors and
maintain consistency across both handlers.
- Line 28: Replace the info-level log in OAuth2FailureHandler with a higher
severity and include the exception object; specifically, in the
OAuth2FailureHandler (onAuthenticationFailure method) change the call currently
using log.info("OAuth2 로그인 실패: {}", exception.getMessage()) to use log.warn or
log.error and pass the exception itself so the stack trace is recorded (e.g.,
log.warn("OAuth2 로그인 실패", exception) or log.error(...)). This updates the log
level for authentication failures and ensures the full exception details are
logged for investigation.
In
`@apps/api-user/src/main/java/com/ott/api_user/auth/oauth2/userinfo/UserInfo.java`:
- Around line 3-5: The UserInfo interface is empty but should define common
methods used by social providers; add method signatures like getEmail(),
getNickname(), and getProviderId() to the UserInfo interface and then have
KakaoUserInfo implement UserInfo by adding "implements UserInfo" to the
KakaoUserInfo class and implementing those methods (mapping existing
KakaoUserInfo fields to the new interface methods).
In `@apps/api-user/src/main/java/com/ott/api_user/auth/service/AuthService.java`:
- Around line 54-56: Review Member.clearRefreshToken() and
Member.updateRefreshToken(newRefreshToken) implementations to determine if
clearRefreshToken() is redundant; if updateRefreshToken replaces the stored
token atomically, remove the prior call to member.clearRefreshToken() from
AuthService (the two lines in the refresh-token flow) and leave only
member.updateRefreshToken(newRefreshToken); if updateRefreshToken relies on
clearRefreshToken() for side effects (e.g., nulling other fields or triggering
events), keep both but add a comment explaining the dependency and ensure
updateRefreshToken behaves idempotently.
In
`@apps/api-user/src/main/java/com/ott/api_user/auth/service/KakaoAuthService.java`:
- Around line 50-53: The isNewMember method performs only a read query
(preferredTagRepository.existsByMemberId) but the class has a broader
`@Transactional`, so annotate the isNewMember method with `@Transactional`(readOnly
= true) to ensure the call runs in a read-only transaction for performance;
locate the isNewMember(Long memberId) method in KakaoAuthService and add the
method-level `@Transactional`(readOnly = true) annotation to override the
class-level transaction settings.
In
`@apps/api-user/src/main/java/com/ott/api_user/auth/service/SocialAuthService.java`:
- Around line 3-5: The empty SocialAuthService interface currently provides no
contract and should either be removed or turned into a meaningful abstraction:
add clearly named method signatures such as authenticate(String provider, String
accessToken) returning a domain/DTO (e.g., SocialUser or AuthenticationResult)
and/or fetchUserInfo(String provider, String accessToken) returning a
SocialUser, so implementations (e.g., GoogleSocialAuthService) have a defined
API; if no common behaviour is required yet, delete the empty SocialAuthService
to avoid confusion and reintroduce it later when you add methods.
In `@apps/api-user/src/main/resources/application.yml`:
- Around line 48-50: 현재 application.yml의 hibernate.show_sql이 true로 되어 있어 프로덕션에서
로그 과다 및 성능 문제를 야기할 수 있으니, hibernate.show_sql 설정을 프로파일별 또는 환경변수로 분리하여 프로덕션에서는
false로 설정하도록 변경하세요; 예를 들어 Spring 프로파일(application-prod.yml)이나 환경변수(예:
HIBERNATE_SHOW_SQL)를 이용해 기본값을 false로 두고 개발환경에서만 true로 오버라이드하도록 구성하면 됩니다 (참조 설정
키: hibernate.show_sql).
In
`@modules/common-security/src/main/java/com/ott/common/security/handler/JwtAccessDeniedHandler.java`:
- Around line 26-40: The JwtAccessDeniedHandler.handle method lacks runtime
logging despite `@Slf4j`; add a log entry that records the request URI and the
AccessDeniedException message when a 403 occurs. Inside
handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) before writing the ErrorResponse,
call log.warn or log.error (choose warn) with a clear message including
request.getRequestURI() and accessDeniedException.getMessage() (and optionally
the exception itself) so the class JwtAccessDeniedHandler emits useful context
for debugging. Ensure logging does not alter the response flow or status.
In
`@modules/common-security/src/main/java/com/ott/common/security/handler/JwtAuthenticationEntryPoint.java`:
- Around line 28-44: Add logging to JwtAuthenticationEntryPoint.commence to
record authentication failures: log the resolved ErrorCode (from
request.getAttribute(ERROR_CODE)), the authException message and stacktrace, and
minimal request context (e.g., request.getRequestURI()) before writing the
ErrorResponse; use the existing `@Slf4j` logger and choose appropriate level
(warn/error) so operational traces include both the errorCode and the exception
stack for debugging while preserving the current response behavior that uses
ErrorResponse, response status, and objectMapper to write the body.
In
`@modules/common-security/src/main/java/com/ott/common/security/jwt/JwtTokenProvider.java`:
- Around line 89-99: Add logging to JwtTokenProvider: annotate the class with
`@Slf4j` (or create a private static final Logger) and in
validateAndGetErrorCode(String token) log failures inside each catch block
(ExpiredJwtException and JwtException | IllegalArgumentException). Include
contextual info (exception message and a non-sensitive token identifier, e.g.,
first/last chars or masked token) and the caught exception in the log call so
you capture stack/stack message while avoiding full token leakage; then return
the existing ErrorCode values unchanged.
In `@modules/domain/src/main/java/com/ott/domain/member/domain/Member.java`:
- Around line 63-66: Rename Member.updateKakaoProfile to a generic name like
updateProfile since it only sets email and nickname; update the method signature
in the Member class (change method name from updateKakaoProfile to
updateProfile), update all call sites/usages, imports and any tests or Javadoc
referencing updateKakaoProfile to the new name, and run compilation/tests to
ensure there are no remaining references.
| private void addCookie(HttpServletResponse response, String name, String value, int maxAge) { | ||
| Cookie cookie = new Cookie(name, value); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setSecure(false); // 배포 시 true | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge(maxAge); | ||
| response.addCookie(cookie); | ||
| } | ||
|
|
||
| private void deleteCookie(HttpServletResponse response, String name) { | ||
| Cookie cookie = new Cookie(name, null); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setSecure(false); // 배포 시 true | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge(0); | ||
| response.addCookie(cookie); |
There was a problem hiding this comment.
쿠키에 Secure/SameSite가 없어 CSRF/중간자 위험이 큽니다.
현재 setSecure(false)와 SameSite 미설정으로 HTTPS 환경에서도 안전하지 않습니다. ResponseCookie로 SameSite=Lax/Strict와 Secure를 기본 적용하고, 환경별 분기를 두세요.
🛡️ 쿠키 보안 옵션 적용 예시
- private void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
- Cookie cookie = new Cookie(name, value);
- cookie.setHttpOnly(true);
- cookie.setSecure(false); // 배포 시 true
- cookie.setPath("/");
- cookie.setMaxAge(maxAge);
- response.addCookie(cookie);
- }
+ private void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
+ ResponseCookie cookie = ResponseCookie.from(name, value)
+ .httpOnly(true)
+ .secure(true) // 환경변수로 분기 권장
+ .sameSite("Lax")
+ .path("/")
+ .maxAge(maxAge)
+ .build();
+ response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
+ }
...
- private void deleteCookie(HttpServletResponse response, String name) {
- Cookie cookie = new Cookie(name, null);
- cookie.setHttpOnly(true);
- cookie.setSecure(false); // 배포 시 true
- cookie.setPath("/");
- cookie.setMaxAge(0);
- response.addCookie(cookie);
- }
+ private void deleteCookie(HttpServletResponse response, String name) {
+ ResponseCookie cookie = ResponseCookie.from(name, "")
+ .httpOnly(true)
+ .secure(true) // 환경변수로 분기 권장
+ .sameSite("Lax")
+ .path("/")
+ .maxAge(0)
+ .build();
+ response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
+ }🧰 Tools
🪛 ast-grep (0.40.5)
[warning] 99-99: A cookie was detected without setting the 'secure' flag. The 'secure' flag for cookies prevents the client from transmitting the cookie over insecure channels such as HTTP. Set the 'secure' flag by calling 'cookie.setSecure(true);'.
Context: cookie.setSecure(false);
Note: [CWE-614] Sensitive Cookie in HTTPS Session Without 'Secure' Attribute. [REFERENCES]
- https://owasp.org/www-community/controls/SecureCookieAttribute
(cookie-secure-flag-false-java)
[warning] 108-108: A cookie was detected without setting the 'secure' flag. The 'secure' flag for cookies prevents the client from transmitting the cookie over insecure channels such as HTTP. Set the 'secure' flag by calling 'cookie.setSecure(true);'.
Context: cookie.setSecure(false);
Note: [CWE-614] Sensitive Cookie in HTTPS Session Without 'Secure' Attribute. [REFERENCES]
- https://owasp.org/www-community/controls/SecureCookieAttribute
(cookie-secure-flag-false-java)
[warning] 102-102: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: response.addCookie(cookie);
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application
(cookie-missing-samesite-java)
[warning] 111-111: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: response.addCookie(cookie);
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application
(cookie-missing-samesite-java)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@apps/api-admin/src/main/java/com/ott/api_admin/auth/controller/AdminAuthController.java`
around lines 97 - 112, The addCookie and deleteCookie methods currently create
Cookie objects without SameSite and with setSecure(false); update these to build
cookies with ResponseCookie (or set the SameSite header manually) inside
addCookie and deleteCookie, ensure HttpOnly is true, set SameSite to "Lax" or
"Strict" (e.g., Lax for auth), set Secure based on environment (true in
production, false in local/dev), keep Path="/" and Max-Age behavior (use maxAge
for addCookie and maxAge=0 for deleteCookie), and use
response.addHeader("Set-Cookie", responseCookie.toString()) so SameSite and
Secure are included.
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; | ||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
| import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; | ||
| import org.springframework.security.config.http.SessionCreationPolicy; |
There was a problem hiding this comment.
CORS 비활성화로 관리자 UI 연동이 막힐 수 있습니다.
관리자 프론트가 다른 Origin이면 preflight 실패 및 쿠키 전송/수신이 막힙니다. Security 레벨에서 CORS를 활성화해 WebMvcConfig(또는 별도 CorsConfigurationSource) 설정이 적용되게 해주세요.
🔧 CORS 활성화 예시
+import org.springframework.security.config.Customizer;
...
- .cors(AbstractHttpConfigurer::disable)
+ .cors(Customizer.withDefaults())Also applies to: 30-34
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api-admin/src/main/java/com/ott/api_admin/config/SecurityConfig.java`
around lines 7 - 12, Security 레벨에서 CORS가 비활성화되어 관리자 프론트의 다른 Origin 요청(프리플라이트, 쿠키
전송 등)이 차단되고 있으므로 SecurityConfig 내 HttpSecurity 설정에서 CORS를 활성화하여 WebMvcConfig 또는
등록된 CorsConfigurationSource 설정이 적용되도록 변경하세요; 구체적으로 SecurityConfig의 HttpSecurity
구성(예: 메서드명 configure 또는 securityFilterChain 생성 부분, 참조: HttpSecurity,
AbstractHttpConfigurer)에서 현재 cors().disable() 또는 cors 설정 누락 부분을 제거하고 http.cors()
호출을 추가한 뒤 애플리케이션에 이미 정의된 CorsConfigurationSource/CorsMappings(예: WebMvcConfig)
또는 별도 CorsConfigurationSource 빈이 사용되도록 보장하세요.
| .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // JWT 기반 인증 | ||
|
|
||
| .exceptionHandling(e -> e | ||
| .authenticationEntryPoint(jwtAuthenticationEntryPoint) // 401 | ||
| .accessDeniedHandler(jwtAccessDeniedHandler)) // 403 | ||
|
|
||
| .authorizeHttpRequests(auth -> auth | ||
| // 인증 불필요 | ||
| .requestMatchers( | ||
| "/actuator/health/**", | ||
| "/actuator/info", | ||
| "/auth/**", | ||
| "/oauth2/**", | ||
| "/login/oauth2/**", | ||
| "/auth/reissue", | ||
| "/auth/logout", | ||
| "/swagger-ui/**", | ||
| "/v3/api-docs/**", | ||
| "/swagger-resources/**" | ||
| ).permitAll() | ||
|
|
||
| // 나머지 url에 대해서는 인증 필요 | ||
| .anyRequest().authenticated() | ||
| ) | ||
|
|
||
| // OAuth2 카카오 로그인 | ||
| .oauth2Login(oauth2 -> oauth2 | ||
| .userInfoEndpoint(userInfo -> | ||
| userInfo.userService(CustomOAuth2UserService)) | ||
| .successHandler(oAuth2SuccessHandler) | ||
| .failureHandler(oAuth2FailureHandler) | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for OAuth2AuthorizationRequestRepository or similar implementations
rg -n "AuthorizationRequestRepository|OAuth2AuthorizationRequestRepository|HttpCookieOAuth2AuthorizationRequestRepository" -g '*.java'Repository: OpenTheTaste/backend
Length of output: 46
🏁 Script executed:
# Also check the SecurityConfig.java file content to see the complete configuration
fd -t f "SecurityConfig.java" | head -5Repository: OpenTheTaste/backend
Length of output: 209
🏁 Script executed:
# Search for any OAuth2 related configuration classes
rg -n "oauth2Login|authorizationRequestRepository" -g '*.java' -A 3Repository: OpenTheTaste/backend
Length of output: 604
🏁 Script executed:
# Check if there are any bean configurations for OAuth2
rg -n "@Bean.*OAuth2|@Configuration.*OAuth" -g '*.java'Repository: OpenTheTaste/backend
Length of output: 46
🏁 Script executed:
# Let's examine the full SecurityConfig.java file for the api-user module
cat -n apps/api-user/src/main/java/com/ott/api_user/config/SecurityConfig.javaRepository: OpenTheTaste/backend
Length of output: 5787
🏁 Script executed:
# Search for any other OAuth2 or session management configurations
rg -n "sessionCreationPolicy|SessionCreationPolicy" -g '*.java' -B 3 -A 3Repository: OpenTheTaste/backend
Length of output: 4146
🏁 Script executed:
# Check for any cookie-based repository or session handling
rg -n "Cookie|cookie" -g '*.java' --type-add 'config:*.java' -A 2Repository: OpenTheTaste/backend
Length of output: 15035
🏁 Script executed:
# Look for any imports related to OAuth2
rg -n "import.*oauth2|import.*authorization" -g '*.java' apps/api-user/Repository: OpenTheTaste/backend
Length of output: 1749
OAuth2 인증 플로우에 필요한 AuthorizationRequestRepository 설정이 누락되었습니다.
STATELESS 세션 정책 상태에서 OAuth2 인증 플로우가 작동하려면 AuthorizationRequest를 저장할 저장소가 필요합니다. 현재 코드에는 기본값인 HttpSession 기반 저장소 대신 쿠키 기반 또는 커스텀 OAuth2AuthorizationRequestRepository가 설정되어 있지 않아 OAuth2 콜백 단계에서 원본 요청을 매칭할 수 없게 됩니다. 결과적으로 카카오 로그인이 실패합니다.
oauth2Login() 설정에 authorizationRequestRepository()를 추가하고 HttpCookieOAuth2AuthorizationRequestRepository를 Bean으로 등록하거나, 커스텀 저장소를 구현해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api-user/src/main/java/com/ott/api_user/config/SecurityConfig.java`
around lines 53 - 84, Add an AuthorizationRequestRepository to the oauth2Login
flow so OAuth2 requests are persisted with a stateless session policy: update
the oauth2Login(...) call in SecurityConfig to call
authorizationRequestRepository(...) and pass a bean instance of a cookie-based
or custom repository (e.g., HttpCookieOAuth2AuthorizationRequestRepository), and
register that repository as a `@Bean` in your configuration so the oauth2 callback
can match the original request; keep existing
userInfo.userService(CustomOAuth2UserService), oAuth2SuccessHandler and
oAuth2FailureHandler intact.
📝 작업 내용
📷 스크린샷
브라우저상에서는 로그인 시 쿠키에 토큰이 저장되서
자동으로 API 요청 시 토큰이 전달되지만,
현재 Postman에서는 직접 토큰을 쿠키를 넣어주셔야 됩니다.
자세한 내용은 스크린샷 확인 부탁드립니다.
사용자 로그인
1. 카카오 로그인

http://localhost:8080/oauth2/authorization/kakao -> 해당 페이지로 이동
2. 로그인 성공 화면



브라우저, 관리자도구에서 AccessToken 및 RefreshToken 확인 가능합니다.
AccessToken은 메모장에 저장해두시길 바랍니다.
배포에서는 브라우저상에서 알아서 토큰이 전달되지만
Postman 테스트 시 쿠키에 직접 토큰을 삽입해야됩니다.
응답코드는 204입니다.
관리자 로그인의 경우 DB에 미리 ID, PW 삽입해주셔야 됩니다.
해당 ID, PW 로그인 시 JWT 토큰이 발행됩니다.
☑️ 체크 리스트
#️⃣ 연관된 이슈
#25
💬 리뷰 요구사항
카카오 로그인
Postman에서 실행이 불가능 합니다.
http://localhost:8080/oauth2/authorization/kakao -> 해당 페이지 들어가서 카카오 로그인 하셔야됩니다.
토큰
로그인 하시면 F12나 화면에 AccessToken, RefreshToken 정보 확인 가능합니다.
AccessToken은 메모장에 저장해 두시길 바랍니다. 현재 유효기간 30분입니다.
더 자세한 내용은 노션이 기재하겠습니다.
https://www.notion.so/Spring-Security-OAuth2-30c2753972b7803a8263c51e8d153fe7
https://www.notion.so/30b2753972b780e4acaff0f5ebcac745
env 필수로 먹이셔야됩니다!!!
Summary by CodeRabbit
릴리스 노트
New Features
Chores