Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,11 @@ public ApiResponse<CheckNicknameResponseDTO> checkNickname(@RequestParam String
}
return ApiResponse.onSuccess(UserConverter.toCheckNicknameResponseDto(nickname, true));
}

// refresh token 재발급이요 진짜 제발 되길 바라요 제발요
Comment thread
ye-zin marked this conversation as resolved.
@GetMapping("/user/refresh")
Comment thread
ye-zin marked this conversation as resolved.
@Operation(summary = "refreshToken 재발급", security = {@SecurityRequirement(name = "JWT TOKEN")})
public ApiResponse<RefreshTokenResponseDTO> reissueRefreshToken(HttpServletRequest request) {
return ApiResponse.onSuccess(userService.reissueRefreshToken(request));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package EatPic.spring.domain.user.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
@AllArgsConstructor
public class RefreshTokenResponseDTO {
private String accessToken;
private String refreshToken;
private Long accessTokenExpiresIn;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import EatPic.spring.domain.user.dto.request.LoginRequestDTO;
import EatPic.spring.domain.user.dto.request.UserRequest;
import EatPic.spring.domain.user.dto.response.LoginResponseDTO;
import EatPic.spring.domain.user.dto.response.RefreshTokenResponseDTO;
import EatPic.spring.domain.user.dto.response.UserResponseDTO;
import EatPic.spring.domain.user.dto.request.SignupRequestDTO;
import EatPic.spring.domain.user.dto.response.SignupResponseDTO;
Expand All @@ -21,6 +22,7 @@ public interface UserService {
boolean isEmailDuplicate(String email);
boolean isnameIdDuplicate(String nameId);
boolean isNicknameDuplicate(String nickname);
RefreshTokenResponseDTO reissueRefreshToken(HttpServletRequest request);

// UserQueryService
UserInfoDTO getUserInfo(HttpServletRequest request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import EatPic.spring.domain.user.dto.request.SignupRequestDTO;
import EatPic.spring.domain.user.dto.request.UserRequest;
import EatPic.spring.domain.user.dto.response.LoginResponseDTO;
import EatPic.spring.domain.user.dto.response.RefreshTokenResponseDTO;
import EatPic.spring.domain.user.dto.response.SignupResponseDTO;
import EatPic.spring.domain.user.dto.response.UserResponseDTO;
import EatPic.spring.domain.user.entity.User;
Expand All @@ -18,6 +19,7 @@
import EatPic.spring.global.common.code.status.ErrorStatus;
import EatPic.spring.global.common.exception.GeneralException;
import EatPic.spring.global.common.exception.handler.ExceptionHandler;
import EatPic.spring.global.config.Properties.JwtProperties;
import EatPic.spring.global.config.jwt.JwtTokenProvider;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -46,6 +48,7 @@ public class UserServiceImpl implements UserService{
private final UserBadgeService userBadgeService;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
private final JwtProperties jwtProperties;

// s3 설정
private final AmazonS3Manager s3Manager;
Expand Down Expand Up @@ -126,6 +129,72 @@ public UserInfoDTO getUserInfo(HttpServletRequest request) {
return UserConverter.toUserInfoDTO(user);
}

// refreshToken 재발급
@Override
public RefreshTokenResponseDTO reissueRefreshToken(HttpServletRequest request){
Comment thread
ye-zin marked this conversation as resolved.
// refresh token 추출
String requestRefreshToken = jwtTokenProvider.resolveToken(request);

// 토큰이 없으면 예외 발생
if (!jwtTokenProvider.validateRefreshToken(requestRefreshToken)){
throw new ExceptionHandler(ErrorStatus.INVALID_TOKEN);
}

// 토큰에서 이메일 추출, 사용자 정보 조회
final String email = jwtTokenProvider.getSubject(requestRefreshToken);

User user = userRepository.findByEmail(email)
.orElseThrow(() -> new ExceptionHandler(ErrorStatus.MEMBER_NOT_FOUND));
Comment thread
ye-zin marked this conversation as resolved.

// 저장된 refresh token과 요청으로 들어온 token의 일치 여부 확인
final String storedRefreshToken = user.getRefreshToken();

if (!requestRefreshToken.equals(user.getRefreshToken())) {
throw new ExceptionHandler(ErrorStatus.INVALID_TOKEN);
}

// if (storedRefreshToken == null || !storedRefreshToken.equals(requestRefreshToken)) {
// throw new ExceptionHandler(ErrorStatus.INVALID_TOKEN);
// }
Comment thread
ye-zin marked this conversation as resolved.
Outdated

// access token 재발급
Authentication authentication = new UsernamePasswordAuthenticationToken(
user.getEmail(), null,
//Collections.emptyList()
Collections.singleton(() -> user.getRole().name())
);

String newAccessToken = jwtTokenProvider.generateToken(authentication);

// refresh token 재발급 필요 여부 확인
// access -> 30시간, refresh -> 5일
// 재발급 임계일 설정 -> 3일
boolean needReissueRefreshToken = ExpireWithinDays(requestRefreshToken, 3);
Comment thread
ye-zin marked this conversation as resolved.
Outdated
String oldRefreshToken = storedRefreshToken;

if (needReissueRefreshToken) {
oldRefreshToken = jwtTokenProvider.generateRefreshToken(user.getEmail());
user.updateRefreshToken(oldRefreshToken);
userRepository.save(user);
}

// long accessExpiredtime = System.currentTimeMillis() + jwtProperties.getAccessTokenValidity();

return RefreshTokenResponseDTO.builder()
.accessToken(newAccessToken)
.refreshToken(oldRefreshToken)
.accessTokenExpiresIn(jwtProperties.getAccessTokenValidity())
.build();
}

// refreshToken이 유효 기간 이내에 만료되는지 체크
private boolean ExpireWithinDays(String jwt, int days) {
Comment thread
ye-zin marked this conversation as resolved.
Outdated
long isRemained = jwtTokenProvider.getExpriedTime(jwt) - System.currentTimeMillis();
long threshold = days * 24L * 60L * 60L * 1000L;

return isRemained <= threshold;
}

// 팔로잉한 유저의 프로필 아이콘 목록 조회
@Override
public UserResponseDTO.UserIconListResponseDto followingUserIconList(HttpServletRequest request,int page, int size) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public String generateToken(Authentication authentication) {

return Jwts.builder()
.setSubject(email)
.claim("tokenType", "accessToken")
.claim(ROLES, roles) // 🔹 roles 클레임 추가
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getAccessTokenValidity()))
Expand All @@ -66,14 +67,15 @@ public String generateToken(Authentication authentication) {
public String generateRefreshToken(String email) {
return Jwts.builder()
.setSubject(email)
.claim("tokenType", "refreshToken")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getRefreshTokenValidity()))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}


// WT 토큰이 유효한지 검증
// JWT 토큰이 유효한지 검증
public boolean validateToken(String token) {
try {
Jwts.parser()
Expand Down Expand Up @@ -126,9 +128,64 @@ public static String resolveToken(HttpServletRequest request) {
// getAuthentication 메소드를 이용해서 Spring Security의 Authentication 객체로 변환
public Authentication extractAuthentication(HttpServletRequest request){
String accessToken = resolveToken(request);
if(accessToken == null || !validateToken(accessToken)) {
if(accessToken == null || !validateAccessToken(accessToken)) {
throw new ExceptionHandler(ErrorStatus.INVALID_TOKEN);
}
return getAuthentication(accessToken);
}

// accessToken 유효성 검증
public boolean validateAccessToken(String accessToken){
try{
Claims claims = Jwts.parser()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(accessToken)
.getBody();

String type = claims.get("tokenType", String.class);

return "accessToken".equals(type) && claims.getExpiration().after(new Date());
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}

// refreshToken 유효성 검증
public boolean validateRefreshToken(String refreshToken) {
try{
Claims claims = Jwts.parser()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(refreshToken)
.getBody();

String type = claims.get("tokenType", String.class);

return "refreshToken".equals(type) && claims.getExpiration().after(new Date());
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
Comment thread
ye-zin marked this conversation as resolved.
Outdated

// token의 email 꺼내기
public String getSubject(String token) {
return Jwts.parser()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}

// token 만료 시간
public long getExpriedTime(String token) {
Comment thread
ye-zin marked this conversation as resolved.
Outdated
Date exp = Jwts.parser()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody()
.getExpiration();
return exp.getTime();
}
}
Loading