Skip to content

Conversation

@ohyuchan123
Copy link
Member

@ohyuchan123 ohyuchan123 commented Jan 14, 2025

๐Ÿ“Œ ๊ด€๋ จ ์ด์Šˆ

#26 ์ด๋ฉ”์ผ ์ธ์ฆ ๊ธฐ๋Šฅ Redis ๋„์ž…

โœจ ๊ณผ์ œ ๋‚ด์šฉ

  1. ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ฐœ์„ 
  • ConcurrentHashMap ๋Œ€์‹  Redis ์‚ฌ์šฉ
  • TTL ๊ธฐ๋Šฅ์œผ๋กœ ๋งŒ๋ฃŒ๋œ ๋ฐ์ดํ„ฐ ์ž๋™ ์‚ญ์ œ
  1. Rate Limiting ๊ตฌํ˜„
  • 1๋ถ„๋‹น ์ตœ๋Œ€ 3ํšŒ๋กœ ์ธ์ฆ ์š”์ฒญ ์ œํ•œ
  • Redis increment์™€ TTL ํ™œ์šฉ
  1. ์ธ์ฆ ํ”„๋กœ์„ธ์Šค ๊ฐœ์„ 
  • ์ธ์ฆ๋ฒˆํ˜ธ ์œ ํšจ์‹œ๊ฐ„: 3๋ถ„
  • Rate Limit: 1๋ถ„๋‹น 3ํšŒ
  • ์ธ์ฆ๋œ ์ด๋ฉ”์ผ ์œ ํšจ์‹œ๊ฐ„: 30๋ถ„

๐Ÿ“ธ ์Šคํฌ๋ฆฐ์ƒท(์„ ํƒ)

  • ์ด๋ฉ”์ผ ์ธ์ฆ ์ „
image
  • ์ด๋ฉ”์ผ ์ธ์ฆ ํ›„
image
  • ์ธ์ฆ๋ฒˆํ˜ธ์˜ ์œ ํšจ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„ (๋กœ์ง ํ•„์š” ์—†์ด ์ž๋™์œผ๋กœ ์‚ญ์ œ)
image

๐Ÿ“š ๋ ˆํผ๋Ÿฐ์Šค (๋˜๋Š” ์ƒˆ๋กœ ์•Œ๊ฒŒ ๋œ ๋‚ด์šฉ) ํ˜น์€ ๊ถ๊ธˆํ•œ ์‚ฌํ•ญ๋“ค

Summary by CodeRabbit

Summary by CodeRabbit

  • ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ

    • Redis ์ง€์›์„ ์œ„ํ•œ ์˜์กด์„ฑ ์ถ”๊ฐ€
    • ์ด๋ฉ”์ผ ์ธ์ฆ์„ ์œ„ํ•œ ์ƒˆ๋กœ์šด ์„œ๋น„์Šค ๋„์ž…
    • ์ด๋ฉ”์ผ ๊ฒ€์ฆ ์—”๋“œํฌ์ธํŠธ ๊ฒฝ๋กœ ์—…๋ฐ์ดํŠธ
    • Redis ๊ธฐ๋ฐ˜์˜ ์ด๋ฉ”์ผ ๊ฒ€์ฆ ๋ฐ ์š”์ฒญ ์นด์šดํŠธ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
    • Redis ๊ตฌ์„ฑ ๋ฐ ์†์„ฑ ๊ด€๋ฆฌ ํด๋ž˜์Šค ์ถ”๊ฐ€
  • ๋ฒ„๊ทธ ์ˆ˜์ •

    • ์ด๋ฉ”์ผ ์ธ์ฆ ํ”„๋กœ์„ธ์Šค ๊ฐœ์„ 
    • ์š”์ฒญ ์†๋„ ์ œํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์ตœ์ ํ™”
  • ๋ฆฌํŒฉํ† ๋ง

    • JWT ์†์„ฑ ๋ฐ ๊ตฌ์„ฑ ๊ด€๋ฆฌ ๊ฐœ์„ 
    • ์ธ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์—์„œ Redis ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜
    • ๋ณด์•ˆ ๊ตฌ์„ฑ์—์„œ ๋ถˆํ•„์š”ํ•œ JWT ์†์„ฑ ์ œ๊ฑฐ
    • ํŒจํ‚ค์ง€ ๊ตฌ์กฐ ๋ณ€๊ฒฝ์— ๋”ฐ๋ฅธ ํด๋ž˜์Šค ์œ„์น˜ ์กฐ์ •
    • RateLimiter ํด๋ž˜์Šค ์ œ๊ฑฐ

@ohyuchan123 ohyuchan123 added โš™ Setting ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ธํŒ… ๐Ÿ”จ Refactor ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง โœจ Feature ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ labels Jan 14, 2025
@ohyuchan123 ohyuchan123 self-assigned this Jan 14, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2025

