Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/main/java/com/mycom/socket/auth/config/JWTProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
@ConfigurationProperties(prefix = "jwt")
public class JWTProperties {
private String secret;
private long accessTokenValidityInSeconds = 1800;
private String cookieName = "Authorization";
private long accessTokenValidityInSeconds;
private long refreshTokenValidityInSeconds;
private String accessTokenCookieName;
private String refreshTokenCookieName;
Comment on lines +13 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

ν•„μˆ˜ 속성에 λŒ€ν•œ μœ νš¨μ„± 검증 μΆ”κ°€ ν•„μš”

토큰 유효 κΈ°κ°„κ³Ό μΏ ν‚€ 이름은 ν•„μˆ˜ κ°’μ΄λ―€λ‘œ, 이에 λŒ€ν•œ μœ νš¨μ„± 검증이 ν•„μš”ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Positive;

 @Getter
 @Setter
 @Component
 @ConfigurationProperties(prefix = "jwt")
 public class JWTProperties {
     private String secret;
-    private long accessTokenValidityInSeconds;
-    private long refreshTokenValidityInSeconds;
-    private String accessTokenCookieName;
-    private String refreshTokenCookieName;
+    @Positive(message = "μ•‘μ„ΈμŠ€ 토큰 유효 기간은 μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€")
+    private long accessTokenValidityInSeconds;
+    @Positive(message = "λ¦¬ν”„λ ˆμ‹œ 토큰 유효 기간은 μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€")
+    private long refreshTokenValidityInSeconds;
+    @NotBlank(message = "μ•‘μ„ΈμŠ€ 토큰 μΏ ν‚€ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€")
+    private String accessTokenCookieName;
+    @NotBlank(message = "λ¦¬ν”„λ ˆμ‹œ 토큰 μΏ ν‚€ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€")
+    private String refreshTokenCookieName;
     private String issuer = "go_socket";
     private boolean secureCookie = false;
 }
πŸ“ 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
private long accessTokenValidityInSeconds;
private long refreshTokenValidityInSeconds;
private String accessTokenCookieName;
private String refreshTokenCookieName;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "jwt")
public class JWTProperties {
private String secret;
@Positive(message = "μ•‘μ„ΈμŠ€ 토큰 유효 기간은 μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€")
private long accessTokenValidityInSeconds;
@Positive(message = "λ¦¬ν”„λ ˆμ‹œ 토큰 유효 기간은 μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€")
private long refreshTokenValidityInSeconds;
@NotBlank(message = "μ•‘μ„ΈμŠ€ 토큰 μΏ ν‚€ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€")
private String accessTokenCookieName;
@NotBlank(message = "λ¦¬ν”„λ ˆμ‹œ 토큰 μΏ ν‚€ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€")
private String refreshTokenCookieName;
private String issuer = "go_socket";
private boolean secureCookie = false;
}

private String issuer = "go_socket";
private boolean secureCookie = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.mycom.socket.auth.controller;

