Skip to content

Commit

Permalink
feat :: 카카오 로그인 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
hyundong-L committed Nov 26, 2024
1 parent 701545a commit 7a3732c
Show file tree
Hide file tree
Showing 31 changed files with 617 additions and 668 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
// implementation 'org.springframework.boot:spring-boot-starter-security'
// testImplementation 'org.springframework.security:spring-security-test'
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
developmentOnly 'org.springframework.boot:spring-boot-devtools'

// lombok
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
//package com.danpoong.onchung.domain.auth.controller;
//
//import com.danpoong.onchung.domain.auth.service.AuthService;
//import lombok.RequiredArgsConstructor;
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.RestController;
//
//@RestController
//@RequestMapping("/api/auth")
//@RequiredArgsConstructor
//public class AuthController {
// private final AuthService authService;
//
//// @Operation(summary = "로그인", description = "만약 엑세스 토큰이 존재하지 않으면 쿠키에서 리프레시 토큰을 찾아 로그인")
//// @PostMapping("/login")
//// public ResponseTemplate<LoginResponseDto> login(@AuthenticationPrincipal Long userId, HttpServletRequest request, HttpServletResponse response) {
//// return new ResponseTemplate<>(HttpStatus.OK, "로그인 성공", authService.login(userId, request, response));
//// }
////
//// @Operation(summary = "회원가입", description = "아이디 중복 확인 버튼 클릭 시 닉네임, 아이디 같이 보내면 중복 검사 밑 회원가입 진행")
//// @PostMapping("/signup")
//// public ResponseTemplate<AccessTokenDto> signup(@RequestBody SignUpRequestDto sign, HttpServletResponse response) {
//// return new ResponseTemplate<>(HttpStatus.CREATED, "회원가입 성공", authService.signUp(sign, response));
//// }
////
//// @Operation(summary = "토큰 재발급")
//// @PostMapping("/reissue")
//// public ResponseTemplate<AccessTokenDto> reissue(@RequestBody TokenDto tokenRequestDto, HttpServletRequest request, HttpServletResponse response) {
//// return new ResponseTemplate<>(HttpStatus.OK, "토큰 재발급 성공", authService.reissue(request, response, tokenRequestDto));
//// }
//}
package com.danpoong.onchung.domain.auth.controller;

