Skip to content

Feat: OAuth2 로그인 시 target_url 쿠키를 통한 동적 리다이렉트 기능 구현#179

Merged
Developer-Choi-Jae-Young merged 1 commit into
prography:mainfrom
Developer-Choi-Jae-Young:feat/177-oauth-dynamic-redirect
Jul 21, 2026
Merged

Feat: OAuth2 로그인 시 target_url 쿠키를 통한 동적 리다이렉트 기능 구현#179
Developer-Choi-Jae-Young merged 1 commit into
prography:mainfrom
Developer-Choi-Jae-Young:feat/177-oauth-dynamic-redirect

Conversation

@Developer-Choi-Jae-Young

@Developer-Choi-Jae-Young Developer-Choi-Jae-Young commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

PR 타입(하나 이상의 PR 타입을 선택해주세요)

  • Feat : 새로운 백엔드 기능 추가
  • Bug : 서버/로직 버그 발견 및 수정
  • Refactor : 백엔드 코드 구조 개선
  • Chore : 의존성/설정/인프라 유지보수
  • API : API 엔드포인트 및 스펙 변경
  • DB : DB 스키마/쿼리/마이그레이션 변경
  • Performance : 성능 개선 및 병목 최적화
  • Test : 백엔드 테스트 코드 추가/수정
  • Docs : 백엔드 문서 수정
  • Security : 보안 취약점 대응 및 강화

변경 사항

OAuth2 로그인 완료 시 고정된 주소(`https://knock-in.com/auth/success`)로만 리다이렉트되던 한계를 해결하기 위해, 요청자의 도메인에 따라 동적으로 리다이렉트할 수 있도록 개선하였습니다.

* `HttpCookieOAuth2AuthorizationRequestRepository`와 `CookieUtils`를 추가하여, OAuth2 인증 시작 시 요청 파라미터에 포함된 `target_url`을 쿠키에 임시 저장합니다.
* `SecurityConfig`에 해당 쿠키 기반 Authorization Request Repository를 등록하였습니다.
* `OAuth2SuccessHandler`에서 인증 성공 시 저장된 쿠키 값을 읽어 해당 주소(예: `bo.knock-in.com`)로 동적 리다이렉트하며, 쿠키가 없을 경우 기존 설정 파일에 지정된 기본값(`app.client-success-url`)으로

리다이렉트하도록 안전장치(Fallback)를 적용했습니다.

테스트 결과

* `api.knock-in.com/oauth2/authorization/...` 요청 시 `target_url` 파라미터가 쿠키에 정상 등록되는 것을 확인했습니다.
* 구글/카카오 로그인 성공 후 쿠키에 기록되어 있던 `target_url` 목적지 주소로 올바르게 redirect되는 동작을 확인했습니다.

관련 이슈

* Closes #177

Summary by CodeRabbit

  • New Features

    • Added support for preserving a requested destination during OAuth login.
    • Users are redirected to their requested destination after successful authentication when available.
    • Added secure cookie handling for OAuth login state and redirect information.
  • Bug Fixes

    • OAuth login cookies are now cleared after authentication to prevent stale login data from affecting future sign-ins.

@github-actions github-actions Bot added the ✨ feat 새로운 백엔드 기능 추가 (New backend feature) label Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

OAuth2 cookie redirect flow

