-
Notifications
You must be signed in to change notification settings - Fork 0
refactor(user): 토큰 전달 방식 수정 #54
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: "feature/#53-\uCE74\uCE74\uC624\uB85C\uADF8\uC778_\uB9AC\uD329\uD1A0\uB9C1"
Changes from 2 commits
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 |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
|
||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.Cookie; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
@@ -28,7 +29,6 @@ | |
| // 1. JWT 토큰 발급 | ||
| // - 이때, JWT payload는 보안상 최소한의 정보(userId, role)만 담겠다 | ||
| // 2. refreshToken만 DB에 저장 | ||
| // 3. JSON 응답으로, accessToken과 refreshToken 을 반환해준다. | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| @Slf4j | ||
|
|
@@ -40,42 +40,34 @@ public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHan | |
| public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, | ||
| Authentication authentication) throws IOException { | ||
|
|
||
| // 1. CustomOAuth2UserService에서 설정한 OAuth2User 정보 가져오기 | ||
| CustomOAuth2User customUserDetails = (CustomOAuth2User)authentication.getPrincipal(); | ||
|
|
||
| CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal(); | ||
| User user = customUserDetails.getUser(); | ||
| Long userId = customUserDetails.getUserId(); | ||
| String email = customUserDetails.getName(); | ||
|
|
||
| Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); | ||
| Iterator<? extends GrantedAuthority> iterator = authorities.iterator(); | ||
| GrantedAuthority auth = iterator.next(); | ||
|
|
||
| String role = auth.getAuthority(); | ||
| String role = authentication.getAuthorities().iterator().next().getAuthority(); | ||
|
|
||
| log.info("user, userId, email, role :: {} {} {} {}", user, userId, email, role); | ||
| // JWT 발급 | ||
| String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, 30 * 60 * 1000L); // 30분 | ||
| String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, 30L * 24 * 60 * 60 * 1000L); // 30일 | ||
|
|
||
| // 2. 1)의 사용자 정보를 담아, accessToken과 refreshToken 발행 | ||
| String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, 30 * 60 * 1000L); // 유효기간 30분 | ||
| String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, | ||
| 30 * 24 * 60 * 60 * 1000L); // 유효기간 30일 | ||
|
|
||
| // 3. refreshToken을 DB에 저장 | ||
| // 1. refreshToken을 DB에 저장 | ||
| Token refreshTokenEntity = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); | ||
| tokenRepository.save(refreshTokenEntity); | ||
|
|
||
| // 4. JSON 응답으로, accessToken과 refreshToken 을 반환해준다. | ||
| response.setContentType("application/json"); | ||
| response.setCharacterEncoding("utf-8"); | ||
|
|
||
| ObjectMapper objectMapper = new ObjectMapper(); // 객체 -> json 문자열로 변환 | ||
| String body = objectMapper.writeValueAsString( | ||
| Map.of( | ||
| "accessToken", accessToken, | ||
| "refreshToken", refreshToken | ||
| ) | ||
| ); | ||
| response.getWriter().write(body); | ||
| // 2. refreshToken을 HttpOnly 쿠키로 설정 | ||
| Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken); | ||
| refreshTokenCookie.setHttpOnly(true); // JS 접근 불가 | ||
| refreshTokenCookie.setSecure(false); // 운영환경 https라면 true로 변경 필요 | ||
| refreshTokenCookie.setPath("/"); | ||
| refreshTokenCookie.setMaxAge(30 * 24 * 60 * 60); // 30일 | ||
| // CSRF 방지를 위한 SameSite 속성 추가 (Servlet 3.1+ 지원시) | ||
| response.setHeader("Set-Cookie", | ||
| String.format("%s=%s; HttpOnly; Secure; Path=/; Max-Age=%d; SameSite=Lax", | ||
| "refreshToken", refreshToken, 30 * 24 * 60 * 60)); | ||
| response.addCookie(refreshTokenCookie); | ||
|
|
||
| // 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달) | ||
| String targetUrl = "http://localhost:5173/login/success?accessToken=" + accessToken; | ||
| response.sendRedirect(targetUrl); | ||
|
Comment on lines
+66
to
+68
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. 🛠️ Refactor suggestion 리다이렉트 URL 하드코딩 문제 해결 필요 현재 프론트엔드 URL이 하드코딩되어 있어서 개발/스테이징/운영 환경별로 다른 URL을 사용하기 어려울 것 같습니다. 또한 쿼리 파라미터로 access token을 전달하는 것도 보안상 고려사항이 있습니다. 다음과 같은 개선을 권장합니다: -// 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달)
-String targetUrl = "http://localhost:5173/login/success?accessToken=" + accessToken;
-response.sendRedirect(targetUrl);
+// 3. 프론트엔드로 리다이렉트 (환경별 URL 설정)
+String frontendUrl = getFrontendUrl(); // 환경별 설정에서 가져오기
+String targetUrl = frontendUrl + "/login/success";
+
+// Access token도 쿠키로 전달 (보안 강화)
+Cookie accessTokenCookie = new Cookie("accessToken", accessToken);
+accessTokenCookie.setHttpOnly(false); // JS에서 접근 가능해야 함
+accessTokenCookie.setSecure(true);
+accessTokenCookie.setPath("/");
+accessTokenCookie.setMaxAge(30 * 60); // 30분
+response.addCookie(accessTokenCookie);
+
+response.sendRedirect(targetUrl);환경별 URL 설정을 위해 application.yml에 설정값 추가도 고려해보세요: app:
frontend:
url: ${FRONTEND_URL:http://localhost:5173}🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| } | ||
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.
쿠키 보안 설정에 일관성 문제가 있습니다
쿠키 설정에서 보안 관련 설정이 서로 상충되고 있습니다. 59번 줄에서는
setSecure(false)로 설정하고 있지만, 64번 줄의 Set-Cookie 헤더에서는Secure플래그를 true로 설정하고 있어 혼란을 야기할 수 있습니다.다음과 같이 일관성 있게 수정해주세요:
Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken); refreshTokenCookie.setHttpOnly(true); -refreshTokenCookie.setSecure(false); // 운영환경 https라면 true로 변경 필요 +refreshTokenCookie.setSecure(true); // HTTPS 환경에서 보안 강화 refreshTokenCookie.setPath("/"); refreshTokenCookie.setMaxAge(30 * 24 * 60 * 60); -// CSRF 방지를 위한 SameSite 속성 추가 (Servlet 3.1+ 지원시) -response.setHeader("Set-Cookie", - String.format("%s=%s; HttpOnly; Secure; Path=/; Max-Age=%d; SameSite=Lax", - "refreshToken", refreshToken, 30 * 24 * 60 * 60)); response.addCookie(refreshTokenCookie); + +// SameSite 속성을 위한 추가 헤더 설정 +response.addHeader("Set-Cookie", + response.getHeader("Set-Cookie") + "; SameSite=Lax");📝 Committable suggestion
🧰 Tools
🪛 ast-grep (0.38.1)
[warning] 58-58: 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 'refreshTokenCookie.setSecure(true);'.
Context: refreshTokenCookie.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)
🤖 Prompt for AI Agents