From f0ff9ffcd669b25e1abf87a2c8a4c406a43dd105 Mon Sep 17 00:00:00 2001 From: oxdjww Date: Mon, 23 Sep 2024 00:12:30 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20mobile=20=EC=A0=84=EC=9A=A9=20accessTok?= =?UTF-8?q?en=20=EB=B0=9C=EA=B8=89=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CoffeeChat/config/SecurityConfig.java | 2 +- .../CoffeeChat/config/jwt/JWTFilter.java | 2 +- .../controller/RefreshTokenController.java | 33 +++++++++---- .../exception/enums/RefreshErrorCode.java | 3 +- .../service/CustomOAuth2UserService.java | 49 +++++++++++++++---- 5 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/soongsil/CoffeeChat/config/SecurityConfig.java b/src/main/java/com/soongsil/CoffeeChat/config/SecurityConfig.java index 9b9de11..14dde83 100644 --- a/src/main/java/com/soongsil/CoffeeChat/config/SecurityConfig.java +++ b/src/main/java/com/soongsil/CoffeeChat/config/SecurityConfig.java @@ -80,7 +80,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .successHandler(customSuccessHandler)) .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 모든 OPTIONS 요청에 대해 인증을 요구하지 않음 - .requestMatchers("/health-check", "/", "/reissue", "/security-check").permitAll() + .requestMatchers("/health-check", "/", "/auth/reissue/**", "/security-check").permitAll() .requestMatchers("/api/v2/users/**", "/auth/**").hasRole("USER") .requestMatchers("/api/v2/possibleDates/**").hasAnyRole("MENTOR", "MENTEE") .requestMatchers("/api/v2/mentors/**").hasAnyRole("MENTOR", "MENTEE") diff --git a/src/main/java/com/soongsil/CoffeeChat/config/jwt/JWTFilter.java b/src/main/java/com/soongsil/CoffeeChat/config/jwt/JWTFilter.java index 288bffa..25933cd 100644 --- a/src/main/java/com/soongsil/CoffeeChat/config/jwt/JWTFilter.java +++ b/src/main/java/com/soongsil/CoffeeChat/config/jwt/JWTFilter.java @@ -37,7 +37,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } String path = request.getRequestURI(); - if (path.startsWith("/health-check") || path.startsWith("/security-check") || path.startsWith("/reissue")) { + if (path.startsWith("/health-check") || path.startsWith("/security-check") || path.startsWith("/auth/reissue")) { System.out.println("jwt필터 통과로직"); filterChain.doFilter(request, response); return; diff --git a/src/main/java/com/soongsil/CoffeeChat/controller/RefreshTokenController.java b/src/main/java/com/soongsil/CoffeeChat/controller/RefreshTokenController.java index a05486a..12e3e98 100644 --- a/src/main/java/com/soongsil/CoffeeChat/controller/RefreshTokenController.java +++ b/src/main/java/com/soongsil/CoffeeChat/controller/RefreshTokenController.java @@ -1,11 +1,14 @@ package com.soongsil.CoffeeChat.controller; -import com.soongsil.CoffeeChat.controller.handler.ApiResponseGenerator; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.soongsil.CoffeeChat.config.jwt.JWTUtil; +import com.soongsil.CoffeeChat.controller.handler.ApiResponseGenerator; +import com.soongsil.CoffeeChat.service.CustomOAuth2UserService; import com.soongsil.CoffeeChat.service.RefreshTokenService; import io.swagger.v3.oas.annotations.Operation; @@ -13,26 +16,38 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; @RestController //RestController=Controller+ResponseBody +@RequestMapping("/auth") @Tag(name = "REFRESHTOKEN", description = "리프레쉬 토큰 관련 api") +@RequiredArgsConstructor public class RefreshTokenController { //Refresh토큰으로 Access토큰 발급 및 2차회원가입 컨트롤러 private final JWTUtil jwtUtil; private final RefreshTokenService refreshTokenService; - public RefreshTokenController(JWTUtil jwtUtil, RefreshTokenService refreshTokenService) { - this.jwtUtil = jwtUtil; - this.refreshTokenService = refreshTokenService; - } + private final CustomOAuth2UserService oAuth2UserService; @PostMapping("/reissue") @Operation(summary = "리프레쉬 토큰으로 액세스 토큰 reissue") @ApiResponse(responseCode = "200", description = "헤더 : access, refresh, loginStatus") - public ResponseEntity> reissue(HttpServletRequest request, HttpServletResponse response) { + public ResponseEntity> reissue(HttpServletRequest request, + HttpServletResponse response) { + return ResponseEntity.ok().body( + ApiResponseGenerator.onSuccessOK( + refreshTokenService.reissueByRefreshToken(request, response) + ) + ); + } + + @PostMapping("/reissue/mobile") + @Operation(summary = "리소스 서버에서 받은 accessToken으로 서비스 accessToken 발급") + @ApiResponse(responseCode = "200", description = "유효한 google accessToken으로 요청시 body로 ROLE_USER 토큰 반환") + public ResponseEntity> issueAccessToken(@RequestParam String accessToken) { return ResponseEntity.ok().body( - ApiResponseGenerator.onSuccessOK( - refreshTokenService.reissueByRefreshToken(request, response) - ) + ApiResponseGenerator.onSuccessOK( + oAuth2UserService.verifyGoogleToken(accessToken) + ) ); } } diff --git a/src/main/java/com/soongsil/CoffeeChat/controller/exception/enums/RefreshErrorCode.java b/src/main/java/com/soongsil/CoffeeChat/controller/exception/enums/RefreshErrorCode.java index badaad7..e7b71fb 100644 --- a/src/main/java/com/soongsil/CoffeeChat/controller/exception/enums/RefreshErrorCode.java +++ b/src/main/java/com/soongsil/CoffeeChat/controller/exception/enums/RefreshErrorCode.java @@ -12,7 +12,8 @@ public enum RefreshErrorCode { REFRESH_NOT_FOUND(HttpStatus.NOT_FOUND, "REFRESH_404", "refresh 토큰이 들어오지 않았습니다."), REFRESH_EXPIRED(HttpStatus.FORBIDDEN, "REFRESH_403", "refresh 토큰이 만료되었습니다."), REFRESH_NOT_MATCHED(HttpStatus.UNAUTHORIZED, "REFRESH_401", "저장되지 않은 refresh 토큰입니다."), - REFRESH_BAD_REQUEST(HttpStatus.BAD_REQUEST, "REFRESH_400", "refresh 토큰이 아닌 다른 종류의 토큰이 들어왔습니다."); + REFRESH_BAD_REQUEST(HttpStatus.BAD_REQUEST, "REFRESH_400", "refresh 토큰이 아닌 다른 종류의 토큰이 들어왔습니다."), + INVALID_TOKEN(HttpStatus.BAD_REQUEST, "GOOGLE_ACCESS_400", "유효하지 않은 Google accessToken입니다."); private final HttpStatusCode httpStatusCode; private final String errorCode; diff --git a/src/main/java/com/soongsil/CoffeeChat/service/CustomOAuth2UserService.java b/src/main/java/com/soongsil/CoffeeChat/service/CustomOAuth2UserService.java index ff71120..a16150a 100644 --- a/src/main/java/com/soongsil/CoffeeChat/service/CustomOAuth2UserService.java +++ b/src/main/java/com/soongsil/CoffeeChat/service/CustomOAuth2UserService.java @@ -1,31 +1,42 @@ package com.soongsil.CoffeeChat.service; -import com.soongsil.CoffeeChat.controller.exception.CustomException; -import com.soongsil.CoffeeChat.dto.Oauth.*; +import static com.soongsil.CoffeeChat.controller.exception.enums.RefreshErrorCode.*; + +import java.util.Map; + import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import com.soongsil.CoffeeChat.config.jwt.JWTUtil; +import com.soongsil.CoffeeChat.controller.exception.CustomException; +import com.soongsil.CoffeeChat.dto.Oauth.CustomOAuth2User; +import com.soongsil.CoffeeChat.dto.Oauth.GoogleResponse; +import com.soongsil.CoffeeChat.dto.Oauth.KakaoResponse; +import com.soongsil.CoffeeChat.dto.Oauth.NaverResponse; +import com.soongsil.CoffeeChat.dto.Oauth.OAuth2Response; +import com.soongsil.CoffeeChat.dto.Oauth.UserDTO; import com.soongsil.CoffeeChat.entity.User; import com.soongsil.CoffeeChat.repository.User.UserRepository; import jakarta.transaction.Transactional; - -import static com.soongsil.CoffeeChat.controller.exception.enums.UserErrorCode.USER_NOT_FOUND; +import lombok.RequiredArgsConstructor; @Service +@RequiredArgsConstructor public class CustomOAuth2UserService extends DefaultOAuth2UserService { private final UserRepository userRepository; - public CustomOAuth2UserService(UserRepository userRepository) { - this.userRepository = userRepository; - } + private final JWTUtil jwtUtil; - private User findUserByUsername(String username){ + private static final String GOOGLE_TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v3/tokeninfo?access_token="; + + private User findUserByUsername(String username) { return userRepository.findByUsername(username) - .orElse(null); + .orElse(null); } //리소스 서버에서 제공되는 유저정보 가져오기 @@ -85,4 +96,24 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic } } + + public String verifyGoogleToken(String accessToken) { + RestTemplate restTemplate = new RestTemplate(); + String url = GOOGLE_TOKEN_INFO_URL + accessToken; + Map tokenInfo = restTemplate.getForObject(url, Map.class); + if (tokenInfo != null && tokenInfo.containsKey("sub")) { + String googleId = (String)tokenInfo.get("sub"); + return jwtUtil.createJwt( + "access", + googleId, + "ROLE_USER", + 180000L + ); + } else { + throw new CustomException( + INVALID_TOKEN.getHttpStatusCode(), + INVALID_TOKEN.getErrorMessage() + ); + } + } }