Layer / File(s) Summary
Cookie handling and serialization
src/main/java/org/example/knockin/auth/util/CookieUtils.java
Adds cookie lookup, creation, deletion, and Base64-url object serialization helpers.
Authorization request cookie storage
src/main/java/org/example/knockin/auth/repository/HttpCookieOAuth2AuthorizationRequestRepository.java, src/main/java/org/example/knockin/config/SecurityConfig.java
Stores OAuth2 authorization requests and optional target_url values in cookies, then registers the repository in the OAuth2 login configuration.
Dynamic success redirect
src/main/java/org/example/knockin/auth/handler/OAuth2SuccessHandler.java
Reads the redirect target cookie, clears authorization cookies, and redirects successful non-SDK logins to that target or the configured fallback URL.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: yourjinkr

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SecurityConfig
  participant HttpCookieOAuth2AuthorizationRequestRepository
  participant CookieUtils
  participant OAuth2SuccessHandler

  Client->>SecurityConfig: Start OAuth2 login with target_url
  SecurityConfig->>HttpCookieOAuth2AuthorizationRequestRepository: Save authorization request
  HttpCookieOAuth2AuthorizationRequestRepository->>CookieUtils: Add authorization and target URL cookies
  Client->>OAuth2SuccessHandler: Complete OAuth2 authentication
  OAuth2SuccessHandler->>CookieUtils: Read target_url cookie
  OAuth2SuccessHandler->>HttpCookieOAuth2AuthorizationRequestRepository: Clear authorization cookies
  OAuth2SuccessHandler-->>Client: Redirect to target URL
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The redirect-cookie flow matches #177, but the success-token cookie behavior on the redirected domain isn't explicitly verifiable from the summary. Confirm or document how the success-token cookie remains usable after redirect across the supported domains.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the OAuth2 dynamic redirect feature using target_url cookies.
Description check ✅ Passed The description includes the required PR type, change summary, test results, and linked issue.
Out of Scope Changes check ✅ Passed The changes stay focused on OAuth2 cookie storage, success handling, and security configuration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/org/example/knockin/auth/handler/OAuth2SuccessHandler.java (1)

56-69: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Open Redirect vulnerability.

The targetUrl is read from the target_url cookie (originally populated from an unvalidated user request parameter) and passed directly to response.sendRedirect(targetUrl). This allows an attacker to craft a malicious login link that redirects the user to an arbitrary external site after a successful login, facilitating phishing attacks.

