Skip to content

Commit

Permalink
Feat(#6 #20): 로그인/회원가입 기능 / 테스트코드 작성
Browse files Browse the repository at this point in the history
  • Loading branch information
giwoong01 authored Jul 17, 2024
2 parents 0e92957 + 3b0d7d3 commit 1b9cc7f
Show file tree
Hide file tree
Showing 26 changed files with 990 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package shop.kkeujeok.kkeujeokbackend.auth.api;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.request.RefreshTokenReqDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.request.TokenReqDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.MemberLoginResDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.UserInfo;
import shop.kkeujeok.kkeujeokbackend.auth.application.AuthMemberService;
import shop.kkeujeok.kkeujeokbackend.auth.application.AuthService;
import shop.kkeujeok.kkeujeokbackend.auth.application.AuthServiceFactory;
import shop.kkeujeok.kkeujeokbackend.auth.application.TokenService;
import shop.kkeujeok.kkeujeokbackend.global.jwt.api.dto.TokenDto;
import shop.kkeujeok.kkeujeokbackend.global.oauth.GoogleAuthService;
import shop.kkeujeok.kkeujeokbackend.global.oauth.KakaoAuthService;
import shop.kkeujeok.kkeujeokbackend.global.template.RspTemplate;
import shop.kkeujeok.kkeujeokbackend.member.domain.SocialType;

@RestController
@Slf4j
@RequestMapping("/api")
@RequiredArgsConstructor
public class AuthController {
private final AuthServiceFactory authServiceFactory;
private final AuthMemberService memberService;
private final TokenService tokenService;
private final GoogleAuthService getGoogleAccessToken;
private final KakaoAuthService kakaoAuthService;

@GetMapping("oauth2/callback/google")
public JsonNode googleCallback(@RequestParam(name = "code") String code) {
return getGoogleAccessToken.getGoogleIdToken(code);
}

@GetMapping("oauth2/callback/kakao")
public JsonNode kakaoCallback(@RequestParam(name = "code") String code) {
return kakaoAuthService.getKakaoAccessToken(code);
}

// @Operation(summary = "로그인 후 토큰 발급", description = "액세스, 리프레쉬 토큰을 발급합니다.")
@PostMapping("/{provider}/token")
public RspTemplate<TokenDto> generateAccessAndRefreshToken(
@PathVariable(name = "provider") String provider,
@RequestBody TokenReqDto tokenReqDto) {
AuthService authService = authServiceFactory.getAuthService(provider);
UserInfo userInfo = authService.getUserInfo(tokenReqDto.authCode());

MemberLoginResDto getMemberDto = memberService.saveUserInfo(userInfo,
SocialType.valueOf(provider.toUpperCase()));
TokenDto getToken = tokenService.getToken(getMemberDto);

return new RspTemplate<>(HttpStatus.OK, "토큰 발급", getToken);
}

// @Operation(summary = "액세스 토큰 재발급", description = "리프레쉬 토큰으로 액세스 토큰을 발급합니다.")
@PostMapping("/token/access")
public RspTemplate<TokenDto> generateAccessToken(@RequestBody RefreshTokenReqDto refreshTokenReqDto) {
TokenDto getToken = tokenService.generateAccessToken(refreshTokenReqDto);

return new RspTemplate<>(HttpStatus.OK, "액세스 토큰 발급", getToken);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.request;

public record RefreshTokenReqDto(
String refreshToken
){
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.request;

public record TokenReqDto(
String authCode
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.response;

import lombok.Builder;

@Builder
public record AccessAndRefreshTokenResDto(
String accessToken,
String refreshToken
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.response;


import lombok.Builder;
import shop.kkeujeok.kkeujeokbackend.member.domain.Member;

@Builder
public record MemberLoginResDto(
Member findMember
) {
public static MemberLoginResDto from(Member member) {
return MemberLoginResDto.builder()
.findMember(member)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.response;

public record UserInfo(
String email,
String name,
String picture,
String nickname
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package shop.kkeujeok.kkeujeokbackend.auth.application;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.MemberLoginResDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.UserInfo;
import shop.kkeujeok.kkeujeokbackend.global.entity.Status;
import shop.kkeujeok.kkeujeokbackend.member.domain.Member;
import shop.kkeujeok.kkeujeokbackend.member.domain.Role;
import shop.kkeujeok.kkeujeokbackend.member.domain.SocialType;
import shop.kkeujeok.kkeujeokbackend.member.domain.repository.MemberRepository;

import java.util.Optional;

@Service
@Transactional(readOnly = true)
public class AuthMemberService {
private final MemberRepository memberRepository;

public AuthMemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

@Transactional
public MemberLoginResDto saveUserInfo(UserInfo userInfo, SocialType provider) {
validateNotFoundEmail(userInfo.email());

Member member = getExistingMemberOrCreateNew(userInfo, provider);

validateSocialType(member, provider);

return MemberLoginResDto.from(member);
}

private void validateNotFoundEmail(String email) {
if (email == null) {
throw new RuntimeException();
}
}

private Member getExistingMemberOrCreateNew(UserInfo userInfo, SocialType provider) {
return memberRepository.findByEmail(userInfo.email()).orElseGet(() -> createMember(userInfo, provider));
}

private Member createMember(UserInfo userInfo, SocialType provider) {
String userPicture = getUserPicture(userInfo.picture());
String name = userInfo.name();
String nickname = userInfo.nickname();

if (name == null && nickname != null) {
name = nickname;
} else if (nickname == null && name != null) {
nickname = name;
}

return memberRepository.save(
Member.builder()
.status(Status.A)
.email(userInfo.email())
.name(name)
.picture(userPicture)
.socialType(provider)
.role(Role.ROLE_USER)
.firstLogin(true)
.nickname(nickname)
.build()
);
}

private String getUserPicture(String picture) {
return Optional.ofNullable(picture)
.map(this::convertToHighRes).orElseThrow();
}

private String convertToHighRes(String url){
return url.replace("s96-c", "s2048-c");
}

private void validateSocialType(Member member, SocialType provider) {
if (!provider.equals(member.getSocialType())) {
throw new RuntimeException();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package shop.kkeujeok.kkeujeokbackend.auth.application;

import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.UserInfo;

public interface AuthService {
UserInfo getUserInfo(String authCode);

String getProvider();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package shop.kkeujeok.kkeujeokbackend.auth.application;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class AuthServiceFactory {
private final Map<String, AuthService> authServiceMap;

@Autowired
public AuthServiceFactory(List<AuthService> authServiceList) {
authServiceMap = new HashMap<>();
for (AuthService authService : authServiceList) {
authServiceMap.put(authService.getProvider(), authService);
}
}

public AuthService getAuthService(String provider) {
return authServiceMap.get(provider);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package shop.kkeujeok.kkeujeokbackend.auth.application;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.request.RefreshTokenReqDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.MemberLoginResDto;
import shop.kkeujeok.kkeujeokbackend.global.jwt.TokenProvider;
import shop.kkeujeok.kkeujeokbackend.global.jwt.api.dto.TokenDto;
import shop.kkeujeok.kkeujeokbackend.global.jwt.domain.Token;
import shop.kkeujeok.kkeujeokbackend.global.jwt.domain.repository.TokenRepository;
import shop.kkeujeok.kkeujeokbackend.member.domain.Member;
import shop.kkeujeok.kkeujeokbackend.member.domain.repository.MemberRepository;

@Service
@Transactional(readOnly = true)
public class TokenService {

private final TokenProvider tokenProvider;
private final TokenRepository tokenRepository;
private final MemberRepository memberRepository;

public TokenService(TokenProvider tokenProvider, TokenRepository tokenRepository, MemberRepository memberRepository) {
this.tokenProvider = tokenProvider;
this.tokenRepository = tokenRepository;
this.memberRepository = memberRepository;
}

@Transactional
public TokenDto getToken(MemberLoginResDto memberLoginResDto) {
TokenDto tokenDto = tokenProvider.generateToken(memberLoginResDto.findMember().getEmail());

tokenSaveAndUpdate(memberLoginResDto, tokenDto);

return tokenDto;
}

private void tokenSaveAndUpdate(MemberLoginResDto memberLoginResDto, TokenDto tokenDto) {
if (!tokenRepository.existsByMember(memberLoginResDto.findMember())) {
tokenRepository.save(Token.builder()
.member(memberLoginResDto.findMember())
.refreshToken(tokenDto.refreshToken())
.build());
}

refreshTokenUpdate(memberLoginResDto, tokenDto);
}

private void refreshTokenUpdate(MemberLoginResDto memberLoginResDto, TokenDto tokenDto) {
Token token = tokenRepository.findByMember(memberLoginResDto.findMember()).orElseThrow();
token.refreshTokenUpdate(tokenDto.refreshToken());
}

@Transactional
public TokenDto generateAccessToken(RefreshTokenReqDto refreshTokenReqDto) {
if (!tokenRepository.existsByRefreshToken(refreshTokenReqDto.refreshToken()) || !tokenProvider.validateToken(refreshTokenReqDto.refreshToken())) {
throw new RuntimeException();
}

Token token = tokenRepository.findByRefreshToken(refreshTokenReqDto.refreshToken()).orElseThrow();
Member member = memberRepository.findById(token.getMember().getId()).orElseThrow();

return tokenProvider.generateAccessTokenByRefreshToken(member.getEmail(), token.getRefreshToken());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class Token {
private String refreshToken;

@Builder
private Token(Member member, String refreshToken) {
public Token(Member member, String refreshToken) {
this.member = member;
this.refreshToken = refreshToken;
}
Expand Down
Loading

0 comments on commit 1b9cc7f

Please sign in to comment.