Skip to content

Commit fb1cf55

Browse files
committed
[Hotfix] 소셜 로그린 콜백 설정
1 parent a9db916 commit fb1cf55

3 files changed

Lines changed: 116 additions & 25 deletions

File tree

devicelife-api/src/main/java/com/devicelife/devicelife_api/common/config/SecurityConfig.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.devicelife.devicelife_api.common.security.InternalTokenAuthFilter;
44
import com.devicelife.devicelife_api.common.security.JwtAuthFilter;
55
import com.devicelife.devicelife_api.common.security.JwtUtil;
6+
import com.devicelife.devicelife_api.common.security.oauth2.CustomAuthorizationRequestResolver;
67
import com.devicelife.devicelife_api.common.security.oauth2.OAuth2SuccessHandler;
78
import com.devicelife.devicelife_api.common.security.oauth2.OAuth2UserProviderRouter;
89
import com.devicelife.devicelife_api.service.auth.CustomUserDetailsService;
@@ -17,6 +18,11 @@
1718
import org.springframework.security.config.http.SessionCreationPolicy;
1819
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
1920
import org.springframework.security.crypto.password.PasswordEncoder;
21+
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
22+
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
23+
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
24+
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
25+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
2026
import org.springframework.security.web.SecurityFilterChain;
2127
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
2228
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -34,16 +40,28 @@ public class SecurityConfig {
3440
private final CustomUserDetailsService customUserDetailsService;
3541
private final OAuth2UserProviderRouter oAuth2UserProviderRouter;
3642
private final OAuth2SuccessHandler oAuth2SuccessHandler;
43+
private final ClientRegistrationRepository clientRegistrationRepository;
3744

3845
@Value("${INTERNAL_API_TOKEN}")
3946
private String internalApiToken;
4047

48+
@Bean
49+
public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() {
50+
return new HttpSessionOAuth2AuthorizationRequestRepository();
51+
}
52+
4153
@Bean
4254
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
55+
56+
DefaultOAuth2AuthorizationRequestResolver defaultResolver =
57+
new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, "/oauth2/authorization");
58+
CustomAuthorizationRequestResolver customResolver =
59+
new CustomAuthorizationRequestResolver(defaultResolver);
60+
4361
http
4462
.csrf(csrf -> csrf.disable())
4563
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
46-
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
64+
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
4765
.formLogin((auth) -> auth.disable())
4866
.httpBasic((auth) -> auth.disable())
4967
.addFilterBefore(internalTokenAuthFilter(), UsernamePasswordAuthenticationFilter.class)
@@ -85,6 +103,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
85103
}))
86104

87105
.oauth2Login(oauth2 -> oauth2
106+
.authorizationEndpoint(auth -> auth
107+
.authorizationRequestRepository(authorizationRequestRepository())
108+
.authorizationRequestResolver(customResolver)
109+
)
88110
.userInfoEndpoint(userInfo -> userInfo
89111
.userService(oAuth2UserProviderRouter) // 사용자 정보 받아오기
90112
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.devicelife.devicelife_api.common.security.oauth2;
2+
3+
import jakarta.servlet.http.HttpServletRequest;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
6+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
7+
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
11+
@RequiredArgsConstructor
12+
public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
13+
14+
private final OAuth2AuthorizationRequestResolver delegate;
15+
16+
@Override
17+
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
18+
OAuth2AuthorizationRequest authRequest = delegate.resolve(request);
19+
return customize(request, authRequest);
20+
}
21+
22+
@Override
23+
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
24+
OAuth2AuthorizationRequest authRequest = delegate.resolve(request, clientRegistrationId);
25+
return customize(request, authRequest);
26+
}
27+
28+
private OAuth2AuthorizationRequest customize(HttpServletRequest request, OAuth2AuthorizationRequest authRequest) {
29+
if (authRequest == null) return null;
30+
31+
String redirectUri = request.getParameter("redirect_uri");
32+
33+
// redirect_uri가 없으면 그대로(= 기본 흐름)
34+
if (redirectUri == null || redirectUri.isBlank()) {
35+
return authRequest;
36+
}
37+
38+
Map<String, Object> additional = new HashMap<>(authRequest.getAdditionalParameters());
39+
additional.put("redirect_uri", redirectUri);
40+
41+
return OAuth2AuthorizationRequest.from(authRequest)
42+
.additionalParameters(additional)
43+
.build();
44+
}
45+
}

devicelife-api/src/main/java/com/devicelife/devicelife_api/common/security/oauth2/OAuth2SuccessHandler.java

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
import lombok.RequiredArgsConstructor;
1717
import org.springframework.http.ResponseCookie;
1818
import org.springframework.security.core.Authentication;
19+
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
20+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
1921
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
2022
import org.springframework.stereotype.Component;
2123
import org.springframework.transaction.annotation.Propagation;
2224
import org.springframework.transaction.annotation.Transactional;
2325
import org.springframework.web.util.UriComponentsBuilder;
2426

2527
import java.io.IOException;
28+
import java.net.URI;
2629

2730
import static com.devicelife.devicelife_api.common.response.SuccessCode.*;
2831
import 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

Comments
 (0)