"""

์›Œํฌ์Šค๋ฃจ

์ด ํ’€ ๋ฆฌํ€˜์ŠคํŠธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ด๋ฉ”์ผ ๊ฒ€์ฆ ๋ฐ ์ธ์ฆ ์‹œ์Šคํ…œ์„ Redis ๊ธฐ๋ฐ˜์œผ๋กœ ์žฌ์„ค๊ณ„ํ•˜๋Š” ์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด์˜ ๋ฉ”๋ชจ๋ฆฌ ๋‚ด ์ ‘๊ทผ ๋ฐฉ์‹์—์„œ Redis๋ฅผ ํ™œ์šฉํ•œ ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๊ณ  ์˜๊ตฌ์ ์ธ ์†”๋ฃจ์…˜์œผ๋กœ ์ „ํ™˜ํ•˜์—ฌ ์ด๋ฉ”์ผ ๊ฒ€์ฆ, ์†๋„ ์ œํ•œ, ํ† ํฐ ๊ด€๋ฆฌ ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ณ€๊ฒฝ ์‚ฌํ•ญ

ํŒŒ์ผ ๋ณ€๊ฒฝ ์š”์•ฝ
build.gradle Redis ์ง€์›์„ ์œ„ํ•œ ์ƒˆ๋กœ์šด ์˜์กด์„ฑ ์ถ”๊ฐ€
JWTProperties.java ํŒจํ‚ค์ง€ ์œ„์น˜๋ฅผ jwt์—์„œ config๋กœ ๋ณ€๊ฒฝ
AuthController.java ์ด๋ฉ”์ผ ๊ฒ€์ฆ ์—”๋“œํฌ์ธํŠธ ๊ฒฝ๋กœ ์—…๋ฐ์ดํŠธ
MailService.java Redis ๊ธฐ๋ฐ˜ ์ด๋ฉ”์ผ ๊ฒ€์ฆ ๋กœ์ง์œผ๋กœ ๋ฆฌํŒฉํ† ๋ง
RedisConfig.java, RedisProperties.java, RedisService.java Redis ํ†ตํ•ฉ์„ ์œ„ํ•œ ์ƒˆ๋กœ์šด ๊ตฌ์„ฑ ๋ฐ ์„œ๋น„์Šค ํด๋ž˜์Šค ์ถ”๊ฐ€
SecurityConfig.java JWTProperties์˜ import ๋ฌธ ์ œ๊ฑฐ
JWTFilter.java, JWTUtil.java, CookieUtil.java JWTProperties import ๊ฒฝ๋กœ ๋ณ€๊ฒฝ

์‹œํ€€์Šค ๋‹ค์ด์–ด๊ทธ๋žจ

sequenceDiagram
    participant Client
    participant AuthController
    participant MailService
    participant RedisService
    
    Client->>AuthController: ์ด๋ฉ”์ผ ๊ฒ€์ฆ ์š”์ฒญ
    AuthController->>MailService: ๊ฒ€์ฆ ์ฝ”๋“œ ์ƒ์„ฑ ๋ฐ ์ „์†ก
    MailService->>RedisService: ๊ฒ€์ฆ ์ฝ”๋“œ ์ €์žฅ
    RedisService-->>MailService: ์ฝ”๋“œ ์ €์žฅ ํ™•์ธ
    MailService->>Client: ๊ฒ€์ฆ ์ฝ”๋“œ ์ „์†ก
    
    Client->>AuthController: ๊ฒ€์ฆ ์ฝ”๋“œ ํ™•์ธ
    AuthController->>MailService: ์ฝ”๋“œ ๊ฒ€์ฆ ์š”์ฒญ
    MailService->>RedisService: ์ฝ”๋“œ ์œ ํšจ์„ฑ ํ™•์ธ
    RedisService-->>MailService: ์ฝ”๋“œ ์œ ํšจ์„ฑ ๊ฒฐ๊ณผ
    MailService-->>AuthController: ๊ฒ€์ฆ ๊ฒฐ๊ณผ
    AuthController-->>Client: ๊ฒ€์ฆ ์‘๋‹ต
Loading

๊ด€๋ จ ๊ฐ€๋Šฅ์„ฑ ์žˆ๋Š” ์ด์Šˆ

๊ด€๋ จ ๊ฐ€๋Šฅ์„ฑ ์žˆ๋Š” PR

ํ† ๋ผ์˜ ์‹œ

๐Ÿฐ Redis์˜ ๋งˆ๋ฒ•, ์ฝ”๋“œ์˜ ์ถค
์ด๋ฉ”์ผ ๊ฒ€์ฆ ๋ฐ˜์ง์ด๋„ค
์†๋„์™€ ๋ณด์•ˆ, ํ•œ ๋ฒˆ์— ํ’ˆ์–ด
ํ† ํฐ์€ ์ถค์ถ”๊ณ  ๋ฐ์ดํ„ฐ๋Š” ๋…ธ๋ž˜ํ•ด
๊ฐœ๋ฐœ์˜ ์ฆ๊ฑฐ์›€, ํ† ๋ผ๊ฐ€ ๋…ธ๋ž˜ํ•ด! ๐ŸŒˆ
"""

Finishing Touches

  • ๐Ÿ“ Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

โค๏ธ Share
๐Ÿชง Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

๐Ÿ”ญ Outside diff range comments (3)
src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java (1)

Line range hint 66-74: ์ด๋ฉ”์ผ ์ถ”์ถœ ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์™ธ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

 public String getEmail(String token) {
+    try {
         return Jwts.parser()
                 .verifyWith(secretKey)
                 .requireIssuer(jwtProperties.getIssuer())
                 .build()
                 .parseSignedClaims(token)
                 .getPayload()
                 .getSubject();
+    } catch (Exception e) {
+        log.error("ํ† ํฐ์—์„œ ์ด๋ฉ”์ผ์„ ์ถ”์ถœํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ", e);
+        throw new IllegalArgumentException("์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค.", e);
+    }
 }
src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java (2)

Line range hint 52-65: ์‚ฌ์šฉ์ž ์ƒํƒœ ๊ฒ€์ฆ ๋กœ์ง ์ถ”๊ฐ€ ํ•„์š”

