diff --git a/ontime-back/docker-compose.yml b/ontime-back/docker-compose.yml index e41be58..491a6a0 100644 --- a/ontime-back/docker-compose.yml +++ b/ontime-back/docker-compose.yml @@ -48,6 +48,7 @@ services: volumes: - /home/ubuntu/OnTime-back/ontime-back/src/main/resources/application.properties:/app/src/main/resources/application.properties + - /home/ubuntu/OnTime-back/ontime-back/src/main/resources/key/:/app/resources/key/ depends_on: - mysql # mysql 서비스가 실행된 이후에 backend를 실행 diff --git a/ontime-back/src/main/java/devkor/ontime_back/config/SecurityConfig.java b/ontime-back/src/main/java/devkor/ontime_back/config/SecurityConfig.java index 2212a14..b3b8ace 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/config/SecurityConfig.java +++ b/ontime-back/src/main/java/devkor/ontime_back/config/SecurityConfig.java @@ -12,6 +12,7 @@ import devkor.ontime_back.global.oauth.apple.AppleLoginFilter; import devkor.ontime_back.global.oauth.apple.AppleLoginService; import devkor.ontime_back.global.oauth.apple.ApplePublicKeyGenerator; +import devkor.ontime_back.global.oauth.google.GoogleLoginService; import devkor.ontime_back.global.oauth.kakao.KakaoLoginFilter; import devkor.ontime_back.global.oauth.google.GoogleLoginFilter; import devkor.ontime_back.repository.UserRepository; @@ -50,6 +51,7 @@ public class SecurityConfig { private final UserRepository userRepository; private final ObjectMapper objectMapper; private final AppleLoginService appleLoginService; + private final GoogleLoginService googleLoginService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -73,7 +75,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .addFilterBefore(new KakaoLoginFilter("/oauth2/kakao/registerOrLogin", jwtTokenProvider, userRepository), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(new GoogleLoginFilter("/oauth2/google/registerOrLogin", jwtTokenProvider, userRepository), + .addFilterBefore(new GoogleLoginFilter("/oauth2/google/registerOrLogin", googleLoginService, userRepository), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new AppleLoginFilter("/oauth2/apple/registerOrLogin", appleLoginService, userRepository), UsernamePasswordAuthenticationFilter.class) diff --git a/ontime-back/src/main/java/devkor/ontime_back/dto/OAuthGoogleRequestDto.java b/ontime-back/src/main/java/devkor/ontime_back/dto/OAuthGoogleRequestDto.java index d81d89f..89fc467 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/dto/OAuthGoogleRequestDto.java +++ b/ontime-back/src/main/java/devkor/ontime_back/dto/OAuthGoogleRequestDto.java @@ -5,4 +5,5 @@ @Getter public class OAuthGoogleRequestDto { private String accessToken; + private String refreshToken; } diff --git a/ontime-back/src/main/java/devkor/ontime_back/entity/User.java b/ontime-back/src/main/java/devkor/ontime_back/entity/User.java index 41f1d81..e54a38b 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/entity/User.java +++ b/ontime-back/src/main/java/devkor/ontime_back/entity/User.java @@ -55,6 +55,8 @@ public class User { @Setter private String firebaseToken; + private String socialLoginToken; + @OneToOne(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL) private UserSetting userSetting; @@ -98,6 +100,10 @@ public void resetPunctualityScore() { this.latenessCountAfterReset = 0; } + public void updateSocialLoginToken(String refreshToken) { + this.socialLoginToken = refreshToken; + } + //여유 시간 업데이트 public void setSpareTime(Integer newSpareTime) { this.spareTime = newSpareTime; } diff --git a/ontime-back/src/main/java/devkor/ontime_back/global/generallogin/handler/LoginSuccessHandler.java b/ontime-back/src/main/java/devkor/ontime_back/global/generallogin/handler/LoginSuccessHandler.java index b086071..6854325 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/global/generallogin/handler/LoginSuccessHandler.java +++ b/ontime-back/src/main/java/devkor/ontime_back/global/generallogin/handler/LoginSuccessHandler.java @@ -58,7 +58,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String responseBody = String.format( "{ \"status\": \"success\", \"code\": \"200\", \"message\": \"%s\", \"data\": { " + "\"userId\": %d, \"email\": \"%s\", \"name\": \"%s\", " + - "\"spareTime\": \"%s\", \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }", + "\"spareTime\": %d, \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }", msg, user.getId(), user.getEmail(), user.getName(), user.getSpareTime(), user.getNote(), user.getPunctualityScore(), user.getRole().name() ); diff --git a/ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginService.java b/ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginService.java index e7f985f..b94014c 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginService.java +++ b/ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginService.java @@ -73,7 +73,7 @@ public Authentication handleLogin(User user, HttpServletResponse response) throw String refreshToken = jwtTokenProvider.createRefreshToken(); jwtTokenProvider.updateRefreshToken(user.getEmail(), refreshToken); - jwtTokenProvider.sendAccessToken(response, accessToken); + jwtTokenProvider.sendAccessAndRefreshToken(response, accessToken, refreshToken); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); @@ -83,7 +83,7 @@ public Authentication handleLogin(User user, HttpServletResponse response) throw String responseBody = String.format( "{ \"status\": \"success\", \"code\": \"200\", \"message\": \"%s\", \"data\": { " + "\"userId\": %d, \"email\": \"%s\", \"name\": \"%s\", " + - "\"spareTime\": \"%s\", \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }", + "\"spareTime\": %d, \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }", msg, user.getId(), user.getEmail(), user.getName(), user.getSpareTime(), user.getNote(), user.getPunctualityScore(), user.getRole().name() ); @@ -140,8 +140,6 @@ public Claims verifyIdentityToken(String identityToken) throws throw new IllegalArgumentException("Invalid JWT: Issuer mismatch. Expected: " + issuer); } // aud 확인 - log.info("clientId: {}", clientId); - log.info("tokenClaims.getAudience(): {}", tokenClaims.getAudience()); if (!clientId.equals(tokenClaims.getAudience())) { throw new IllegalArgumentException("Invalid JWT: Audience mismatch. Expected: " + clientId); } @@ -172,7 +170,6 @@ public AppleTokenResponseDto getAppleAccessTokenAndRefreshToken(String authCode) APPLE_TOKEN_URL, HttpMethod.POST, requestEntity, JsonNode.class); JsonNode response = responseEntity.getBody(); - log.info("Apple Token Response: {}", response.toString()); ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.treeToValue(response, AppleTokenResponseDto.class); @@ -210,8 +207,6 @@ private String generateClientSecret() throws Exception { public boolean appleLoginRevoked(String appleRefreshToken) throws Exception { log.info("checkAppleLoginRevoked"); String clientSecret = generateClientSecret(); - log.info("client_id: {}", clientId); - log.info("client_secret: {}", clientSecret); String revokeUrl = "https://appleid.apple.com/auth/revoke"; HttpHeaders headers = new HttpHeaders(); @@ -230,10 +225,8 @@ public boolean appleLoginRevoked(String appleRefreshToken) throws Exception { try { ResponseEntity response = restTemplate.exchange( revokeUrl, HttpMethod.POST, requestEntity, String.class); - log.info("response.getStatusCode(): {}", response.getStatusCode()); return response.getStatusCode() != HttpStatus.OK; // -> 토큰이 아직 유효함 } catch (HttpClientErrorException e) { - log.info("e: {}", e); return true; // 요청 실패 -> 이미 철회된 refreshToken } } diff --git a/ontime-back/src/main/java/devkor/ontime_back/global/oauth/google/GoogleLoginFilter.java b/ontime-back/src/main/java/devkor/ontime_back/global/oauth/google/GoogleLoginFilter.java index fd84411..41b42de 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/global/oauth/google/GoogleLoginFilter.java +++ b/ontime-back/src/main/java/devkor/ontime_back/global/oauth/google/GoogleLoginFilter.java @@ -30,14 +30,13 @@ @Slf4j public class GoogleLoginFilter extends AbstractAuthenticationProcessingFilter { - private final JwtTokenProvider jwtTokenProvider; private final UserRepository userRepository; - private static final String GOOGLE_USER_INFO_URL = "https://www.googleapis.com/userinfo/v2/me"; + private final GoogleLoginService googleLoginService; - public GoogleLoginFilter(String defaultFilterProcessesUrl, JwtTokenProvider jwtTokenProvider, UserRepository userRepository) { + public GoogleLoginFilter(String defaultFilterProcessesUrl, GoogleLoginService googleLoginService, UserRepository userRepository) { super(defaultFilterProcessesUrl); - this.jwtTokenProvider = jwtTokenProvider; + this.googleLoginService = googleLoginService; this.userRepository = userRepository; } @@ -46,91 +45,21 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ throws AuthenticationException, IOException, ServletException { ObjectMapper objectMapper = new ObjectMapper(); OAuthGoogleRequestDto oAuthGoogleRequestDto = objectMapper.readValue(request.getInputStream(), OAuthGoogleRequestDto.class); - OAuthGoogleUserDto oAuthGoogleUserInfo = getUserInfoFromAccessToken(oAuthGoogleRequestDto.getAccessToken()); + OAuthGoogleUserDto oAuthGoogleUserInfo = googleLoginService.getUserInfoFromAccessToken(oAuthGoogleRequestDto.getAccessToken()); Optional existingUser = userRepository.findBySocialTypeAndSocialId(SocialType.GOOGLE, oAuthGoogleUserInfo.getSub()); + if (existingUser.isPresent()) { - return handleLogin(existingUser.get(), response); + return googleLoginService.handleLogin(oAuthGoogleRequestDto, existingUser.get(), response); } else { - return handleRegister(oAuthGoogleUserInfo, response); + return googleLoginService.handleRegister(oAuthGoogleRequestDto, oAuthGoogleUserInfo, response); } } - public OAuthGoogleUserDto getUserInfoFromAccessToken(String accessToken) { - RestTemplate restTemplate = new RestTemplate(); - - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", "Bearer " + accessToken); - - HttpEntity entity = new HttpEntity<>(headers); - - ResponseEntity response = restTemplate.exchange( - GOOGLE_USER_INFO_URL, - org.springframework.http.HttpMethod.GET, - entity, - OAuthGoogleUserDto.class - ); - - return response.getBody(); - } - - private Authentication handleLogin(User user, HttpServletResponse response) throws IOException { - - String accessToken = jwtTokenProvider.createAccessToken(user.getEmail(), user.getId()); - String refreshToken = jwtTokenProvider.createRefreshToken(); - - jwtTokenProvider.updateRefreshToken(user.getEmail(), refreshToken); - jwtTokenProvider.sendAccessToken(response, accessToken); - - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - String msg = user.getRole().name().equals("GUEST") ? "유저의 ROLE이 GUEST이므로 온보딩API를 호출해 온보딩을 진행해야합니다." : "로그인에 성공하였습니다."; - // JSON 응답 생성 - String responseBody = String.format( - "{ \"status\": \"success\", \"code\": \"200\", \"message\": \"%s\", \"data\": { " + - "\"userId\": %d, \"email\": \"%s\", \"name\": \"%s\", " + - "\"spareTime\": \"%s\", \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }", - msg, user.getId(), user.getEmail(), user.getName(), - user.getSpareTime(), user.getNote(), user.getPunctualityScore(), user.getRole().name() - ); - response.getWriter().write(responseBody); - response.getWriter().flush(); - return new UsernamePasswordAuthenticationToken(user, null, Collections.singletonList(new SimpleGrantedAuthority(user.getRole().name()))); - } - - private Authentication handleRegister(OAuthGoogleUserDto oAuthGoogleUserDto, HttpServletResponse response) throws IOException { - User newUser = User.builder() - .socialType(SocialType.GOOGLE) - .socialId(oAuthGoogleUserDto.getSub()) - .email(oAuthGoogleUserDto.getEmail()) - .name(oAuthGoogleUserDto.getName()) - .imageUrl(oAuthGoogleUserDto.getPicture()) - .role(Role.GUEST) - .build(); - - User savedUser = userRepository.save(newUser); - - String accessToken = jwtTokenProvider.createAccessToken(newUser.getEmail(), newUser.getId()); - jwtTokenProvider.sendAccessToken(response, accessToken); - - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - - String responseBody = String.format( - "{\"message\": \"%s\", \"role\": \"%s\"}", - "회원가입이 완료되었습니다. ROLE이 GUEST이므로 온보딩이 필요합니다.", - savedUser.getRole().name() - ); - - response.getWriter().write(responseBody); - response.getWriter().flush(); - - return new UsernamePasswordAuthenticationToken(newUser, null, Collections.singletonList(new SimpleGrantedAuthority(newUser.getRole().name()))); - } // 인증 성공 처리 @Override diff --git a/ontime-back/src/main/java/devkor/ontime_back/global/oauth/google/GoogleLoginService.java b/ontime-back/src/main/java/devkor/ontime_back/global/oauth/google/GoogleLoginService.java new file mode 100644 index 0000000..bfcbfa5 --- /dev/null +++ b/ontime-back/src/main/java/devkor/ontime_back/global/oauth/google/GoogleLoginService.java @@ -0,0 +1,112 @@ +package devkor.ontime_back.global.oauth.google; + +import devkor.ontime_back.dto.OAuthGoogleRequestDto; +import devkor.ontime_back.dto.OAuthGoogleUserDto; +import devkor.ontime_back.entity.Role; +import devkor.ontime_back.entity.SocialType; +import devkor.ontime_back.entity.User; +import devkor.ontime_back.global.jwt.JwtTokenProvider; +import devkor.ontime_back.repository.UserRepository; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.Collections; + +@Slf4j +@Service +@RequiredArgsConstructor +public class GoogleLoginService { + + private final JwtTokenProvider jwtTokenProvider; + private final UserRepository userRepository; + private static final String GOOGLE_USER_INFO_URL = "https://www.googleapis.com/userinfo/v2/me"; + + + public OAuthGoogleUserDto getUserInfoFromAccessToken(String accessToken) { + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer " + accessToken); + + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + GOOGLE_USER_INFO_URL, + org.springframework.http.HttpMethod.GET, + entity, + OAuthGoogleUserDto.class + ); + + return response.getBody(); + } + public Authentication handleLogin(OAuthGoogleRequestDto oAuthGoogleRequestDto, User user, HttpServletResponse response) throws IOException { + user.updateSocialLoginToken(oAuthGoogleRequestDto.getRefreshToken()); + + String accessToken = jwtTokenProvider.createAccessToken(user.getEmail(), user.getId()); + String refreshToken = jwtTokenProvider.createRefreshToken(); + + jwtTokenProvider.updateRefreshToken(user.getEmail(), refreshToken); + jwtTokenProvider.sendAccessAndRefreshToken(response, accessToken, refreshToken); + + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + + String msg = user.getRole().name().equals("GUEST") ? "유저의 ROLE이 GUEST이므로 온보딩API를 호출해 온보딩을 진행해야합니다." : "로그인에 성공하였습니다."; + // JSON 응답 생성 + String responseBody = String.format( + "{ \"status\": \"success\", \"code\": \"200\", \"message\": \"%s\", \"data\": { " + + "\"userId\": %d, \"email\": \"%s\", \"name\": \"%s\", " + + "\"spareTime\": %d, \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }", + msg, user.getId(), user.getEmail(), user.getName(), + user.getSpareTime(), user.getNote(), user.getPunctualityScore(), user.getRole().name() + ); + + response.getWriter().write(responseBody); + response.getWriter().flush(); + + return new UsernamePasswordAuthenticationToken(user, null, Collections.singletonList(new SimpleGrantedAuthority(user.getRole().name()))); + } + + public Authentication handleRegister(OAuthGoogleRequestDto oAuthGoogleRequestDto, OAuthGoogleUserDto oAuthGoogleUserDto, HttpServletResponse response) throws IOException { + User newUser = User.builder() + .socialType(SocialType.GOOGLE) + .socialId(oAuthGoogleUserDto.getSub()) + .email(oAuthGoogleUserDto.getEmail()) + .name(oAuthGoogleUserDto.getName()) + .imageUrl(oAuthGoogleUserDto.getPicture()) + .role(Role.GUEST) + .socialLoginToken(oAuthGoogleRequestDto.getRefreshToken()) + .build(); + + User savedUser = userRepository.save(newUser); + + String accessToken = jwtTokenProvider.createAccessToken(newUser.getEmail(), newUser.getId()); + jwtTokenProvider.sendAccessToken(response, accessToken); + + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + + String responseBody = String.format( + "{\"message\": \"%s\", \"role\": \"%s\"}", + "회원가입이 완료되었습니다. ROLE이 GUEST이므로 온보딩이 필요합니다.", + savedUser.getRole().name() + ); + + response.getWriter().write(responseBody); + response.getWriter().flush(); + + return new UsernamePasswordAuthenticationToken(newUser, null, Collections.singletonList(new SimpleGrantedAuthority(newUser.getRole().name()))); + } + + +}