Skip to content

Commit 43c6e26

Browse files
committed
feat: 카카오 로그인 API
1 parent 7d44c34 commit 43c6e26

File tree

6 files changed

+157
-24
lines changed

6 files changed

+157
-24
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.oronaminc.join.member.dto;
2+
3+
public record KakaoLoginRequest(
4+
String code,
5+
String state
6+
) {
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.oronaminc.join.member.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
5+
public record KakaoLoginResponse(
6+
@Schema(description = "회원 id", example = "1001")
7+
Long id
8+
) {
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.oronaminc.join.member.dto;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record KakaoUserResponse(
7+
String email,
8+
String nickname,
9+
String profileImageUrl
10+
) {
11+
}

src/main/java/com/oronaminc/join/member/security/AuthController.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
package com.oronaminc.join.member.security;
22

3-
import io.swagger.v3.oas.annotations.Operation;
4-
import io.swagger.v3.oas.annotations.responses.ApiResponse;
5-
import io.swagger.v3.oas.annotations.responses.ApiResponses;
6-
import io.swagger.v3.oas.annotations.tags.Tag;
7-
import io.swagger.v3.oas.annotations.tags.Tags;
83
import java.util.List;
94

105
import org.springframework.http.HttpStatus;
@@ -24,8 +19,12 @@
2419

2520
import com.oronaminc.join.member.dto.GuestLoginRequest;
2621
import com.oronaminc.join.member.dto.GuestLoginResponse;
22+
import com.oronaminc.join.member.dto.KakaoLoginRequest;
2723
import com.oronaminc.join.member.dto.SessionInfoResponse;
2824

25+
import io.swagger.v3.oas.annotations.Operation;
26+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
27+
import io.swagger.v3.oas.annotations.tags.Tag;
2928
import jakarta.servlet.http.Cookie;
3029
import jakarta.servlet.http.HttpServletRequest;
3130
import jakarta.servlet.http.HttpServletResponse;
@@ -40,6 +39,29 @@
4039
public class AuthController {
4140
private final AuthService authService;
4241

42+
@PostMapping("/kakao")
43+
@ResponseStatus(HttpStatus.OK)
44+
public Long kakaoLogin(
45+
@RequestBody KakaoLoginRequest kakaoLoginRequest,
46+
HttpServletRequest request
47+
) {
48+
MemberDetails memberDetails = authService.kakaoLogin(kakaoLoginRequest.code());
49+
50+
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
51+
memberDetails, null, List.of(new SimpleGrantedAuthority(memberDetails.getRole()))
52+
);
53+
54+
SecurityContext context = SecurityContextHolder.createEmptyContext();
55+
context.setAuthentication(authentication);
56+
SecurityContextHolder.setContext(context);
57+
58+
SecurityContextHolder.getContext().setAuthentication(authentication);
59+
60+
request.getSession(true).setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
61+
62+
return memberDetails.getId();
63+
}
64+
4365
@Operation(
4466
summary = "비회원 로그인",
4567
description = "닉네임을 입력하면 비회원 세션이 생성되고 인증이 설정됩니다. 이후 모든 요청에 세션 인증이 적용됩니다.",

src/main/java/com/oronaminc/join/member/security/AuthService.java

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,26 @@
33
import static com.oronaminc.join.member.util.MemberMapper.*;
44

55
import java.util.Map;
6-
import java.util.Optional;
76

7+
import org.springframework.beans.factory.annotation.Value;
8+
import org.springframework.http.HttpEntity;
9+
import org.springframework.http.HttpHeaders;
10+
import org.springframework.http.HttpMethod;
11+
import org.springframework.http.MediaType;
12+
import org.springframework.http.ResponseEntity;
813
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
9-
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
10-
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
11-
import org.springframework.security.oauth2.core.user.OAuth2User;
1214
import org.springframework.stereotype.Service;
1315
import org.springframework.transaction.annotation.Transactional;
16+
import org.springframework.util.LinkedMultiValueMap;
17+
import org.springframework.util.MultiValueMap;
18+
import org.springframework.web.client.RestTemplate;
1419

1520
import com.oronaminc.join.member.dao.MemberRepository;
1621
import com.oronaminc.join.member.domain.Member;
1722
import com.oronaminc.join.member.dto.GuestLoginRequest;
23+
import com.oronaminc.join.member.dto.KakaoUserResponse;
1824
import com.oronaminc.join.member.service.MemberReader;
25+
import com.oronaminc.join.member.util.MemberMapper;
1926

2027
import lombok.RequiredArgsConstructor;
2128
import lombok.extern.slf4j.Slf4j;
@@ -27,32 +34,91 @@ public class AuthService extends DefaultOAuth2UserService {
2734
private final MemberRepository memberRepository;
2835
private final MemberReader memberReader;
2936

30-
@Override
31-
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
32-
OAuth2User oAuth2User = super.loadUser(userRequest);
33-
Map<String, Object> attributes = oAuth2User.getAttributes();
37+
private final RestTemplate restTemplate = new RestTemplate();
38+
39+
private static final String TOKEN_URI = "https://kauth.kakao.com/oauth/token";
40+
private static final String USER_INFO_URI = "https://kapi.kakao.com/v2/user/me";
41+
42+
@Value("${spring.security.oauth2.client.registration.kakao.client-id}")
43+
private String clientId;
44+
45+
@Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}")
46+
private String redirectUri;
47+
48+
@Value("${spring.security.oauth2.client.registration.kakao.client-secret}")
49+
private String clientSecret;
50+
51+
// @Override
52+
// public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
53+
// OAuth2User oAuth2User = super.loadUser(userRequest);
54+
// Map<String, Object> attributes = oAuth2User.getAttributes();
55+
//
56+
// log.info("attributes :: " + attributes);
57+
//
58+
// Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
59+
// Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
60+
//
61+
//
62+
// Optional<Member> optionalMember = memberReader.findByEmail(kakaoAccount.get("email").toString());
63+
//
64+
// Member member = optionalMember.orElseGet(() -> memberRepository.save(toKakaoMember(kakaoAccount, profile)));
65+
//
66+
// return toOAuth2MemberDetails(member);
67+
// }
3468

35-
log.info("attributes :: " + attributes);
69+
@Transactional
70+
public MemberDetails loadGuest(GuestLoginRequest guestLoginRequest) {
71+
Member guest = toGuestMember(guestLoginRequest);
3672

37-
Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
38-
Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
73+
memberRepository.save(guest);
74+
guest.registerGuest();
3975

76+
return toGuestMemberDetails(guest);
77+
}
4078

41-
Optional<Member> optionalMember = memberReader.findByEmail(kakaoAccount.get("email").toString());
79+
@Transactional
80+
public MemberDetails kakaoLogin(String code) {
81+
String accessToken = getAccessToken(code);
82+
KakaoUserResponse kakaoUser = getUserInfo(accessToken);
4283

43-
Member member = optionalMember.orElseGet(() -> memberRepository.save(toKakaoMember(kakaoAccount, profile)));
84+
Member member = memberRepository.findByEmail(kakaoUser.email())
85+
.orElseGet(() -> memberRepository.save(MemberMapper.toNewKakaoMember(kakaoUser)));
4486

4587
return toOAuth2MemberDetails(member);
4688
}
4789

48-
@Transactional
49-
public MemberDetails loadGuest(GuestLoginRequest guestLoginRequest) {
50-
Member guest = toGuestMember(guestLoginRequest);
90+
private String getAccessToken(String code) {
91+
HttpHeaders headers = new HttpHeaders();
92+
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
5193

52-
memberRepository.save(guest);
53-
guest.registerGuest();
94+
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
95+
params.add("grant_type", "authorization_code");
96+
params.add("client_id", clientId);
97+
params.add("redirect_uri", redirectUri);
98+
params.add("code", code);
99+
params.add("client_secret", clientSecret);
54100

55-
return toGuestMemberDetails(guest);
101+
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
102+
103+
ResponseEntity<Map> response = restTemplate.postForEntity(TOKEN_URI, request, Map.class);
104+
105+
return (String) response.getBody().get("access_token");
56106
}
57107

108+
private KakaoUserResponse getUserInfo(String accessToken) {
109+
HttpHeaders headers = new HttpHeaders();
110+
headers.setBearerAuth(accessToken);
111+
headers.setContentType(MediaType.APPLICATION_JSON);
112+
113+
HttpEntity<Void> entity = new HttpEntity<>(headers);
114+
115+
ResponseEntity<Map> response = restTemplate.exchange(USER_INFO_URI, HttpMethod.GET, entity, Map.class);
116+
117+
Map<String, Object> attributes = response.getBody();
118+
119+
Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
120+
Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
121+
122+
return MemberMapper.toKakaoUserResponse(kakaoAccount, profile);
123+
}
58124
}

src/main/java/com/oronaminc/join/member/util/MemberMapper.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.oronaminc.join.member.domain.Member;
66
import com.oronaminc.join.member.domain.MemberType;
77
import com.oronaminc.join.member.dto.GuestLoginRequest;
8+
import com.oronaminc.join.member.dto.KakaoUserResponse;
89
import com.oronaminc.join.member.security.MemberDetails;
910

1011
import lombok.AccessLevel;
@@ -47,4 +48,21 @@ public static Member toKakaoMember(Map<String, Object> kakaoAccount, Map<String,
4748
.memberType(MemberType.MEMBER)
4849
.build();
4950
}
51+
52+
public static Member toNewKakaoMember(KakaoUserResponse kakaoUser) {
53+
return Member.builder()
54+
.email(kakaoUser.email())
55+
.nickname(kakaoUser.nickname())
56+
.profileImage(kakaoUser.profileImageUrl())
57+
.memberType(MemberType.MEMBER)
58+
.build();
59+
}
60+
61+
public static KakaoUserResponse toKakaoUserResponse(Map<String, Object> kakaoAccount, Map<String, Object> profile) {
62+
return KakaoUserResponse.builder()
63+
.email((String) kakaoAccount.get("email"))
64+
.nickname((String) profile.get("nickname"))
65+
.profileImageUrl((String) profile.get("profile_image_url"))
66+
.build();
67+
}
5068
}

0 commit comments

Comments
 (0)