1616import lombok .RequiredArgsConstructor ;
1717import org .springframework .http .ResponseCookie ;
1818import org .springframework .security .core .Authentication ;
19+ import org .springframework .security .oauth2 .client .web .AuthorizationRequestRepository ;
20+ import org .springframework .security .oauth2 .core .endpoint .OAuth2AuthorizationRequest ;
1921import org .springframework .security .web .authentication .AuthenticationSuccessHandler ;
2022import org .springframework .stereotype .Component ;
2123import org .springframework .transaction .annotation .Propagation ;
2224import org .springframework .transaction .annotation .Transactional ;
2325import org .springframework .web .util .UriComponentsBuilder ;
2426
2527import java .io .IOException ;
28+ import java .net .URI ;
2629
2730import static com .devicelife .devicelife_api .common .response .SuccessCode .*;
2831import static org .springframework .http .HttpHeaders .SET_COOKIE ;
@@ -36,6 +39,8 @@ public class OAuth2SuccessHandler implements AuthenticationSuccessHandler {
3639 private final RefreshTokenRepository refreshTokenRepository ;
3740 private final SHA256 sha256 ;
3841
42+ private final AuthorizationRequestRepository <OAuth2AuthorizationRequest > authorizationRequestRepository ;
43+
3944 @ Override
4045 @ Transactional
4146 public void onAuthenticationSuccess (HttpServletRequest request ,
@@ -44,10 +49,6 @@ public void onAuthenticationSuccess(HttpServletRequest request,
4449 throws IOException {
4550 CustomUserDetails userDetails = (CustomUserDetails ) authentication .getPrincipal ();
4651
47- String resultStr = (String ) userDetails .getAttributes ().get ("oauth_result" );
48- OAuthResult result = (resultStr == null ) ? OAuthResult .EXISTING_SOCIAL : OAuthResult .valueOf (resultStr );
49-
50-
5152 User user = userDetails .getUser ();
5253 String accessToken = jwtUtil .createAccessToken (userDetails );
5354 String refreshToken = jwtUtil .createRefreshToken (userDetails );
@@ -57,48 +58,71 @@ public void onAuthenticationSuccess(HttpServletRequest request,
5758 .user (user )
5859 .build ());
5960
60- String redirectUri = request .getParameter ("redirect_uri" );
61+ OAuth2AuthorizationRequest authRequest =
62+ authorizationRequestRepository .removeAuthorizationRequest (request , response );
63+
64+ String redirectUri = null ;
65+ if (authRequest != null && authRequest .getAdditionalParameters () != null ) {
66+ Object v = authRequest .getAdditionalParameters ().get ("redirect_uri" );
67+ redirectUri = (v == null ) ? null : String .valueOf (v );
68+ }
6169 String target = resolveTarget (redirectUri );
6270
63- // 로컬 여부 판단(redirect_uri 기준)
64- boolean isLocal = target .startsWith ("http://localhost:5173" );
71+ boolean isLocal = isLocalTarget (target );
72+
73+ ResponseCookie refreshCookie = buildRefreshCookie (refreshToken , isLocal );
74+
75+ response .addHeader (SET_COOKIE , refreshCookie .toString ());
76+ response .sendRedirect (target );
77+ }
6578
66- ResponseCookie refreshCookie ;
79+ private boolean isLocalTarget (String target ) {
80+ try {
81+ URI uri = URI .create (target );
82+ String host = uri .getHost ();
83+ int port = uri .getPort ();
84+
85+ // localhost:5173 / 127.0.0.1:5173 둘 다 허용
86+ boolean isLocalHost = "localhost" .equalsIgnoreCase (host ) || "127.0.0.1" .equals (host );
87+ return isLocalHost && port == 5173 ;
88+ } catch (Exception e ) {
89+ return false ;
90+ }
91+ }
92+
93+ private ResponseCookie buildRefreshCookie (String refreshToken , boolean isLocal ) {
6794 if (isLocal ) {
68- // 로컬(http) 개발용: SameSite=Lax + Secure=false + Domain 미설정
69- refreshCookie = ResponseCookie .from ("refreshToken" , refreshToken )
95+ // 로컬(http) 개발용
96+ return ResponseCookie .from ("refreshToken" , refreshToken )
7097 .httpOnly (true )
7198 .secure (false )
7299 .sameSite ("Lax" )
73100 .path ("/" )
74101 .maxAge (60L * 60 * 24 * 30 ) // 30일
75102 .build ();
76- } else {
77- // 운영(https)용: SameSite=None + Secure=true + Domain 설정
78- refreshCookie = ResponseCookie .from ("refreshToken" , refreshToken )
79- .httpOnly (true )
80- .secure (true )
81- .sameSite ("None" )
82- .domain (".devicelife.site" )
83- .path ("/" )
84- .maxAge (60L * 60 * 24 * 30 ) // 30일
85- .build ();
86103 }
87104
88- response .sendRedirect (target );
105+ // 운영(https)용
106+ return ResponseCookie .from ("refreshToken" , refreshToken )
107+ .httpOnly (true )
108+ .secure (true )
109+ .sameSite ("None" )
110+ .domain (".devicelife.site" )
111+ .path ("/" )
112+ .maxAge (60L * 60 * 24 * 30 ) // 30일
113+ .build ();
89114 }
90115
91116 private String resolveTarget (String redirectUri ) {
92- // 기본값: 운영 프론트
93117 String defaultTarget = "https://devicelife.site/auth/callback/google" ;
94118
95119 if (redirectUri == null || redirectUri .isBlank ()) {
96120 return defaultTarget ;
97121 }
98122
99- // 허용된 프론트만 리다이렉트
100123 if (redirectUri .startsWith ("https://devicelife.site" )
101- || redirectUri .startsWith ("http://localhost:5173" )) {
124+ || redirectUri .startsWith ("http://localhost:5173" )
125+ || redirectUri .startsWith ("http://127.0.0.1:5173" )) {
102126 return redirectUri ;
103127 }
104128
0 commit comments