Skip to content

Commit ab7cb06

Browse files
authored
Merge pull request #13 from GraduationProject-SayUp/feature/develop
✨ [feature] 카카오 로그인 구현
2 parents 116a62d + 6f900e3 commit ab7cb06

14 files changed

Lines changed: 444 additions & 5 deletions

File tree

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ dependencies {
7373

7474
// AOP
7575
implementation 'org.springframework.boot:spring-boot-starter-aop'
76+
77+
implementation 'org.springframework.boot:spring-boot-starter-webflux'
7678
}
7779

7880
tasks.named('test') {

src/main/java/com/sayup/SayUp/config/SecurityConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
3737
final String[] PUBLIC_URLS = {
3838
"/api/auth/**", // 인증 관련 API
3939
"/swagger-ui/**", // Swagger UI
40-
"/v3/api-docs/**" // OpenAPI 문서
40+
"/v3/api-docs/**", // OpenAPI 문서
41+
"/callback/**" // 카카오 로그인
4142
};
4243

4344
return http

src/main/java/com/sayup/SayUp/controller/AuthController.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
import com.sayup.SayUp.dto.AuthResponseDTO;
55
import com.sayup.SayUp.service.AuthService;
66
import jakarta.validation.Valid;
7+
import lombok.AllArgsConstructor;
78
import org.springframework.http.HttpStatus;
89
import org.springframework.http.ResponseEntity;
910
import org.springframework.web.bind.annotation.*;
1011

12+
import java.util.Map;
13+
1114
@RestController
1215
@RequestMapping("/api/auth")
16+
@AllArgsConstructor
1317
public class AuthController {
1418
private final AuthService authService;
1519

16-
public AuthController(AuthService authService) {
17-
this.authService = authService;
18-
}
19-
2020
/**
2121
* 사용자 회원가입 처리
2222
* @param authRequestDTO 사용자 이메일 및 비밀번호 정보

src/main/java/com/sayup/SayUp/entity/User.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.sayup.SayUp.entity;
22

33
import jakarta.persistence.*;
4+
import lombok.Builder;
45
import lombok.Getter;
56
import lombok.Setter;
67

@@ -13,6 +14,9 @@
1314
@Getter
1415
@Setter
1516
public class User {
17+
public User() {
18+
}
19+
1620
@Id
1721
@GeneratedValue(strategy = GenerationType.IDENTITY)
1822
private Long userId;
@@ -31,6 +35,8 @@ public class User {
3135
@Column(nullable = false, updatable = false)
3236
private LocalDateTime createdAt = LocalDateTime.now();
3337

38+
private String role;
39+
3440
@Override
3541
public boolean equals(Object obj) {
3642
if (this == obj) return true;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.sayup.SayUp.kakao.controller;
2+
3+
import com.sayup.SayUp.dto.AuthResponseDTO;
4+
import com.sayup.SayUp.kakao.dto.KakaoUserInfoResponseDto;
5+
import com.sayup.SayUp.kakao.service.KakaoService;
6+
import com.sayup.SayUp.security.JwtTokenProvider;
7+
import com.sayup.SayUp.service.AuthService;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.RequestMapping;
13+
import org.springframework.web.bind.annotation.RequestParam;
14+
import org.springframework.web.bind.annotation.RestController;
15+
16+
import java.io.IOException;
17+
18+
@Slf4j
19+
@RestController
20+
@RequiredArgsConstructor
21+
@RequestMapping("")
22+
public class KakaoLoginController {
23+
24+
private final KakaoService kakaoService;
25+
private final JwtTokenProvider jwtTokenProvider;
26+
private final AuthService authService;
27+
28+
@GetMapping("/callback")
29+
public ResponseEntity<?> callback(@RequestParam("code") String code) throws IOException {
30+
String accessToken = kakaoService.getAccessTokenFromKakao(code);
31+
32+
KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken);
33+
34+
String email = null;
35+
String jwt = null;
36+
if (userInfo.getKakaoAccount() != null) {
37+
email = userInfo.getKakaoAccount().getEmail();
38+
authService.loadOrCreateUser(email);
39+
jwt = jwtTokenProvider.createTokenFromEmail(email);
40+
}
41+
42+
return ResponseEntity.ok(new AuthResponseDTO(jwt, email));
43+
}
44+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.sayup.SayUp.kakao.controller;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.stereotype.Controller;
5+
import org.springframework.ui.Model;
6+
import org.springframework.web.bind.annotation.GetMapping;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
9+
@Controller
10+
@RequestMapping("/login")
11+
public class KakaoLoginPageController {
12+
13+
@Value("${kakao.client_id}")
14+
private String client_id;
15+
16+
@Value("${kakao.redirect_uri}")
17+
private String redirect_uri;
18+
19+
@GetMapping("/page")
20+
public String loginPage(Model model) {
21+
String location = "https://kauth.kakao.com/oauth/authorize?response_type=code&client_id="+client_id+"&redirect_uri="+redirect_uri;
22+
model.addAttribute("location", location);
23+
24+
return "login";
25+
}
26+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.sayup.SayUp.kakao.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
@AllArgsConstructor
8+
@Builder
9+
@Getter
10+
public class KakaoLoginDto {
11+
12+
public String accessToken;
13+
public String refreshToken;
14+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.sayup.SayUp.kakao.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
8+
@Getter
9+
@NoArgsConstructor //역직렬화를 위한 기본 생성자
10+
@JsonIgnoreProperties(ignoreUnknown = true)
11+
public class KakaoTokenResponseDto {
12+
13+
@JsonProperty("token_type")
14+
public String tokenType;
15+
@JsonProperty("access_token")
16+
public String accessToken;
17+
@JsonProperty("id_token")
18+
public String idToken;
19+
@JsonProperty("expires_in")
20+
public Integer expiresIn;
21+
@JsonProperty("refresh_token")
22+
public String refreshToken;
23+
@JsonProperty("refresh_token_expires_in")
24+
public Integer refreshTokenExpiresIn;
25+
@JsonProperty("scope")
26+
public String scope;
27+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package com.sayup.SayUp.kakao.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
8+
import java.util.Date;
9+
import java.util.HashMap;
10+
11+
@Getter
12+
@NoArgsConstructor //역직렬화를 위한 기본 생성자
13+
@JsonIgnoreProperties(ignoreUnknown = true)
14+
public class KakaoUserInfoResponseDto {
15+
16+
//회원 번호
17+
@JsonProperty("id")
18+
public Long id;
19+
20+
//자동 연결 설정을 비활성화한 경우만 존재.
21+
//true : 연결 상태, false : 연결 대기 상태
22+
@JsonProperty("has_signed_up")
23+
public Boolean hasSignedUp;
24+
25+
//서비스에 연결 완료된 시각. UTC
26+
@JsonProperty("connected_at")
27+
public Date connectedAt;
28+
29+
//카카오싱크 간편가입을 통해 로그인한 시각. UTC
30+
@JsonProperty("synched_at")
31+
public Date synchedAt;
32+
33+
//사용자 프로퍼티
34+
@JsonProperty("properties")
35+
public HashMap<String, String> properties;
36+
37+
//카카오 계정 정보
38+
@JsonProperty("kakao_account")
39+
public KakaoAccount kakaoAccount;
40+
41+
//uuid 등 추가 정보
42+
@JsonProperty("for_partner")
43+
public Partner partner;
44+
45+
@Getter
46+
@NoArgsConstructor
47+
@JsonIgnoreProperties(ignoreUnknown = true)
48+
public class KakaoAccount {
49+
50+
//프로필 정보 제공 동의 여부
51+
@JsonProperty("profile_needs_agreement")
52+
public Boolean isProfileAgree;
53+
54+
//닉네임 제공 동의 여부
55+
@JsonProperty("profile_nickname_needs_agreement")
56+
public Boolean isNickNameAgree;
57+
58+
//프로필 사진 제공 동의 여부
59+
@JsonProperty("profile_image_needs_agreement")
60+
public Boolean isProfileImageAgree;
61+
62+
//사용자 프로필 정보
63+
@JsonProperty("profile")
64+
public Profile profile;
65+
66+
//이름 제공 동의 여부
67+
@JsonProperty("name_needs_agreement")
68+
public Boolean isNameAgree;
69+
70+
//카카오계정 이름
71+
@JsonProperty("name")
72+
public String name;
73+
74+
//이메일 제공 동의 여부
75+
@JsonProperty("email_needs_agreement")
76+
public Boolean isEmailAgree;
77+
78+
//이메일이 유효 여부
79+
// true : 유효한 이메일, false : 이메일이 다른 카카오 계정에 사용돼 만료
80+
@JsonProperty("is_email_valid")
81+
public Boolean isEmailValid;
82+
83+
//이메일이 인증 여부
84+
//true : 인증된 이메일, false : 인증되지 않은 이메일
85+
@JsonProperty("is_email_verified")
86+
public Boolean isEmailVerified;
87+
88+
//카카오계정 대표 이메일
89+
@JsonProperty("email")
90+
public String email;
91+
92+
//연령대 제공 동의 여부
93+
@JsonProperty("age_range_needs_agreement")
94+
public Boolean isAgeAgree;
95+
96+
//연령대
97+
//참고 https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info
98+
@JsonProperty("age_range")
99+
public String ageRange;
100+
101+
//출생 연도 제공 동의 여부
102+
@JsonProperty("birthyear_needs_agreement")
103+
public Boolean isBirthYearAgree;
104+
105+
//출생 연도 (YYYY 형식)
106+
@JsonProperty("birthyear")
107+
public String birthYear;
108+
109+
//생일 제공 동의 여부
110+
@JsonProperty("birthday_needs_agreement")
111+
public Boolean isBirthDayAgree;
112+
113+
//생일 (MMDD 형식)
114+
@JsonProperty("birthday")
115+
public String birthDay;
116+
117+
//생일 타입
118+
// SOLAR(양력) 혹은 LUNAR(음력)
119+
@JsonProperty("birthday_type")
120+
public String birthDayType;
121+
122+
//성별 제공 동의 여부
123+
@JsonProperty("gender_needs_agreement")
124+
public Boolean isGenderAgree;
125+
126+
//성별
127+
@JsonProperty("gender")
128+
public String gender;
129+
130+
//전화번호 제공 동의 여부
131+
@JsonProperty("phone_number_needs_agreement")
132+
public Boolean isPhoneNumberAgree;
133+
134+
//전화번호
135+
//국내 번호인 경우 +82 00-0000-0000 형식
136+
@JsonProperty("phone_number")
137+
public String phoneNumber;
138+
139+
//CI 동의 여부
140+
@JsonProperty("ci_needs_agreement")
141+
public Boolean isCIAgree;
142+
143+
//CI, 연계 정보
144+
@JsonProperty("ci")
145+
public String ci;
146+
147+
//CI 발급 시각, UTC
148+
@JsonProperty("ci_authenticated_at")
149+
public Date ciCreatedAt;
150+
151+
@Getter
152+
@NoArgsConstructor
153+
@JsonIgnoreProperties(ignoreUnknown = true)
154+
public class Profile {
155+
156+
//닉네임
157+
@JsonProperty("nickname")
158+
public String nickName;
159+
160+
//프로필 미리보기 이미지 URL
161+
@JsonProperty("thumbnail_image_url")
162+
public String thumbnailImageUrl;
163+
164+
//프로필 사진 URL
165+
@JsonProperty("profile_image_url")
166+
public String profileImageUrl;
167+
168+
//프로필 사진 URL 기본 프로필인지 여부
169+
//true : 기본 프로필, false : 사용자 등록
170+
@JsonProperty("is_default_image")
171+
public String isDefaultImage;
172+
173+
//닉네임이 기본 닉네임인지 여부
174+
//true : 기본 닉네임, false : 사용자 등록
175+
@JsonProperty("is_default_nickname")
176+
public Boolean isDefaultNickName;
177+
178+
}
179+
}
180+
181+
@Getter
182+
@NoArgsConstructor
183+
@JsonIgnoreProperties(ignoreUnknown = true)
184+
public class Partner {
185+
//고유 ID
186+
@JsonProperty("uuid")
187+
public String uuid;
188+
}
189+
190+
}

0 commit comments

Comments
 (0)