Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.oronaminc.join.member.dto;

public record KakaoLoginRequest(
String code,
String state
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.oronaminc.join.member.dto;

import io.swagger.v3.oas.annotations.media.Schema;

public record KakaoLoginResponse(
@Schema(description = "회원 id", example = "1001")
Long id
) {
}
11 changes: 11 additions & 0 deletions src/main/java/com/oronaminc/join/member/dto/KakaoUserResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.oronaminc.join.member.dto;

import lombok.Builder;

@Builder
public record KakaoUserResponse(
String email,
String nickname,
String profileImageUrl
) {
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package com.oronaminc.join.member.security;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.tags.Tags;
import java.util.List;

import org.springframework.http.HttpStatus;
Expand All @@ -24,8 +19,13 @@

import com.oronaminc.join.member.dto.GuestLoginRequest;
import com.oronaminc.join.member.dto.GuestLoginResponse;
import com.oronaminc.join.member.dto.KakaoLoginRequest;
import com.oronaminc.join.member.dto.KakaoLoginResponse;
import com.oronaminc.join.member.dto.SessionInfoResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -40,6 +40,33 @@
public class AuthController {
private final AuthService authService;

@Operation(
summary = "카카오 로그인",
description = "redirect url 에 포함된 파라미터의 code와 state를 입력해주세요. 이후 모든 요청에 세션 인증이 적용됩니다."
)
@PostMapping("/kakao")
@ResponseStatus(HttpStatus.OK)
public KakaoLoginResponse kakaoLogin(
@RequestBody KakaoLoginRequest kakaoLoginRequest,
HttpServletRequest request
) {
MemberDetails memberDetails = authService.kakaoLogin(kakaoLoginRequest.code());

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
memberDetails, null, List.of(new SimpleGrantedAuthority(memberDetails.getRole()))
);

SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);

SecurityContextHolder.getContext().setAuthentication(authentication);

request.getSession(true).setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);

return new KakaoLoginResponse(memberDetails.getId());
}

@Operation(
summary = "비회원 로그인",
description = "닉네임을 입력하면 비회원 세션이 생성되고 인증이 설정됩니다. 이후 모든 요청에 세션 인증이 적용됩니다.",
Expand Down
104 changes: 85 additions & 19 deletions src/main/java/com/oronaminc/join/member/security/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@
import static com.oronaminc.join.member.util.MemberMapper.*;

import java.util.Map;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import com.oronaminc.join.member.dao.MemberRepository;
import com.oronaminc.join.member.domain.Member;
import com.oronaminc.join.member.dto.GuestLoginRequest;
import com.oronaminc.join.member.dto.KakaoUserResponse;
import com.oronaminc.join.member.service.MemberReader;
import com.oronaminc.join.member.util.MemberMapper;

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

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
Map<String, Object> attributes = oAuth2User.getAttributes();
private final RestTemplate restTemplate = new RestTemplate();

private static final String TOKEN_URI = "https://kauth.kakao.com/oauth/token";
private static final String USER_INFO_URI = "https://kapi.kakao.com/v2/user/me";

@Value("${spring.security.oauth2.client.registration.kakao.client-id}")
private String clientId;

@Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}")
private String redirectUri;

@Value("${spring.security.oauth2.client.registration.kakao.client-secret}")
private String clientSecret;

// @Override
// public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// OAuth2User oAuth2User = super.loadUser(userRequest);
// Map<String, Object> attributes = oAuth2User.getAttributes();
//
// log.info("attributes :: " + attributes);
//
// Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
// Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
//
//
// Optional<Member> optionalMember = memberReader.findByEmail(kakaoAccount.get("email").toString());
//
// Member member = optionalMember.orElseGet(() -> memberRepository.save(toKakaoMember(kakaoAccount, profile)));
//
// return toOAuth2MemberDetails(member);
// }

log.info("attributes :: " + attributes);
@Transactional
public MemberDetails loadGuest(GuestLoginRequest guestLoginRequest) {
Member guest = toGuestMember(guestLoginRequest);

Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
memberRepository.save(guest);
guest.registerGuest();

return toGuestMemberDetails(guest);
}

Optional<Member> optionalMember = memberReader.findByEmail(kakaoAccount.get("email").toString());
@Transactional
public MemberDetails kakaoLogin(String code) {
String accessToken = getAccessToken(code);
KakaoUserResponse kakaoUser = getUserInfo(accessToken);

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

return toOAuth2MemberDetails(member);
}

@Transactional
public MemberDetails loadGuest(GuestLoginRequest guestLoginRequest) {
Member guest = toGuestMember(guestLoginRequest);
private String getAccessToken(String code) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

memberRepository.save(guest);
guest.registerGuest();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", clientId);
params.add("redirect_uri", redirectUri);
params.add("code", code);
params.add("client_secret", clientSecret);

return toGuestMemberDetails(guest);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

ResponseEntity<Map> response = restTemplate.postForEntity(TOKEN_URI, request, Map.class);

return (String) response.getBody().get("access_token");
}

private KakaoUserResponse getUserInfo(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<Void> entity = new HttpEntity<>(headers);

ResponseEntity<Map> response = restTemplate.exchange(USER_INFO_URI, HttpMethod.GET, entity, Map.class);

Map<String, Object> attributes = response.getBody();

Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");

return MemberMapper.toKakaoUserResponse(kakaoAccount, profile);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ public class SecurityConfig {
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/api/auth/guest",
"/api/auth/kakao",
"/login"
)
.anonymous()
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/oronaminc/join/member/util/MemberMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.oronaminc.join.member.domain.Member;
import com.oronaminc.join.member.domain.MemberType;
import com.oronaminc.join.member.dto.GuestLoginRequest;
import com.oronaminc.join.member.dto.KakaoUserResponse;
import com.oronaminc.join.member.security.MemberDetails;

import lombok.AccessLevel;
Expand Down Expand Up @@ -47,4 +48,21 @@ public static Member toKakaoMember(Map<String, Object> kakaoAccount, Map<String,
.memberType(MemberType.MEMBER)
.build();
}

public static Member toNewKakaoMember(KakaoUserResponse kakaoUser) {
return Member.builder()
.email(kakaoUser.email())
.nickname(kakaoUser.nickname())
.profileImage(kakaoUser.profileImageUrl())
.memberType(MemberType.MEMBER)
.build();
}

public static KakaoUserResponse toKakaoUserResponse(Map<String, Object> kakaoAccount, Map<String, Object> profile) {
return KakaoUserResponse.builder()
.email((String) kakaoAccount.get("email"))
.nickname((String) profile.get("nickname"))
.profileImageUrl((String) profile.get("profile_image_url"))
.build();
}
}
22 changes: 22 additions & 0 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ spring:
hibernate:
ddl-auto: create

security:
oauth2:
client:
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
registration:
kakao:
client-name: Kakao
client-id: KAKAO_CLIENT_ID
client-secret: KAKAO_CLIENT_SECRET
redirect-uri: KAKAO_REDIRECT_URI
authorization-grant-type: authorization_code
client-authentication-method: client_secret_post
scope:
- profile_nickname
- profile_image
- account_email

cloud:
aws:
region:
Expand Down