ํ˜„์žฌ ๊ตฌํ˜„์—์„œ๋Š” ์‚ฌ์šฉ์ž์˜ ํ™œ์„ฑ ์ƒํƒœ๋‚˜ ๊ณ„์ • ๋งŒ๋ฃŒ ์—ฌ๋ถ€ ๋“ฑ์„ ํ™•์ธํ•˜์ง€ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๊ฒ€์ฆ๋“ค์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค:

  • ๊ณ„์ • ํ™œ์„ฑํ™” ์ƒํƒœ ํ™•์ธ
  • ๊ณ„์ • ์ž ๊ธˆ ์ƒํƒœ ํ™•์ธ
  • ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์—ฌ๋ถ€ ํ™•์ธ
     private void setAuthentication(String token) {
         String email = jwtUtil.getEmail(token);
         UserDetails userDetails = memberDetailsService.loadUserByUsername(email);
 
+        if (!userDetails.isEnabled() || !userDetails.isAccountNonLocked() 
+            || !userDetails.isAccountNonExpired() || !userDetails.isCredentialsNonExpired()) {
+            throw new IllegalStateException("๊ณ„์ • ์ƒํƒœ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค");
+        }
+
         UsernamePasswordAuthenticationToken authentication =
                 new UsernamePasswordAuthenticationToken(
                         userDetails,

์ฟ ํ‚ค ๋ณด์•ˆ ์„ค์ •์ด ์ผ๋ถ€ ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค

ํ˜„์žฌ ๊ตฌํ˜„๋œ ๋ณด์•ˆ ์„ค์ •:

  • โœ… HttpOnly ํ”Œ๋ž˜๊ทธ ์„ค์ •๋จ
  • โœ… Secure ํ”Œ๋ž˜๊ทธ ์„ค์ •๋จ (properties ํ†ตํ•ด ๊ตฌ์„ฑ ๊ฐ€๋Šฅ)
  • โœ… Path๊ฐ€ "/" ๋กœ ์„ค์ •๋จ

๋ณด์™„์ด ํ•„์š”ํ•œ ์„ค์ •:

  • SameSite ์†์„ฑ์ด ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค (CSRF ๊ณต๊ฒฉ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด 'Strict' ๋˜๋Š” 'Lax' ์„ค์ • ๊ถŒ์žฅ)
  • ์ฟ ํ‚ค์˜ ๋„๋ฉ”์ธ ์ œํ•œ์ด ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค (์˜๋„ํ•˜์ง€ ์•Š์€ ๋„๋ฉ”์ธ๊ณผ์˜ ์ฟ ํ‚ค ๊ณต์œ  ๋ฐฉ์ง€ ํ•„์š”)
๐Ÿ”— Analysis chain

Line range hint 41-50: ์ฟ ํ‚ค ๋ณด์•ˆ ์„ค์ •์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”

JWT ํ† ํฐ์„ ์ฟ ํ‚ค์— ์ €์žฅํ•  ๋•Œ ๋‹ค์Œ ๋ณด์•ˆ ์„ค์ •๋“ค์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • HttpOnly ํ”Œ๋ž˜๊ทธ ์„ค์ •
  • Secure ํ”Œ๋ž˜๊ทธ ์„ค์ • (HTTPS ํ™˜๊ฒฝ)
  • SameSite ์†์„ฑ ์„ค์ •
  • ์ ์ ˆํ•œ ๋„๋ฉ”์ธ ๋ฐ ๊ฒฝ๋กœ ์„ค์ •

์ฟ ํ‚ค ์ƒ์„ฑ ๋ถ€๋ถ„์˜ ๋ณด์•ˆ ์„ค์ •์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•ด์ฃผ์„ธ์š”:

๐Ÿ Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check cookie security settings in the codebase

# Search for cookie creation/configuration
rg -l 'new Cookie|cookie\.set' 

# Search for specific security settings
rg 'setHttpOnly|setSecure|setSameSite'

Length of output: 793


Script:

#!/bin/bash
# Check complete cookie configuration
rg -A 5 'new Cookie' src/main/java/com/mycom/socket/auth/security/CookieUtil.java

# Check JWT properties configuration
fd -e java -e yml -e properties -e yaml | xargs rg -l 'cookie|Cookie'

Length of output: 1121

๐Ÿงน Nitpick comments (8)
src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java (3)

Line range hint 20-25: ์ƒ์„ฑ์ž์— ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

JWTProperties์™€ secret ๊ฐ’์ด null์ด ์•„๋‹Œ์ง€ ํ™•์ธํ•˜๋Š” ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

 public JWTUtil(JWTProperties jwtProperties) {
+    if (jwtProperties == null || jwtProperties.getSecret() == null) {
+        throw new IllegalArgumentException("JWTProperties์™€ secret์€ null์ด ๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
+    }
     this.jwtProperties = jwtProperties;
     this.secretKey = Keys.hmacShaKeyFor(
             jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8)
     );
 }

Line range hint 29-42: ํ† ํฐ ์ƒ์„ฑ ๋กœ๊น…์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

๋””๋ฒ„๊น…๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์œ„ํ•ด ํ† ํฐ ์ƒ์„ฑ ์‹œ์ ์— ๋กœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

 public String createToken(String email) {
+    log.debug("JWT ํ† ํฐ ์ƒ์„ฑ ์‹œ์ž‘ - ์ด๋ฉ”์ผ: {}", email);
     Date now = new Date();
     Date validity = new Date(now.getTime() +
             (jwtProperties.getAccessTokenValidityInSeconds() * 1000));

-    return Jwts.builder()
+    String token = Jwts.builder()
             .issuer(jwtProperties.getIssuer())
             .subject(email)
             .issuedAt(now)
             .expiration(validity)
             .signWith(secretKey)
             .compact();
+    log.debug("JWT ํ† ํฐ ์ƒ์„ฑ ์™„๋ฃŒ - ๋งŒ๋ฃŒ์‹œ๊ฐ„: {}", validity);
+    return token;
 }

Line range hint 47-61: ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๋” ๊ตฌ์ฒดํ™”ํ•˜๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ๋Š” ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ํ•˜๋‚˜๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ๋Š”๋ฐ, ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ์œ ํ˜•๋ณ„๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋””๋ฒ„๊น…์— ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 public boolean validateToken(String token) {
     try {
         if (!StringUtils.hasText(token)) {
+            log.warn("ํ† ํฐ์ด ๋น„์–ด์žˆ๊ฑฐ๋‚˜ null์ž…๋‹ˆ๋‹ค.");
             return false;
         }

         Jwts.parser()
                 .verifyWith(secretKey)
                 .requireIssuer(jwtProperties.getIssuer())
                 .build()
                 .parseSignedClaims(token);
         return true;
-    } catch (Exception e) {
-        log.warn("JWT ํ† ํฐ ๊ฒ€์ฆ ์‹คํŒจ", e);
+    } catch (ExpiredJwtException e) {
+        log.warn("๋งŒ๋ฃŒ๋œ JWT ํ† ํฐ์ž…๋‹ˆ๋‹ค.", e);
+        return false;
+    } catch (MalformedJwtException e) {
+        log.warn("์ž˜๋ชป๋œ ํ˜•์‹์˜ JWT ํ† ํฐ์ž…๋‹ˆ๋‹ค.", e);
+        return false;
+    } catch (Exception e) {
+        log.warn("JWT ํ† ํฐ ๊ฒ€์ฆ ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ", e);
         return false;
     }
 }
src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java (1)

Line range hint 33-37: ๋กœ๊น… ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค

ํ˜„์žฌ ๋กœ๊ทธ ๋ฉ”์‹œ์ง€("์ธ์ฆ ์ฒ˜๋ฆฌ ์‹คํŒจ")๋Š” ๋„ˆ๋ฌด ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์ •๋ณด๋“ค์„ ํฌํ•จํ•˜์—ฌ ๋กœ๊น…์„ ๊ฐœ์„ ํ•ด์ฃผ์„ธ์š”:

  • ์‹คํŒจํ•œ ์š”์ฒญ์˜ URI
  • ์‹คํŒจ ์›์ธ ๊ตฌ๋ถ„ (ํ† ํฐ ๋ˆ„๋ฝ, ํ† ํฐ ๋งŒ๋ฃŒ, ๊ฒ€์ฆ ์‹คํŒจ ๋“ฑ)
  • ์š”์ฒญ ์‹๋ณ„์ž (request ID)
-            log.warn("์ธ์ฆ ์ฒ˜๋ฆฌ ์‹คํŒจ", e);
+            log.warn("์ธ์ฆ ์ฒ˜๋ฆฌ ์‹คํŒจ - URI: {}, ์›์ธ: {}, Request ID: {}",
+                    request.getRequestURI(),
+                    e.getMessage(),
+                    request.getAttribute("requestId"));
src/main/java/com/mycom/socket/auth/service/MailService.java (1)

30-30: SecureRandom ์ธ์Šคํ„ด์Šค ์žฌ์‚ฌ์šฉ ๊ถŒ์žฅ

createVerificationCode() ๋ฉ”์„œ๋“œ์—์„œ new SecureRandom()๋ฅผ ๋งค๋ฒˆ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์œ„ํ•ด SecureRandom ์ธ์Šคํ„ด์Šค๋ฅผ ํด๋ž˜์Šค ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•˜์—ฌ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

+private static final SecureRandom secureRandom = new SecureRandom();

private String createVerificationCode() {
-    return String.format("%06d", new SecureRandom().nextInt(1000000));
+    return String.format("%06d", secureRandom.nextInt(1000000));
}
src/main/java/com/mycom/socket/global/config/RedisConfig.java (1)

23-30: RedisTemplate ์ง๋ ฌํ™” ์„ค์ • ๊ฐœ์„  ํ•„์š”

ํ˜„์žฌ ๊ฐ’ ์ง๋ ฌํ™”๊ฐ€ StringRedisSerializer๋กœ ์„ค์ •๋˜์–ด ์žˆ์–ด ๋ฌธ์ž์—ด๋งŒ ์ €์žฅ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์ฒด ์ €์žฅ์„ ์œ„ํ•ด Jackson2JsonRedisSerializer ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

 @Bean
 public RedisTemplate<?, ?> redisTemplate() {
     RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
     redisTemplate.setConnectionFactory(redisConnectionFactory());
 
     redisTemplate.setKeySerializer(new StringRedisSerializer());
-    redisTemplate.setValueSerializer(new StringRedisSerializer());
+    redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
     return redisTemplate;
 }
src/main/java/com/mycom/socket/global/service/RedisService.java (1)

82-85: ์ด๋ฉ”์ผ ์ธ์ฆ ํ™•์ธ ๋กœ์ง ๊ฐœ์„  ํ•„์š”

๋ฌธ์ž์—ด ๋น„๊ต ๋Œ€์‹  Boolean ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

 public boolean isEmailVerified(String email) {
     Object verified = redisTemplate.opsForValue().get(VERIFIED_EMAIL_PREFIX + email);
-    return "true".equals(verified);
+    return Boolean.TRUE.equals(verified);
 }
build.gradle (1)

49-49: Redis ์˜์กด์„ฑ ๋ฒ„์ „ ๋ช…์‹œ ๊ถŒ์žฅ

Spring Boot parent์—์„œ ์ƒ์†๋ฐ›์€ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์ธ ๋ฒ„์ „ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ๋ฒ„์ „์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

-    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
+    implementation 'org.springframework.boot:spring-boot-starter-data-redis:3.3.4'
๐Ÿ“œ Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between 16bf7a5 and 81dbc82.

๐Ÿ“’ Files selected for processing (15)
  • build.gradle (1 hunks)
  • src/main/java/com/mycom/socket/auth/config/JWTProperties.java (1 hunks)
  • src/main/java/com/mycom/socket/auth/config/MailProperties.java (1 hunks)
  • src/main/java/com/mycom/socket/auth/config/SecurityConfig.java (0 hunks)
  • src/main/java/com/mycom/socket/auth/controller/AuthController.java (1 hunks)
  • src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java (1 hunks)
  • src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java (1 hunks)
  • src/main/java/com/mycom/socket/auth/security/CookieUtil.java (1 hunks)
  • src/main/java/com/mycom/socket/auth/service/MailService.java (4 hunks)
  • src/main/java/com/mycom/socket/auth/service/RateLimiter.java (0 hunks)
  • src/main/java/com/mycom/socket/global/config/RedisConfig.java (1 hunks)
  • src/main/java/com/mycom/socket/global/config/RedisProperties.java (1 hunks)
  • src/main/java/com/mycom/socket/global/service/RedisService.java (1 hunks)
  • src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java (1 hunks)
  • src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java (1 hunks)
๐Ÿ’ค Files with no reviewable changes (2)
  • src/main/java/com/mycom/socket/auth/config/SecurityConfig.java
  • src/main/java/com/mycom/socket/auth/service/RateLimiter.java
โœ… Files skipped from review due to trivial changes (4)
  • src/main/java/com/mycom/socket/auth/config/JWTProperties.java
  • src/test/java/com/mycom/socket/member/controller/AuthControllerTest.java
  • src/main/java/com/mycom/socket/auth/security/CookieUtil.java
  • src/test/java/com/mycom/socket/member/service/LoginIntegrationTest.java
โฐ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build-test
๐Ÿ”‡ Additional comments (5)
src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java (1)

3-3: ๊ตฌ์„ฑ์ด ์ž˜ ์ •๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!

JWTProperties๋ฅผ config ํŒจํ‚ค์ง€๋กœ ์ด๋™ํ•˜๊ณ  final ํ•„๋“œ๋กœ ์„ ์–ธํ•œ ๊ฒƒ์ด ์ข‹์€ ์„ค๊ณ„ ๊ฒฐ์ •์ž…๋‹ˆ๋‹ค.

Also applies to: 17-18

src/main/java/com/mycom/socket/auth/controller/AuthController.java (1)

46-49: Redis ํ†ตํ•ฉ ๊ฒ€์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์—”๋“œํฌ์ธํŠธ ์ด๋ฆ„์ด /verification-code๋กœ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์€ ์ข‹์€ ๊ฐœ์„ ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Redis ํ†ตํ•ฉ๊ณผ ๊ด€๋ จ๋œ ๋ถ€๋ถ„์„ ๊ฒ€์ฆํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”:

  1. Redis ์—ฐ๋™์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ๋Š”์ง€
  2. ์ด๋ฉ”์ผ ์ธ์ฆ ์ฝ”๋“œ์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์ด ์„ค์ •๋˜์—ˆ๋Š”์ง€
  3. ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ๊ฐ€ ์ ์ ˆํžˆ ๋˜์–ด์žˆ๋Š”์ง€
src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java (1)

3-3: ํŒจํ‚ค์ง€ ๊ตฌ์กฐ ๊ฐœ์„ ์ด ์ž˜ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!

JWTProperties๋ฅผ config ํŒจํ‚ค์ง€๋กœ ์ด๋™ํ•œ ๊ฒƒ์€ ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ ์›์น™์„ ์ž˜ ๋”ฐ๋ฅธ ์ข‹์€ ๋ณ€๊ฒฝ์ž…๋‹ˆ๋‹ค.

src/main/java/com/mycom/socket/global/config/RedisProperties.java (1)

1-16: RedisProperties ๊ตฌ์„ฑ ํ™•์ธ

RedisProperties ํด๋ž˜์Šค๊ฐ€ Redis ์„ค์ •์„ ์œ„ํ•œ ํ•„์š”ํ•œ ์†์„ฑ์„ ์ ์ ˆํžˆ ๋งคํ•‘ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. @ConfigurationProperties์™€ @Component ์• ๋„ˆํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์ • ํŒŒ์ผ๋กœ๋ถ€ํ„ฐ ๊ฐ’์„ ์ฃผ์ž…๋ฐ›๋Š” ๋ฐฉ์‹์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

src/main/java/com/mycom/socket/auth/config/MailProperties.java (1)

1-22: MailProperties ๊ตฌ์„ฑ ํ™•์ธ

MailProperties ํด๋ž˜์Šค๋Š” ๋ฉ”์ผ ์ „์†ก์— ํ•„์š”ํ•œ ๋‹ค์–‘ํ•œ ์†์„ฑ๋“ค์„ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, @ConfigurationProperties๋ฅผ ํ†ตํ•ด spring.mail ํ”„๋ฆฌํ”ฝ์Šค์˜ ์„ค์ •์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋งคํ•‘ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”์ผ ๋ฐœ์†ก์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.

Comment on lines +41 to 44
@PostMapping("/verify-email")
public EmailVerificationResponse sendVerificationEmail(@Valid @RequestBody EmailRequest request) {
return mailService.sendMail(request.email());
}
Copy link

Choose a reason for hiding this comment

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

๐Ÿ’ก Codebase verification

API ๋ฌธ์„œํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค

์—”๋“œํฌ์ธํŠธ ๋ณ€๊ฒฝ์‚ฌํ•ญ(/verify-email)์ด ๋ฌธ์„œํ™”๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ์ถ”๊ฐ€ํ•ด์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค:

  • API ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ ๋ช…์„ธ ๋ฌธ์„œ ์ž‘์„ฑ
  • ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ ์ƒˆ๋กœ์šด ์—”๋“œํฌ์ธํŠธ ์‚ฌ์šฉ ์—ฌ๋ถ€ ํ™•์ธ
  • README.md์— API ์‚ฌ์šฉ ๋ฐฉ๋ฒ• ์ถ”๊ฐ€
๐Ÿ”— Analysis chain

์—”๋“œํฌ์ธํŠธ ์ด๋ฆ„์ด ๊ฐœ์„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!

์ƒˆ๋กœ์šด ์—”๋“œํฌ์ธํŠธ ์ด๋ฆ„ /verify-email์ด ๊ธฐ๋Šฅ์„ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

API ๋ฌธ์„œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”:

๐Ÿ Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# API ๋ฌธ์„œ์—์„œ ์ด์ „ ์—”๋“œํฌ์ธํŠธ ์ฐธ์กฐ ํ™•์ธ
rg -i "/verification" --type md
rg -i "/verify-email" --type md

Length of output: 63


Script:

#!/bin/bash
# Search for API documentation files
echo "=== Documentation files ==="
fd -e yaml -e yml -e json . | grep -i "api\|swagger\|openapi"

echo -e "\n=== Searching for endpoint references ==="
# Search in all text files for both old and new endpoints
rg -t txt -t yaml -t yml -t json -t java "/verification|/verify-email"

echo -e "\n=== README files ==="
fd README

Length of output: 407

Comment on lines +63 to +66
if (redisService.incrementCount(email) > 3) {
throw new BaseException("๋„ˆ๋ฌด ๋งŽ์€ ์š”์ฒญ์ž…๋‹ˆ๋‹ค. 1๋ถ„ ํ›„์— ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.",
HttpStatus.TOO_MANY_REQUESTS);
}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

์š”์ฒญ ํšŸ์ˆ˜ ์ œํ•œ์— ๋Œ€ํ•œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์„ค์ • ํ•„์š”

redisService.incrementCount(email)๋ฅผ ํ†ตํ•ด ์ด๋ฉ”์ผ๋ณ„ ์š”์ฒญ ํšŸ์ˆ˜๋ฅผ ์ฆ๊ฐ€์‹œํ‚ค์ง€๋งŒ, ํ˜„์žฌ ์š”์ฒญ ํšŸ์ˆ˜๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ์‹œํ‚ค๋Š” ๋กœ์ง์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์˜๊ตฌ์ ์œผ๋กœ ์ด๋ฉ”์ผ ์ „์†ก์„ ํ•  ์ˆ˜ ์—†๊ฒŒ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์š”์ฒญ ํšŸ์ˆ˜์— ๋Œ€ํ•œ Redis ํ‚ค์— TTL(๋งŒ๋ฃŒ ์‹œ๊ฐ„)์„ ์„ค์ •ํ•˜์—ฌ ์ผ์ • ์‹œ๊ฐ„ ํ›„์— ์ž๋™์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜๋„๋ก ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Comment on lines 45 to 48
message.setFrom(mailProperties.getUsername());
message.setRecipients(MimeMessage.RecipientType.TO, email);
message.setSubject("์ด๋ฉ”์ผ ์ธ์ฆ");
String body = String.format("""
<h3>์š”์ฒญํ•˜์‹  ์ธ์ฆ ๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค.</h3>
<h1>%s</h1>
<h3>๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.</h3>
""", verificationCode);
String body = String.format(mailProperties.getBodyTemplate(), verificationCode);
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

๋ฐœ์‹  ์ด๋ฉ”์ผ ์ฃผ์†Œ ์„ค์ • ์ˆ˜์ • ํ•„์š”

ํ˜„์žฌ message.setFrom(mailProperties.getUsername());๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”๋ฐ, ์ด๋Š” ์ธ์ฆ์— ์‚ฌ์šฉ๋˜๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ๋ฐœ์‹ ์ž ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด mailProperties.getSenderEmail()์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

-message.setFrom(mailProperties.getUsername());
+message.setFrom(mailProperties.getSenderEmail());

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

Comment on lines 93 to 97
try {
redisService.getCode(code); // ์ธ์ฆ์ฝ”๋“œ ๊ฒ€์ฆ
redisService.saveVerifiedEmail(email); // ์ธ์ฆ๋œ ์ด๋ฉ”์ผ ์ €์žฅ
return EmailVerificationResponse.of("์ด๋ฉ”์ผ ์ธ์ฆ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
} catch (Exception e) {
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

์ธ์ฆ ์ฝ”๋“œ ๊ฒ€์ฆ ๋กœ์ง ๋ณด์™„ ํ•„์š”

์ธ์ฆ ์ฝ”๋“œ ๊ฒ€์ฆ ์‹œ redisService.getCode(code);๋กœ ์ฝ”๋“œ๋งŒ์„ ์ด์šฉํ•˜์—ฌ ๊ฒ€์ฆํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ธ์ฆ ์ฝ”๋“œ์™€ ์ด๋ฉ”์ผ์ด ๋งคํ•‘๋˜์ง€ ์•Š์•„, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ณด์•ˆ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ €์žฅํ•  ๋•Œ ์ด๋ฉ”์ผ๊ณผ ํ•จ๊ป˜ ์ €์žฅํ•˜๊ณ , ๊ฒ€์ฆ ์‹œ์—๋„ ํ•ด๋‹น ์ด๋ฉ”์ผ์— ์ €์žฅ๋œ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€ ๋น„๊ตํ•˜๋„๋ก ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

-public EmailVerificationResponse verifyCode(String email, String code) {
     if (!code.matches("\\d{6}")) {
         throw new BaseException("์œ ํšจํ•˜์ง€ ์•Š์€ ์ธ์ฆ ์ฝ”๋“œ ํ˜•์‹์ž…๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
     }

     try {
-        redisService.getCode(code);  // ์ธ์ฆ์ฝ”๋“œ ๊ฒ€์ฆ
+        String savedCode = redisService.getCode(email);  // ์ด๋ฉ”์ผ๋กœ๋ถ€ํ„ฐ ์ฝ”๋“œ ์กฐํšŒ
+        if (!code.equals(savedCode)) {
+            throw new BaseException("์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
+        }
         redisService.saveVerifiedEmail(email);  // ์ธ์ฆ๋œ ์ด๋ฉ”์ผ ์ €์žฅ
         return EmailVerificationResponse.of("์ด๋ฉ”์ผ ์ธ์ฆ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
     } catch (Exception e) {
         throw new BaseException("์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
     }
 }

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

Comment on lines 18 to 20
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

Redis ์—ฐ๊ฒฐ ์„ค์ • ๊ฐœ์„  ํ•„์š”

์—ฐ๊ฒฐ ์‹œ๊ฐ„ ์ดˆ๊ณผ ๋ฐ ํ’€ ์„ค์ •์ด ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋‹ค์Œ ์„ค์ •๋“ค์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค:

  • ์—ฐ๊ฒฐ ์‹œ๊ฐ„ ์ดˆ๊ณผ ์„ค์ •
  • ์—ฐ๊ฒฐ ํ’€ ์„ค์ •
 @Bean
 public LettuceConnectionFactory redisConnectionFactory() {
-    return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
+    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
+        .commandTimeout(Duration.ofSeconds(1))
+        .build();
+    RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(
+        redisProperties.getHost(),
+        redisProperties.getPort()
+    );
+    return new LettuceConnectionFactory(serverConfig, clientConfig);
 }
๐Ÿ“ 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
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
public LettuceConnectionFactory redisConnectionFactory() {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(1))
.build();
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(
redisProperties.getHost(),
redisProperties.getPort()
);
return new LettuceConnectionFactory(serverConfig, clientConfig);
}

Comment on lines 19 to 20
private static final String VERIFIED_EMAIL_PREFIX = "verified:email:";
private static final String RATE_LIMIT_PREFIX = "rate-limit:";
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

Redis ํ‚ค ์ ‘๋‘์‚ฌ ์ฒด๊ณ„ ๊ฐœ์„  ํ•„์š”

์ธ์ฆ ์ฝ”๋“œ์— ๋Œ€ํ•œ ํ‚ค ์ ‘๋‘์‚ฌ๊ฐ€ ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ‚ค ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋ชจ๋“  ํ‚ค์— ์ ‘๋‘์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

 private static final String VERIFIED_EMAIL_PREFIX = "verified:email:";
 private static final String RATE_LIMIT_PREFIX = "rate-limit:";
+private static final String VERIFICATION_CODE_PREFIX = "verification:code:";
๐Ÿ“ 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 static final String VERIFIED_EMAIL_PREFIX = "verified:email:";
private static final String RATE_LIMIT_PREFIX = "rate-limit:";
private static final String VERIFIED_EMAIL_PREFIX = "verified:email:";
private static final String RATE_LIMIT_PREFIX = "rate-limit:";
private static final String VERIFICATION_CODE_PREFIX = "verification:code:";

Comment on lines 35 to 37
public void saveCode(String code) {
redisTemplate.opsForValue().set(code, code, VERIFICATION_TTL);
}
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

์ธ์ฆ ์ฝ”๋“œ ์ €์žฅ ๋กœ์ง ๊ฐœ์„  ํ•„์š”

์ธ์ฆ ์ฝ”๋“œ ์ €์žฅ ์‹œ ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„ ํ‚ค ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 public void saveCode(String code) {
-    redisTemplate.opsForValue().set(code, code, VERIFICATION_TTL);
+    redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX + code, code, VERIFICATION_TTL);
 }

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

@ohyuchan123 ohyuchan123 added the ๐Ÿž BugFix Something isn't working label Jan 14, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

๐Ÿงน Nitpick comments (3)
src/main/java/com/mycom/socket/global/service/RedisService.java (2)

19-28: Redis ๊ด€๋ จ ์ƒ์ˆ˜๊ฐ’๋“ค์˜ ๊ตฌ์„ฑ ๊ฐœ์„  ํ•„์š”

์ƒ์ˆ˜๊ฐ’๋“ค์„ ์ฝ”๋“œ์—์„œ ์ง์ ‘ ์ •์˜ํ•˜๋Š” ๋Œ€์‹  ์„ค์ • ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํ™˜๊ฒฝ๋ณ„๋กœ ๋‹ค๋ฅธ ๊ฐ’์„ ์ ์šฉํ•˜๊ฑฐ๋‚˜ ์šด์˜ ์ค‘์— ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.

+@Value("${redis.ttl.verification:180}")  // 3๋ถ„
+private int verificationTtlSeconds;
+@Value("${redis.ttl.verified-email:1800}")  // 30๋ถ„
+private int verifiedEmailTtlSeconds;
+@Value("${redis.ttl.rate-limit:60}")  // 1๋ถ„
+private int rateLimitTtlSeconds;

-private static final Duration VERIFICATION_TTL = Duration.ofMinutes(3);
-private static final Duration VERIFIED_EMAIL_TTL = Duration.ofMinutes(30);
-private static final Duration RATE_LIMIT_TTL = Duration.ofMinutes(1);

+private Duration getVerificationTtl() {
+    return Duration.ofSeconds(verificationTtlSeconds);
+}

71-86: ์ด๋ฉ”์ผ ์ธ์ฆ ์ƒํƒœ ์ €์žฅ ๋ฐฉ์‹ ๊ฐœ์„  ํ•„์š”

Redis์— ๋ฌธ์ž์—ด๋กœ boolean ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค Redis์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

 public void saveVerifiedEmail(String email) {
-    redisTemplate.opsForValue().set(
-            VERIFIED_EMAIL_PREFIX + email,
-            "true",
-            VERIFIED_EMAIL_TTL
-    );
+    redisTemplate.opsForValue().set(VERIFIED_EMAIL_PREFIX + email, "", VERIFIED_EMAIL_TTL);
 }

 public boolean isEmailVerified(String email) {
-    Object verified = redisTemplate.opsForValue().get(VERIFIED_EMAIL_PREFIX + email);
-    return "true".equals(verified);
+    return Boolean.TRUE.equals(redisTemplate.hasKey(VERIFIED_EMAIL_PREFIX + email));
 }
src/main/java/com/mycom/socket/auth/service/MailService.java (1)

28-31: ์ธ์ฆ ์ฝ”๋“œ ์ƒ์„ฑ ๋กœ์ง ๊ฐœ์„  ํ•„์š”

SecureRandom ์ธ์Šคํ„ด์Šค๋ฅผ ๋งค๋ฒˆ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ๋น„ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ ํ˜„์žฌ ๊ตฌํ˜„์€ 1,000,000์œผ๋กœ ๋‚˜๋ˆ„์ง€ ์•Š์•„ 7์ž๋ฆฌ ์ˆซ์ž๊ฐ€ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

+private static final SecureRandom secureRandom = new SecureRandom();

 private String createVerificationCode() {
-    return String.format("%06d", new SecureRandom().nextInt(1000000));
+    return String.format("%06d", secureRandom.nextInt(900000) + 100000);
 }
๐Ÿ“œ Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between 81dbc82 and 4121cf6.

๐Ÿ“’ Files selected for processing (3)
  • src/main/java/com/mycom/socket/auth/service/MailService.java (4 hunks)
  • src/main/java/com/mycom/socket/global/config/RedisConfig.java (1 hunks)
  • src/main/java/com/mycom/socket/global/service/RedisService.java (1 hunks)
๐Ÿšง Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/mycom/socket/global/config/RedisConfig.java
๐Ÿ”‡ Additional comments (3)
src/main/java/com/mycom/socket/global/service/RedisService.java (1)

58-65: ์š”์ฒญ ํšŸ์ˆ˜ ์ œํ•œ ๋กœ์ง ๋ณด์™„ ํ•„์š”

์ตœ๋Œ€ ์š”์ฒญ ํšŸ์ˆ˜ ์ œํ•œ์ด ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์•„ ๋ฌดํ•œ์ • ์š”์ฒญ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ race condition์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 public Long incrementCount(String email) {
     String key = RATE_LIMIT_PREFIX + email;
     Long count = redisTemplate.opsForValue().increment(key);
     if (count == 1) {
         redisTemplate.expire(key, RATE_LIMIT_TTL);
     }
+    if (count > 3) {  // PR ๋ชฉํ‘œ: 1๋ถ„๋‹น 3ํšŒ๋กœ ์ œํ•œ
+        throw new BaseException("๋„ˆ๋ฌด ๋งŽ์€ ์š”์ฒญ์ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.", HttpStatus.TOO_MANY_REQUESTS);
+    }
     return count;
 }
src/main/java/com/mycom/socket/auth/service/MailService.java (2)

63-70: โš ๏ธ Potential issue

์ด๋ฉ”์ผ ๋ฐœ์†ก ๋กœ์ง ๊ฐœ์„  ํ•„์š”

์ธ์ฆ ์ฝ”๋“œ ์ €์žฅ ์‹œ ์ด๋ฉ”์ผ ์ •๋ณด๊ฐ€ ๋ˆ„๋ฝ๋˜์–ด ์žˆ์–ด ๋ณด์•ˆ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 if (redisService.incrementCount(email) > 3) {
     throw new BaseException("๋„ˆ๋ฌด ๋งŽ์€ ์š”์ฒญ์ž…๋‹ˆ๋‹ค. 1๋ถ„ ํ›„์— ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.",
             HttpStatus.TOO_MANY_REQUESTS);
 }

 String verificationCode = createVerificationCode();
-redisService.saveCode(verificationCode);
+redisService.saveCode(email, verificationCode);

Likely invalid or redundant comment.


89-99: โš ๏ธ Potential issue

์ธ์ฆ ์ฝ”๋“œ ๊ฒ€์ฆ ๋กœ์ง ๊ฐœ์„  ํ•„์š”

์ด๋ฉ”์ผ๊ณผ ์ธ์ฆ ์ฝ”๋“œ์˜ ๋งคํ•‘์ด ๋ˆ„๋ฝ๋˜์–ด ์žˆ์–ด ๋ณด์•ˆ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ธ์ฆ ์„ฑ๊ณต ์‹œ ์ด๋ฉ”์ผ ์ธ์ฆ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๋กœ์ง์ด ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

 try {
-    String saveCode = redisService.getCode(code);  // ์ธ์ฆ์ฝ”๋“œ ๊ฒ€์ฆ
-    if(!saveCode.equals(code)) {
+    String savedCode = redisService.getCode(email);  // ์ด๋ฉ”์ผ๋กœ ์ธ์ฆ์ฝ”๋“œ ์กฐํšŒ
+    if(!savedCode.equals(code)) {
         throw new BaseException("์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
     }
+    redisService.saveVerifiedEmail(email);  // ์ด๋ฉ”์ผ ์ธ์ฆ ์™„๋ฃŒ ์ฒ˜๋ฆฌ
     return EmailVerificationResponse.of("์ด๋ฉ”์ผ ์ธ์ฆ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
 } catch (Exception e) {
     throw new BaseException("์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
 }

Likely invalid or redundant comment.

Comment on lines 36 to 38
public void saveCode(String code) {
redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX +code, code, VERIFICATION_TTL);
}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

์ธ์ฆ ์ฝ”๋“œ ์ €์žฅ ์‹œ ์ด๋ฉ”์ผ ์—ฐ๋™ ํ•„์š”

ํ˜„์žฌ ๊ตฌํ˜„์€ ์ธ์ฆ ์ฝ”๋“œ๋งŒ์„ ์ €์žฅํ•˜๊ณ  ์žˆ์–ด, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ณด์•ˆ ์ทจ์•ฝ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ด๋ฉ”์ผ๊ณผ ํ•จ๊ป˜ ์ €์žฅํ•˜์—ฌ ๊ฒ€์ฆ ์‹œ ํŠน์ • ์ด๋ฉ”์ผ์— ๋Œ€ํ•œ ์ธ์ฆ ์ฝ”๋“œ์ธ์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

-public void saveCode(String code) {
-    redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX + code, code, VERIFICATION_TTL);
+public void saveCode(String email, String code) {
+    redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX + email, code, VERIFICATION_TTL);
}
๐Ÿ“ 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
public void saveCode(String code) {
redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX +code, code, VERIFICATION_TTL);
}
public void saveCode(String email, String code) {
redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX + email, code, VERIFICATION_TTL);
}

Comment on lines 45 to 51
public String getCode(String code) {
Object savedCode = redisTemplate.opsForValue().get(code);
if (savedCode == null) {
throw new BaseException("์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
}
return savedCode.toString();
}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

์ธ์ฆ ์ฝ”๋“œ ์กฐํšŒ ๋กœ์ง ๊ฐœ์„  ํ•„์š”

์ด๋ฉ”์ผ๊ณผ ์ฝ”๋“œ์˜ ๋งคํ•‘์„ ์œ„ํ•ด ์กฐํšŒ ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๋กœ์ง์„ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

-public String getCode(String code) {
-    Object savedCode = redisTemplate.opsForValue().get(code);
+public String getCode(String email) {
+    Object savedCode = redisTemplate.opsForValue().get(VERIFICATION_CODE_PREFIX + email);
     if (savedCode == null) {
         throw new BaseException("์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
     }
     return savedCode.toString();
}
๐Ÿ“ 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
public String getCode(String code) {
Object savedCode = redisTemplate.opsForValue().get(code);
if (savedCode == null) {
throw new BaseException("์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
}
return savedCode.toString();
}
public String getCode(String email) {
Object savedCode = redisTemplate.opsForValue().get(VERIFICATION_CODE_PREFIX + email);
if (savedCode == null) {
throw new BaseException("์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
}
return savedCode.toString();
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

๐Ÿงน Nitpick comments (1)
src/main/java/com/mycom/socket/global/service/RedisService.java (1)

36-38: ์ด๋ฏธ ์ธ์ฆ๋œ ์ด๋ฉ”์ผ์— ๋Œ€ํ•œ ์ธ์ฆ ์ฝ”๋“œ ๋ฐœ์†ก ๋ฐฉ์ง€

saveCode ๋ฉ”์„œ๋“œ์—์„œ ์ด๋ฏธ ์ธ์ฆ๋œ ์ด๋ฉ”์ผ์— ๋Œ€ํ•ด ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์žฌ๋ฐœ์†กํ•˜์ง€ ์•Š๋„๋ก ์ด๋ฉ”์ผ์˜ ์ธ์ฆ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ , ์ธ์ฆ๋œ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

public void saveCode(String email, String code) {
    if (isEmailVerified(email)) {
        throw new BaseException("์ด๋ฏธ ์ธ์ฆ๋œ ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST);
    }
    redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX + email, code, VERIFICATION_TTL);
}
๐Ÿ“œ Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between 4121cf6 and c3ea46e.

๐Ÿ“’ Files selected for processing (1)
  • src/main/java/com/mycom/socket/global/service/RedisService.java (1 hunks)
๐Ÿ”‡ Additional comments (1)
src/main/java/com/mycom/socket/global/service/RedisService.java (1)

58-65: ์š”์ฒญ ํšŸ์ˆ˜ ์ œํ•œ ๋กœ์ง ๋ณด์™„ ํ•„์š”

incrementCount ๋ฉ”์„œ๋“œ์—์„œ ์ตœ๋Œ€ ์š”์ฒญ ํšŸ์ˆ˜ ์ œํ•œ์ด ์ ์šฉ๋˜์ง€ ์•Š์•„ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌด์ œํ•œ์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. PR ๋ชฉํ‘œ์— ๋ช…์‹œ๋œ "1๋ถ„๋‹น ์ตœ๋Œ€ 3ํšŒ์˜ ์š”์ฒญ ์ œํ•œ"์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด, ์š”์ฒญ ํšŸ์ˆ˜๊ฐ€ ์ œํ•œ์„ ์ดˆ๊ณผํ•˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด์ „ ๋ฆฌ๋ทฐ์—์„œ ์ œ์•ˆํ•œ ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

public Long incrementCount(String email) {
    String key = RATE_LIMIT_PREFIX + email;
    Long count = redisTemplate.opsForValue().increment(key);
    if (count == 1) {
        redisTemplate.expire(key, RATE_LIMIT_TTL);
    }
    if (count > 3) {
        throw new BaseException("์š”์ฒญ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๋ฅผ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.", HttpStatus.TOO_MANY_REQUESTS);
    }
    return count;
}

Comment on lines 58 to 65
public Long incrementCount(String email) {
String key = RATE_LIMIT_PREFIX + email;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, RATE_LIMIT_TTL);
}
return count;
}
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•œ ์›์ž์  ์—ฐ์‚ฐ ์‚ฌ์šฉ ๊ถŒ์žฅ

incrementCount ๋ฉ”์„œ๋“œ์—์„œ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ์ ‘๊ทผํ•  ๊ฒฝ์šฐ, race condition์ด ๋ฐœ์ƒํ•˜์—ฌ ์š”์ฒญ ํšŸ์ˆ˜ ์ œํ•œ ๋กœ์ง์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Redis์˜ ์›์ž์  ๋ช…๋ น์–ด ๋˜๋Š” Lua ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

๐Ÿงน Nitpick comments (2)
src/main/java/com/mycom/socket/global/service/RedisService.java (2)

54-73: Rate Limiting ๋กœ์ง ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

ํ˜„์žฌ ๊ตฌํ˜„์€ ๊ธฐ๋ณธ์ ์ธ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์ง€๋งŒ, Lua ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ์•ˆ์ „ํ•œ ์›์ž์  ์—ฐ์‚ฐ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด Lua ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

 public Long incrementCount(String email) {
     String key = RATE_LIMIT_PREFIX + email;
-    return redisTemplate.execute(
-            (RedisConnection connection) -> {
-                Long count = connection.incr(key.getBytes());
-                if (count == 1) {
-                    connection.expire(key.getBytes(), RATE_LIMIT_TTL.getSeconds());
-                }
-                return count;
-            }
-    );
+    String script = "local count = redis.call('incr', KEYS[1]) " +
+                    "if count == 1 then " +
+                    "  redis.call('expire', KEYS[1], ARGV[1]) " +
+                    "end " +
+                    "return count";
+    return redisTemplate.execute(
+        new DefaultRedisScript<>(script, Long.class),
+        Collections.singletonList(key),
+        RATE_LIMIT_TTL.getSeconds()
+    );
 }

75-95: ์ด๋ฉ”์ผ ์ธ์ฆ ์ƒํƒœ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ ํ™•์žฅ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

ํ˜„์žฌ ๊ตฌํ˜„์€ ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ์„ ์ž˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์ง€๋งŒ, ์ธ์ฆ ์ƒํƒœ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ธฐ๋Šฅ์ด ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ๋ฉ”์†Œ๋“œ ์ถ”๊ฐ€๋ฅผ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค:

/**
 * ์ด๋ฉ”์ผ ์ธ์ฆ ์ƒํƒœ ์ œ๊ฑฐ
 * ์‚ฌ์šฉ์ž๊ฐ€ ์žฌ์ธ์ฆ์„ ์š”์ฒญํ•˜๊ฑฐ๋‚˜ ์ธ์ฆ์„ ์ทจ์†Œํ•  ๋•Œ ์‚ฌ์šฉ
 */
public void removeVerifiedEmail(String email) {
    redisTemplate.delete(VERIFIED_EMAIL_PREFIX + email);
}
๐Ÿ“œ Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between f7ec6d2 and 371a66b.

๐Ÿ“’ Files selected for processing (1)
  • src/main/java/com/mycom/socket/global/service/RedisService.java (1 hunks)
๐Ÿ”‡ Additional comments (3)
src/main/java/com/mycom/socket/global/service/RedisService.java (3)

1-16: ํด๋ž˜์Šค ๊ตฌ์กฐ๊ฐ€ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค!

Spring๊ณผ Lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ƒ์„ฑ์ž ์ฃผ์ž… ํŒจํ„ด์ด ์ž˜ ์ ์šฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.


17-30: ์ƒ์ˆ˜ ์ •์˜๊ฐ€ ๊ฐœ์„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!

์ด์ „ ๋ฆฌ๋ทฐ์—์„œ ์ง€์ ๋œ ํ‚ค ์ ‘๋‘์‚ฌ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ์œผ๋ฉฐ, TTL ์„ค์ •๋„ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.


31-52: ์ธ์ฆ ์ฝ”๋“œ ์ €์žฅ ๋ฐ ์กฐํšŒ ๋กœ์ง์ด ๊ฐœ์„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!

์ด์ „ ๋ฆฌ๋ทฐ์—์„œ ์ง€์ ๋œ ์ด๋ฉ”์ผ-์ฝ”๋“œ ๋งคํ•‘ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ์œผ๋ฉฐ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋„ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

@ohyuchan123 ohyuchan123 merged commit 5ef4d93 into develop Jan 14, 2025
2 checks passed
@ohyuchan123 ohyuchan123 changed the title Redis Email Send ๐Ÿ”€ Redis Email Send Jan 14, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jan 14, 2025
@ohyuchan123 ohyuchan123 deleted the refactor/redis-send branch January 15, 2025 09:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

๐Ÿž BugFix Something isn't working โœจ Feature ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ ๐Ÿ”จ Refactor ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง โš™ Setting ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ธํŒ…

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants