diff --git a/src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java b/src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java index c410b02..53610bb 100644 --- a/src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java +++ b/src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java @@ -39,6 +39,6 @@ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserInterceptor(userService, jwtUtil)) .addPathPatterns("/**") // 스프링 경로는 /*와 /**이 다름 - .excludePathPatterns("/swagger-ui/**", "/v3/api-docs/**", "/oauth2/**", "/on/**", "/health","/message/**"); + .excludePathPatterns("/swagger-ui/**", "/v3/api-docs/**", "/oauth2/**", "/on/**", "/health", "/token/**" , "/message/**"); } } diff --git a/src/main/java/com/onnoff/onnoff/auth/controller/LoginController.java b/src/main/java/com/onnoff/onnoff/auth/controller/LoginController.java index 0230ce0..0cbead2 100644 --- a/src/main/java/com/onnoff/onnoff/auth/controller/LoginController.java +++ b/src/main/java/com/onnoff/onnoff/auth/controller/LoginController.java @@ -3,11 +3,14 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.onnoff.onnoff.apiPayload.ApiResponse; +import com.onnoff.onnoff.apiPayload.code.status.ErrorStatus; +import com.onnoff.onnoff.apiPayload.exception.GeneralException; import com.onnoff.onnoff.auth.UserContext; import com.onnoff.onnoff.auth.dto.LoginRequestDTO; import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse; import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO; import com.onnoff.onnoff.auth.jwt.dto.JwtToken; +import com.onnoff.onnoff.auth.jwt.service.JwtTokenProvider; import com.onnoff.onnoff.auth.jwt.service.JwtUtil; import com.onnoff.onnoff.auth.service.AppleLoginService; import com.onnoff.onnoff.auth.service.KakaoLoginService; @@ -15,6 +18,10 @@ import com.onnoff.onnoff.domain.user.converter.UserConverter; import com.onnoff.onnoff.domain.user.dto.UserResponseDTO; import com.onnoff.onnoff.domain.user.service.UserService; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.InvalidClaimException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -32,6 +39,7 @@ public class LoginController { private final KakaoLoginService kakaoLoginService; private final AppleLoginService appleLoginService; private final UserService userService; + private final JwtTokenProvider jwtTokenProvider; private final JwtUtil jwtUtil; @Value("${kakao.redirect-uri}") @@ -39,7 +47,6 @@ public class LoginController { - /* 테스트용 API, CORS 때문에 직접 호출하지 않고 redirect */ @@ -59,8 +66,7 @@ public String login(){ public ResponseEntity getAccessToken(@RequestParam(name = "code") String code){ TokenResponse tokenResponse = kakaoLoginService.getAccessTokenByCode(code); return ResponseEntity.ok("accessToken="+ tokenResponse.getAccessToken() + - "idToken=" + tokenResponse.getIdToken()); - + "\n\nidToken=" + tokenResponse.getIdToken()); } /* 1. ID 토큰 유효성 검증 @@ -69,7 +75,7 @@ public ResponseEntity getAccessToken(@RequestParam(name = "code") String 4. 응답 헤더에 Jwt 토큰 추가 */ - @Operation(summary = "토큰 검증 API",description = "토큰을 검증 하고 이에 대한 결과를 응답합니다. 추가 정보 입력 여부도 같이 응답 합니다.") + @Operation(summary = "소셜 토큰 검증 API",description = "토큰을 검증 하고 이에 대한 결과를 응답합니다. 추가 정보 입력 여부도 같이 응답 합니다.") @ResponseBody @PostMapping("/oauth2/kakao/token/validate") public ApiResponse validateKakoToken(HttpServletResponse response, @RequestBody LoginRequestDTO.KakaoTokenValidateDTO requestDTO) { @@ -93,16 +99,14 @@ public ApiResponse validateKakoToken(HttpServletRespon user = UserConverter.toUser(userInfo); user = userService.create(user); } - // 응답헤더에 토큰 추가 + // 응답본문에 토큰 추가 JwtToken token = jwtUtil.generateToken(String.valueOf(user.getId())); - response.addHeader("Access-Token", token.getAccessToken()); - response.addHeader("Refresh-Token", token.getRefreshToken()); - return ApiResponse.onSuccess(UserConverter.toLoginDTO(user)); + return ApiResponse.onSuccess(UserConverter.toLoginDTO(user, token.getAccessToken(), token.getRefreshToken())); } @ResponseBody @PostMapping("/oauth2/apple/token/validate") - public ApiResponse validateAppleToken(HttpServletResponse response, @RequestBody LoginRequestDTO.AppleTokenValidateDTO requestDTO) { + public ApiResponse validateAppleToken(HttpServletResponse response, @RequestBody LoginRequestDTO.AppleTokenValidateDTO requestDTO) { // 검증하기 appleLoginService.validate(requestDTO.getIdentityToken()); // 검증 성공 시 리프레시 토큰 발급받아 저장(기한 무제한, 회원탈퇴 시 필요) @@ -120,16 +124,34 @@ public ApiResponse validateAppleToken(HttpServletResponse response, @RequestB } // 응답헤더에 토큰 추가 JwtToken token = jwtUtil.generateToken(String.valueOf(user.getId())); - response.addHeader("Access-Token", token.getAccessToken()); - response.addHeader("Refresh-Token", token.getRefreshToken()); - return ApiResponse.onSuccess(UserConverter.toLoginDTO(user)); + return ApiResponse.onSuccess(UserConverter.toLoginDTO(user, token.getAccessToken(), token.getRefreshToken())); } + @Operation(summary = "서버 토큰 검증 API",description = "토큰의 유효성을 검증하고 액세스 토큰이 만료되었으면" + + "재발급, 리프레시 토큰까지 만료되었으면 오류 응답 추가. 정보 입력 여부도 같이 응답 합니다.") + @ResponseBody + @PostMapping("/token/validate") + public ApiResponse validateServerToken(@RequestBody JwtToken tokenDTO){ + String accessToken = tokenDTO.getAccessToken(); + String refreshToken = tokenDTO.getRefreshToken(); + if( jwtTokenProvider.verifyToken(accessToken) ){ + // accessToken 유효 + String userId = jwtUtil.getUserId(accessToken); + User user = userService.getUser(Long.valueOf(userId)); + UserResponseDTO.LoginDTO loginDTO = UserConverter.toLoginDTO(user, accessToken, refreshToken); + return ApiResponse.onSuccess(loginDTO); + } + if (jwtTokenProvider.verifyToken(refreshToken)) { + //refreshToken 유효 + String userId = jwtUtil.getUserId(refreshToken); + User user = userService.getUser(Long.valueOf(userId)); - @GetMapping("/token/validate") - public ApiResponse validateServerToken(@RequestParam(name = "code") String code){ - TokenResponse tokenResponse = kakaoLoginService.getAccessTokenByCode(code); - return ApiResponse.onSuccess(null); + String newRefreshToken = jwtUtil.generateRefreshToken(jwtUtil.getUserId(refreshToken)); + String newAccessToken = jwtUtil.generateAccessToken(jwtUtil.getUserId(refreshToken)); + return ApiResponse.onSuccess(UserConverter.toLoginDTO(user, newAccessToken, newRefreshToken)); + } + // 둘 다 유효하지 않은 경우 + throw new GeneralException(ErrorStatus.INVALID_TOKEN_ERROR); } /* 테스트용 API diff --git a/src/main/java/com/onnoff/onnoff/auth/dto/LoginRequestDTO.java b/src/main/java/com/onnoff/onnoff/auth/dto/LoginRequestDTO.java index 347bdf3..d9e021b 100644 --- a/src/main/java/com/onnoff/onnoff/auth/dto/LoginRequestDTO.java +++ b/src/main/java/com/onnoff/onnoff/auth/dto/LoginRequestDTO.java @@ -4,23 +4,18 @@ import lombok.Getter; public class LoginRequestDTO { + @Getter public static class AppleTokenValidateDTO{ - @JsonProperty("user") private String oauthId; - @JsonProperty("full_name") private String fullName; private String email; - @JsonProperty("identity_token") private String identityToken; - @JsonProperty("authorization_code") private String authorizationCode; } @Getter public static class KakaoTokenValidateDTO{ - @JsonProperty("identity_token") private String identityToken; - @JsonProperty("access_token") private String accessToken; } } diff --git a/src/main/java/com/onnoff/onnoff/auth/jwt/dto/JwtToken.java b/src/main/java/com/onnoff/onnoff/auth/jwt/dto/JwtToken.java index a208ddc..c5b3fbc 100644 --- a/src/main/java/com/onnoff/onnoff/auth/jwt/dto/JwtToken.java +++ b/src/main/java/com/onnoff/onnoff/auth/jwt/dto/JwtToken.java @@ -1,12 +1,10 @@ package com.onnoff.onnoff.auth.jwt.dto; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; @Getter @AllArgsConstructor +@NoArgsConstructor @Builder public class JwtToken { private String accessToken; diff --git a/src/main/java/com/onnoff/onnoff/auth/jwt/filter/JwtAuthFilter.java b/src/main/java/com/onnoff/onnoff/auth/jwt/filter/JwtAuthFilter.java index ad8c591..c7ee977 100644 --- a/src/main/java/com/onnoff/onnoff/auth/jwt/filter/JwtAuthFilter.java +++ b/src/main/java/com/onnoff/onnoff/auth/jwt/filter/JwtAuthFilter.java @@ -25,7 +25,7 @@ @RequiredArgsConstructor public class JwtAuthFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; - private final static String[] ignorePrefix = {"/swagger-ui", "/v3/api-docs", "/oauth2", "/on", "/health","/message"}; + private final static String[] ignorePrefix = {"/swagger-ui", "/v3/api-docs", "/oauth2", "/on", "/health", "/token/validate" , "/message"}; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { log.info("url ={}", request.getRequestURI()); diff --git a/src/main/java/com/onnoff/onnoff/auth/jwt/filter/UserInterceptor.java b/src/main/java/com/onnoff/onnoff/auth/jwt/filter/UserInterceptor.java index d214769..b208310 100644 --- a/src/main/java/com/onnoff/onnoff/auth/jwt/filter/UserInterceptor.java +++ b/src/main/java/com/onnoff/onnoff/auth/jwt/filter/UserInterceptor.java @@ -36,6 +36,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons return true; } catch (IllegalArgumentException exception){ + log.info("url={} 인터셉터 인증 실패", request.getRequestURI() ); // jwt 필터에서 검증된 토큰이라 예외 안나겠지만 없지만 혹시 모르니 response.sendError(HttpStatus.UNAUTHORIZED.value()); return false; diff --git a/src/main/java/com/onnoff/onnoff/auth/jwt/service/JwtTokenProvider.java b/src/main/java/com/onnoff/onnoff/auth/jwt/service/JwtTokenProvider.java index 87d668a..35226fd 100644 --- a/src/main/java/com/onnoff/onnoff/auth/jwt/service/JwtTokenProvider.java +++ b/src/main/java/com/onnoff/onnoff/auth/jwt/service/JwtTokenProvider.java @@ -43,7 +43,7 @@ public boolean verifyToken(String token) { .getExpiration() .after(new Date()); // 만료 시간이 현재 시간 이후인지 확인하여 유효성 검사 결과를 반환 } catch (Exception e) { - log.info("토큰 만료 = {}", token); + log.info("토큰 검증 실패 = {}", token); return false; } } diff --git a/src/main/java/com/onnoff/onnoff/domain/user/converter/UserConverter.java b/src/main/java/com/onnoff/onnoff/domain/user/converter/UserConverter.java index 327ebc2..d42d469 100644 --- a/src/main/java/com/onnoff/onnoff/domain/user/converter/UserConverter.java +++ b/src/main/java/com/onnoff/onnoff/domain/user/converter/UserConverter.java @@ -23,11 +23,11 @@ public static User toUser(LoginRequestDTO.AppleTokenValidateDTO request){ .socialType(SocialType.APPLE) .build(); } - public static UserResponseDTO.LoginDTO toLoginDTO(User user){ + public static UserResponseDTO.LoginDTO toLoginDTO(User user, String accessToken, String refreshToken){ return UserResponseDTO.LoginDTO.builder() - .id(user.getId()) + .accessToken(accessToken) + .refreshToken(refreshToken) .infoSet(user.isInfoSet()) - .createdAt(user.getCreatedAt()) .build(); } diff --git a/src/main/java/com/onnoff/onnoff/domain/user/dto/UserResponseDTO.java b/src/main/java/com/onnoff/onnoff/domain/user/dto/UserResponseDTO.java index 8482817..b870810 100644 --- a/src/main/java/com/onnoff/onnoff/domain/user/dto/UserResponseDTO.java +++ b/src/main/java/com/onnoff/onnoff/domain/user/dto/UserResponseDTO.java @@ -16,9 +16,9 @@ public class UserResponseDTO { @NoArgsConstructor @AllArgsConstructor public static class LoginDTO{ - private Long id; private boolean infoSet; - private LocalDateTime createdAt; + private String accessToken; + private String refreshToken; } @Builder