You must validate the targetUrl against a trusted list of allowed redirect URIs (e.g., your configured CORS origins) before executing the redirect. Ensure the validation logic prevents substring bypasses (like an attacker registering https://yourdomain.com.malicious.com).

🛡️ Proposed fix to securely validate the redirect URI
-            String targetUrl = CookieUtils.getCookie(request, HttpCookieOAuth2AuthorizationRequestRepository.REDIRECT_URI_PARAM_COOKIE_NAME).map(Cookie::getValue).orElse(knockInProps.getClientSuccessUrl());
+            String targetUrl = CookieUtils.getCookie(request, HttpCookieOAuth2AuthorizationRequestRepository.REDIRECT_URI_PARAM_COOKIE_NAME).map(Cookie::getValue).orElse(knockInProps.getClientSuccessUrl());
+            
+            // Validate that the target URL exactly matches an allowed origin or is a safe subpath
+            boolean isAuthorized = knockInProps.getCorsUrls().stream()
+                    .anyMatch(allowedUrl -> targetUrl.equals(allowedUrl) || targetUrl.startsWith(allowedUrl + "/"));
+            
+            if (!isAuthorized && !targetUrl.equals(knockInProps.getClientSuccessUrl())) {
+                targetUrl = knockInProps.getClientSuccessUrl();
+            }
             boolean secureCookie = knockInProps.getClientSuccessUrl().startsWith("https://");
 
             ResponseCookie accessTokenCookie = ResponseCookie.from(TokenConstants.ACCESS_TOKEN_COOKIE_NAME, accessToken)
                     .httpOnly(true)
                     .secure(secureCookie)
                     .sameSite(secureCookie ? "None" : "Lax")
                     .path("/")
                     .maxAge(TokenProvider.ACCESS_TOKEN_EXPIRE_DURATION)
                     .build();
 
             response.addHeader(HttpHeaders.SET_COOKIE, accessTokenCookie.toString());
             httpCookieOAuth2AuthorizationRequestRepository.clearCookies(request, response);
             response.sendRedirect(targetUrl);
🤖 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/org/example/knockin/auth/handler/OAuth2SuccessHandler.java`
around lines 56 - 69, Validate targetUrl in OAuth2SuccessHandler against the
configured trusted redirect origins before calling response.sendRedirect, using
exact origin/host matching rather than substring checks so lookalike domains
such as yourdomain.com.malicious.com are rejected. Fall back to
knockInProps.getClientSuccessUrl() or otherwise avoid redirecting when the
cookie value is not allowed, while preserving valid trusted redirects.
🤖 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/org/example/knockin/auth/util/CookieUtils.java`:
- Around line 23-29: Update CookieUtils.addCookie and deleteCookie to use
Spring’s ResponseCookie instead of jakarta.servlet.http.Cookie, configuring
Secure, HttpOnly, and SameSite attributes consistently. Ensure deletion uses
matching attributes and cookie path so browsers accept the clearing cookie in
cross-origin scenarios.
- Around line 45-51: Replace Java serialization in CookieUtils.serialize and
CookieUtils.deserialize with a safe explicit representation for
OAuth2AuthorizationRequest, validating all decoded fields before reconstruction;
alternatively, store and resolve only an opaque server-side identifier. Ensure
deserialize never passes attacker-controlled cookie bytes to
SerializationUtils.deserialize.

---

Outside diff comments:
In `@src/main/java/org/example/knockin/auth/handler/OAuth2SuccessHandler.java`:
- Around line 56-69: Validate targetUrl in OAuth2SuccessHandler against the
configured trusted redirect origins before calling response.sendRedirect, using
exact origin/host matching rather than substring checks so lookalike domains
such as yourdomain.com.malicious.com are rejected. Fall back to
knockInProps.getClientSuccessUrl() or otherwise avoid redirecting when the
cookie value is not allowed, while preserving valid trusted redirects.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf89a53a-fbe8-408e-9de0-7477402d18ee

📥 Commits

Reviewing files that changed from the base of the PR and between 1d74fba and 39f9fb3.

📒 Files selected for processing (4)
  • src/main/java/org/example/knockin/auth/handler/OAuth2SuccessHandler.java
  • src/main/java/org/example/knockin/auth/repository/HttpCookieOAuth2AuthorizationRequestRepository.java
  • src/main/java/org/example/knockin/auth/util/CookieUtils.java
  • src/main/java/org/example/knockin/config/SecurityConfig.java

Comment on lines +23 to +29
public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}

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

Set Secure and SameSite attributes for cookies.

Cookies are added without the Secure flag. If the application is accessed over HTTPS, sensitive OAuth2 authorization and redirect cookies could be transmitted in plaintext over insecure connections.

Additionally, standard jakarta.servlet.http.Cookie lacks a straightforward way to set the SameSite attribute dynamically. Consider using Spring's ResponseCookie (for both adding and deleting cookies) to securely configure Secure, HttpOnly, and SameSite flags.

🛡️ Proposed fix using `ResponseCookie`
-    public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
-        Cookie cookie = new Cookie(name, value);
-        cookie.setPath("/");
-        cookie.setHttpOnly(true);
-        cookie.setMaxAge(maxAge);
-        response.addCookie(cookie);
-    }
+    public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
+        org.springframework.http.ResponseCookie cookie = org.springframework.http.ResponseCookie.from(name, value)
+                .path("/")
+                .httpOnly(true)
+                .secure(true) // Set dynamically based on environment if needed
+                .sameSite("Lax")
+                .maxAge(maxAge)
+                .build();
+        response.addHeader(org.springframework.http.HttpHeaders.SET_COOKIE, cookie.toString());
+    }

(Note: Apply a similar change to deleteCookie to ensure the clearing cookie is accepted by the browser when cross-origin configurations are in place.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}
public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
org.springframework.http.ResponseCookie cookie = org.springframework.http.ResponseCookie.from(name, value)
.path("/")
.httpOnly(true)
.secure(true) // Set dynamically based on environment if needed
.sameSite("Lax")
.maxAge(maxAge)
.build();
response.addHeader(org.springframework.http.HttpHeaders.SET_COOKIE, cookie.toString());
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 27-27: 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).

