Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 카카오 OAuth 로그인 기능 구현 #826

Merged
merged 8 commits into from
Nov 16, 2024
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
1 change: 1 addition & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {

implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'com.auth0:java-jwt:4.4.0'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
33 changes: 27 additions & 6 deletions server/src/main/java/server/haengdong/application/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,43 @@


import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import server.haengdong.domain.TokenProvider;
import server.haengdong.domain.user.Role;
import server.haengdong.exception.AuthenticationException;
import server.haengdong.exception.HaengdongErrorCode;

@Slf4j
public class AuthService {

private static final String TOKEN_NAME = "eventToken";
private static final String TOKEN_NAME = "accessToken";
private static final String CLAIM_SUB = "sub";
private static final String ROLE = "role";

private final TokenProvider tokenProvider;
private final EventService eventService;

public AuthService(TokenProvider tokenProvider) {
public AuthService(TokenProvider tokenProvider, EventService eventService) {
this.tokenProvider = tokenProvider;
this.eventService = eventService;
}

public String createToken(String eventId) {
Map<String, Object> payload = Map.of(CLAIM_SUB, eventId);
public String createGuestToken(Long userId) {
Map<String, Object> payload = Map.of(CLAIM_SUB, userId, ROLE, Role.GUEST);

return tokenProvider.createToken(payload);
}

public String findEventIdByToken(String token) {
public String createMemberToken(Long userId) {
Map<String, Object> payload = Map.of(CLAIM_SUB, userId, ROLE, Role.MEMBER);

return tokenProvider.createToken(payload);
}

public Long findUserIdByToken(String token) {
validateToken(token);
Map<String, Object> payload = tokenProvider.getPayload(token);
return (String) payload.get(CLAIM_SUB);
return (Long) payload.get(CLAIM_SUB);
}

private void validateToken(String token) {
Expand All @@ -38,4 +50,13 @@ private void validateToken(String token) {
public String getTokenName() {
return TOKEN_NAME;
}

public void checkAuth(String eventToken, Long userId) {
boolean hasEvent = eventService.existsByTokenAndUserId(eventToken, userId);

if (!hasEvent) {
log.warn("[행사 접근 불가] Cookie EventId = {}, UserId = {}", eventToken, userId);
throw new AuthenticationException(HaengdongErrorCode.FORBIDDEN);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import server.haengdong.domain.bill.BillRepository;
import server.haengdong.domain.event.Event;
import server.haengdong.domain.event.EventRepository;
import server.haengdong.domain.member.Member;
import server.haengdong.domain.member.MemberRepository;
import server.haengdong.domain.eventmember.EventMember;
import server.haengdong.domain.eventmember.EventMemberRepository;
import server.haengdong.domain.step.Steps;
import server.haengdong.exception.HaengdongErrorCode;
import server.haengdong.exception.HaengdongException;
Expand All @@ -28,22 +28,22 @@ public class BillService {

private final BillRepository billRepository;
private final EventRepository eventRepository;
private final MemberRepository memberRepository;
private final EventMemberRepository eventMemberRepository;

@Transactional
public void saveBill(String eventToken, BillAppRequest request) {
Event event = getEvent(eventToken);
List<Long> memberIds = request.memberIds();
List<Member> members = memberIds.stream()
List<EventMember> eventMembers = memberIds.stream()
.map(this::findMember)
.toList();

Bill bill = request.toBill(event, members);
Bill bill = request.toBill(event, eventMembers);
billRepository.save(bill);
}

private Member findMember(Long memberId) {
return memberRepository.findById(memberId)
private EventMember findMember(Long memberId) {
return eventMemberRepository.findById(memberId)
.orElseThrow(() -> new HaengdongException(HaengdongErrorCode.MEMBER_NOT_FOUND));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@
import server.haengdong.domain.bill.BillRepository;
import server.haengdong.domain.event.Event;
import server.haengdong.domain.event.EventRepository;
import server.haengdong.domain.member.Member;
import server.haengdong.domain.member.MemberRepository;
import server.haengdong.domain.member.UpdatedMembers;
import server.haengdong.domain.eventmember.EventMember;
import server.haengdong.domain.eventmember.EventMemberRepository;
import server.haengdong.domain.eventmember.UpdatedMembers;
import server.haengdong.exception.HaengdongErrorCode;
import server.haengdong.exception.HaengdongException;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class MemberService {
public class EventMemberService {

private final MemberRepository memberRepository;
private final EventMemberRepository eventMemberRepository;
private final EventRepository eventRepository;
private final BillRepository billRepository;

Expand All @@ -40,12 +40,12 @@ public MembersSaveAppResponse saveMembers(String token, MembersSaveAppRequest re

validateMemberSave(memberNames, event);

List<Member> members = memberNames.stream()
.map(name -> new Member(event, name))
List<EventMember> eventMembers = memberNames.stream()
.map(name -> new EventMember(event, name))
.toList();

List<Member> savedMembers = memberRepository.saveAll(members);
return MembersSaveAppResponse.of(savedMembers);
List<EventMember> savedEventMembers = eventMemberRepository.saveAll(eventMembers);
return MembersSaveAppResponse.of(savedEventMembers);
}

private void validateMemberSave(List<String> memberNames, Event event) {
Expand All @@ -59,7 +59,7 @@ private void validateMemberSave(List<String> memberNames, Event event) {
}

private boolean isDuplicatedMemberNames(Set<String> uniqueMemberNames, Event event) {
return memberRepository.findAllByEvent(event).stream()
return eventMemberRepository.findAllByEvent(event).stream()
.anyMatch(member -> uniqueMemberNames.contains(member.getName()));
}

Expand All @@ -68,7 +68,7 @@ public List<MemberAppResponse> getCurrentMembers(String token) {

return billRepository.findFirstByEventOrderByIdDesc(event)
.map(Bill::getMembers)
.orElseGet(() -> memberRepository.findAllByEvent(event))
.orElseGet(() -> eventMemberRepository.findAllByEvent(event))
.stream()
.map(MemberAppResponse::of)
.toList();
Expand All @@ -77,38 +77,38 @@ public List<MemberAppResponse> getCurrentMembers(String token) {
public MembersDepositAppResponse findAllMembers(String token) {
Event event = getEvent(token);

List<Member> members = memberRepository.findAllByEvent(event);
List<EventMember> eventMembers = eventMemberRepository.findAllByEvent(event);

return MembersDepositAppResponse.of(members);
return MembersDepositAppResponse.of(eventMembers);
}

@Transactional
public void updateMembers(String token, MembersUpdateAppRequest request) {
Event event = getEvent(token);
UpdatedMembers updatedMembers = new UpdatedMembers(request.toMembers(event));
List<Member> originMembers = memberRepository.findAllByEvent(event);
List<EventMember> originEventMembers = eventMemberRepository.findAllByEvent(event);

updatedMembers.validateUpdatable(originMembers);
memberRepository.saveAll(updatedMembers.getMembers());
updatedMembers.validateUpdatable(originEventMembers);
eventMemberRepository.saveAll(updatedMembers.getMembers());
}

@Transactional
public void deleteMember(String token, Long memberId) {
memberRepository.findById(memberId)
eventMemberRepository.findById(memberId)
.ifPresent(member -> deleteMember(token, member));
}

private void deleteMember(String token, Member member) {
Event event = member.getEvent();
private void deleteMember(String token, EventMember eventMember) {
Event event = eventMember.getEvent();
if (event.isTokenMismatch(token)) {
throw new HaengdongException(HaengdongErrorCode.MEMBER_NOT_FOUND);
}

billRepository.findAllByEvent(event).stream()
.filter(bill -> bill.containMember(member))
.forEach(bill -> bill.removeMemberBillDetail(member));
.filter(bill -> bill.containMember(eventMember))
.forEach(bill -> bill.removeMemberBillDetail(eventMember));
billRepository.flush();
memberRepository.delete(member);
eventMemberRepository.delete(eventMember);
}

private Event getEvent(String token) {
Expand Down
43 changes: 32 additions & 11 deletions server/src/main/java/server/haengdong/application/EventService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import server.haengdong.application.request.EventAppRequest;
import server.haengdong.application.request.EventGuestAppRequest;
import server.haengdong.application.request.EventLoginAppRequest;
import server.haengdong.application.request.EventUpdateAppRequest;
import server.haengdong.application.response.EventAppResponse;
Expand All @@ -23,7 +24,8 @@
import server.haengdong.domain.event.EventImage;
import server.haengdong.domain.event.EventImageRepository;
import server.haengdong.domain.event.EventRepository;
import server.haengdong.domain.member.Member;
import server.haengdong.domain.eventmember.EventMember;
import server.haengdong.domain.eventmember.EventMemberRepository;
import server.haengdong.exception.AuthenticationException;
import server.haengdong.exception.HaengdongErrorCode;
import server.haengdong.exception.HaengdongException;
Expand All @@ -39,16 +41,32 @@ public class EventService {
private final RandomValueProvider randomValueProvider;
private final BillRepository billRepository;
private final EventImageRepository eventImageRepository;
private final EventMemberRepository eventMemberRepository;
private final UserService userService;

@Value("${image.base-url}")
private String baseUrl;

@Transactional
public EventAppResponse saveEventGuest(EventGuestAppRequest request) {
Long userId = userService.joinGuest(request.toUserRequest());
String token = randomValueProvider.createRandomValue();
Event event = new Event(request.eventName(), userId, token);
eventRepository.save(event);

eventMemberRepository.save(new EventMember(event, request.nickname()));
return EventAppResponse.of(event);
}

@Transactional
public EventAppResponse saveEvent(EventAppRequest request) {
String token = randomValueProvider.createRandomValue();
Event event = request.toEvent(token);
Event event = new Event(request.name(), request.userId(), token);
eventRepository.save(event);

String nickname = userService.findNicknameById(request.userId());
eventMemberRepository.save(new EventMember(event, nickname));

return EventAppResponse.of(event);
}

Expand All @@ -58,11 +76,10 @@ public EventDetailAppResponse findEvent(String token) {
return EventDetailAppResponse.of(event);
}

public void validatePassword(EventLoginAppRequest request) throws HaengdongException {
public EventAppResponse findByGuestPassword(EventLoginAppRequest request) {
Event event = getEvent(request.token());
if (event.isPasswordMismatch(request.password())) {
throw new AuthenticationException(HaengdongErrorCode.PASSWORD_INVALID);
}
userService.validateUser(event.getUserId(), request.password());
return EventAppResponse.of(event);
}

public List<MemberBillReportAppResponse> getMemberBillReports(String token) {
Expand All @@ -77,14 +94,14 @@ public List<MemberBillReportAppResponse> getMemberBillReports(String token) {
.toList();
}

private MemberBillReportAppResponse createMemberBillReportResponse(Entry<Member, Long> entry) {
Member member = entry.getKey();
private MemberBillReportAppResponse createMemberBillReportResponse(Entry<EventMember, Long> entry) {
EventMember eventMember = entry.getKey();
Long price = entry.getValue();

return new MemberBillReportAppResponse(
member.getId(),
member.getName(),
member.isDeposited(),
eventMember.getId(),
eventMember.getName(),
eventMember.isDeposited(),
price
);
}
Expand Down Expand Up @@ -175,4 +192,8 @@ public List<EventImageSaveAppResponse> findImagesDateBefore(Instant date) {
.map(EventImageSaveAppResponse::of)
.toList();
}

public boolean existsByTokenAndUserId(String eventToken, Long userId) {
return eventRepository.existsByTokenAndUserId(eventToken, userId);
}
}
46 changes: 46 additions & 0 deletions server/src/main/java/server/haengdong/application/KakaoClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package server.haengdong.application;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;
import server.haengdong.application.response.KakaoTokenResponse;
import server.haengdong.config.KakaoProperties;
import server.haengdong.exception.HaengdongErrorCode;
import server.haengdong.exception.HaengdongException;

@RequiredArgsConstructor
@EnableConfigurationProperties(KakaoProperties.class)
@Component
public class KakaoClient {

private final KakaoProperties kakaoProperties;
private final RestClient restClient;

public KakaoTokenResponse join(String code, String redirectUri) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", kakaoProperties.clientId());
params.add("redirect_uri", redirectUri);
params.add("code", code);

try {
return restClient.post()
.uri(kakaoProperties.baseUri() + kakaoProperties.tokenRequestUri())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.body(params)
.retrieve()
.body(KakaoTokenResponse.class);
} catch (Exception e) {
throw new HaengdongException(HaengdongErrorCode.KAKAO_LOGIN_FAIL, e);
}
}

public String getClientId() {
return kakaoProperties.clientId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package server.haengdong.application;

import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import server.haengdong.application.response.KakaoTokenResponse;

@RequiredArgsConstructor
@Service
public class KakaoUserService {

private static final String NICKNAME_KEY = "nickname";

private final UserService userService;
private final KakaoClient kakaoClient;

public Long joinByKakao(String code, String redirectUri) {
KakaoTokenResponse kakaoToken = kakaoClient.join(code, redirectUri);
String idToken = kakaoToken.idToken();
DecodedJWT decodedJWT = JWT.decode(idToken);

String memberNumber = decodedJWT.getSubject();
String nickname = decodedJWT.getClaim(NICKNAME_KEY).asString();

return userService.join(memberNumber, nickname);
}

public String getClientId() {
return kakaoClient.getClientId();
}
}
Loading
Loading