Skip to content

Commit a368899

Browse files
authored
Merge pull request #21 from gdsc-ssu/feat/#20
2 parents 52aaafb + e02f54c commit a368899

File tree

24 files changed

+778
-146
lines changed

24 files changed

+778
-146
lines changed

.github/workflows/google-cloudrun-docker.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ jobs:
106106
AI_GEMINI_CHAT_PROMPT_LOCATION=${{ secrets.AI_GEMINI_CHAT_PROMPT_LOCATION }},
107107
AI_GEMINI_SUMMARY_PROMPT_LOCATION=${{ secrets.AI_GEMINI_SUMMARY_PROMPT_LOCATION }},
108108
MAIL_USERNAME=${{ secrets.MAIL_USERNAME }},
109-
MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}
109+
JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }},
110+
GOOGLE_REDIRECT=${{ secrets.GOOGLE_REDIRECT }},
111+
KAKAO_REDIRECT=${{ secrets.KAKAO_REDIRECT }}
110112
111113
# 10) 배포 후 URL 출력
112114
- name: 'Show output'

build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ repositories {
2525

2626
dependencies {
2727
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
28-
//implementation 'org.springframework.boot:spring-boot-starter-security'
28+
implementation 'org.springframework.boot:spring-boot-starter-security'
29+
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
30+
implementation 'com.auth0:java-jwt:4.5.0'
2931
implementation 'org.springframework.boot:spring-boot-starter-web'
3032
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
3133

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.gdg.poppet.auth.application.dto.response;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
@Data
8+
@NoArgsConstructor
9+
public class GoogleBasicProfileDTO {
10+
11+
/** Google 고유 식별자 */
12+
private String sub;
13+
14+
/** 사용자 전체 이름 */
15+
private String name;
16+
17+
/** 사용자 Given name */
18+
@JsonProperty("given_name")
19+
private String givenName;
20+
21+
/** 사용자 Family name */
22+
@JsonProperty("family_name")
23+
private String familyName;
24+
25+
/** 프로필 사진 URL */
26+
private String picture;
27+
28+
/** 이메일 주소 */
29+
private String email;
30+
31+
/** 이메일 검증 여부 */
32+
@JsonProperty("email_verified")
33+
private Boolean emailVerified;
34+
35+
/** 로케일 정보 (예: "en", "ko") */
36+
private String locale;
37+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.gdg.poppet.auth.application.dto.response;
2+
3+
import java.util.List;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
@Data
8+
@NoArgsConstructor
9+
public class GoogleExtraProfileDTO {
10+
private List<GenderWrapper> genders;
11+
private List<BirthdayWrapper> birthdays;
12+
13+
@Data public static class GenderWrapper {
14+
private String value;
15+
}
16+
@Data public static class BirthdayWrapper {
17+
private DateWrapper date; // ← date 객체 매핑
18+
}
19+
@Data public static class DateWrapper {
20+
private Integer year;
21+
private Integer month;
22+
private Integer day;
23+
}
24+
}
25+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.gdg.poppet.auth.application.dto.response;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
@Data
8+
@NoArgsConstructor
9+
public class GoogleOAuthTokenDTO {
10+
11+
@JsonProperty("access_token")
12+
private String accessToken;
13+
14+
@JsonProperty("expires_in")
15+
private Long expiresIn;
16+
17+
@JsonProperty("refresh_token")
18+
private String refreshToken;
19+
20+
@JsonProperty("scope")
21+
private String scope;
22+
23+
@JsonProperty("token_type")
24+
private String tokenType;
25+
26+
@JsonProperty("id_token")
27+
private String idToken;
28+
}

src/main/java/com/gdg/poppet/auth/application/dto/response/KakaoProfileDTO.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
@Data
1212
@NoArgsConstructor
1313
public class KakaoProfileDTO {
14-
private long id;
14+
private String id;
1515

1616
@JsonProperty("connected_at")
1717
private String connectedAt;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.gdg.poppet.auth.application.dto.response;
2+
3+
import com.gdg.poppet.user.application.dto.response.UserDto;
4+
5+
public record OAuthResult(String accessToken, UserDto userDto) {
6+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.gdg.poppet.auth.application.service;
22

3+
import com.gdg.poppet.auth.application.dto.response.OAuthResult;
34
import com.gdg.poppet.user.application.dto.response.UserDto;
45
import jakarta.servlet.http.HttpServletResponse;
56

67
public interface AuthService {
7-
UserDto kakaoOAuthLogin(String accessCode, HttpServletResponse httpServletResponse);
8+
OAuthResult kakaoOAuthLogin(String accessCode);
9+
OAuthResult googleOAuthLogin(String accessCode);
810
}

src/main/java/com/gdg/poppet/auth/application/service/AuthServiceImpl.java

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
package com.gdg.poppet.auth.application.service;
22

3+
import com.gdg.poppet.auth.application.dto.response.GoogleExtraProfileDTO;
4+
import com.gdg.poppet.auth.application.dto.response.GoogleOAuthTokenDTO;
5+
import com.gdg.poppet.auth.application.dto.response.GoogleBasicProfileDTO;
36
import com.gdg.poppet.auth.application.dto.response.KakaoOAuthTokenDTO;
47
import com.gdg.poppet.auth.application.dto.response.KakaoProfileDTO;
5-
import com.gdg.poppet.auth.domain.converter.AuthConverter;
8+
import com.gdg.poppet.auth.application.dto.response.OAuthResult;
9+
import com.gdg.poppet.auth.infra.util.GoogleAuthClient;
610
import com.gdg.poppet.auth.infra.util.KakaoAuthClient;
11+
import com.gdg.poppet.email.domain.enums.EmailPeriod;
712
import com.gdg.poppet.user.application.dto.response.UserDto;
13+
import com.gdg.poppet.user.domain.enums.Gender;
14+
import com.gdg.poppet.user.domain.enums.Provider;
815
import com.gdg.poppet.user.domain.model.User;
916
import com.gdg.poppet.user.domain.repository.UserRepository;
10-
import jakarta.servlet.http.HttpServletResponse;
17+
18+
import java.time.LocalDate;
19+
import java.time.Period;
20+
1121
import lombok.RequiredArgsConstructor;
1222
import lombok.extern.slf4j.Slf4j;
1323
import org.springframework.stereotype.Service;
@@ -16,30 +26,103 @@
1626
@RequiredArgsConstructor
1727
@Slf4j
1828
public class AuthServiceImpl implements AuthService {
29+
private final GoogleAuthClient googleAuthClient;
1930
private final KakaoAuthClient kakaoAuthClient;
2031
private final UserRepository userRepository;
32+
private final JwtService jwtService;
33+
2134

2235
@Override
23-
public UserDto kakaoOAuthLogin(String accessCode, HttpServletResponse httpServletResponse) {
36+
public OAuthResult kakaoOAuthLogin(String accessCode) {
2437
// 인가코드로 토근 발급
25-
KakaoOAuthTokenDTO oAuthToken = kakaoAuthClient.requestToken(accessCode);
38+
KakaoOAuthTokenDTO oAuthToken = kakaoAuthClient.requestToken(accessCode).block();
2639
log.info("Kakao OAuth token: {}", oAuthToken);
2740
// 토큰으로 유저정보 가져오기
28-
KakaoProfileDTO kakaoProfile = kakaoAuthClient.requestProfile(oAuthToken);
41+
KakaoProfileDTO kakaoProfile = kakaoAuthClient.requestProfile(oAuthToken).block();
2942
log.info("Kakao profile: {}", kakaoProfile);
3043

44+
Provider provider = Provider.KAKAO;
45+
3146
// 유저정보 ID로 조회 후, 없을 경우 User 생성
32-
User user = userRepository.findByUserId(kakaoProfile.getId())
47+
User user = userRepository.findByUserIdAndProvider(kakaoProfile.getId(), provider)
3348
.orElseGet(() -> createNewUser(kakaoProfile));
3449

35-
return UserDto.of(user.getUsername());
50+
// 4) JWT 생성 + 헤더 추가
51+
String jwt = jwtService.createAccessToken(user.getUserId(), user.getProvider());
52+
UserDto dto = UserDto.of(user.getUsername());
53+
54+
return new OAuthResult(jwt, dto);
55+
}
56+
57+
@Override
58+
public OAuthResult googleOAuthLogin(String accessCode) {
59+
// 1) 코드→토큰, 2) 토큰→프로필
60+
GoogleOAuthTokenDTO token = googleAuthClient.requestToken(accessCode).block();
61+
GoogleBasicProfileDTO profile = googleAuthClient.requestProfile(token.getAccessToken()).block();
62+
GoogleExtraProfileDTO extra = googleAuthClient.requestExtraProfile(token.getAccessToken()).block();
63+
Provider provider = Provider.GOOGLE;
64+
65+
// 3) 외부 ID + Provider 로 사용자 조회
66+
User user = userRepository.findByUserIdAndProvider(profile.getSub(), provider)
67+
.orElseGet(() -> createNewUser(profile, extra));
68+
69+
// 4) JWT 발급 후 헤더 세팅
70+
String jwt = jwtService.createAccessToken(user.getUserId(), user.getProvider());
71+
UserDto dto = UserDto.of(user.getUsername());
72+
73+
return new OAuthResult(jwt, dto);
3674
}
3775

3876
private User createNewUser(KakaoProfileDTO kakaoProfile) {
39-
User newUser = AuthConverter.toUser(kakaoProfile);
40-
newUser.setAge(getEstimatedAge(kakaoProfile.getKakaoAccount().getAgeRange()));
77+
Gender gender = null;
78+
if (kakaoProfile.getKakaoAccount().getGender() != null && !kakaoProfile.getKakaoAccount().getGender().isEmpty()) {
79+
String genderValue = kakaoProfile.getKakaoAccount().getGender();
80+
gender = Gender.fromString(genderValue);
81+
}
82+
83+
int estimatedAge = getEstimatedAge(kakaoProfile.getKakaoAccount().getAgeRange());
84+
85+
return userRepository.save(
86+
User.builder()
87+
.userId(kakaoProfile.getId())
88+
.provider(Provider.KAKAO)
89+
.username(kakaoProfile.getKakaoAccount().getName())
90+
.gender(gender)
91+
.emailPeriod(EmailPeriod.THREE)
92+
.age(estimatedAge)
93+
.build());
94+
}
95+
96+
private User createNewUser(GoogleBasicProfileDTO profile, GoogleExtraProfileDTO extra) {
97+
Gender gender = null;
98+
if (extra.getGenders() != null && !extra.getGenders().isEmpty()) {
99+
String genderValue = extra.getGenders().get(0).getValue();
100+
gender = Gender.fromString(genderValue);
101+
}
102+
103+
// 2) birthday → age 계산
104+
int age = -1;
105+
if (!extra.getBirthdays().isEmpty()) {
106+
GoogleExtraProfileDTO.DateWrapper d = extra.getBirthdays().get(0).getDate();
107+
if (d.getYear() != null) {
108+
age = Period.between(
109+
LocalDate.of(d.getYear(), d.getMonth(), d.getDay()),
110+
LocalDate.now()
111+
).getYears();
112+
}
113+
}
41114

42-
return userRepository.save(newUser);
115+
// 3) User 엔티티 빌드 및 저장
116+
return userRepository.save(
117+
User.builder()
118+
.userId(profile.getSub())
119+
.provider(Provider.GOOGLE)
120+
.username(profile.getName())
121+
.gender(gender) // enum 타입 필드
122+
.age(age) // 계산된 나이
123+
.emailPeriod(EmailPeriod.THREE)
124+
.build()
125+
);
43126
}
44127

45128
// 카카오는 나이를 20대, 30대 형태로 제공해 줌.

0 commit comments

Comments
 (0)