-
Notifications
You must be signed in to change notification settings - Fork 0
π Jwt Refresh #36
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
π Jwt Refresh #36
Changes from 2 commits
d34abc3
49b9c5f
7341b5b
2a4d71e
ed35dc5
230cb35
c63c7ff
608a2e9
d2e5305
f16eaec
c25e10d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| 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; | ||
|
|
||
| @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); | ||
|
|
||
| try { | ||
| jwtUtil.validateToken(refreshToken); | ||
| } catch (JwtException e) { | ||
| response.addCookie(cookieUtil.createExpiredRefreshCookie()); | ||
| throw new BadRequestException("μ ν¨νμ§ μμ 리νλ μ ν ν°μ λλ€. λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ."); | ||
| } | ||
|
|
||
| String email = jwtUtil.getEmail(refreshToken); | ||
| String newAccessToken = jwtUtil.createToken(email, jwtProperties.getAccessTokenValidityInSeconds()); | ||
|
|
||
| Cookie accessTokenCookie = cookieUtil.createAuthCookie(newAccessToken); | ||
| response.addCookie(accessTokenCookie); | ||
|
|
||
| String newRefreshToken = jwtUtil.createToken(email, jwtProperties.getRefreshTokenValidityInSeconds()); | ||
| Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(newRefreshToken); | ||
| response.addCookie(refreshTokenCookie); | ||
|
|
||
| return TokenResponse.of(newAccessToken); | ||
| } | ||
|
|
||
| private String extractRefreshToken(HttpServletRequest request) { | ||
| if (request.getCookies() == null) { | ||
| throw new BadRequestException("리νλ μ ν ν°μ΄ μμ΅λλ€. λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ."); | ||
| } | ||
|
|
||
| for (Cookie cookie : request.getCookies()) { | ||
| if (jwtProperties.getRefreshTokenCookieName().equals(cookie.getName())) { | ||
| return cookie.getValue(); | ||
| } | ||
| } | ||
| throw new BadRequestException("리νλ μ ν ν°μ΄ μμ΅λλ€. λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ."); | ||
| } | ||
| } |
| 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); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,13 +25,26 @@ public JWTUtil(JWTProperties jwtProperties) { | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * μ‘μΈμ€ ν ν° μμ± | ||
| */ | ||
| public String createAccessToken(String email) { | ||
| return createToken(email, jwtProperties.getAccessTokenValidityInSeconds()); | ||
| } | ||
|
||
|
|
||
| /** | ||
| * 리νλ μ ν ν° μμ± | ||
| */ | ||
| public String createRefreshToken(String email) { | ||
| return createToken(email, jwtProperties.getRefreshTokenValidityInSeconds()); | ||
| } | ||
|
|
||
| /** | ||
| * JWT ν ν° μμ± | ||
| */ | ||
| 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()) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,19 +14,44 @@ public class CookieUtil { | |
| * μΈμ¦ μΏ ν€ μμ± | ||
| */ | ||
| public Cookie createAuthCookie(String token) { | ||
| Cookie cookie = new Cookie(jwtProperties.getCookieName(), token); | ||
| Cookie cookie = new Cookie(jwtProperties.getAccessTokenCookieName(), token); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setSecure(jwtProperties.isSecureCookie()); | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge((int) jwtProperties.getAccessTokenValidityInSeconds()); | ||
| return cookie; | ||
|
||
| } | ||
|
|
||
| /** | ||
| * 리νλ μ ν ν° μΏ ν€ μμ± | ||
| */ | ||
| public Cookie createRefreshCookie(String token) { | ||
| Cookie cookie = new Cookie(jwtProperties.getRefreshTokenCookieName(), token); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setSecure(jwtProperties.isSecureCookie()); | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge((int) jwtProperties.getRefreshTokenValidityInSeconds()); | ||
| return cookie; | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * μΈμ¦ μΏ ν€ λ§λ£ μ²λ¦¬ | ||
| */ | ||
| public Cookie createExpiredAuthCookie() { | ||
| Cookie cookie = new Cookie(jwtProperties.getCookieName(), null); | ||
| Cookie cookie = new Cookie(jwtProperties.getAccessTokenCookieName(), null); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setSecure(true); | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge(0); // μ¦μ λ§λ£ | ||
| return cookie; | ||
| } | ||
|
||
|
|
||
| /** | ||
| * 리νλ μ ν ν° μΏ ν€ λ§λ£ μ²λ¦¬ | ||
| */ | ||
| public Cookie createExpiredRefreshCookie() { | ||
| Cookie cookie = new Cookie(jwtProperties.getRefreshTokenCookieName(), null); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setSecure(true); | ||
| cookie.setPath("/"); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,11 +51,14 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR | |
| MemberDetails memberDetails = (MemberDetails) authResult.getPrincipal(); | ||
| Member member = memberDetails.getMember(); | ||
|
|
||
| String token = jwtUtil.createToken(member.getEmail()); | ||
| String accessToken = jwtUtil.createAccessToken(member.getEmail()); //μ‘μΈμ€ ν ν° μμ± | ||
| String refreshToken = jwtUtil.createRefreshToken(member.getEmail()); //리νλ μ ν ν° μμ± | ||
|
|
||
| // μΏ ν€ μμ± λ° μ€μ | ||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Codebase verification μΏ ν€ λ³΄μ μ€μ μ΄ λΆμμ ν©λλ€. λ€μκ³Ό κ°μ 보μ λ¬Έμ κ° λ°κ²¬λμμ΅λλ€:
μμ μ΄ νμν νμΌ:
κΆμ₯ μ¬ν:
π Analysis chainμΏ ν€ λ³΄μ μ€μ μ νμΈν΄μ£ΌμΈμ. μΏ ν€ μμ± μ λ€μ 보μ μμ±λ€μ΄ μ¬λ°λ₯΄κ² μ€μ λμ΄ μλμ§ νμΈμ΄ νμν©λλ€:
π Scripts executedThe 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()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -90,15 +90,13 @@ public EmailVerificationResponse verifyCode(String email, String code) { | |||||||||||
| throw new BaseException("μ ν¨νμ§ μμ μΈμ¦ μ½λ νμμ λλ€.", HttpStatus.BAD_REQUEST); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| try { | ||||||||||||
| String saveCode = redisService.getCode(code); // μΈμ¦μ½λ κ²μ¦ | ||||||||||||
| if(!saveCode.equals(code)) { | ||||||||||||
| throw new BaseException("μΈμ¦ μ½λκ° μΌμΉνμ§ μμ΅λλ€.", HttpStatus.BAD_REQUEST); | ||||||||||||
| } | ||||||||||||
| return EmailVerificationResponse.of("μ΄λ©μΌ μΈμ¦μ΄ μλ£λμμ΅λλ€."); | ||||||||||||
| } catch (Exception e) { | ||||||||||||
| String savedCode = redisService.getCode(email); // Redisμμ μ½λλ₯Ό κ°μ Έμ΄ | ||||||||||||
|
|
||||||||||||
|
Comment on lines
+93
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redisμμ κ°μ Έμ¨ μ½λμ null 체ν¬κ° νμν©λλ€.
λ€μκ³Ό κ°μ΄ null 체ν¬λ₯Ό μΆκ°νλ κ²μ κΆμ₯ν©λλ€: String savedCode = redisService.getCode(email); // Redisμμ μ½λλ₯Ό κ°μ Έμ΄
+if (savedCode == null) {
+ throw new BaseException("λ§λ£λμκ±°λ μ‘΄μ¬νμ§ μλ μΈμ¦ μ½λμ
λλ€.", HttpStatus.BAD_REQUEST);
+}π Committable suggestion
Suggested change
|
||||||||||||
| if(!savedCode.equals(code)){ | ||||||||||||
| throw new BaseException("μΈμ¦ μ½λκ° μΌμΉνμ§ μμ΅λλ€.", HttpStatus.BAD_REQUEST); | ||||||||||||
| } | ||||||||||||
| redisService.saveVerifiedEmail(email); | ||||||||||||
| return EmailVerificationResponse.of("μ΄λ©μΌ μΈμ¦μ΄ μλ£λμμ΅λλ€."); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| package com.mycom.socket.member.service; | ||
|
|
||
| import com.mycom.socket.auth.config.JWTProperties; | ||
| import com.mycom.socket.auth.dto.request.LoginRequest; | ||
| import com.mycom.socket.auth.dto.response.LoginResponse; | ||
| import com.mycom.socket.auth.jwt.JWTUtil; | ||
|
|
@@ -13,7 +14,6 @@ | |
| import jakarta.servlet.http.HttpServletResponse; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.extension.ExtendWith; | ||
| import org.mockito.ArgumentCaptor; | ||
| import org.mockito.InjectMocks; | ||
| import org.mockito.Mock; | ||
| import org.mockito.junit.jupiter.MockitoExtension; | ||
|
|
@@ -36,6 +36,9 @@ class LoginTest { | |
| @Mock | ||
| private PasswordEncoder passwordEncoder; | ||
|
|
||
| @Mock | ||
| private JWTProperties jwtProperties; | ||
|
|
||
|
Comment on lines
+39
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π οΈ Refactor suggestion 리νλ μ ν ν° ν μ€νΈ μΌμ΄μ€ 보μ νμ λ€μ ν μ€νΈ μΌμ΄μ€λ€μ μΆκ°ν΄μΌ ν©λλ€:
@Test
void 리νλ μν ν°_λ§λ£() {
// given
String expiredToken = "expired.refresh.token";
when(jwtUtil.validateToken(expiredToken, "REFRESH_TOKEN")).thenReturn(false);
// when & then
assertThrows(BadRequestException.class,
() -> authService.refreshAccessToken(expiredToken));
}Also applies to: 76-76, 88-89 |
||
| @Mock | ||
| private JWTUtil jwtUtil; | ||
|
|
||
|
|
@@ -70,7 +73,7 @@ class LoginTest { | |
|
|
||
| when(memberRepository.findByEmail(email)).thenReturn(Optional.of(member)); | ||
| when(passwordEncoder.matches(password, encodedPassword)).thenReturn(true); | ||
| when(jwtUtil.createToken(email)).thenReturn(token); | ||
| when(jwtUtil.createToken(email, jwtProperties.getRefreshTokenValidityInSeconds())).thenReturn(token); | ||
|
||
| when(cookieUtil.createAuthCookie(token)).thenReturn(authCookie); // CookieUtil λμ μ μ | ||
|
|
||
| // when | ||
|
|
@@ -82,7 +85,7 @@ class LoginTest { | |
| assertEquals(nickname, response.nickname()); | ||
| verify(memberRepository).findByEmail(email); | ||
| verify(passwordEncoder).matches(password, encodedPassword); | ||
| verify(jwtUtil).createToken(email); | ||
| verify(jwtUtil).createToken(email, jwtProperties.getRefreshTokenValidityInSeconds()); | ||
| verify(cookieUtil).createAuthCookie(token); | ||
| } | ||
|
|
||
|
|
||
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
νμ μμ±μ λν μ ν¨μ± κ²μ¦ μΆκ° νμ
ν ν° μ ν¨ κΈ°κ°κ³Ό μΏ ν€ μ΄λ¦μ νμ κ°μ΄λ―λ‘, μ΄μ λν μ ν¨μ± κ²μ¦μ΄ νμν©λλ€.
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
π Committable suggestion