Skip to content

Commit bf07e49

Browse files
authored
๐Ÿ› Bug - Security Filter๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜์—ฌ ์•ก์„ธ์Šค ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋กœ์ง์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค
๐Ÿ› Bug - Security Filter๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜์—ฌ ์•ก์„ธ์Šค ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋กœ์ง์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค
2 parents 010df67 + 7b9bcce commit bf07e49

8 files changed

Lines changed: 95 additions & 62 deletions

File tree

โ€Žsrc/main/java/sopt/comfit/auth/controller/AuthController.javaโ€Ž

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
package sopt.comfit.auth.controller;
22

3-
import jakarta.servlet.http.Cookie;
43
import jakarta.servlet.http.HttpServletResponse;
54
import jakarta.validation.Valid;
65
import lombok.RequiredArgsConstructor;
76
import org.springframework.web.bind.annotation.*;
7+
import sopt.comfit.auth.dto.AccessTokenResponseDto;
88
import sopt.comfit.auth.dto.LoginResponseDto;
9-
import sopt.comfit.auth.dto.ReIssueTokenResponseDto;
109
import sopt.comfit.auth.dto.command.LoginCommandDto;
1110
import sopt.comfit.auth.dto.command.OnBoardingCommandDto;
1211
import sopt.comfit.auth.dto.query.LoginQueryDto;
1312
import sopt.comfit.auth.dto.request.LoginRequestDto;
1413
import sopt.comfit.auth.dto.request.OnBoardingRequestDTO;
15-
import sopt.comfit.auth.dto.request.ReIssueTokenRequestDto;
1614
import sopt.comfit.auth.kakao.service.KakaoAuthService;
1715
import sopt.comfit.auth.service.AuthService;
1816
import sopt.comfit.global.annotation.LoginUser;
@@ -28,23 +26,41 @@ public class AuthController implements AuthSwagger{
2826

2927
@PostMapping("/login")
3028
public JwtDto join(
31-
@RequestBody @Valid LoginRequestDto request
29+
@RequestBody @Valid LoginRequestDto request,
30+
HttpServletResponse response
3231
){
33-
return authService.login(LoginCommandDto.from(request));
32+
JwtDto dto = authService.login(LoginCommandDto.from(request));
33+
34+
response.addHeader("Set-Cookie",
35+
"refreshToken=" + dto.refreshToken() +
36+
"; Path=/; HttpOnly; Secure; SameSite=None; Max-Age=86400");
37+
38+
return dto;
3439
}
3540

3641
@Override
3742
public void logout(
38-
@LoginUser Long userId
43+
@LoginUser Long userId,
44+
HttpServletResponse response
3945
){
4046
authService.logout(userId);
47+
48+
response.addHeader("Set-Cookie",
49+
"refreshToken=; Path=/; HttpOnly; Secure; SameSite=None; Max-Age=0");
4150
}
4251

4352
@Override
44-
public ReIssueTokenResponseDto reissueToken(
45-
@RequestBody @Valid ReIssueTokenRequestDto request
53+
public AccessTokenResponseDto reissueToken(
54+
@CookieValue(value = "refreshToken", required = false) String refreshToken,
55+
HttpServletResponse response
4656
) {
47-
return authService.reissueToken(request.refreshToken());
57+
JwtDto dto = authService.reissueToken(refreshToken);
58+
59+
response.addHeader("Set-Cookie",
60+
"refreshToken=" + dto.refreshToken() +
61+
"; Path=/; HttpOnly; Secure; SameSite=None; Max-Age=86400");
62+
63+
return AccessTokenResponseDto.from(dto.accessToken());
4864
}
4965

5066
@Override
@@ -62,11 +78,9 @@ public LoginResponseDto kakaoCallback(
6278
) {
6379
LoginQueryDto loginQueryDto = kakaoAuthService.getKakaoUserInfoByCode(code);
6480

65-
Cookie cookie = new Cookie("refreshToken", loginQueryDto.jwtDto().refreshToken());
66-
cookie.setPath("/");
67-
cookie.setHttpOnly(true);
68-
cookie.setMaxAge(7 * 24 * 60 * 60); // 7์ผ (refreshToken ๋งŒ๋ฃŒ์™€ ๋งž์ถฐ์„œ ์กฐ์ •)
69-
response.addCookie(cookie);
81+
response.addHeader("Set-Cookie",
82+
"refreshToken=" + loginQueryDto.jwtDto().refreshToken() +
83+
"; Path=/; HttpOnly; Secure; SameSite=None; Max-Age=86400");
7084

7185
return LoginResponseDto.of(loginQueryDto);
7286
}

โ€Žsrc/main/java/sopt/comfit/auth/controller/AuthSwagger.javaโ€Ž

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,10 @@
99
import io.swagger.v3.oas.annotations.tags.Tag;
1010
import jakarta.servlet.http.HttpServletResponse;
1111
import jakarta.validation.Valid;
12-
import org.springframework.web.bind.annotation.GetMapping;
13-
import org.springframework.web.bind.annotation.PostMapping;
14-
import org.springframework.web.bind.annotation.RequestBody;
15-
import org.springframework.web.bind.annotation.RequestParam;
12+
import org.springframework.web.bind.annotation.*;
1613
import sopt.comfit.auth.dto.LoginResponseDto;
17-
import sopt.comfit.auth.dto.ReIssueTokenResponseDto;
14+
import sopt.comfit.auth.dto.AccessTokenResponseDto;
1815
import sopt.comfit.auth.dto.request.OnBoardingRequestDTO;
19-
import sopt.comfit.auth.dto.request.ReIssueTokenRequestDto;
2016
import sopt.comfit.global.annotation.LoginUser;
2117
import sopt.comfit.global.dto.CommonApiResponse;
2218
import sopt.comfit.global.dto.CustomErrorResponse;
@@ -28,7 +24,7 @@ public interface AuthSwagger {
2824
@ApiResponses({
2925
@ApiResponse(responseCode = "200", description = "๊ฒฝํ—˜ ์ƒ์„ฑ ์„ฑ๊ณต",
3026
content = @Content(mediaType = "application/json",
31-
schema = @Schema(implementation = ReIssueTokenResponseDto.class))),
27+
schema = @Schema(implementation = AccessTokenResponseDto.class))),
3228

3329
@ApiResponse(responseCode = "403", description = "๊ถŒํ•œ ์˜ค๋ฅ˜",
3430
content = @Content(mediaType = "application/json",
@@ -41,8 +37,9 @@ public interface AuthSwagger {
4137
schema = @Schema(implementation = CustomErrorResponse.class)))
4238
})
4339
@PostMapping("/re-issued")
44-
ReIssueTokenResponseDto reissueToken(
45-
@RequestBody @Valid ReIssueTokenRequestDto request
40+
AccessTokenResponseDto reissueToken(
41+
@CookieValue(value = "refreshToken", required = false) String refreshToken,
42+
HttpServletResponse response
4643
);
4744

4845
@Operation(summary = "์˜จ๋ณด๋”ฉ ", description = "ํšŒ์›๊ฐ€์ž… ์‹œ ํ•„์ˆ˜ ์ •๋ณด ์ž…๋ ฅ")
@@ -109,5 +106,8 @@ LoginResponseDto kakaoCallback(
109106
})
110107
@PostMapping("/logout")
111108
@SecurityRequirement(name = "JWT")
112-
void logout(@LoginUser Long userId);
109+
void logout(
110+
@LoginUser Long userId,
111+
HttpServletResponse response
112+
);
113113
}

โ€Žsrc/main/java/sopt/comfit/auth/domain/RefreshTokenRepository.javaโ€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@
55
import java.util.Optional;
66

77
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {
8-
Optional<RefreshToken> findByToken(String refreshTokenStr);
98
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package sopt.comfit.auth.dto;
2+
3+
public record AccessTokenResponseDto(
4+
String accessToken
5+
) {
6+
public static AccessTokenResponseDto from(String accessToken) {
7+
return new AccessTokenResponseDto(accessToken);
8+
}
9+
}

โ€Žsrc/main/java/sopt/comfit/auth/dto/ReIssueTokenResponseDto.javaโ€Ž

Lines changed: 0 additions & 9 deletions
This file was deleted.

โ€Žsrc/main/java/sopt/comfit/auth/service/AuthService.javaโ€Ž

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package sopt.comfit.auth.service;
22

3+
import io.jsonwebtoken.Claims;
34
import lombok.RequiredArgsConstructor;
45
import lombok.extern.slf4j.Slf4j;
5-
import org.springframework.security.crypto.password.PasswordEncoder;
66
import org.springframework.stereotype.Service;
77
import org.springframework.transaction.annotation.Transactional;
88
import sopt.comfit.auth.domain.RefreshToken;
99
import sopt.comfit.auth.domain.RefreshTokenRepository;
10-
import sopt.comfit.auth.dto.ReIssueTokenResponseDto;
1110
import sopt.comfit.auth.dto.command.LoginCommandDto;
1211
import sopt.comfit.auth.dto.command.OnBoardingCommandDto;
1312
import sopt.comfit.auth.dto.query.LoginQueryDto;
14-
import sopt.comfit.auth.dto.request.OnBoardingRequestDTO;
1513
import sopt.comfit.auth.exception.AuthErrorCode;
1614
import sopt.comfit.auth.kakao.dto.KakaoUserApiResponseDto;
15+
import sopt.comfit.global.constants.Constants;
1716
import sopt.comfit.global.dto.JwtDto;
1817
import sopt.comfit.global.exception.BaseException;
1918
import sopt.comfit.global.exception.CommonErrorCode;
@@ -53,27 +52,35 @@ public void logout(Long userId){
5352
refreshTokenRepository.deleteById(userId.toString());
5453
}
5554

56-
public ReIssueTokenResponseDto reissueToken(String refreshTokenStr) {
55+
public JwtDto reissueToken(String refreshTokenStr) {
5756

58-
RefreshToken refreshToken = refreshTokenRepository.findByToken(refreshTokenStr)
59-
.orElseThrow(() -> {
60-
log.warn("๋งŒ๋ฃŒ๋œ ํ† ํฐ์ž…๋‹ˆ๋‹ค.");
61-
return BaseException.type(AuthErrorCode.REFRESH_TOKEN_EXPIRATION);
62-
});
63-
64-
try {
65-
jwtUtil.validateToken(refreshTokenStr);
66-
} catch (Exception e) {
67-
log.warn("RefreshToken ๊ฒ€์ฆ ์‹คํŒจ: {}", e.getMessage());
68-
throw BaseException.type(CommonErrorCode.TOKEN_MALFORMED_ERROR);
57+
if (refreshTokenStr == null) {
58+
throw BaseException.type(CommonErrorCode.REFRESH_TOKEN_EMPTY);
6959
}
7060

71-
Long userId = Long.parseLong(refreshToken.getId());
61+
Claims claims = jwtUtil.validateToken(refreshTokenStr);
62+
63+
Long userId = Long.valueOf(claims.get(Constants.CLAIM_USER_ID).toString());
64+
65+
RefreshToken savedToken = refreshTokenRepository.findById(userId.toString())
66+
.orElseThrow(() -> BaseException.type(AuthErrorCode.REFRESH_TOKEN_EXPIRATION));
67+
68+
if (!savedToken.getToken().equals(refreshTokenStr)) {
69+
throw BaseException.type(AuthErrorCode.REFRESH_TOKEN_EXPIRATION);
70+
}
7271

7372
User user = userRepository.findById(userId)
7473
.orElseThrow(() -> BaseException.type(UserErrorCode.USER_NOT_FOUND));
7574

76-
return ReIssueTokenResponseDto.from(jwtUtil.generateAccessToken(userId, user.getRole()));
75+
refreshTokenRepository.deleteById(userId.toString());
76+
77+
JwtDto jwtDto = jwtUtil.generateTokens(user.getId(), user.getRole());
78+
79+
refreshTokenRepository.save(
80+
RefreshToken.issueRefreshToken(user.getId(), jwtDto.refreshToken())
81+
);
82+
83+
return jwtDto;
7784
}
7885

7986
@Transactional
@@ -111,6 +118,9 @@ public LoginQueryDto registerOrLogin(KakaoUserApiResponseDto dto) {
111118
);
112119

113120
JwtDto jwtDto = jwtUtil.generateTokens(user.getId(), user.getRole());
121+
122+
refreshTokenRepository.save(RefreshToken.issueRefreshToken(user.getId(), jwtDto.refreshToken()));
123+
114124
return LoginQueryDto.of(user.getId(), isNew, user.getName(), jwtDto);
115125
}
116126
}

โ€Žsrc/main/java/sopt/comfit/global/exception/CommonErrorCode.javaโ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public enum CommonErrorCode implements ErrorCode {
3030
TOKEN_UNSUPPORTED_ERROR(HttpStatus.UNAUTHORIZED, "AUTH_401_005", "์ง€์›ํ•˜์ง€์•Š๋Š” ํ† ํฐ์ž…๋‹ˆ๋‹ค."),
3131
TOKEN_UNKNOWN_ERROR(HttpStatus.UNAUTHORIZED, "AUTH_401_006", "์•Œ ์ˆ˜ ์—†๋Š” ํ† ํฐ์ž…๋‹ˆ๋‹ค."),
3232
AUTHENTICATION_USER_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH_401_007", "์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"),
33+
REFRESH_TOKEN_EMPTY(HttpStatus.BAD_REQUEST, "AUTH_400_001", "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค."),
3334

3435
// ===== ์„œ๋ฒ„ ์—๋Ÿฌ (5xx) =====
3536
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "SERVER_500_001", "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."),

โ€Žsrc/main/java/sopt/comfit/global/security/util/JwtUtil.javaโ€Ž

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package sopt.comfit.global.security.util;
22

3-
import io.jsonwebtoken.Claims;
4-
import io.jsonwebtoken.Header;
5-
import io.jsonwebtoken.Jwts;
3+
import io.jsonwebtoken.*;
64
import io.jsonwebtoken.io.Decoders;
75
import io.jsonwebtoken.security.Keys;
86
import lombok.Getter;
@@ -11,6 +9,8 @@
119
import org.springframework.stereotype.Component;
1210
import sopt.comfit.global.constants.Constants;
1311
import sopt.comfit.global.dto.JwtDto;
12+
import sopt.comfit.global.exception.BaseException;
13+
import sopt.comfit.global.exception.CommonErrorCode;
1414
import sopt.comfit.user.domain.ERole;
1515

1616
import java.security.Key;
@@ -39,11 +39,23 @@ public void afterPropertiesSet() throws Exception {
3939
}
4040

4141
public Claims validateToken(String token) {
42-
return Jwts.parserBuilder()
43-
.setSigningKey(key)
44-
.build()
45-
.parseClaimsJws(token)
46-
.getBody();
42+
try {
43+
return Jwts.parserBuilder()
44+
.setSigningKey(key)
45+
.build()
46+
.parseClaimsJws(token)
47+
.getBody();
48+
49+
} catch (ExpiredJwtException e) {
50+
throw BaseException.type(CommonErrorCode.EXPIRED_TOKEN_ERROR);
51+
52+
} catch (UnsupportedJwtException e) {
53+
throw BaseException.type(CommonErrorCode.TOKEN_UNSUPPORTED_ERROR);
54+
55+
} catch (MalformedJwtException e) {
56+
throw BaseException.type(CommonErrorCode.TOKEN_MALFORMED_ERROR);
57+
58+
}
4759
}
4860

4961
private String generateToken(Long id, ERole role, Integer expiration) {
@@ -68,7 +80,4 @@ public JwtDto generateTokens(Long id, ERole role) {
6880
);
6981
}
7082

71-
public String generateAccessToken(Long id, ERole role) {
72-
return generateToken(id, role, accessExpiration);
73-
}
7483
}

0 commit comments

Comments
ย (0)