import com.mycom.socket.auth.config.JWTProperties;
import com.mycom.socket.auth.dto.response.TokenResponse;
import com.mycom.socket.auth.jwt.JWTUtil;
import com.mycom.socket.auth.security.CookieUtil;
import com.mycom.socket.global.exception.BadRequestException;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class RefreshController {

private final JWTUtil jwtUtil;
private final CookieUtil cookieUtil;
private final JWTProperties jwtProperties;

@PostMapping("/refresh")
public TokenResponse refreshAccessToken(HttpServletRequest request, HttpServletResponse response) {
String refreshToken = extractRefreshToken(request)
.orElseThrow(() -> new BadRequestException("λ¦¬ν”„λ ˆμ‹œ 토큰이 μ—†μŠ΅λ‹ˆλ‹€. λ‹€μ‹œ λ‘œκ·ΈμΈν•΄μ£Όμ„Έμš”."));

try {
if (!jwtUtil.validateToken(refreshToken)) {
throw new JwtException("Invalid refresh token");
}

String email = jwtUtil.getEmail(refreshToken);
String newAccessToken = jwtUtil.createToken(email, jwtProperties.getAccessTokenValidityInSeconds());
String newRefreshToken = jwtUtil.createToken(email, jwtProperties.getRefreshTokenValidityInSeconds());

response.addCookie(cookieUtil.createAuthCookie(newAccessToken));
response.addCookie(cookieUtil.createRefreshCookie(newRefreshToken));

return TokenResponse.of(newAccessToken);
} catch (JwtException e) {
response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getRefreshTokenCookieName()));
throw new BadRequestException("μœ νš¨ν•˜μ§€ μ•Šμ€ λ¦¬ν”„λ ˆμ‹œ ν† ν°μž…λ‹ˆλ‹€. λ‹€μ‹œ λ‘œκ·ΈμΈν•΄μ£Όμ„Έμš”.");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

λ³΄μ•ˆ κ°œμ„ μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

λ‹€μŒ λ³΄μ•ˆ κ΄€λ ¨ 사항듀을 κ³ λ €ν•΄μ£Όμ„Έμš”:

  1. CSRF λ³΄ν˜Έκ°€ ν•„μš”ν•©λ‹ˆλ‹€. @CrossOrigin μ„€μ •μ΄λ‚˜ CSRF 토큰 검증을 μΆ”κ°€ν•˜μ„Έμš”.
  2. 토큰 μž¬λ°œκΈ‰ μ—”λ“œν¬μΈνŠΈμ— λŒ€ν•œ rate limiting이 ν•„μš”ν•©λ‹ˆλ‹€.
  3. 토큰 μž¬λ°œκΈ‰ μ‹œλ„ μ‹€νŒ¨μ— λŒ€ν•œ 감사(audit) λ‘œκΉ…μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 rate limiting을 μΆ”κ°€ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

+    @RateLimiter(name = "refreshToken", fallbackMethod = "refreshTokenFallback")
     @PostMapping("/refresh")
     public TokenResponse refreshAccessToken(...)

그리고 μ‹€νŒ¨ν•œ μ‹œλ„μ— λŒ€ν•œ λ‘œκΉ…μ„ μΆ”κ°€ν•˜μ„Έμš”:

     } catch (JwtException e) {
+        log.warn("Failed refresh token attempt for IP: {}", request.getRemoteAddr());
         response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getRefreshTokenCookieName()));
         throw new BadRequestException("μœ νš¨ν•˜μ§€ μ•Šμ€ λ¦¬ν”„λ ˆμ‹œ ν† ν°μž…λ‹ˆλ‹€. λ‹€μ‹œ λ‘œκ·ΈμΈν•΄μ£Όμ„Έμš”.");
     }

Committable suggestion skipped: line range outside the PR's diff.