(cookie-missing-samesite-java)


[warning] 27-27: Ensure cookies have the secure flag
Context: response.addCookie(cookie)
Note: [CWE-614] Sensitive Cookie in HTTPS Session Without 'Secure' Attribute.

(cookies-secure-flag)


[warning] 27-27: 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 '.setSecure(true);'.
Context: response.addCookie(cookie);
Note: [CWE-614] Sensitive Cookie in HTTPS Session Without 'Secure' Attribute.

(cookie-missing-secure-flag-java)

🤖 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/org/example/knockin/auth/util/CookieUtils.java` around lines 23
- 29, Update CookieUtils.addCookie and deleteCookie to use Spring’s
ResponseCookie instead of jakarta.servlet.http.Cookie, configuring Secure,
HttpOnly, and SameSite attributes consistently. Ensure deletion uses matching
attributes and cookie path so browsers accept the clearing cookie in
cross-origin scenarios.

Source: Linters/SAST tools

Comment on lines +45 to +51
public static String serialize(Object object) {
return Base64.getUrlEncoder().encodeToString(SerializationUtils.serialize(object));
}

public static <T> T deserialize(Cookie cookie, Class<T> cls) {
return cls.cast(SerializationUtils.deserialize(Base64.getUrlDecoder().decode(cookie.getValue())));
}

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 | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for usages of SerializationUtils and verify Jackson ObjectMapper existence.
rg 'SerializationUtils\.deserialize'
rg 'ObjectMapper'

Repository: prography/11th-1team-BE

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and related auth utilities.
git ls-files 'src/main/java/org/example/knockin/auth/util/CookieUtils.java' \
  'src/main/java/org/example/knockin/auth/**' \
  'src/main/java/**/OAuth2AuthorizationRequest*' \
  'src/main/java/**/Cookie*' | sort

echo '--- CookieUtils outline ---'
ast-grep outline src/main/java/org/example/knockin/auth/util/CookieUtils.java --view expanded || true

echo '--- CookieUtils contents ---'
cat -n src/main/java/org/example/knockin/auth/util/CookieUtils.java

echo '--- Search for CookieUtils usages ---'
rg -n 'CookieUtils\.(serialize|deserialize)|deserialize\(cookie|serialize\(' src/main/java || true

echo '--- Search for OAuth2AuthorizationRequest serialization helpers ---'
rg -n 'OAuth2AuthorizationRequest|OAuth2ClientJackson2Module|Jackson2Module|ObjectMapper|SerializationUtils' src/main/java || true

Repository: prography/11th-1team-BE

Length of output: 10944


Avoid Java deserialization for the cookie payload. CookieUtils.deserialize() feeds attacker-controlled cookie data straight into SerializationUtils.deserialize, which exposes this OAuth2 flow to gadget-based deserialization attacks. Use a safe explicit format for OAuth2AuthorizationRequest or store only an opaque server-side ID in the cookie.

🤖 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/org/example/knockin/auth/util/CookieUtils.java` around lines 45
- 51, Replace Java serialization in CookieUtils.serialize and
CookieUtils.deserialize with a safe explicit representation for
OAuth2AuthorizationRequest, validating all decoded fields before reconstruction;
alternatively, store and resolve only an opaque server-side identifier. Ensure
deserialize never passes attacker-controlled cookie bytes to
SerializationUtils.deserialize.

@yourjinKR yourjinKR 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.

고생하셨습니다.

@Developer-Choi-Jae-Young
Developer-Choi-Jae-Young merged commit ecad13b into prography:main Jul 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feat 새로운 백엔드 기능 추가 (New backend feature)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OAuth2 로그인 후 Target URL 동적 리다이렉트 미지원 문제

2 participants