-
Notifications
You must be signed in to change notification settings - Fork 0
๐ Redis Email Send #34
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
Conversation
|
""" ์ํฌ์ค๋ฃจ์ด ํ ๋ฆฌํ์คํธ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด๋ฉ์ผ ๊ฒ์ฆ ๋ฐ ์ธ์ฆ ์์คํ ์ Redis ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ค๊ณํ๋ ์ฃผ์ ๋ณ๊ฒฝ ์ฌํญ์ ํฌํจํฉ๋๋ค. ๊ธฐ์กด์ ๋ฉ๋ชจ๋ฆฌ ๋ด ์ ๊ทผ ๋ฐฉ์์์ Redis๋ฅผ ํ์ฉํ ํ์ฅ ๊ฐ๋ฅํ๊ณ ์๊ตฌ์ ์ธ ์๋ฃจ์ ์ผ๋ก ์ ํํ์ฌ ์ด๋ฉ์ผ ๊ฒ์ฆ, ์๋ ์ ํ, ํ ํฐ ๊ด๋ฆฌ ๋ฑ์ ๊ธฐ๋ฅ์ ๊ฐ์ ํ์ต๋๋ค. ๋ณ๊ฒฝ ์ฌํญ
์ํ์ค ๋ค์ด์ด๊ทธ๋จ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: ๊ฒ์ฆ ์๋ต
๊ด๋ จ ๊ฐ๋ฅ์ฑ ์๋ ์ด์
๊ด๋ จ ๊ฐ๋ฅ์ฑ ์๋ PR
ํ ๋ผ์ ์
Finishing Touches
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? ๐ชง TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
Documentation and Community
|
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.
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
๐ 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 ํตํฉ๊ณผ ๊ด๋ จ๋ ๋ถ๋ถ์ ๊ฒ์ฆํด์ผ ํฉ๋๋ค.๋ค์ ์ฌํญ๋ค์ ํ์ธํด์ฃผ์ธ์:
- Redis ์ฐ๋์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์๋์ง
- ์ด๋ฉ์ผ ์ธ์ฆ ์ฝ๋์ ๋ง๋ฃ ์๊ฐ์ด ์ค์ ๋์๋์ง
- ๋์์ฑ ์ฒ๋ฆฌ๊ฐ ์ ์ ํ ๋์ด์๋์ง
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ํ๋ฆฌํฝ์ค์ ์ค์ ์ ์ฌ๋ฐ๋ฅด๊ฒ ๋งคํํ๊ณ ์์ต๋๋ค. ๋ฉ์ผ ๋ฐ์ก์ ํ์ํ ์ ๋ณด๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
| @PostMapping("/verify-email") | ||
| public EmailVerificationResponse sendVerificationEmail(@Valid @RequestBody EmailRequest request) { | ||
| return mailService.sendMail(request.email()); | ||
| } |
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.
๐ก 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
| if (redisService.incrementCount(email) > 3) { | ||
| throw new BaseException("๋๋ฌด ๋ง์ ์์ฒญ์ ๋๋ค. 1๋ถ ํ์ ๋ค์ ์๋ํด์ฃผ์ธ์.", | ||
| HttpStatus.TOO_MANY_REQUESTS); | ||
| } |
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.
์์ฒญ ํ์ ์ ํ์ ๋ํ ๋ง๋ฃ ์๊ฐ ์ค์ ํ์
redisService.incrementCount(email)๋ฅผ ํตํด ์ด๋ฉ์ผ๋ณ ์์ฒญ ํ์๋ฅผ ์ฆ๊ฐ์ํค์ง๋ง, ํ์ฌ ์์ฒญ ํ์๋ฅผ ์ด๊ธฐํํ๊ฑฐ๋ ๋ง๋ฃ์ํค๋ ๋ก์ง์ด ์์ต๋๋ค. ์ด๋ก ์ธํด ์ฌ์ฉ์๊ฐ ์๊ตฌ์ ์ผ๋ก ์ด๋ฉ์ผ ์ ์ก์ ํ ์ ์๊ฒ ๋ ์ ์์ต๋๋ค. ์์ฒญ ํ์์ ๋ํ Redis ํค์ TTL(๋ง๋ฃ ์๊ฐ)์ ์ค์ ํ์ฌ ์ผ์ ์๊ฐ ํ์ ์๋์ผ๋ก ์ด๊ธฐํ๋๋๋ก ์์ ํด์ผ ํฉ๋๋ค.
| 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); |
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.
๋ฐ์ ์ด๋ฉ์ผ ์ฃผ์ ์ค์ ์์ ํ์
ํ์ฌ message.setFrom(mailProperties.getUsername());๋ก ์ค์ ๋์ด ์๋๋ฐ, ์ด๋ ์ธ์ฆ์ ์ฌ์ฉ๋๋ ์ฌ์ฉ์ ์ด๋ฆ์
๋๋ค. ์ค์ ๋ฐ์ ์ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์ค์ ํ๋ ค๋ฉด mailProperties.getSenderEmail()์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ ์ ํฉ๋๋ค.
-message.setFrom(mailProperties.getUsername());
+message.setFrom(mailProperties.getSenderEmail());Committable suggestion skipped: line range outside the PR's diff.
| try { | ||
| redisService.getCode(code); // ์ธ์ฆ์ฝ๋ ๊ฒ์ฆ | ||
| redisService.saveVerifiedEmail(email); // ์ธ์ฆ๋ ์ด๋ฉ์ผ ์ ์ฅ | ||
| return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ธ์ฆ์ด ์๋ฃ๋์์ต๋๋ค."); | ||
| } catch (Exception e) { |
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.
์ธ์ฆ ์ฝ๋ ๊ฒ์ฆ ๋ก์ง ๋ณด์ ํ์
์ธ์ฆ ์ฝ๋ ๊ฒ์ฆ ์ 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.
| public LettuceConnectionFactory redisConnectionFactory() { | ||
| return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort()); | ||
| } |
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.
๐ ๏ธ 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.
| 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); | |
| } |
| private static final String VERIFIED_EMAIL_PREFIX = "verified:email:"; | ||
| private static final String RATE_LIMIT_PREFIX = "rate-limit:"; |
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.
๐ ๏ธ 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.
| 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:"; |
| public void saveCode(String code) { | ||
| redisTemplate.opsForValue().set(code, code, VERIFICATION_TTL); | ||
| } |
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.
๐ ๏ธ 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.
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.
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
๐ 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.
| public void saveCode(String code) { | ||
| redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX +code, code, VERIFICATION_TTL); | ||
| } |
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.
์ธ์ฆ ์ฝ๋ ์ ์ฅ ์ ์ด๋ฉ์ผ ์ฐ๋ ํ์
ํ์ฌ ๊ตฌํ์ ์ธ์ฆ ์ฝ๋๋ง์ ์ ์ฅํ๊ณ ์์ด, ๋ค๋ฅธ ์ฌ์ฉ์์ ์ธ์ฆ ์ฝ๋๋ฅผ ์ฌ์ฉํ ์ ์๋ ๋ณด์ ์ทจ์ฝ์ ์ด ์์ต๋๋ค. ์ธ์ฆ ์ฝ๋๋ฅผ ์ด๋ฉ์ผ๊ณผ ํจ๊ป ์ ์ฅํ์ฌ ๊ฒ์ฆ ์ ํน์ ์ด๋ฉ์ผ์ ๋ํ ์ธ์ฆ ์ฝ๋์ธ์ง ํ์ธํด์ผ ํฉ๋๋ค.
-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.
| 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); | |
| } |
| public String getCode(String code) { | ||
| Object savedCode = redisTemplate.opsForValue().get(code); | ||
| if (savedCode == null) { | ||
| throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ๋ง๋ฃ๋์๊ฑฐ๋ ์กด์ฌํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| } | ||
| return savedCode.toString(); | ||
| } |
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.
์ธ์ฆ ์ฝ๋ ์กฐํ ๋ก์ง ๊ฐ์ ํ์
์ด๋ฉ์ผ๊ณผ ์ฝ๋์ ๋งคํ์ ์ํด ์กฐํ ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ์ ๋ก์ง์ ์์ ํด์ผ ํฉ๋๋ค.
-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.
| 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(); | |
| } |
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.
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
๐ 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; }
| 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; | ||
| } |
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.
๐ ๏ธ Refactor suggestion
๋์์ฑ ๋ฌธ์ ํด๊ฒฐ์ ์ํ ์์์ ์ฐ์ฐ ์ฌ์ฉ ๊ถ์ฅ
incrementCount ๋ฉ์๋์์ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ ๊ทผํ ๊ฒฝ์ฐ, race condition์ด ๋ฐ์ํ์ฌ ์์ฒญ ํ์ ์ ํ ๋ก์ง์ด ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ์ง ์์ ์ ์์ต๋๋ค. Redis์ ์์์ ๋ช
๋ น์ด ๋๋ Lua ์คํฌ๋ฆฝํธ๋ฅผ ํ์ฉํ์ฌ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
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.
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
๐ 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: ์ธ์ฆ ์ฝ๋ ์ ์ฅ ๋ฐ ์กฐํ ๋ก์ง์ด ๊ฐ์ ๋์์ต๋๋ค!์ด์ ๋ฆฌ๋ทฐ์์ ์ง์ ๋ ์ด๋ฉ์ผ-์ฝ๋ ๋งคํ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์์ผ๋ฉฐ, ์์ธ ์ฒ๋ฆฌ๋ ์ ์ ํฉ๋๋ค.
๐ ๊ด๋ จ ์ด์
#26 ์ด๋ฉ์ผ ์ธ์ฆ ๊ธฐ๋ฅ Redis ๋์
โจ ๊ณผ์ ๋ด์ฉ
๐ธ ์คํฌ๋ฆฐ์ท(์ ํ)
๐ ๋ ํผ๋ฐ์ค (๋๋ ์๋ก ์๊ฒ ๋ ๋ด์ฉ) ํน์ ๊ถ๊ธํ ์ฌํญ๋ค
Summary by CodeRabbit
Summary by CodeRabbit
์๋ก์ด ๊ธฐ๋ฅ
๋ฒ๊ทธ ์์
๋ฆฌํฉํ ๋ง