private Optional<String> extractRefreshToken(HttpServletRequest request) {
if (request.getCookies() == null) {
return Optional.empty();
}

return Arrays.stream(request.getCookies())
.filter(cookie -> jwtProperties.getRefreshTokenCookieName().equals(cookie.getName()))
.map(Cookie::getValue)
.findFirst();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mycom.socket.auth.dto.response;

public record TokenResponse(
String accessToken
) {
public static TokenResponse of(String accessToken) {
return new TokenResponse(accessToken);
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private String resolveTokenFromCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (jwtProperties.getCookieName().equals(cookie.getName())) {
if (jwtProperties.getAccessTokenCookieName().equals(cookie.getName())) {
return cookie.getValue();
}
}
Expand Down
99 changes: 81 additions & 18 deletions src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.mycom.socket.auth.jwt;

import com.mycom.socket.auth.config.JWTProperties;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -18,53 +21,113 @@ public class JWTUtil {
private final SecretKey secretKey;
private final JWTProperties jwtProperties;

/**
* JWTUtil μƒμ„±μž
* μ„€μ •λœ μ‹œν¬λ¦Ώ ν‚€λ₯Ό λ°”νƒ•μœΌλ‘œ HMAC-SHA μ•Œκ³ λ¦¬μ¦˜μš© SecretKeyλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
*
* @param jwtProperties JWT κ΄€λ ¨ 섀정값을 λ‹΄κ³  μžˆλŠ” ν”„λ‘œνΌν‹° 객체
*/
public JWTUtil(JWTProperties jwtProperties) {
this.jwtProperties = jwtProperties;
this.secretKey = Keys.hmacShaKeyFor(
jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8)
);
}

/**
* JWT Parser 생성
* 토큰 검증 및 정보 μΆ”μΆœμ— μ‚¬μš©λ˜λŠ” 곡톡 Parserλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
*
* @return μ„€μ •λœ JWT Parser 객체
*/
private JwtParser createParser() {
return Jwts.parser()
.verifyWith(secretKey)
.requireIssuer(jwtProperties.getIssuer())
.build();
}


/**
* JWT 토큰 생성
* μ£Όμ–΄μ§„ 이메일과 μœ νš¨κΈ°κ°„μœΌλ‘œ μƒˆλ‘œμš΄ JWTλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
*
* @param email 토큰에 포함될 μ‚¬μš©μž 이메일
* @param validityInSeconds 토큰 유효 κΈ°κ°„ (초)
* @return μƒμ„±λœ JWT λ¬Έμžμ—΄
* @throws IllegalStateException 토큰 생성 쀑 였λ₯˜ λ°œμƒ μ‹œ
*/
public String createToken(String email) {
public String createToken(String email, long validityInSeconds) {
Date now = new Date();
Date validity = new Date(now.getTime() +
(jwtProperties.getAccessTokenValidityInSeconds() * 1000));
Date validity = new Date(now.getTime() + (validityInSeconds * 1000));

return Jwts.builder()
.issuer(jwtProperties.getIssuer())
.subject(email)
.issuedAt(now)
.expiration(validity)
.signWith(secretKey)
.compact();
try {
return Jwts.builder()
.issuer(jwtProperties.getIssuer())
.subject(email)
.issuedAt(now)
.expiration(validity)
.signWith(secretKey)
.compact();
} catch (JwtException e) {
log.error("토큰 생성 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", e);
throw new IllegalStateException("토큰 생성 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", e);
}
}

/**
* 토큰 μœ νš¨μ„± 검증
* μ£Όμ–΄μ§„ 토큰이 μœ νš¨ν•œμ§€ κ²€μ‚¬ν•©λ‹ˆλ‹€. ν† ν°μ˜ μ„œλͺ…, 만료 μ—¬λΆ€, λ°œκΈ‰μž 등을 ν™•μΈν•©λ‹ˆλ‹€.
*
* @param token 검증할 JWT λ¬Έμžμ—΄
* @return 토큰이 μœ νš¨ν•˜λ©΄ true, κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄ false
*/
public boolean validateToken(String token) {
try {
if (!StringUtils.hasText(token)) {
return false;
}
if (!StringUtils.hasText(token)) {
return false;
}

try {
Jwts.parser()
.verifyWith(secretKey)
.requireIssuer(jwtProperties.getIssuer())
.build()
.parseSignedClaims(token);
return true;
} catch (Exception e) {
log.warn("JWT 토큰 검증 μ‹€νŒ¨", e);

Date expiration = getExpirationFromToken(token);
return new Date().before(expiration);
} catch (ExpiredJwtException e) {
log.warn("만료된 JWT ν† ν°μž…λ‹ˆλ‹€.");
return false;
} catch (JwtException e) {
log.warn("μœ νš¨ν•˜μ§€ μ•Šμ€ JWT ν† ν°μž…λ‹ˆλ‹€.", e);
return false;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

토큰 검증 둜직 κ°•ν™”κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

ν˜„μž¬ κ΅¬ν˜„μ€ 기본적인 κ²€μ¦λ§Œ μˆ˜ν–‰ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ‹€μŒ 검증 ν•­λͺ©λ“€μ„ μΆ”κ°€ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€:

  1. 토큰 νƒ€μž… 검증
  2. NBF (Not Before) ν΄λ ˆμž„ 검증
  3. 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ 확인

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

     public boolean validateToken(String token) {
         try {
-            Jwts.parser()
+            var claims = Jwts.parser()
                     .verifyWith(secretKey)
                     .requireIssuer(jwtProperties.getIssuer())
                     .build()
-                    .parseSignedClaims(token);
+                    .parseSignedClaims(token)
+                    .getPayload();
+
+            // 토큰 νƒ€μž… 검증
+            String tokenType = claims.get("type", String.class);
+            if (tokenType == null) {
+                return false;
+            }
+
+            // NBF 검증
+            Date nbf = claims.getNotBefore();
+            if (nbf != null && nbf.after(new Date())) {
+                return false;
+            }

Committable suggestion skipped: line range outside the PR's diff.


/**
* ν† ν°μ—μ„œ 이메일 μΆ”μΆœ
* ν† ν°μ˜ 만료 μ‹œκ°„ μΆ”μΆœ
*
* @param token JWT λ¬Έμžμ—΄
* @return ν† ν°μ˜ 만료 μ‹œκ°„
*/
private Date getExpirationFromToken(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload()
.getExpiration();
}


/**
* ν† ν°μ—μ„œ μ‚¬μš©μž 이메일 μΆ”μΆœ
* JWT의 subject ν΄λ ˆμž„μ—μ„œ μ‚¬μš©μž 이메일을 μΆ”μΆœν•©λ‹ˆλ‹€.
*
* @param token JWT λ¬Έμžμ—΄
* @return 토큰에 ν¬ν•¨λœ μ‚¬μš©μž 이메일
* @throws IllegalStateException ν† ν°μ—μ„œ 이메일을 μΆ”μΆœν•  수 μ—†λŠ” 경우
*/
public String getEmail(String token) {
return Jwts.parser()
Expand Down
64 changes: 51 additions & 13 deletions src/main/java/com/mycom/socket/auth/security/CookieUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,64 @@ public class CookieUtil {
private final JWTProperties jwtProperties;

/**
* 인증 μΏ ν‚€ 생성
* 곡톡 μΏ ν‚€ 생성 λ©”μ†Œλ“œ
* λͺ¨λ“  μ’…λ₯˜μ˜ μΏ ν‚€ 생성에 μ‚¬μš©λ˜λŠ” κΈ°λ³Έ λ©”μ†Œλ“œμž…λ‹ˆλ‹€.
*
* @param name μΏ ν‚€μ˜ 이름
* @param value 쿠킀에 μ €μž₯될 κ°’ (토큰)
* @param maxAge μΏ ν‚€μ˜ 유효 μ‹œκ°„ (초 λ‹¨μœ„)
* @param secure HTTPS ν”„λ‘œν† μ½œμ—μ„œλ§Œ 전솑 μ—¬λΆ€
* @return μƒμ„±λœ μΏ ν‚€ 객체
*/
public Cookie createAuthCookie(String token) {
Cookie cookie = new Cookie(jwtProperties.getCookieName(), token);
private Cookie createCookie(String name, String value, long maxAge, boolean secure) {
Cookie cookie = new Cookie(name, value);
cookie.setHttpOnly(true);
cookie.setSecure(jwtProperties.isSecureCookie());
cookie.setSecure(secure);
cookie.setPath("/");
cookie.setMaxAge((int) jwtProperties.getAccessTokenValidityInSeconds());
cookie.setMaxAge((int) maxAge);
return cookie;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

λ³΄μ•ˆ κ°•ν™”λ₯Ό μœ„ν•œ SameSite 속성 μΆ”κ°€ ν•„μš”

μΏ ν‚€ 생성 μ‹œ CSRF 곡격 λ°©μ§€λ₯Ό μœ„ν•΄ SameSite 속성을 μΆ”κ°€ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

    private Cookie createCookie(String name, String value, long maxAge, boolean secure) {
        Cookie cookie = new Cookie(name, value);
        cookie.setHttpOnly(true);
        cookie.setSecure(secure);
        cookie.setPath("/");
        cookie.setMaxAge((int) maxAge);
+       cookie.setAttribute("SameSite", "Strict");
        return cookie;
    }
πŸ“ 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
private Cookie createCookie(String name, String value, long maxAge, boolean secure) {
Cookie cookie = new Cookie(name, value);
cookie.setHttpOnly(true);
cookie.setSecure(jwtProperties.isSecureCookie());
cookie.setSecure(secure);
cookie.setPath("/");
cookie.setMaxAge((int) jwtProperties.getAccessTokenValidityInSeconds());
cookie.setMaxAge((int) maxAge);
return cookie;
}
private Cookie createCookie(String name, String value, long maxAge, boolean secure) {
Cookie cookie = new Cookie(name, value);
cookie.setHttpOnly(true);
cookie.setSecure(secure);
cookie.setPath("/");
cookie.setMaxAge((int) maxAge);
cookie.setAttribute("SameSite", "Strict");
return cookie;
}


/**
* 인증 μΏ ν‚€ 만료 처리
* Access Token을 μ €μž₯ν•˜λŠ” μΏ ν‚€ 생성
* ν΄λΌμ΄μ–ΈνŠΈ 인증에 μ‚¬μš©λ˜λŠ” Access Token을 쿠킀에 μ €μž₯ν•©λ‹ˆλ‹€.
*
* @param token JWT Access Token λ¬Έμžμ—΄
* @return Access Token이 μ €μž₯된 μΏ ν‚€
*/
public Cookie createExpiredAuthCookie() {
Cookie cookie = new Cookie(jwtProperties.getCookieName(), null);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setMaxAge(0); // μ¦‰μ‹œ 만료
return cookie;
public Cookie createAuthCookie(String token) {
return createCookie(
jwtProperties.getAccessTokenCookieName(),
token,
jwtProperties.getAccessTokenValidityInSeconds(),
jwtProperties.isSecureCookie()
);
}

/**
* Refresh Token을 μ €μž₯ν•˜λŠ” μΏ ν‚€ 생성
* Access Token μž¬λ°œκΈ‰μ— μ‚¬μš©λ˜λŠ” Refresh Token을 쿠킀에 μ €μž₯ν•©λ‹ˆλ‹€.
*
* @param token JWT Refresh Token λ¬Έμžμ—΄
* @return Refresh Token이 μ €μž₯된 μΏ ν‚€
*/
public Cookie createRefreshCookie(String token) {
return createCookie(
jwtProperties.getRefreshTokenCookieName(),
token,
jwtProperties.getRefreshTokenValidityInSeconds(),
jwtProperties.isSecureCookie()
);
}
Comment on lines +49 to +63
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codebase verification

토큰 μœ νš¨κΈ°κ°„ 검증 둜직 κ΅¬ν˜„ λ°©μ•ˆ

λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ 유효 기간이 μ•‘μ„ΈμŠ€ 토큰보닀 길도둝 보μž₯ν•˜κΈ° μœ„ν•΄ λ‹€μŒκ³Ό 같은 검증 둜직 μΆ”κ°€λ₯Ό μ œμ•ˆλ“œλ¦½λ‹ˆλ‹€:

  • @PostConstructλ₯Ό μ‚¬μš©ν•˜μ—¬ μ΄ˆκΈ°ν™” μ‹œμ μ— 토큰 μœ νš¨κΈ°κ°„μ„ 검증
  • Bean Validation을 ν™œμš©ν•˜μ—¬ 각 ν•„λ“œμ˜ μ΅œμ†Œκ°’ 검증 (@Min)
  • refreshTokenValidityInSecondsκ°€ accessTokenValidityInSeconds보닀 큰지 ν™•μΈν•˜λŠ” μ»€μŠ€ν…€ μœ νš¨μ„± 검사 μΆ”κ°€
πŸ”— Analysis chain

λ¦¬ν”„λ ˆμ‹œ 토큰 유효 κΈ°κ°„ 검증 μΆ”κ°€ κ³ λ €

λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ 유효 기간이 μ•‘μ„ΈμŠ€ 토큰보닀 길게 μ„€μ •λ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜λŠ” 검증 둜직 μΆ”κ°€λ₯Ό κ³ λ €ν•΄λ³΄μ„Έμš”.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# JWTPropertiesμ—μ„œ 토큰 유효 κΈ°κ°„ 섀정을 확인
rg "tokenValidityInSeconds|refreshTokenValidityInSeconds" -A 2

Length of output: 383


Script:

#!/bin/bash
# Get complete JWTProperties class
cat src/main/java/com/mycom/socket/auth/config/JWTProperties.java

# Check application properties/yaml files
fd -e yml -e yaml -e properties --exec cat {}

Length of output: 1162


/**
* 만료된 μΏ ν‚€ 생성
* λ‘œκ·Έμ•„μ›ƒ λ˜λŠ” 토큰 λ¬΄νš¨ν™” μ‹œ κΈ°μ‘΄ μΏ ν‚€λ₯Ό λ§Œλ£Œμ‹œν‚€κΈ° μœ„ν•΄ μ‚¬μš©λ©λ‹ˆλ‹€.
*
* @param name λ§Œλ£Œμ‹œν‚¬ μΏ ν‚€μ˜ 이름
* @return μ¦‰μ‹œ λ§Œλ£Œλ˜λ„λ‘ μ„€μ •λœ μΏ ν‚€
*/
public Cookie createExpiredCookie(String name) {
return createCookie(name, null, 0, true);
}
}
18 changes: 15 additions & 3 deletions src/main/java/com/mycom/socket/auth/security/LoginFilter.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mycom.socket.auth.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mycom.socket.auth.config.JWTProperties;
import com.mycom.socket.auth.jwt.JWTUtil;
import com.mycom.socket.global.dto.ApiResponse;
import com.mycom.socket.auth.dto.request.LoginRequest;
Expand Down Expand Up @@ -28,6 +29,7 @@ public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final CookieUtil cookieUtil;
private final ObjectMapper objectMapper;
private final JWTProperties jwtProperties;

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
Expand All @@ -51,11 +53,21 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR
MemberDetails memberDetails = (MemberDetails) authResult.getPrincipal();
Member member = memberDetails.getMember();

String token = jwtUtil.createToken(member.getEmail());
// JWT 토큰 생성
String accessToken = jwtUtil.createToken(
member.getEmail(),
jwtProperties.getAccessTokenValidityInSeconds()
);
String refreshToken = jwtUtil.createToken(
member.getEmail(),
jwtProperties.getRefreshTokenValidityInSeconds()
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

토큰 λ³΄μ•ˆ κ°•ν™”κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

ν˜„μž¬ μ•‘μ„ΈμŠ€ 토큰과 λ¦¬ν”„λ ˆμ‹œ 토큰이 λ™μΌν•œ ν΄λ ˆμž„μ„ μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ³΄μ•ˆ κ°•ν™”λ₯Ό μœ„ν•΄ λ‹€μŒ 사항듀을 κ³ λ €ν•΄μ£Όμ„Έμš”:

  1. λ¦¬ν”„λ ˆμ‹œ ν† ν°μ—λŠ” μ΅œμ†Œν•œμ˜ ν΄λ ˆμž„λ§Œ 포함
  2. 토큰 νƒ€μž…μ„ κ΅¬λΆ„ν•˜λŠ” ν΄λ ˆμž„ μΆ”κ°€
  3. λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ μ‹λ³„μžλ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν•˜μ—¬ 좔적
 String accessToken = jwtUtil.createToken(
         member.getEmail(),
-        jwtProperties.getAccessTokenValidityInSeconds()
+        jwtProperties.getAccessTokenValidityInSeconds(),
+        Map.of("type", "ACCESS_TOKEN")
 );
 String refreshToken = jwtUtil.createToken(
         member.getEmail(),
-        jwtProperties.getRefreshTokenValidityInSeconds()
+        jwtProperties.getRefreshTokenValidityInSeconds(),
+        Map.of("type", "REFRESH_TOKEN")
 );

Committable suggestion skipped: line range outside the PR's diff.


// μΏ ν‚€ 생성 및 μ„€μ •
Cookie authCookie = cookieUtil.createAuthCookie(token);
response.addCookie(authCookie);
Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken); //μ•‘μ„ΈμŠ€ 토큰 μΏ ν‚€
Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken); //λ¦¬ν”„λ ˆμ‹œ 토큰 μΏ ν‚€
response.addCookie(accessTokenCookie);
response.addCookie(refreshTokenCookie);
Comment on lines +69 to +72
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codebase verification

μΏ ν‚€ λ³΄μ•ˆ 섀정이 λΆˆμ™„μ „ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 λ³΄μ•ˆ λ¬Έμ œκ°€ λ°œκ²¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€:

  • SameSite 속성이 μ„€μ •λ˜μ–΄ μžˆμ§€ μ•Šμ•„ CSRF 곡격에 μ·¨μ•½ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • Pathκ°€ "/"둜 μ„€μ •λ˜μ–΄ μžˆμ–΄ ν•„μš” μ΄μƒμœΌλ‘œ 넓은 λ²”μœ„μ— μΏ ν‚€κ°€ λ…ΈμΆœλ©λ‹ˆλ‹€.

μˆ˜μ •μ΄ ν•„μš”ν•œ 파일:

  • src/main/java/com/mycom/socket/auth/security/CookieUtil.java

ꢌμž₯ 사항:

  1. SameSite=Strict λ˜λŠ” Lax 속성 μΆ”κ°€
  2. μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ‹€μ œ μš”κ΅¬μ‚¬ν•­μ— 맞게 Path λ²”μœ„ μΆ•μ†Œ
  3. 운영 ν™˜κ²½μ—μ„œ Secure ν”Œλž˜κ·Έκ°€ λ°˜λ“œμ‹œ ν™œμ„±ν™”λ˜λ„λ‘ μ„€μ • κ²€ν† 
πŸ”— Analysis chain

μΏ ν‚€ λ³΄μ•ˆ 섀정을 ν™•μΈν•΄μ£Όμ„Έμš”.

μΏ ν‚€ 생성 μ‹œ λ‹€μŒ λ³΄μ•ˆ 속성듀이 μ˜¬λ°”λ₯΄κ²Œ μ„€μ •λ˜μ–΄ μžˆλŠ”μ§€ 확인이 ν•„μš”ν•©λ‹ˆλ‹€:

  • HttpOnly
  • Secure
  • SameSite
  • Path
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# CookieUtil의 κ΅¬ν˜„μ„ ν™•μΈν•˜μ—¬ λ³΄μ•ˆ μ„€μ • 검증
rg -A 10 "createAuthCookie|createRefreshCookie" --type java

Length of output: 8548


Script:

#!/bin/bash
# Check createCookie implementation and JwtProperties
echo "=== CookieUtil.createCookie implementation ==="
ast-grep --pattern 'createCookie($$$) { $$$ }'

echo -e "\n=== JwtProperties configuration ==="
rg -A 5 "class JwtProperties" --type java

Length of output: 282


Script:

#!/bin/bash
# μΏ ν‚€ 생성 λ©”μ†Œλ“œμ™€ μ„€μ • 파일 검색
echo "=== Cookie creation implementation ==="
rg -A 15 "private.*createCookie" --type java

echo -e "\n=== Security and JWT configuration ==="
fd -e yml -e properties -e yaml | xargs rg -l "jwt|cookie|secure"

Length of output: 1755


// 둜그인 응닡 생성
LoginResponse loginResponse = new LoginResponse(member.getEmail(), member.getNickname());
Expand Down
Loading
Loading