diff --git a/src/main/java/space/space_spring/config/WebConfig.java b/src/main/java/space/space_spring/config/WebConfig.java index 3a0fca90..6676d459 100644 --- a/src/main/java/space/space_spring/config/WebConfig.java +++ b/src/main/java/space/space_spring/config/WebConfig.java @@ -22,11 +22,11 @@ public class WebConfig implements WebMvcConfigurer { private final JwtLoginAuthInterceptor jwtLoginAuthInterceptor; + private final UserSpaceValidationInterceptor userSpaceValidationInterceptor; private final JwtLoginAuthHandlerArgumentResolver jwtLoginAuthHandlerArgumentResolver; private final UserSpaceIdHandlerArgumentResolver userSpaceIdHandlerArgumentResolver; private final UserSpaceAuthHandlerArgumentResolver userSpaceAuthHandlerArgumentResolver; - private final UserSpaceValidationInterceptor userSpaceValidationInterceptor; @Override @@ -45,8 +45,6 @@ public void addInterceptors(InterceptorRegistry registry) { for(UserSpaceValidationInterceptorURL url:UserSpaceValidationInterceptorURL.values()) { userSpaceRegistration.addPathPatterns(url.getUrlPattern()); } - - } @Override diff --git a/src/main/java/space/space_spring/controller/TestController.java b/src/main/java/space/space_spring/controller/TestController.java index a2e2f6cd..25f09cb0 100644 --- a/src/main/java/space/space_spring/controller/TestController.java +++ b/src/main/java/space/space_spring/controller/TestController.java @@ -1,6 +1,7 @@ package space.space_spring.controller; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @@ -52,6 +53,4 @@ public BaseResponse LoginPassAnnotaionTest( +""); } - - } diff --git a/src/main/java/space/space_spring/controller/TestOAuthController.java b/src/main/java/space/space_spring/controller/ViewTestController.java similarity index 85% rename from src/main/java/space/space_spring/controller/TestOAuthController.java rename to src/main/java/space/space_spring/controller/ViewTestController.java index 3006d483..327a225f 100644 --- a/src/main/java/space/space_spring/controller/TestOAuthController.java +++ b/src/main/java/space/space_spring/controller/ViewTestController.java @@ -1,27 +1,27 @@ package space.space_spring.controller; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; @Controller -@RequestMapping("/oauth") @Slf4j -public class TestOAuthController { +public class ViewTestController { + /** + * 카카오 로그인 요청 처리 + * 카카오 인증 서버의 인증 및 동의 요청 페이지로 redirect + */ @Value("${oauth.kakao.client.id}") private String clientId; @Value("${oauth.kakao.redirect.uri}") private String redirectUri; - /** - * 카카오 로그인 요청 처리 - * 카카오 인증 서버의 인증 및 동의 요청 페이지로 redirect - */ - @GetMapping("/kakao") + @GetMapping("/oauth/kakao") public String kakaoConnect() { StringBuffer url = new StringBuffer(); url.append("https://kauth.kakao.com/oauth/authorize?"); diff --git a/src/main/java/space/space_spring/dao/UserSpaceDao.java b/src/main/java/space/space_spring/dao/UserSpaceDao.java index 4dbad3eb..147e4175 100644 --- a/src/main/java/space/space_spring/dao/UserSpaceDao.java +++ b/src/main/java/space/space_spring/dao/UserSpaceDao.java @@ -5,8 +5,8 @@ import jakarta.persistence.TypedQuery; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import space.space_spring.dto.user.dto.SpaceChoiceInfo; -import space.space_spring.dto.user.dto.SpaceChoiceViewDto; +import space.space_spring.domain.user.model.dto.SpaceChoiceInfo; +import space.space_spring.domain.user.model.dto.SpaceChoiceViewDto; import space.space_spring.dto.userSpace.UserInfoInSpace; import space.space_spring.entity.Space; import space.space_spring.entity.User; @@ -14,9 +14,6 @@ import space.space_spring.entity.enumStatus.UserSpaceAuth; import java.util.*; -import java.util.stream.Stream; - -import static space.space_spring.entity.enumStatus.UserSpaceAuth.MANAGER; @Repository public class UserSpaceDao { diff --git a/src/main/java/space/space_spring/domain/.DS_Store b/src/main/java/space/space_spring/domain/.DS_Store new file mode 100644 index 00000000..cf1810f3 Binary files /dev/null and b/src/main/java/space/space_spring/domain/.DS_Store differ diff --git a/src/main/java/space/space_spring/controller/OAuthController.java b/src/main/java/space/space_spring/domain/authorization/OAuth/controller/OAuthController.java similarity index 67% rename from src/main/java/space/space_spring/controller/OAuthController.java rename to src/main/java/space/space_spring/domain/authorization/OAuth/controller/OAuthController.java index 631e0414..410350d4 100644 --- a/src/main/java/space/space_spring/controller/OAuthController.java +++ b/src/main/java/space/space_spring/domain/authorization/OAuth/controller/OAuthController.java @@ -1,19 +1,16 @@ -package space.space_spring.controller; +package space.space_spring.domain.authorization.OAuth.controller; import com.fasterxml.jackson.core.JsonProcessingException; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import space.space_spring.domain.authorization.jwt.service.JwtService; +import space.space_spring.domain.authorization.jwt.model.TokenPairDTO; import space.space_spring.dto.oAuth.KakaoInfo; -import space.space_spring.dto.oAuth.OAuthLoginResponse; import space.space_spring.entity.User; -import space.space_spring.response.BaseResponse; -import space.space_spring.service.OAuthService; +import space.space_spring.domain.authorization.OAuth.service.OAuthService; import java.io.IOException; @@ -24,6 +21,7 @@ public class OAuthController { private final OAuthService oAuthService; + private final JwtService jwtService; @Value("${oauth.kakao.client.id}") private String clientId; @@ -69,18 +67,25 @@ public void kakaoCallback(@RequestParam(name = "code") String code, HttpServletR User userByOAuthInfo = oAuthService.findUserByOAuthInfo(kakaoInfo); // TODO 5. 카카오 로그인 유저에게 jwt 발급 - String jwtOAuthLogin = oAuthService.provideJwtToOAuthUser(userByOAuthInfo); - response.setHeader("Authorization", "Bearer " + jwtOAuthLogin); - log.info("jwtOAuthLogin = {}", jwtOAuthLogin); + TokenPairDTO tokenPairDTO = jwtService.provideJwtToOAuthUser(userByOAuthInfo); - // Construct the redirect URL with the JWT and userId as query parameters + // TODO 6. 카카오 로그인 유저에게 발급한 refresh token을 db에 저장 + jwtService.updateRefreshToken(userByOAuthInfo, tokenPairDTO.getRefreshToken()); + + System.out.println("tokenPairDTO.getAccessToken() = " + tokenPairDTO.getAccessToken()); + System.out.println("tokenPairDTO.getRefreshToken() = " + tokenPairDTO.getRefreshToken()); + + // 클라이언트로 response 전달 + // -> 메서드 분리 ?? + // 공백문자가 %20 으로 전달되는 듯 함 -> 프론트 분들과 협의 필요할 듯 String redirectUrl = String.format( - "https://kuit-space.github.io/KUIT-Space-front/login?jwt=Bearer %s&userId=%s", - jwtOAuthLogin, + "https://kuit-space.github.io/KUIT-Space-front/login?access-token=%s&refresh-token=%s&userId=%s", + "Bearer " + tokenPairDTO.getAccessToken(), + "Bearer " + tokenPairDTO.getRefreshToken(), userByOAuthInfo.getUserId() ); - // Redirect to the specified URL response.sendRedirect(redirectUrl); } + } diff --git a/src/main/java/space/space_spring/service/OAuthService.java b/src/main/java/space/space_spring/domain/authorization/OAuth/service/OAuthService.java similarity index 93% rename from src/main/java/space/space_spring/service/OAuthService.java rename to src/main/java/space/space_spring/domain/authorization/OAuth/service/OAuthService.java index 9d8902f7..e4934b5c 100644 --- a/src/main/java/space/space_spring/service/OAuthService.java +++ b/src/main/java/space/space_spring/domain/authorization/OAuth/service/OAuthService.java @@ -1,4 +1,4 @@ -package space.space_spring.service; +package space.space_spring.domain.authorization.OAuth.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -13,9 +13,9 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; +import space.space_spring.domain.user.repository.UserDao; import space.space_spring.dto.oAuth.KakaoInfo; import space.space_spring.entity.User; -import space.space_spring.jwt.JwtLoginProvider; import space.space_spring.util.user.UserUtils; import static space.space_spring.entity.enumStatus.UserSignupType.KAKAO; @@ -25,7 +25,8 @@ public class OAuthService { private final UserUtils userUtils; - private final JwtLoginProvider jwtLoginProvider; + private final UserDao userDao; + /** * 카카오 인증 서버가 전달해준 유저의 인가코드로 토큰 발급 요청 @@ -103,7 +104,7 @@ public User findUserByOAuthInfo(KakaoInfo kakaoInfo) { return userUtils.findOrCreateUserForOAuthInfo(email, nickname, KAKAO); } - public String provideJwtToOAuthUser(User userByOAuthInfo) { - return jwtLoginProvider.generateToken(userByOAuthInfo); - } + + + } diff --git a/src/main/java/space/space_spring/domain/authorization/auth/controller/AuthController.java b/src/main/java/space/space_spring/domain/authorization/auth/controller/AuthController.java new file mode 100644 index 00000000..c2b86e10 --- /dev/null +++ b/src/main/java/space/space_spring/domain/authorization/auth/controller/AuthController.java @@ -0,0 +1,45 @@ +package space.space_spring.domain.authorization.auth.controller; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import space.space_spring.domain.authorization.auth.service.AuthService; +import space.space_spring.domain.user.model.PostLoginDto; +import space.space_spring.exception.CustomException; +import space.space_spring.response.BaseResponse; + +import static space.space_spring.response.status.BaseExceptionResponseStatus.INVALID_USER_LOGIN; +import static space.space_spring.util.bindingResult.BindingResultUtils.getErrorMessage; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/user") +public class AuthController { + + private final AuthService authService; + + /** + * 로그인 + */ + @PostMapping("/login") + public BaseResponse login(@Validated @RequestBody PostLoginDto.Request request, BindingResult bindingResult, HttpServletResponse response) { + if (bindingResult.hasErrors()) { + throw new CustomException(INVALID_USER_LOGIN, getErrorMessage(bindingResult)); + } + + PostLoginDto login = authService.login(request); + + System.out.println("login.getTokenPairDTO().getRefreshToken() = " + login.getTokenPairDTO().getRefreshToken()); + System.out.println("login.getTokenPairDTO().getAccessToken() = " + login.getTokenPairDTO().getAccessToken()); + + response.setHeader("Authorization-refresh", "Bearer " + login.getTokenPairDTO().getRefreshToken()); + response.setHeader("Authorization", "Bearer " + login.getTokenPairDTO().getAccessToken()); + + return new BaseResponse<>(new PostLoginDto.Response(login.getUserId())); + } +} diff --git a/src/main/java/space/space_spring/domain/authorization/auth/service/AuthService.java b/src/main/java/space/space_spring/domain/authorization/auth/service/AuthService.java new file mode 100644 index 00000000..25484a2d --- /dev/null +++ b/src/main/java/space/space_spring/domain/authorization/auth/service/AuthService.java @@ -0,0 +1,72 @@ +package space.space_spring.domain.authorization.auth.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import space.space_spring.domain.authorization.jwt.model.TokenPairDTO; +import space.space_spring.domain.authorization.jwt.model.TokenType; +import space.space_spring.domain.authorization.jwt.repository.JwtRepository; +import space.space_spring.domain.user.model.PostLoginDto; +import space.space_spring.entity.TokenStorage; +import space.space_spring.entity.User; +import space.space_spring.exception.CustomException; +import space.space_spring.jwt.JwtLoginProvider; +import space.space_spring.util.user.UserUtils; + +import static space.space_spring.entity.enumStatus.UserSignupType.LOCAL; +import static space.space_spring.response.status.BaseExceptionResponseStatus.PASSWORD_NO_MATCH; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +public class AuthService { + + private final JwtLoginProvider jwtLoginProvider; + private final JwtRepository jwtRepository; + private final PasswordEncoder passwordEncoder; + private final UserUtils userUtils; + + + @Transactional + public PostLoginDto login(PostLoginDto.Request request) { + // TODO 1. 이메일 존재 여부 확인(아이디 존재 여부 확인) + User userByEmail = userUtils.findUserByEmail(request.getEmail(), LOCAL); + log.info("userByEmail.getUserId: {}", userByEmail.getUserId()); + + // TODO 2. 비밀번호 일치 여부 확인 + validatePassword(userByEmail, request.getPassword()); + + // TODO 3. JWT 발급 -> access token, refresh token 2개 발급 + String accessToken = jwtLoginProvider.generateToken(userByEmail, TokenType.ACCESS); + String refreshToken = jwtLoginProvider.generateToken(userByEmail, TokenType.REFRESH); + + // TODO 4. refresh token db에 저장 + TokenStorage tokenStorage = TokenStorage.builder() + .user(userByEmail) + .tokenValue(refreshToken) + .build(); + jwtRepository.save(tokenStorage); + + // TODO 5. return + TokenPairDTO tokenPairDTO = TokenPairDTO.builder() + .refreshToken(refreshToken) + .accessToken(accessToken) + .build(); + + return PostLoginDto.builder() + .TokenPairDTO(tokenPairDTO) + .userId(userByEmail.getUserId()) + .build(); + } + + private void validatePassword(User userByEmail, String password) { + String encodePassword = userByEmail.getPassword(); + if(!passwordEncoder.matches(password,encodePassword)){ + throw new CustomException(PASSWORD_NO_MATCH); + } + + } +} diff --git a/src/main/java/space/space_spring/domain/authorization/jwt/controller/JwtController.java b/src/main/java/space/space_spring/domain/authorization/jwt/controller/JwtController.java new file mode 100644 index 00000000..b2cd2d93 --- /dev/null +++ b/src/main/java/space/space_spring/domain/authorization/jwt/controller/JwtController.java @@ -0,0 +1,51 @@ +package space.space_spring.domain.authorization.jwt.controller; + +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import space.space_spring.domain.authorization.jwt.service.JwtService; +import space.space_spring.domain.authorization.jwt.model.TokenPairDTO; +import space.space_spring.entity.User; +import space.space_spring.response.BaseResponse; + +import java.io.IOException; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/jwt") +public class JwtController { + + private final JwtService jwtService; + + /** + * 엑세스 토큰 갱신 요청 처리 + * -> 엑세스 토큰, 리프레시 토큰 갱신 (RTR 패턴) + */ + @PostMapping("/new-token") + public BaseResponse updateAccessToken(HttpServletRequest request, HttpServletResponse response) throws IOException { + // access token, refresh token 파싱 + TokenPairDTO tokenPairDTO = jwtService.resolveTokenPair(request); + + // access token 로부터 user find + User userByAccessToken = jwtService.getUserByAccessToken(tokenPairDTO.getAccessToken()); + + // refresh token 유효성 검사 + jwtService.validateRefreshToken(userByAccessToken, tokenPairDTO.getRefreshToken()); + + // access token, refresh token 새로 발급 + TokenPairDTO newTokenPairDTO = jwtService.updateTokenPair(userByAccessToken); + + // response header에 새로 발급한 token pair set + response.setHeader("Authorization-refresh", "Bearer " + newTokenPairDTO.getRefreshToken()); + response.setHeader("Authorization", "Bearer " + newTokenPairDTO.getAccessToken()); + + System.out.println("tokenPairDTO.getAccessToken() = " + newTokenPairDTO.getAccessToken()); + System.out.println("tokenPairDTO.getRefreshToken() = " + newTokenPairDTO.getRefreshToken()); + + // return + return new BaseResponse<>("토큰 갱신 요청 성공"); + } +} diff --git a/src/main/java/space/space_spring/domain/authorization/jwt/model/TokenPairDTO.java b/src/main/java/space/space_spring/domain/authorization/jwt/model/TokenPairDTO.java new file mode 100644 index 00000000..79f64f26 --- /dev/null +++ b/src/main/java/space/space_spring/domain/authorization/jwt/model/TokenPairDTO.java @@ -0,0 +1,16 @@ +package space.space_spring.domain.authorization.jwt.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TokenPairDTO { + + private String refreshToken; + private String accessToken; +} diff --git a/src/main/java/space/space_spring/domain/authorization/jwt/model/TokenType.java b/src/main/java/space/space_spring/domain/authorization/jwt/model/TokenType.java new file mode 100644 index 00000000..aa5f5a1e --- /dev/null +++ b/src/main/java/space/space_spring/domain/authorization/jwt/model/TokenType.java @@ -0,0 +1,6 @@ +package space.space_spring.domain.authorization.jwt.model; + +public enum TokenType { + REFRESH, + ACCESS +} diff --git a/src/main/java/space/space_spring/domain/authorization/jwt/repository/JwtRepository.java b/src/main/java/space/space_spring/domain/authorization/jwt/repository/JwtRepository.java new file mode 100644 index 00000000..dad962d3 --- /dev/null +++ b/src/main/java/space/space_spring/domain/authorization/jwt/repository/JwtRepository.java @@ -0,0 +1,15 @@ +package space.space_spring.domain.authorization.jwt.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import space.space_spring.entity.TokenStorage; +import space.space_spring.entity.User; + +import java.util.Optional; + +@Repository +public interface JwtRepository extends JpaRepository { + + Optional findByUser(User user); + void deleteByUser(User user); +} diff --git a/src/main/java/space/space_spring/domain/authorization/jwt/service/JwtService.java b/src/main/java/space/space_spring/domain/authorization/jwt/service/JwtService.java new file mode 100644 index 00000000..1b3c65af --- /dev/null +++ b/src/main/java/space/space_spring/domain/authorization/jwt/service/JwtService.java @@ -0,0 +1,130 @@ +package space.space_spring.domain.authorization.jwt.service; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import space.space_spring.domain.authorization.jwt.repository.JwtRepository; +import space.space_spring.domain.user.repository.UserRepository; +import space.space_spring.domain.authorization.jwt.model.TokenPairDTO; +import space.space_spring.domain.authorization.jwt.model.TokenType; +import space.space_spring.entity.TokenStorage; +import space.space_spring.entity.User; +import space.space_spring.exception.CustomException; +import space.space_spring.exception.jwt.bad_request.JwtNoTokenException; +import space.space_spring.exception.jwt.bad_request.JwtUnsupportedTokenException; +import space.space_spring.exception.jwt.unauthorized.JwtExpiredTokenException; +import space.space_spring.exception.jwt.unauthorized.JwtUnauthorizedTokenException; +import space.space_spring.jwt.JwtLoginProvider; + +import static space.space_spring.response.status.BaseExceptionResponseStatus.*; + +@Service +@RequiredArgsConstructor +public class JwtService { + + private final JwtRepository jwtRepository; + private final JwtLoginProvider jwtLoginProvider; + private final UserRepository userRepository; + + private static final String JWT_TOKEN_PREFIX = "Bearer "; + + + public TokenPairDTO resolveTokenPair(HttpServletRequest request) { + // TODO 1. access token 파싱 + String accessToken = request.getHeader(HttpHeaders.AUTHORIZATION); + validateToken(accessToken); + + // TODO 2. refresh token 파싱 + String refreshToken = request.getHeader("Authorization-refresh"); + validateToken(refreshToken); + + // TODO 3. return + return TokenPairDTO.builder() + .accessToken(accessToken.substring(JWT_TOKEN_PREFIX.length())) + .refreshToken(refreshToken.substring(JWT_TOKEN_PREFIX.length())) + .build(); + } + + private void validateToken(String token) { + if (token == null) { + throw new JwtNoTokenException(TOKEN_NOT_FOUND); + } + if (!token.startsWith(JWT_TOKEN_PREFIX)) { + throw new JwtUnsupportedTokenException(UNSUPPORTED_TOKEN_TYPE); + } + } + + public User getUserByAccessToken(String accessToken) { + Long userIdFromToken = jwtLoginProvider.getUserIdFromAccessToken(accessToken); + + return userRepository.findByUserId(userIdFromToken) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + } + + @Transactional + public void validateRefreshToken(User user, String refreshToken) { + TokenStorage tokenStorage = jwtRepository.findByUser(user) + .orElseThrow(() -> + { + // db에서 row delete 하는 코드 추가 + jwtRepository.deleteByUser(user); + throw new JwtUnauthorizedTokenException(TOKEN_MISMATCH); + }); + + // TODO 1. refresh token의 만료시간 체크 + if (jwtLoginProvider.isExpiredToken(refreshToken, TokenType.REFRESH)) { + // refresh token이 만료된 경우 -> 예외 발생 -> 유저의 재 로그인 유도 + // db에서 row delete 하는 코드 추가 + jwtRepository.deleteByUser(user); + throw new JwtExpiredTokenException(EXPIRED_REFRESH_TOKEN); + } + + // TODO 2. refresh token이 db에 실제로 존재하는지 체크 + if (!tokenStorage.checkTokenValue(refreshToken)) { + // refresh token이 db에 존재하지 않느 경우 -> 유효하지 않은 refresh token이므로 예외 발생 + // db에서 row delete 하는 코드 추가 + jwtRepository.deleteByUser(user); + throw new JwtUnauthorizedTokenException(TOKEN_MISMATCH); + } + } + + @Transactional + public TokenPairDTO updateTokenPair(User user) { + // TODO 1. new access token, refresh token 발급 + String newAccessToken = jwtLoginProvider.generateToken(user, TokenType.ACCESS); + String newRefreshToken = jwtLoginProvider.generateToken(user, TokenType.REFRESH); + + // TODO 2. db의 refresh token update + TokenStorage tokenStorage = jwtRepository.findByUser(user) + .orElseThrow(() -> new JwtUnauthorizedTokenException(TOKEN_MISMATCH)); + + tokenStorage.updateTokenValue(newRefreshToken); + + // TODO 3. return + return TokenPairDTO.builder() + .accessToken(newAccessToken) + .refreshToken(newRefreshToken) + .build(); + } + + public TokenPairDTO provideJwtToOAuthUser(User userByOAuthInfo) { + String accessToken = jwtLoginProvider.generateToken(userByOAuthInfo, TokenType.ACCESS); + String refreshToken = jwtLoginProvider.generateToken(userByOAuthInfo, TokenType.REFRESH); + + return TokenPairDTO.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + + @Transactional + public void updateRefreshToken(User user, String refreshToken) { + TokenStorage tokenStorage = jwtRepository.findByUser(user) + .orElseThrow(() -> new JwtUnauthorizedTokenException(TOKEN_MISMATCH)); + + tokenStorage.updateTokenValue(refreshToken); + } + +} diff --git a/src/main/java/space/space_spring/controller/UserController.java b/src/main/java/space/space_spring/domain/user/controller/UserController.java similarity index 65% rename from src/main/java/space/space_spring/controller/UserController.java rename to src/main/java/space/space_spring/domain/user/controller/UserController.java index dd2d405e..ccf0f455 100644 --- a/src/main/java/space/space_spring/controller/UserController.java +++ b/src/main/java/space/space_spring/domain/user/controller/UserController.java @@ -1,25 +1,19 @@ -package space.space_spring.controller; +package space.space_spring.domain.user.controller; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import space.space_spring.dto.user.GetUserProfileListDto; import space.space_spring.argumentResolver.jwtLogin.JwtLoginAuth; -import space.space_spring.dto.user.PostLoginDto; -import space.space_spring.dto.user.request.PostUserLoginRequest; -import space.space_spring.dto.user.request.PostUserSignupRequest; -import space.space_spring.dto.user.response.GetSpaceInfoForUserResponse; +import space.space_spring.domain.user.model.GetUserProfileListDto; +import space.space_spring.domain.user.model.request.PostUserSignupRequest; +import space.space_spring.domain.user.model.response.GetSpaceInfoForUserResponse; import space.space_spring.exception.CustomException; import space.space_spring.response.BaseResponse; -import space.space_spring.service.UserService; +import space.space_spring.domain.user.service.UserService; import space.space_spring.util.userSpace.UserSpaceUtils; -import java.util.stream.Collectors; - import static space.space_spring.response.status.BaseExceptionResponseStatus.*; import static space.space_spring.util.bindingResult.BindingResultUtils.getErrorMessage; @@ -46,20 +40,6 @@ public BaseResponse signup(@Validated @RequestBody PostUserSignupRequest return new BaseResponse<>("로컬 회원가입 성공"); } - /** - * 로그인 - */ - @PostMapping("/login") - public BaseResponse login(@Validated @RequestBody PostLoginDto.Request request, BindingResult bindingResult, HttpServletResponse response) { - if (bindingResult.hasErrors()) { - throw new CustomException(INVALID_USER_LOGIN, getErrorMessage(bindingResult)); - } - - PostLoginDto login = userService.login(request); - response.setHeader("Authorization", "Bearer " + login.getJwt()); - - return new BaseResponse<>(new PostLoginDto.Response(login.getUserId())); - } /** * 유저가 속한 스페이스 리스트 @@ -81,4 +61,5 @@ public BaseResponse showUserProfileList(@JwtLogi return new BaseResponse<>(userService.getUserProfileList(userId)); } + } diff --git a/src/main/java/space/space_spring/dto/user/GetUserProfileListDto.java b/src/main/java/space/space_spring/domain/user/model/GetUserProfileListDto.java similarity index 85% rename from src/main/java/space/space_spring/dto/user/GetUserProfileListDto.java rename to src/main/java/space/space_spring/domain/user/model/GetUserProfileListDto.java index 39899402..add4fec0 100644 --- a/src/main/java/space/space_spring/dto/user/GetUserProfileListDto.java +++ b/src/main/java/space/space_spring/domain/user/model/GetUserProfileListDto.java @@ -1,8 +1,7 @@ -package space.space_spring.dto.user; +package space.space_spring.domain.user.model; import lombok.AllArgsConstructor; import lombok.Getter; -import org.checkerframework.checker.units.qual.A; import java.util.List; diff --git a/src/main/java/space/space_spring/dto/user/PostLoginDto.java b/src/main/java/space/space_spring/domain/user/model/PostLoginDto.java similarity index 83% rename from src/main/java/space/space_spring/dto/user/PostLoginDto.java rename to src/main/java/space/space_spring/domain/user/model/PostLoginDto.java index ca2f6068..e33d2ba0 100644 --- a/src/main/java/space/space_spring/dto/user/PostLoginDto.java +++ b/src/main/java/space/space_spring/domain/user/model/PostLoginDto.java @@ -1,19 +1,17 @@ -package space.space_spring.dto.user; +package space.space_spring.domain.user.model; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; +import space.space_spring.domain.authorization.jwt.model.TokenPairDTO; @Getter @NoArgsConstructor @AllArgsConstructor +@Builder public class PostLoginDto { - private String jwt; - + private TokenPairDTO TokenPairDTO; private Long userId; @Getter diff --git a/src/main/java/space/space_spring/dto/user/dto/SpaceChoiceInfo.java b/src/main/java/space/space_spring/domain/user/model/dto/SpaceChoiceInfo.java similarity index 80% rename from src/main/java/space/space_spring/dto/user/dto/SpaceChoiceInfo.java rename to src/main/java/space/space_spring/domain/user/model/dto/SpaceChoiceInfo.java index c62eb499..60d86060 100644 --- a/src/main/java/space/space_spring/dto/user/dto/SpaceChoiceInfo.java +++ b/src/main/java/space/space_spring/domain/user/model/dto/SpaceChoiceInfo.java @@ -1,4 +1,4 @@ -package space.space_spring.dto.user.dto; +package space.space_spring.domain.user.model.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/space/space_spring/dto/user/dto/SpaceChoiceViewDto.java b/src/main/java/space/space_spring/domain/user/model/dto/SpaceChoiceViewDto.java similarity index 82% rename from src/main/java/space/space_spring/dto/user/dto/SpaceChoiceViewDto.java rename to src/main/java/space/space_spring/domain/user/model/dto/SpaceChoiceViewDto.java index 64f58dbe..c4b4bf2e 100644 --- a/src/main/java/space/space_spring/dto/user/dto/SpaceChoiceViewDto.java +++ b/src/main/java/space/space_spring/domain/user/model/dto/SpaceChoiceViewDto.java @@ -1,4 +1,4 @@ -package space.space_spring.dto.user.dto; +package space.space_spring.domain.user.model.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/space/space_spring/dto/user/request/PostUserLoginRequest.java b/src/main/java/space/space_spring/domain/user/model/request/PostUserLoginRequest.java similarity index 93% rename from src/main/java/space/space_spring/dto/user/request/PostUserLoginRequest.java rename to src/main/java/space/space_spring/domain/user/model/request/PostUserLoginRequest.java index 763beb83..00640b9a 100644 --- a/src/main/java/space/space_spring/dto/user/request/PostUserLoginRequest.java +++ b/src/main/java/space/space_spring/domain/user/model/request/PostUserLoginRequest.java @@ -1,4 +1,4 @@ -package space.space_spring.dto.user.request; +package space.space_spring.domain.user.model.request; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; diff --git a/src/main/java/space/space_spring/dto/user/request/PostUserSignupRequest.java b/src/main/java/space/space_spring/domain/user/model/request/PostUserSignupRequest.java similarity index 94% rename from src/main/java/space/space_spring/dto/user/request/PostUserSignupRequest.java rename to src/main/java/space/space_spring/domain/user/model/request/PostUserSignupRequest.java index bf797af5..1258ab0d 100644 --- a/src/main/java/space/space_spring/dto/user/request/PostUserSignupRequest.java +++ b/src/main/java/space/space_spring/domain/user/model/request/PostUserSignupRequest.java @@ -1,4 +1,4 @@ -package space.space_spring.dto.user.request; +package space.space_spring.domain.user.model.request; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; diff --git a/src/main/java/space/space_spring/dto/user/response/GetSpaceInfoForUserResponse.java b/src/main/java/space/space_spring/domain/user/model/response/GetSpaceInfoForUserResponse.java similarity index 69% rename from src/main/java/space/space_spring/dto/user/response/GetSpaceInfoForUserResponse.java rename to src/main/java/space/space_spring/domain/user/model/response/GetSpaceInfoForUserResponse.java index 86f73c48..e9b60a0f 100644 --- a/src/main/java/space/space_spring/dto/user/response/GetSpaceInfoForUserResponse.java +++ b/src/main/java/space/space_spring/domain/user/model/response/GetSpaceInfoForUserResponse.java @@ -1,8 +1,8 @@ -package space.space_spring.dto.user.response; +package space.space_spring.domain.user.model.response; import lombok.AllArgsConstructor; import lombok.Getter; -import space.space_spring.dto.user.dto.SpaceChoiceInfo; +import space.space_spring.domain.user.model.dto.SpaceChoiceInfo; import java.util.List; diff --git a/src/main/java/space/space_spring/dao/UserDao.java b/src/main/java/space/space_spring/domain/user/repository/UserDao.java similarity index 79% rename from src/main/java/space/space_spring/dao/UserDao.java rename to src/main/java/space/space_spring/domain/user/repository/UserDao.java index 8918331c..503657ce 100644 --- a/src/main/java/space/space_spring/dao/UserDao.java +++ b/src/main/java/space/space_spring/domain/user/repository/UserDao.java @@ -1,4 +1,4 @@ -package space.space_spring.dao; +package space.space_spring.domain.user.repository; import jakarta.persistence.EntityManager; import jakarta.persistence.NoResultException; @@ -8,6 +8,8 @@ import space.space_spring.entity.User; import space.space_spring.entity.enumStatus.UserSignupType; +import java.util.Optional; + @Repository public class UserDao { @@ -53,4 +55,15 @@ public User findUserByUserId(Long userId) { return em.find(User.class, userId); } + public Optional findUserByRefreshToken(String refreshToken) { + String jpql = "SELECT u FROM User u WHERE u.refreshToken = :refreshToken AND u.status = 'ACTIVE'"; + TypedQuery query = em.createQuery(jpql, User.class); + query.setParameter("refreshToken", refreshToken); + + try { + return Optional.of(query.getSingleResult()); + } catch (NoResultException e) { + return Optional.empty(); + } + } } diff --git a/src/main/java/space/space_spring/domain/user/repository/UserRepository.java b/src/main/java/space/space_spring/domain/user/repository/UserRepository.java new file mode 100644 index 00000000..60d0dc3e --- /dev/null +++ b/src/main/java/space/space_spring/domain/user/repository/UserRepository.java @@ -0,0 +1,15 @@ +package space.space_spring.domain.user.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import space.space_spring.entity.User; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + + @Query("SELECT u FROM User u WHERE u.userId = :userId AND u.status = 'ACTIVE'") + Optional findByUserId(Long userId); +} diff --git a/src/main/java/space/space_spring/service/UserService.java b/src/main/java/space/space_spring/domain/user/service/UserService.java similarity index 71% rename from src/main/java/space/space_spring/service/UserService.java rename to src/main/java/space/space_spring/domain/user/service/UserService.java index 9420a2d9..e743db8e 100644 --- a/src/main/java/space/space_spring/service/UserService.java +++ b/src/main/java/space/space_spring/domain/user/service/UserService.java @@ -1,4 +1,4 @@ -package space.space_spring.service; +package space.space_spring.domain.user.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -6,23 +6,19 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import space.space_spring.dao.UserSpaceDao; -import space.space_spring.dto.user.GetUserProfileListDto; -import space.space_spring.dto.user.PostLoginDto; -import space.space_spring.dto.user.dto.SpaceChoiceViewDto; -import space.space_spring.dto.user.request.PostUserLoginRequest; -import space.space_spring.dto.user.request.PostUserSignupRequest; -import space.space_spring.dto.user.response.GetSpaceInfoForUserResponse; +import space.space_spring.domain.user.model.GetUserProfileListDto; +import space.space_spring.domain.user.model.dto.SpaceChoiceViewDto; +import space.space_spring.domain.user.model.request.PostUserSignupRequest; +import space.space_spring.domain.user.model.response.GetSpaceInfoForUserResponse; import space.space_spring.entity.UserSpace; import space.space_spring.entity.enumStatus.UserSignupType; import space.space_spring.exception.CustomException; -import space.space_spring.jwt.JwtLoginProvider; -import space.space_spring.dao.UserDao; +import space.space_spring.domain.user.repository.UserDao; import space.space_spring.entity.User; import space.space_spring.util.user.UserUtils; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import static space.space_spring.entity.enumStatus.UserSignupType.LOCAL; import static space.space_spring.response.status.BaseExceptionResponseStatus.*; @@ -33,7 +29,6 @@ public class UserService { private final UserDao userDao; - private final JwtLoginProvider jwtLoginProvider; private final UserSpaceDao userSpaceDao; private final UserUtils userUtils; private final PasswordEncoder passwordEncoder; @@ -61,32 +56,6 @@ private void validateEmailForLocalSignup(String email) { } } - @Transactional - public PostLoginDto login(PostLoginDto.Request request) { - // TODO 1. 이메일 존재 여부 확인(아이디 존재 여부 확인) - User userByEmail = userUtils.findUserByEmail(request.getEmail(), LOCAL); - log.info("userByEmail.getUserId: {}", userByEmail.getUserId()); - - // TODO 2. 비밀번호 일치 여부 확인 - validatePassword(userByEmail, request.getPassword()); - - // TODO 3. JWT 발급 - String jwtLogin = jwtLoginProvider.generateToken(userByEmail); - log.info("jwtLogin: {}", jwtLogin); - - return new PostLoginDto( - jwtLogin, - userByEmail.getUserId() - ); - } - - private void validatePassword(User userByEmail, String password) { - String encodePassword = userByEmail.getPassword(); - if(!passwordEncoder.matches(password,encodePassword)){ - throw new CustomException(PASSWORD_NO_MATCH); - } - - } @Transactional public GetSpaceInfoForUserResponse getSpaceListForUser(Long userId, int size, Long lastUserSpaceId) { diff --git a/src/main/java/space/space_spring/dto/jwt/JwtPayloadDto.java b/src/main/java/space/space_spring/dto/jwt/JwtPayloadDto.java deleted file mode 100644 index f697f852..00000000 --- a/src/main/java/space/space_spring/dto/jwt/JwtPayloadDto.java +++ /dev/null @@ -1,30 +0,0 @@ -package space.space_spring.dto.jwt; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; - -import java.util.ArrayList; -import java.util.List; - -@Getter -@NoArgsConstructor -@ToString -public class JwtPayloadDto { - - private Long userId; - - private List userSpaceList = new ArrayList<>(); - - public void saveUserIdToJwt(Long userId) { - this.userId = userId; - } - - public void saveUserSpaceList(List userSpaceList) { - this.userSpaceList = userSpaceList; - } - - public void addJwtUserSpaceAuth(JwtUserSpaceAuthDto jwtUserSpaceAuthDto) { - this.userSpaceList.add(jwtUserSpaceAuthDto); - } -} diff --git a/src/main/java/space/space_spring/dto/jwt/JwtUserSpaceAuthDto.java b/src/main/java/space/space_spring/dto/jwt/JwtUserSpaceAuthDto.java deleted file mode 100644 index ab6c4f56..00000000 --- a/src/main/java/space/space_spring/dto/jwt/JwtUserSpaceAuthDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package space.space_spring.dto.jwt; - -import lombok.Getter; -import lombok.ToString; - -import java.util.HashMap; -import java.util.Map; - -@Getter -@ToString -public class JwtUserSpaceAuthDto { - - private Map userSpaceAuthMap = new HashMap<>(); - - public void saveUserSpaceAuth(Long spaceId, String userSpaceAuth) { - userSpaceAuthMap.put(spaceId, userSpaceAuth); - } -} diff --git a/src/main/java/space/space_spring/entity/TokenStorage.java b/src/main/java/space/space_spring/entity/TokenStorage.java new file mode 100644 index 00000000..5519d8e2 --- /dev/null +++ b/src/main/java/space/space_spring/entity/TokenStorage.java @@ -0,0 +1,36 @@ +package space.space_spring.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "Token_Storage") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TokenStorage { + + @Id @GeneratedValue + @Column(name = "token_storage_id") + private Long tokenStorageId; + + @OneToOne +// @Column(name = "user_id") + private User user; + + @Column(name = "token_value") + private String tokenValue; + + public void updateTokenValue(String tokenValue) { + this.tokenValue = tokenValue; + } + + public boolean checkTokenValue(String tokenValue) { + return this.tokenValue.equals(tokenValue); + } + +} diff --git a/src/main/java/space/space_spring/interceptor/UserSpaceValidationInterceptor.java b/src/main/java/space/space_spring/interceptor/UserSpaceValidationInterceptor.java index 80031af8..8775541a 100644 --- a/src/main/java/space/space_spring/interceptor/UserSpaceValidationInterceptor.java +++ b/src/main/java/space/space_spring/interceptor/UserSpaceValidationInterceptor.java @@ -9,7 +9,7 @@ import org.springframework.web.servlet.HandlerMapping; import space.space_spring.argumentResolver.userSpace.CheckUserSpace; import space.space_spring.dao.SpaceDao; -import space.space_spring.dao.UserDao; +import space.space_spring.domain.user.repository.UserDao; import space.space_spring.dao.UserSpaceDao; import space.space_spring.entity.Space; import space.space_spring.entity.User; diff --git a/src/main/java/space/space_spring/interceptor/jwtLogin/JwtLoginAuthInterceptor.java b/src/main/java/space/space_spring/interceptor/jwtLogin/JwtLoginAuthInterceptor.java index d196a683..03bcfd59 100644 --- a/src/main/java/space/space_spring/interceptor/jwtLogin/JwtLoginAuthInterceptor.java +++ b/src/main/java/space/space_spring/interceptor/jwtLogin/JwtLoginAuthInterceptor.java @@ -6,6 +6,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; +import space.space_spring.domain.authorization.jwt.model.TokenType; import space.space_spring.exception.jwt.bad_request.JwtNoTokenException; import space.space_spring.exception.jwt.bad_request.JwtUnsupportedTokenException; import space.space_spring.exception.jwt.unauthorized.JwtExpiredTokenException; @@ -23,23 +24,21 @@ public class JwtLoginAuthInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // TODO 1. request header에서 access token 파싱 String accessToken = resolveAccessToken(request); - validateAccessToken(accessToken); - // jwt에서 userId get - Long userIdFromToken = jwtLoginProvider.getUserIdFromToken(accessToken); + // TODO 2. AT 유효성 검사 + if (jwtLoginProvider.isExpiredToken(accessToken, TokenType.ACCESS)) { + throw new JwtExpiredTokenException(EXPIRED_ACCESS_TOKEN); + } + + // TODO 3. AT 의 payload 로 부터 userId 값 get + Long userIdFromToken = jwtLoginProvider.getUserIdFromAccessToken(accessToken); request.setAttribute("userId", userIdFromToken); return true; } - private void validateAccessToken(String accessToken) { - if (jwtLoginProvider.isExpiredToken(accessToken)) { - throw new JwtExpiredTokenException(EXPIRED_TOKEN); - } - - } - private String resolveAccessToken(HttpServletRequest request) { String token = request.getHeader(HttpHeaders.AUTHORIZATION); validateToken(token); diff --git a/src/main/java/space/space_spring/interceptor/jwtSocket/JwtChannelInterceptor.java b/src/main/java/space/space_spring/interceptor/jwtSocket/JwtChannelInterceptor.java index 38bad767..3c137e09 100644 --- a/src/main/java/space/space_spring/interceptor/jwtSocket/JwtChannelInterceptor.java +++ b/src/main/java/space/space_spring/interceptor/jwtSocket/JwtChannelInterceptor.java @@ -8,6 +8,7 @@ import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.stereotype.Component; +import space.space_spring.domain.authorization.jwt.model.TokenType; import space.space_spring.exception.jwt.bad_request.JwtNoTokenException; import space.space_spring.exception.jwt.bad_request.JwtUnsupportedTokenException; import space.space_spring.exception.jwt.unauthorized.JwtExpiredTokenException; @@ -32,7 +33,7 @@ public Message preSend(Message message, MessageChannel channel) { String validatedToken = validateAccessToken(jwtToken); // 검증 후 사용자 정보를 세션에 저장 - Long userId = jwtLoginProvider.getUserIdFromToken(validatedToken); + Long userId = jwtLoginProvider.getUserIdFromAccessToken(validatedToken); accessor.getSessionAttributes().put("userId", userId); } return message; @@ -51,8 +52,8 @@ private String validateAccessToken(String token) { String tokenWithoutPrefix = token.substring(JWT_TOKEN_PREFIX.length()); // access token 값 validate - if (jwtLoginProvider.isExpiredToken(tokenWithoutPrefix)) { - throw new JwtExpiredTokenException(EXPIRED_TOKEN); + if (jwtLoginProvider.isExpiredToken(tokenWithoutPrefix, TokenType.ACCESS)) { + throw new JwtExpiredTokenException(EXPIRED_ACCESS_TOKEN); } return tokenWithoutPrefix; diff --git a/src/main/java/space/space_spring/jwt/JwtLoginProvider.java b/src/main/java/space/space_spring/jwt/JwtLoginProvider.java index 7427b8c4..998b42c5 100644 --- a/src/main/java/space/space_spring/jwt/JwtLoginProvider.java +++ b/src/main/java/space/space_spring/jwt/JwtLoginProvider.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import space.space_spring.domain.authorization.jwt.model.TokenType; import space.space_spring.entity.User; import space.space_spring.exception.CustomException; import space.space_spring.exception.jwt.bad_request.JwtUnsupportedTokenException; @@ -17,41 +18,76 @@ @Slf4j @Component public class JwtLoginProvider { - @Value("${secret.jwt-login-secret-key}") - private String JWT_LOGIN_SECRET_KEY; + @Value("${secret.jwt.access-secret-key}") + private String ACCESS_SECRET_KEY; - @Value("${secret.jwt-expired-in}") - private Long JWT_EXPIRED_IN; + @Value("${secret.jwt.refresh-secret-key}") + private String REFRESH_SECRET_KEY; + @Value("${secret.jwt.access-expired-in}") + private Long ACCESS_EXPIRED_IN; - public String generateToken(User user) { + @Value("${secret.jwt.refresh-expired-in}") + private Long REFRESH_EXPIRED_IN; + + public String generateToken(User user, TokenType tokenType) { // Claims claims = Jwts.claims().setSubject(jwtPayloadDto.getUserId().toString()); Date now = new Date(); - Date expiration = new Date(now.getTime() + JWT_EXPIRED_IN); + Date expiration = setExpiration(now, tokenType); Long userId = user.getUserId(); + return makeToken(tokenType, userId, now, expiration); + } + + private String makeToken(TokenType tokenType, Long userId, Date now, Date expiration) { + if (tokenType.equals(TokenType.ACCESS)) { + return Jwts.builder() +// .setClaims(claims) + .setIssuedAt(now) + .setExpiration(expiration) + .claim("userId", userId) + .signWith(SignatureAlgorithm.HS256, choiceSecretKey(tokenType)) + .compact(); + } + return Jwts.builder() // .setClaims(claims) .setIssuedAt(now) .setExpiration(expiration) - .claim("userId", userId) - .signWith(SignatureAlgorithm.HS256, JWT_LOGIN_SECRET_KEY) + .signWith(SignatureAlgorithm.HS256, choiceSecretKey(tokenType)) .compact(); } - public boolean isExpiredToken(String accessToken) { + private String choiceSecretKey(TokenType tokenType) { + if (tokenType.equals(TokenType.ACCESS)) { + return ACCESS_SECRET_KEY; + } + return REFRESH_SECRET_KEY; + } + + private Date setExpiration(Date now, TokenType tokenType) { + if (tokenType.equals(TokenType.ACCESS)) { + // 엑세스 토큰 : 1시간 + return new Date(now.getTime() + ACCESS_EXPIRED_IN); + } + + // 리프레쉬 토큰 : 7일 + return new Date(now.getTime() + REFRESH_EXPIRED_IN); + } + + public boolean isExpiredToken(String token, TokenType tokenType) { try { Jws claims = Jwts.parserBuilder() - .setSigningKey(JWT_LOGIN_SECRET_KEY).build() - .parseClaimsJws(accessToken); + .setSigningKey(choiceSecretKey(tokenType)).build() + .parseClaimsJws(token); return claims.getBody().getExpiration().before(new Date()); } catch (ExpiredJwtException e) { return true; - }catch (UnsupportedJwtException e) { + } catch (UnsupportedJwtException e) { throw new JwtUnsupportedTokenException(UNSUPPORTED_TOKEN_TYPE); } catch (MalformedJwtException e) { throw new JwtMalformedTokenException(MALFORMED_TOKEN); @@ -59,19 +95,22 @@ public boolean isExpiredToken(String accessToken) { throw new JwtInvalidTokenException(INVALID_TOKEN); } catch (SignatureException e){ throw new CustomException(WRONG_SIGNATURE_JWT); - }catch (JwtException e) { + } catch (JwtException e) { log.error("[JwtTokenProvider.validateAccessToken]", e); throw e; } } - public Long getUserIdFromToken(String accessToken) { + public Long getUserIdFromAccessToken(String token) { try { Jws claims = Jwts.parserBuilder() - .setSigningKey(JWT_LOGIN_SECRET_KEY).build() - .parseClaimsJws(accessToken); + .setSigningKey(ACCESS_SECRET_KEY).build() + .parseClaimsJws(token); return claims.getBody().get("userId", Long.class); + } catch (ExpiredJwtException e) { + // 만료된 토큰에서 userId 추출 + return e.getClaims().get("userId", Long.class); } catch (JwtException e) { log.error("[JwtTokenProvider.getJwtPayloadDtoFromToken]", e); throw e; diff --git a/src/main/java/space/space_spring/response/status/BaseExceptionResponseStatus.java b/src/main/java/space/space_spring/response/status/BaseExceptionResponseStatus.java index 7fdf826c..eef91e92 100644 --- a/src/main/java/space/space_spring/response/status/BaseExceptionResponseStatus.java +++ b/src/main/java/space/space_spring/response/status/BaseExceptionResponseStatus.java @@ -38,10 +38,11 @@ public enum BaseExceptionResponseStatus implements ResponseStatus { UNSUPPORTED_TOKEN_TYPE(4002, HttpStatus.UNAUTHORIZED, "지원되지 않는 토큰 형식입니다."), INVALID_TOKEN(4003, HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), MALFORMED_TOKEN(4004, HttpStatus.UNAUTHORIZED, "토큰이 올바르게 구성되지 않았습니다."), - EXPIRED_TOKEN(4005, HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), - TOKEN_MISMATCH(4006, HttpStatus.UNAUTHORIZED, "로그인 정보가 토큰 정보와 일치하지 않습니다."), + EXPIRED_ACCESS_TOKEN(4005, HttpStatus.UNAUTHORIZED, "만료된 access token 입니다."), + TOKEN_MISMATCH(4006, HttpStatus.UNAUTHORIZED, "저장된 refresh token 과 전달받은 refresh token 이 일치하지 않습니다. 다시 로그인해야합니다."), CANNOT_FIND_USER_ID(4007, HttpStatus.UNAUTHORIZED,"토큰의 userId정보를 찾을 수 없습니다."), WRONG_SIGNATURE_JWT(4008,HttpStatus.UNAUTHORIZED,"JWT 서명이 잘못 되었습니다."), + EXPIRED_REFRESH_TOKEN(4009, HttpStatus.UNAUTHORIZED, "만료된 refresh token 입니다. 다시 로그인해야합니다."), /** * 5000: User 오류 diff --git a/src/main/java/space/space_spring/service/PayService.java b/src/main/java/space/space_spring/service/PayService.java index e236c87a..74b2eec6 100644 --- a/src/main/java/space/space_spring/service/PayService.java +++ b/src/main/java/space/space_spring/service/PayService.java @@ -4,7 +4,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import space.space_spring.dao.PayDao; -import space.space_spring.dao.UserDao; +import space.space_spring.domain.user.repository.UserDao; import space.space_spring.dao.UserSpaceDao; import space.space_spring.dto.pay.dto.*; import space.space_spring.dto.pay.request.PostPayCreateRequest; @@ -16,10 +16,8 @@ import space.space_spring.util.user.UserUtils; import java.time.ZoneId; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import static space.space_spring.response.status.BaseExceptionResponseStatus.USER_IS_NOT_IN_SPACE; diff --git a/src/main/java/space/space_spring/service/SpaceService.java b/src/main/java/space/space_spring/service/SpaceService.java index e4b47b77..c92f02a8 100644 --- a/src/main/java/space/space_spring/service/SpaceService.java +++ b/src/main/java/space/space_spring/service/SpaceService.java @@ -4,7 +4,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import space.space_spring.dao.SpaceDao; -import space.space_spring.dao.UserDao; +import space.space_spring.domain.user.repository.UserDao; import space.space_spring.dao.UserSpaceDao; import space.space_spring.dto.space.GetSpaceHomeDto; import space.space_spring.dto.space.GetSpaceJoinDto; @@ -22,7 +22,6 @@ import space.space_spring.util.userSpace.UserSpaceUtils; import java.time.ZoneId; -import java.time.format.DateTimeFormatter; import java.util.Optional; import static space.space_spring.response.status.BaseExceptionResponseStatus.USER_IS_NOT_IN_SPACE; diff --git a/src/main/java/space/space_spring/service/VoiceRoomService.java b/src/main/java/space/space_spring/service/VoiceRoomService.java index 7c9b709f..c831ef89 100644 --- a/src/main/java/space/space_spring/service/VoiceRoomService.java +++ b/src/main/java/space/space_spring/service/VoiceRoomService.java @@ -5,7 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.task.TaskExecutor; import org.springframework.stereotype.Service; -import space.space_spring.dao.UserDao; +import space.space_spring.domain.user.repository.UserDao; import space.space_spring.dao.UserSpaceDao; import space.space_spring.dao.VoiceRoomDao; import space.space_spring.dao.VoiceRoomRepository; @@ -13,20 +13,15 @@ import space.space_spring.entity.Space; import space.space_spring.entity.User; import space.space_spring.entity.VoiceRoom; -import space.space_spring.exception.CustomException; import space.space_spring.util.LiveKitUtils; import space.space_spring.util.space.SpaceUtils; -import space.space_spring.util.user.UserUtils; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.stream.Collectors; -import static space.space_spring.response.status.BaseExceptionResponseStatus.USER_IS_NOT_IN_SPACE; - @Service @RequiredArgsConstructor public class VoiceRoomService { diff --git a/src/main/java/space/space_spring/util/user/UserUtils.java b/src/main/java/space/space_spring/util/user/UserUtils.java index d85798c7..7414eabd 100644 --- a/src/main/java/space/space_spring/util/user/UserUtils.java +++ b/src/main/java/space/space_spring/util/user/UserUtils.java @@ -3,14 +3,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import space.space_spring.dao.UserDao; +import space.space_spring.domain.user.repository.UserDao; import space.space_spring.entity.User; import space.space_spring.entity.enumStatus.UserSignupType; import space.space_spring.exception.CustomException; import java.util.UUID; -import static space.space_spring.entity.enumStatus.UserSignupType.LOCAL; import static space.space_spring.response.status.BaseExceptionResponseStatus.*; @Component diff --git a/src/main/java/space/space_spring/util/userSpace/MemoryUserSpaceUtils.java b/src/main/java/space/space_spring/util/userSpace/MemoryUserSpaceUtils.java index a7f34c0e..80b06393 100644 --- a/src/main/java/space/space_spring/util/userSpace/MemoryUserSpaceUtils.java +++ b/src/main/java/space/space_spring/util/userSpace/MemoryUserSpaceUtils.java @@ -4,7 +4,7 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import space.space_spring.dao.SpaceDao; -import space.space_spring.dao.UserDao; +import space.space_spring.domain.user.repository.UserDao; import space.space_spring.dao.UserSpaceDao; import space.space_spring.entity.Space; import space.space_spring.entity.User; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 82c93ceb..fd01133e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -86,10 +86,11 @@ spring: on-profile: "devSecret" secret: - jwt-secret-key: ${JWT_SECRET_KEY} - jwt-login-secret-key: ${JWT_SECRET_KEY_LOGIN} - jwt-user-space-secret-key: ${JWT_SECRET_KEY_USER_SPACE} - jwt-expired-in: ${JWT_EXPIRED_IN:3600000} + jwt: + access-secret-key: ${ACCESS_SECRET_KEY} + refresh-secret-key: ${REFRESH_SECRET_KEY} + access-expired-in: ${ACCESS_EXPIRED_IN} + refresh-expired-in: ${REFRESH_EXPIRED_IN} --- spring: