-
Notifications
You must be signed in to change notification settings - Fork 2
[BE] Sisc1 209 [FIX] redirect URL을 환경에 따라 자동 분기 #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "SISC1-209-BE-redirect-url-\uD504\uB860\uD2B8\uB85C-\uC804\uB2EC"
Changes from all commits
f3f0d50
39ac3ff
7cd0180
b21edaa
eba8b21
7c55496
e88792b
bb7bf44
d5c3f22
a4e0e0f
5b4c1ae
e0e1530
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,13 +3,15 @@ | |
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.core.env.Environment; | ||
| import org.sejongisc.backend.auth.dao.UserOauthAccountRepository; | ||
| import org.sejongisc.backend.auth.entity.AuthProvider; | ||
| import org.sejongisc.backend.auth.entity.UserOauthAccount; | ||
| import org.sejongisc.backend.common.auth.jwt.JwtProvider; | ||
| import org.sejongisc.backend.auth.service.RefreshTokenService; | ||
| import org.sejongisc.backend.user.dao.UserRepository; | ||
| import org.sejongisc.backend.user.entity.User; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.http.HttpHeaders; | ||
| import org.springframework.http.ResponseCookie; | ||
| import org.springframework.security.core.Authentication; | ||
|
|
@@ -26,6 +28,7 @@ | |
| import java.io.IOException; | ||
| import java.net.URLEncoder; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.Arrays; | ||
| import java.util.Map; | ||
| import java.util.UUID; | ||
|
|
||
|
|
@@ -38,6 +41,10 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler | |
| private final RefreshTokenService refreshTokenService; | ||
| private final UserRepository userRepository; | ||
| private final UserOauthAccountRepository userOauthAccountRepository; | ||
| private final Environment env; | ||
|
|
||
| @Value("${app.oauth2.redirect-success}") | ||
| private String redirectSuccessBase; | ||
|
|
||
| @Override | ||
| public void onAuthenticationSuccess( | ||
|
|
@@ -94,36 +101,46 @@ public void onAuthenticationSuccess( | |
| // 5. RefreshToken 저장(DB or Redis) | ||
| refreshTokenService.saveOrUpdateToken(user.getUserId(), refreshToken); | ||
|
|
||
| boolean isProd = Arrays.asList(env.getActiveProfiles()).contains("prod"); | ||
|
|
||
| String sameSite = isProd ? "None" : "Lax"; | ||
| boolean secure = isProd; | ||
| String domain = isProd ? "sisc-web.duckdns.org" : "localhost"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 도메인을 하드코딩하지 말고 설정으로 외부화하세요. 도메인 예시: + @Value("${app.oauth2.cookie-domain:localhost}")
+ private String cookieDomain;
+
@Override
public void onAuthenticationSuccess(...) {
- String domain = isProd ? "sisc-web.duckdns.org" : "localhost";
+ String domain = isProd ? cookieDomain : "localhost";또는 환경별 설정 파일에서: # application-prod.yml
app:
oauth2:
cookie-domain: sisc-web.duckdns.org🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| // 6. HttpOnly 쿠키로 refreshToken 저장 | ||
| ResponseCookie accessCookie = ResponseCookie.from("access", accessToken) | ||
| .httpOnly(true) | ||
| .secure(false) // 로컬 개발 | ||
| .sameSite("Lax") // 로컬에서는 None 비추천 | ||
| .secure(secure) // 로컬=false, 배포=true | ||
| .sameSite(sameSite) // 로컬= "Lax", 배포="None" | ||
| .path("/") | ||
| .maxAge(60L * 60) // 1 hour | ||
| .build(); | ||
|
|
||
| ResponseCookie refreshCookie = ResponseCookie.from("refresh", refreshToken) | ||
| .httpOnly(true) | ||
| .secure(false) | ||
| .sameSite("Lax") | ||
| .secure(secure) | ||
| .sameSite(sameSite) | ||
| .path("/") | ||
| .maxAge(60L * 60 * 24 * 14) // 2 weeks | ||
| .build(); | ||
|
Comment on lines
112
to
126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 쿠키에 domain 속성이 설정되지 않았습니다. Line 108에서 다음 diff를 적용하여 수정하세요: ResponseCookie accessCookie = ResponseCookie.from("access", accessToken)
.httpOnly(true)
.secure(secure) // 로컬=false, 배포=true
.sameSite(sameSite) // 로컬= "Lax", 배포="None"
.path("/")
+ .domain(domain)
.maxAge(60L * 60) // 1 hour
.build();
ResponseCookie refreshCookie = ResponseCookie.from("refresh", refreshToken)
.httpOnly(true)
.secure(secure)
.sameSite(sameSite)
.path("/")
+ .domain(domain)
.maxAge(60L * 60 * 24 * 14) // 2 weeks
.build();🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString()); | ||
| response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); | ||
|
|
||
|
|
||
| // 7. 프론트로 redirect | ||
| String redirectUrl = "http://localhost:5173/oauth/success"; | ||
| // application-local.yml → http://localhost:5173/oauth/success | ||
| // application-prod.yml → https://sisc-web.duckdns.org/oauth/success | ||
| //String redirectUrl = redirectSuccessBase; | ||
| // + "?accessToken=" + accessToken | ||
| // + "&name=" + URLEncoder.encode(name, StandardCharsets.UTF_8) | ||
| // + "&userId=" + userId; | ||
|
|
||
| // log.info("[OAuth2 Redirect] {}", redirectUrl); | ||
|
|
||
| getRedirectStrategy().sendRedirect(request, response, redirectUrl); | ||
| getRedirectStrategy().sendRedirect(request, response, redirectSuccessBase); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| import io.jsonwebtoken.JwtException; | ||
| import jakarta.servlet.FilterChain; | ||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.Cookie; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
@@ -40,15 +41,15 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { | |
| "/api/auth/signup", | ||
| "/api/auth/login", | ||
| "/api/auth/login/**", | ||
| "/api/auth/oauth", | ||
| "/api/auth/oauth/**", | ||
| "/api/auth/logout", | ||
| "/api/auth/reissue", | ||
| "/v3/api-docs/**", | ||
| "/swagger-ui/**", | ||
| "/swagger-ui/index.html", | ||
| "/swagger-resources/**", | ||
| "/webjars/**" | ||
| "/webjars/**", | ||
| "/login/**", | ||
| "/oauth2/**" | ||
| ); | ||
|
Comment on lines
+50
to
53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 쿠키 기반 access 토큰 허용에 따른 CSRF 리스크 점검 필요 이제 Authorization 헤더가 없을 때
이 조합이면 공격자가 별도 토큰 탈취 없이도 사용자의 브라우저를 이용해 상태 변경 요청(POST/PUT/DELETE 등)을 보내도록 유도하는 전형적인 CSRF 공격 가능성이 생깁니다. 옵션 예시입니다.
현재 변경은 보안 모델을 꽤 크게 바꾸는 부분이라, 실제 운영 전 보안 관점에서 한 번 더 논의/검증하는 것을 강하게 추천드립니다. Also applies to: 77-80, 130-141 🤖 Prompt for AI Agents |
||
|
|
||
| @Override | ||
|
|
@@ -73,6 +74,10 @@ protected void doFilterInternal(@NotNull HttpServletRequest request, | |
| try { | ||
| String token = resolveToken(request); | ||
|
|
||
| if (token == null) { | ||
| token = resolveTokenFromCookie(request); | ||
| } | ||
|
|
||
| if (token != null && jwtParser.validationToken(token) ) { | ||
| UsernamePasswordAuthenticationToken authentication = jwtParser.getAuthentication(token); | ||
| SecurityContextHolder.getContext().setAuthentication(authentication); | ||
|
|
@@ -122,6 +127,19 @@ private String resolveToken(HttpServletRequest request) { | |
| return null; | ||
| } | ||
|
|
||
| private String resolveTokenFromCookie(HttpServletRequest request) { | ||
| if (request.getCookies() == null) return null; | ||
|
|
||
| for (Cookie cookie : request.getCookies()) { | ||
| if ("access".equals(cookie.getName())) { | ||
| log.info("쿠키에서 access token 추출됨"); | ||
| return cookie.getValue(); | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private String toJson(ErrorResponse errorResponse) throws JsonProcessingException { | ||
| ObjectMapper mapper = new ObjectMapper(); | ||
| mapper.registerModule(new JavaTimeModule()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
환경별 설정을 위한 필드 추가를 확인하세요.
Environment와@Value주입이 적절하게 추가되었습니다. 다만app.oauth2.redirect-success속성이 모든 환경 설정 파일(application-local.yml, application-prod.yml 등)에 정의되어 있는지 확인이 필요합니다.다음 스크립트로 속성 정의를 확인하세요:
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 5925
🏁 Script executed:
Length of output: 198
🏁 Script executed:
Length of output: 96
🏁 Script executed:
Length of output: 31566
🏁 Script executed:
Length of output: 108
app.oauth2.redirect-success속성이 설정 파일에 정의되지 않았습니다. 애플리케이션 시작 실패를 유발합니다.backend/src/main/resources/application.yml에app.oauth2.redirect-success속성이 정의되어 있지 않습니다. Spring의@Value주입은 해당 속성이 존재하지 않을 경우 시작 시점에BeanCreationException을 발생시켜 애플리케이션이 구동되지 않습니다.application.yml에 다음을 추가하세요:🤖 Prompt for AI Agents