import com.danpoong.onchung.domain.auth.dto.LoginResponseDto;
import com.danpoong.onchung.domain.auth.dto.ReissueResponseDto;
import com.danpoong.onchung.domain.auth.service.AuthService;
import com.danpoong.onchung.global.security.oauth.kakao.KakaoLoginParam;
import com.danpoong.onchung.global.template.ResponseTemplate;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/auth")
@Slf4j
public class AuthController {
private final AuthService authService;

@Operation(summary = "카카오 로그인")
@PostMapping("/login")
public ResponseTemplate<LoginResponseDto> kakaoLogin(HttpServletResponse response, @RequestBody KakaoLoginParam kakaoLoginParam) {
return new ResponseTemplate<>(HttpStatus.OK, "카카오 로그인 성공", authService.login(response, kakaoLoginParam));
}

@Operation(summary = "토큰 재발급", description = "만료 시 사용")
@PostMapping("/reissue")
public ResponseTemplate<ReissueResponseDto> reissue(HttpServletRequest request, HttpServletResponse response) {
return new ResponseTemplate<>(HttpStatus.OK, "엑세스 토큰 재발급", authService.reissueToken(request, response));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.danpoong.onchung.domain.auth.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public record LoginResponseDto(
String tokenDto,
String recentPublicOfficeName
) {
public class LoginResponseDto {
private Boolean isNewUser;
private String accessToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import lombok.Builder;

@Builder
public record AccessTokenDto(
public record ReissueResponseDto(
String accessToken
) {
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.danpoong.onchung.domain.auth.exception;

public class RefreshTokenMismatchException extends RuntimeException {
public RefreshTokenMismatchException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,95 +1,80 @@
//package com.danpoong.onchung.domain.auth.service;
//
//import com.danpoong.onchung.domain.auth.dto.AccessTokenDto;
//import com.danpoong.onchung.domain.auth.dto.LoginResponseDto;
//import com.danpoong.onchung.domain.auth.dto.SignUpRequestDto;
//import com.danpoong.onchung.domain.user.domain.UserInfo;
//import com.danpoong.onchung.domain.user.repository.UserRepository;
//import com.danpoong.onchung.global.security.jwt.TokenProvider;
//import com.danpoong.onchung.global.security.jwt.TokenUtil;
//import com.danpoong.onchung.global.security.jwt.domain.Token;
//import com.danpoong.onchung.global.security.jwt.dto.TokenDto;
//import com.danpoong.onchung.global.security.jwt.repository.TokenRepository;
//import jakarta.servlet.http.HttpServletRequest;
//import jakarta.servlet.http.HttpServletResponse;
//import jakarta.transaction.Transactional;
//import lombok.RequiredArgsConstructor;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.stereotype.Service;
//
//@Service
//@RequiredArgsConstructor
//@Slf4j
//public class AuthService {
// private final TokenProvider tokenProvider;
// private final TokenRepository tokenRepository;
// private final UserRepository userRepository;
//
// @Transactional
// public LoginResponseDto login(Long userId, HttpServletRequest req, HttpServletResponse resp) {
// UserInfo userInfo = userRepository.findById(userId)
// .orElseGet(() -> userRepository.findByToken(Token.builder().refreshToken(TokenUtil.getRefreshToken(req)).build())
// .orElseThrow(() -> new RuntimeException("사용자가 존재하지 않습니다.")));
//
// TokenDto tokenGenerateDto = tokenProvider.generateToken(userId);
//
// userInfo.getToken().updateToken(tokenGenerateDto.refreshToken());
// tokenRepository.save(userInfo.getToken());
// TokenUtil.updateRefreshTokenCookie(req, resp, tokenGenerateDto.refreshToken());
//
// return LoginResponseDto.builder()
// .tokenDto(tokenGenerateDto.accessToken())
// .recentPublicOfficeName(userInfo.getRecentPublicOffice().getName())
// .build();
// }
//
// @Transactional
// public AccessTokenDto signUp(SignUpRequestDto sign, HttpServletResponse response) {
// if (checkPresentLoginId(sign.userLoginId())) { // 존재
// throw new RuntimeException("해당 아이디를 사용하는 사용자가 존재합니다.");
// }
//
// UserInfo userInfo = UserInfo.builder()
// .userName(sign.username())
// .userLoginId(sign.userLoginId())
// .build();
// UserInfo test = userRepository.save(userInfo);
// log.info(test.toString());
//
// TokenDto tokenGenerateDto = tokenProvider.generateToken(userInfo.getId());
// userInfo.updateToken(new Token(tokenGenerateDto.refreshToken()));
//
// TokenUtil.saveRefreshToken(response, tokenGenerateDto.refreshToken());
//
// return AccessTokenDto.builder().accessToken(tokenGenerateDto.accessToken()).build();
// }
//
// @Transactional
// public AccessTokenDto reissue(HttpServletRequest req, HttpServletResponse res, TokenDto token) {
// if (!tokenProvider.validateToken(token.refreshToken())) {
// throw new RuntimeException("유효하지 않은 Refresh Token");
// }
//
// Token reqToken = Token.builder()
// .refreshToken(token.refreshToken())
// .build();
//
// UserInfo userInfo = userRepository.findByToken(reqToken)
// .orElseThrow(() -> new RuntimeException("사용자가 존재하지 않습니다."));
//
// if (!userInfo.getToken().getRefreshToken().equals(token.refreshToken())) {
// throw new RuntimeException("저장된 Refresh Token이 일치하지 않습니다.");
// }
//
// TokenDto tokenGenerateDto = tokenProvider.reissueToken(userInfo.getId());
//
// TokenUtil.updateRefreshTokenCookie(req, res, token.refreshToken());
//
// return AccessTokenDto.builder().accessToken(tokenGenerateDto.accessToken()).build();
// }
//
//
// private boolean checkPresentLoginId(String loginId) {
// return userRepository.findByUserLoginId(loginId).isPresent();
// }
//}
package com.danpoong.onchung.domain.auth.service;

import com.danpoong.onchung.domain.auth.dto.LoginResponseDto;
import com.danpoong.onchung.domain.auth.dto.ReissueResponseDto;
import com.danpoong.onchung.domain.auth.exception.RefreshTokenMismatchException;
import com.danpoong.onchung.domain.user.domain.UserInfo;
import com.danpoong.onchung.domain.user.repository.UserInfoRepository;
import com.danpoong.onchung.global.security.jwt.TokenProvider;
import com.danpoong.onchung.global.security.jwt.TokenUtil;
import com.danpoong.onchung.global.security.jwt.dto.TokenDto;
import com.danpoong.onchung.global.security.oauth.kakao.KakaoApiClient;
import com.danpoong.onchung.global.security.oauth.kakao.KakaoLoginParam;
import com.danpoong.onchung.global.security.oauth.kakao.KakaoUserInfo;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class AuthService {
private final UserInfoRepository userInfoRepository;
private final TokenProvider tokenProvider;
private final KakaoApiClient kakaoApiClient;

@Transactional
public LoginResponseDto login(HttpServletResponse response, KakaoLoginParam params) {
String kakaoAccessToken = kakaoApiClient.requestAccessToken(params);
KakaoUserInfo kakaoUserInfo = kakaoApiClient.requestOAuthInfo(kakaoAccessToken);
UserInfo userInfo = findOrCreateUser(kakaoUserInfo);

TokenDto tokenDto = tokenProvider.generateToken(userInfo.getId());
userInfo.updateRefreshToken(tokenDto.refreshToken());
TokenUtil.saveRefreshToken(response, tokenDto.refreshToken());

return LoginResponseDto.builder()
.isNewUser(userInfo.getBirthDate() == null)
.accessToken(tokenDto.accessToken())
.build();
}

@Transactional
public ReissueResponseDto reissueToken(HttpServletRequest request, HttpServletResponse response) {
String refreshToken = TokenUtil.getRefreshToken(request);

if (!tokenProvider.validateToken(refreshToken)) {
throw new JwtException("입력 받은 Refresh Token은 잘못되었습니다.");
}

UserInfo userInfo = userInfoRepository.findByRefreshToken(refreshToken).orElseThrow(() -> new RefreshTokenMismatchException("일치하는 리프레시 토큰이 존재하지 않습니다."));

if (!userInfo.getRefreshToken().equals(refreshToken)) {
throw new RefreshTokenMismatchException("Refresh Token = " + refreshToken);
}

TokenDto tokenDto = tokenProvider.reissueToken(userInfo.getId());
userInfo.updateRefreshToken(tokenDto.refreshToken());
TokenUtil.updateRefreshTokenCookie(request, response, refreshToken);

return ReissueResponseDto.builder().accessToken(tokenDto.accessToken()).build();
}

private UserInfo findOrCreateUser(KakaoUserInfo kakaoUserInfo) {
return userInfoRepository.findByEmail(kakaoUserInfo.getEmail())
.orElseGet(() -> createNewUser(kakaoUserInfo));
}

private UserInfo createNewUser(KakaoUserInfo kakaoUserInfo) {
UserInfo userInfo = UserInfo.builder()
.email(kakaoUserInfo.getEmail())
.nickname(kakaoUserInfo.getNickname())
.build();

return userInfoRepository.save(userInfo);
}
}

This file was deleted.

Loading

0 comments on commit 7a3732c

Please sign in to comment.