diff --git a/src/main/java/com/gdsc/hearo/domain/oauth/controller/OauthController.java b/src/main/java/com/gdsc/hearo/domain/oauth/controller/OauthController.java new file mode 100644 index 0000000..4cb4010 --- /dev/null +++ b/src/main/java/com/gdsc/hearo/domain/oauth/controller/OauthController.java @@ -0,0 +1,45 @@ +package com.gdsc.hearo.domain.oauth.controller; + +import com.gdsc.hearo.domain.member.dto.LoginResponseDto; +import com.gdsc.hearo.domain.member.entity.Member; +import com.gdsc.hearo.domain.oauth.dto.GoogleProfile; +import com.gdsc.hearo.domain.oauth.service.OauthService; +import com.gdsc.hearo.global.common.BaseException; +import com.gdsc.hearo.global.common.BaseResponse; +import com.gdsc.hearo.global.common.BaseResponseStatus; +import com.gdsc.hearo.global.security.JwtUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@RestController +@CrossOrigin +@RequiredArgsConstructor +@RequestMapping(value = "/auth") +@Slf4j +public class OauthController { + + private final OauthService oauthService; + private final JwtUtil jwtUtil; + + @GetMapping(value = "/google") + public void socialLoginType() { + oauthService.request(); + } + @GetMapping(value = "/google/callback") + public BaseResponse googleLogin(@RequestParam(name = "code") String code) { + + try { + GoogleProfile googleProfile = oauthService.requestGoogleProfile(code); + Member member = oauthService.googleLogin(googleProfile); + + String accessToken = jwtUtil.createAccessToken(member.getLoginId()); + String refreshToken = jwtUtil.createRefreshToken(member.getLoginId()); + + LoginResponseDto loginResponseDto = new LoginResponseDto(accessToken, refreshToken); + return new BaseResponse<>(BaseResponseStatus.SUCCESS, loginResponseDto); + } catch (BaseException e) { + return new BaseResponse<>(e.getStatus()); + } + } +} diff --git a/src/main/java/com/gdsc/hearo/domain/oauth/dto/GoogleOAuthToken.java b/src/main/java/com/gdsc/hearo/domain/oauth/dto/GoogleOAuthToken.java new file mode 100644 index 0000000..3c94c58 --- /dev/null +++ b/src/main/java/com/gdsc/hearo/domain/oauth/dto/GoogleOAuthToken.java @@ -0,0 +1,12 @@ +package com.gdsc.hearo.domain.oauth.dto; + +import lombok.Data; + +@Data +public class GoogleOAuthToken { + private String access_token; + private String expires_in; + private String scope; + private String token_type; + private String id_token; +} diff --git a/src/main/java/com/gdsc/hearo/domain/oauth/dto/GoogleProfile.java b/src/main/java/com/gdsc/hearo/domain/oauth/dto/GoogleProfile.java new file mode 100644 index 0000000..1d78b3e --- /dev/null +++ b/src/main/java/com/gdsc/hearo/domain/oauth/dto/GoogleProfile.java @@ -0,0 +1,17 @@ +package com.gdsc.hearo.domain.oauth.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown=true) +public class GoogleProfile { + private String id; + private String email; + private String verified_email; + private String name; + private String given_name; + private String family_name; + private String picture; + private String locale; +} diff --git a/src/main/java/com/gdsc/hearo/domain/oauth/service/OauthService.java b/src/main/java/com/gdsc/hearo/domain/oauth/service/OauthService.java new file mode 100644 index 0000000..bd1b356 --- /dev/null +++ b/src/main/java/com/gdsc/hearo/domain/oauth/service/OauthService.java @@ -0,0 +1,60 @@ +package com.gdsc.hearo.domain.oauth.service; + +import com.gdsc.hearo.domain.member.entity.Member; +import com.gdsc.hearo.domain.member.repository.MemberRepository; +import com.gdsc.hearo.domain.oauth.dto.GoogleOAuthToken; +import com.gdsc.hearo.domain.oauth.dto.GoogleProfile; +import com.gdsc.hearo.domain.oauth.service.social.GoogleOauth; +import com.gdsc.hearo.global.common.BaseException; +import com.gdsc.hearo.global.common.BaseResponseStatus; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +@RequiredArgsConstructor +public class OauthService { + + private final GoogleOauth googleOauth; + private final HttpServletResponse response; + private final MemberRepository memberRepository; + + public void request() { + String redirectURL = googleOauth.getOauthRedirectURL(); + try { + response.sendRedirect(redirectURL); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + public GoogleProfile requestGoogleProfile(String code) { + GoogleOAuthToken googleOAuthToken = googleOauth.requestAccessToken(code); + return googleOauth.requestGoogleProfile(googleOAuthToken); + } + + public Member googleLogin(GoogleProfile googleProfile) throws BaseException { + String email = googleProfile.getEmail(); + String googleId = googleProfile.getId(); + String username = googleProfile.getName(); + + Member member = memberRepository.findByLoginId(email); + + if (member != null) { + throw new BaseException(BaseResponseStatus.DUPICATE_USER_ID); + } + Member newMember = Member.builder() + .username(username) + .loginId(email) + .GoogleId(googleId) + .loginType(Member.LoginType.GOOGLE) + .build(); + + memberRepository.save(newMember); + + return newMember; + } +} diff --git a/src/main/java/com/gdsc/hearo/domain/oauth/service/social/GoogleOauth.java b/src/main/java/com/gdsc/hearo/domain/oauth/service/social/GoogleOauth.java new file mode 100644 index 0000000..9e59f94 --- /dev/null +++ b/src/main/java/com/gdsc/hearo/domain/oauth/service/social/GoogleOauth.java @@ -0,0 +1,106 @@ +package com.gdsc.hearo.domain.oauth.service.social; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gdsc.hearo.domain.oauth.dto.GoogleOAuthToken; +import com.gdsc.hearo.domain.oauth.dto.GoogleProfile; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +@Slf4j +public class GoogleOauth { + + @Value("${spring.google.url}") + private String GOOGLE_SNS_BASE_URL; + @Value("${spring.google.client-id}") + private String GOOGLE_SNS_CLIENT_ID; + @Value("${spring.google.callback}") + private String GOOGLE_SNS_CALLBACK_URL; + @Value("${spring.google.secret}") + private String GOOGLE_SNS_CLIENT_SECRET; + @Value("${spring.google.token}") + private String GOOGLE_SNS_TOKEN_BASE_URL; + + public String getOauthRedirectURL() { + Map params = new HashMap<>(); + params.put("scope", "profile email"); + params.put("response_type", "code"); + params.put("client_id", GOOGLE_SNS_CLIENT_ID); + params.put("redirect_uri", GOOGLE_SNS_CALLBACK_URL); + + String parameterString = params.entrySet().stream() + .map(x -> x.getKey() + "=" + x.getValue()) + .collect(Collectors.joining("&")); + + return GOOGLE_SNS_BASE_URL + "?" + parameterString; + } + + public GoogleOAuthToken requestAccessToken(String code) { + RestTemplate restTemplate = new RestTemplate(); + ObjectMapper objectMapper = new ObjectMapper(); + GoogleOAuthToken oAuthToken = null; + + Map params = new HashMap<>(); + params.put("code", code); + params.put("client_id", GOOGLE_SNS_CLIENT_ID); + params.put("client_secret", GOOGLE_SNS_CLIENT_SECRET); + params.put("redirect_uri", GOOGLE_SNS_CALLBACK_URL); + params.put("grant_type", "authorization_code"); + + ResponseEntity response = + restTemplate.postForEntity(GOOGLE_SNS_TOKEN_BASE_URL, params, String.class); + + try { + oAuthToken = objectMapper.readValue(response.getBody(), GoogleOAuthToken.class); + } catch (JsonMappingException e) { + throw new RuntimeException(e); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return oAuthToken; + } + + public GoogleProfile requestGoogleProfile(GoogleOAuthToken oAuthToken) { + + RestTemplate restTemplate = new RestTemplate(); + ObjectMapper objectMapper = new ObjectMapper(); + + HttpHeaders header = new HttpHeaders(); + header.add("Authorization", "Bearer " + oAuthToken.getAccess_token()); + + HttpEntity> googleProfileRequest = new HttpEntity<>(header); + + ResponseEntity googleProfileResponse = restTemplate.exchange( + "https://www.googleapis.com/oauth2/v2/userinfo", + HttpMethod.GET, + googleProfileRequest, + String.class + ); + + GoogleProfile googleProfile = null; + + try { + googleProfile = objectMapper.readValue(googleProfileResponse.getBody(), GoogleProfile.class); + } catch (JsonMappingException e) { + e.printStackTrace(); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + return googleProfile; + } +} diff --git a/src/main/java/com/gdsc/hearo/global/config/SecurityConfig.java b/src/main/java/com/gdsc/hearo/global/config/SecurityConfig.java index 03b1458..fdb2980 100644 --- a/src/main/java/com/gdsc/hearo/global/config/SecurityConfig.java +++ b/src/main/java/com/gdsc/hearo/global/config/SecurityConfig.java @@ -36,6 +36,7 @@ public CorsConfigurationSource corsConfigurationSource() { corsConfig.addAllowedOrigin("http://localhost:3000"); corsConfig.addAllowedOrigin("http://localhost:8080"); + corsConfig.addAllowedOrigin("http://hearo-server.shop:8080"); corsConfig.addAllowedMethod("*"); corsConfig.addAllowedHeader("*"); corsConfig.setAllowCredentials(true); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5a0dec1..f1c96c1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -17,9 +17,18 @@ spring: tokenExpirationTime: ${JWT_TOKEN_EXPIRATION_TIME} refreshTokenExpTime: ${JWT_REFRESH_TOKEN_EXPIRATION_TIME} secret: ${JWT_SECRET_KEY} + google: + url: https://accounts.google.com/o/oauth2/v2/auth + client-id: ${GOOGLE_CLIENT_ID} + callback: ${GOOGLE_REDIRECT_URL} + secret: ${GOOGLE_CLIENT_SECRET} + token: https://oauth2.googleapis.com/token gemini: api: url: ${GEMINI_URL} key: ${GEMINI_KEY} - +server: + ssl: + key-store: /etc/letsencrypt/live/hearo-server.shop/keystore.p12 + key-store-password: ${KEY_STORE_PASSWORD} \ No newline at end of file