diff --git a/src/main/java/jeje/work/aeatbe/config/WebConfig.java b/src/main/java/jeje/work/aeatbe/config/WebConfig.java index ffbeb431..98410901 100644 --- a/src/main/java/jeje/work/aeatbe/config/WebConfig.java +++ b/src/main/java/jeje/work/aeatbe/config/WebConfig.java @@ -24,7 +24,9 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) - .addPathPatterns("/api/article/likes/**"); + .addPathPatterns("/api/article/likes/**") + .addPathPatterns("/api/users/logout/**") + . addPathPatterns("/api/wishlist/**"); } @Override diff --git a/src/main/java/jeje/work/aeatbe/controller/KakaoAuthController.java b/src/main/java/jeje/work/aeatbe/controller/KakaoAuthController.java index 83121f88..300d558d 100644 --- a/src/main/java/jeje/work/aeatbe/controller/KakaoAuthController.java +++ b/src/main/java/jeje/work/aeatbe/controller/KakaoAuthController.java @@ -2,9 +2,11 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import jeje.work.aeatbe.annotation.LoginUser; import jeje.work.aeatbe.domian.KakaoProperties; import jeje.work.aeatbe.domian.KakaoTokenResponsed; -import jeje.work.aeatbe.dto.user.TokenResponseDto; +import jeje.work.aeatbe.dto.Kakao.LogoutResponseDto; +import jeje.work.aeatbe.dto.Kakao.TokenResponseDto; import jeje.work.aeatbe.service.KakaoService; import jeje.work.aeatbe.service.UserService; import jeje.work.aeatbe.utility.JwtUtil; @@ -12,6 +14,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -26,19 +29,52 @@ public class KakaoAuthController { private final KakaoProperties kakaoProperties; private final KakaoService kakaoService; + /** + * 카카오 로그인페이지로 리다이렉션 + * @param response + * @throws IOException + */ @GetMapping("/login") public void redirectKakaoLogin(HttpServletResponse response) throws IOException { String url = kakaoProperties.authUrl() + - "?scope=talk_message&response_type=code&client_id=" + kakaoProperties.clientId() + + "?scope=talk_message,profile_nickname&response_type=code&client_id=" + kakaoProperties.clientId() + "&redirect_uri=" + kakaoProperties.redirectUrl(); response.sendRedirect(url); } + /** + * 카카오 로그인후 jwt토큰 발급 + * @param code + * @return + */ @GetMapping("/callback") public ResponseEntity getAccessToken(@RequestParam String code){ KakaoTokenResponsed token = kakaoService.getKakaoTokenResponse(code); - String jwt = kakaoService.Login(token.accessToken(), token.refreshToken()); - return new ResponseEntity<>(new TokenResponseDto(jwt), HttpStatus.OK); + String jwt = kakaoService.login(token.accessToken(), token.refreshToken()); + return ResponseEntity.ok(new TokenResponseDto(jwt)); + } + + /** + * 카카오 로그아웃후 카카오계정과 함께 로그아웃으로 리다이렉션 + * @param response + * @param userid + * @throws IOException + */ + @PostMapping("/logout") + public void logout(HttpServletResponse response, @LoginUser Long userid) throws IOException{ + String url = kakaoProperties.logoutUrl() + + "?client_id=" + kakaoProperties.clientId() + "&logout_redirect_uri=" + kakaoProperties.logoutRedirectUrl(); + LogoutResponseDto logoutResponseDto = kakaoService.logout(userid); + response.sendRedirect(url); + } + + /** + * 카카오계정과 함께 로그아웃 + * @return 카카오계정과 함꼐 로그아웃 페이지 + */ + @GetMapping("/logoutWithKakao/callback") + public ResponseEntity logoutWithKakao(){ + return ResponseEntity.ok().build(); } } diff --git a/src/main/java/jeje/work/aeatbe/controller/UserController.java b/src/main/java/jeje/work/aeatbe/controller/UserController.java new file mode 100644 index 00000000..57b15e5d --- /dev/null +++ b/src/main/java/jeje/work/aeatbe/controller/UserController.java @@ -0,0 +1,24 @@ +package jeje.work.aeatbe.controller; + +import jeje.work.aeatbe.annotation.LoginUser; +import jeje.work.aeatbe.dto.user.UserInfoResponseDto; +import jeje.work.aeatbe.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/users") +public class UserController { + + private final UserService userService; + + @GetMapping("/info") + public ResponseEntity getUserInfo(@LoginUser Long userId){ + UserInfoResponseDto userInfoResponseDto = userService.getUserInfo(userId); + return ResponseEntity.ok(userInfoResponseDto); + } +} diff --git a/src/main/java/jeje/work/aeatbe/domian/KakaoAccount.java b/src/main/java/jeje/work/aeatbe/domian/KakaoAccount.java new file mode 100644 index 00000000..fbd76d5e --- /dev/null +++ b/src/main/java/jeje/work/aeatbe/domian/KakaoAccount.java @@ -0,0 +1,10 @@ +package jeje.work.aeatbe.domian; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public record KakaoAccount( + Profile profile +) { +} diff --git a/src/main/java/jeje/work/aeatbe/domian/KakaoProperties.java b/src/main/java/jeje/work/aeatbe/domian/KakaoProperties.java index 4c2cfdea..6cefd627 100644 --- a/src/main/java/jeje/work/aeatbe/domian/KakaoProperties.java +++ b/src/main/java/jeje/work/aeatbe/domian/KakaoProperties.java @@ -7,7 +7,9 @@ public record KakaoProperties( String clientId, String redirectUrl, - String authUrl + String authUrl, + String logoutUrl, + String logoutRedirectUrl ) { } diff --git a/src/main/java/jeje/work/aeatbe/domian/KakaoUserInfo.java b/src/main/java/jeje/work/aeatbe/domian/KakaoUserInfo.java index 22f7bb36..7fdef7fe 100644 --- a/src/main/java/jeje/work/aeatbe/domian/KakaoUserInfo.java +++ b/src/main/java/jeje/work/aeatbe/domian/KakaoUserInfo.java @@ -1,5 +1,11 @@ package jeje.work.aeatbe.domian; -public record KakaoUserInfo(Long id) { +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public record KakaoUserInfo( + Long id, + KakaoAccount kakaoAccount +) { } diff --git a/src/main/java/jeje/work/aeatbe/domian/Profile.java b/src/main/java/jeje/work/aeatbe/domian/Profile.java new file mode 100644 index 00000000..a4d55218 --- /dev/null +++ b/src/main/java/jeje/work/aeatbe/domian/Profile.java @@ -0,0 +1,10 @@ +package jeje.work.aeatbe.domian; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public record Profile( + String nickname +){ +} diff --git a/src/main/java/jeje/work/aeatbe/dto/Kakao/LogoutResponseDto.java b/src/main/java/jeje/work/aeatbe/dto/Kakao/LogoutResponseDto.java new file mode 100644 index 00000000..35186a8e --- /dev/null +++ b/src/main/java/jeje/work/aeatbe/dto/Kakao/LogoutResponseDto.java @@ -0,0 +1,6 @@ +package jeje.work.aeatbe.dto.Kakao; + +public record LogoutResponseDto( + long id +) { +} diff --git a/src/main/java/jeje/work/aeatbe/dto/user/TokenResponseDto.java b/src/main/java/jeje/work/aeatbe/dto/Kakao/TokenResponseDto.java similarity index 71% rename from src/main/java/jeje/work/aeatbe/dto/user/TokenResponseDto.java rename to src/main/java/jeje/work/aeatbe/dto/Kakao/TokenResponseDto.java index b3d79e7b..e017d494 100644 --- a/src/main/java/jeje/work/aeatbe/dto/user/TokenResponseDto.java +++ b/src/main/java/jeje/work/aeatbe/dto/Kakao/TokenResponseDto.java @@ -1,4 +1,4 @@ -package jeje.work.aeatbe.dto.user; +package jeje.work.aeatbe.dto.Kakao; import lombok.Builder; diff --git a/src/main/java/jeje/work/aeatbe/dto/user/UserInfoResponseDto.java b/src/main/java/jeje/work/aeatbe/dto/user/UserInfoResponseDto.java new file mode 100644 index 00000000..7f7a9e05 --- /dev/null +++ b/src/main/java/jeje/work/aeatbe/dto/user/UserInfoResponseDto.java @@ -0,0 +1,11 @@ +package jeje.work.aeatbe.dto.user; + +import lombok.Builder; + +@Builder +public record UserInfoResponseDto( + Long id, + String userName, + String userImageUrl +) { +} diff --git a/src/main/java/jeje/work/aeatbe/service/KakaoService.java b/src/main/java/jeje/work/aeatbe/service/KakaoService.java index ee907484..5a475426 100644 --- a/src/main/java/jeje/work/aeatbe/service/KakaoService.java +++ b/src/main/java/jeje/work/aeatbe/service/KakaoService.java @@ -6,7 +6,9 @@ import jeje.work.aeatbe.domian.KakaoProperties; import jeje.work.aeatbe.domian.KakaoTokenResponsed; import jeje.work.aeatbe.domian.KakaoUserInfo; +import jeje.work.aeatbe.dto.Kakao.LogoutResponseDto; import jeje.work.aeatbe.entity.User; +import jeje.work.aeatbe.exception.UserNotFoundException; import jeje.work.aeatbe.repository.UserRepository; import jeje.work.aeatbe.utility.JwtUtil; import org.springframework.http.MediaType; @@ -29,9 +31,13 @@ public KakaoService(KakaoProperties kakaoProperties, UserRepository userReposito this.jwtUtil = jwtUtil; } + /** + * @param code 인가코드 + * @return KakaoTokenResponsed + */ public KakaoTokenResponsed getKakaoTokenResponse(String code){ var uri = "https://kauth.kakao.com/oauth/token"; - var body = createBody(code); + var body = createLoginBody(code); var response = restClient.post() .uri(URI.create(uri)) .contentType(MediaType.APPLICATION_FORM_URLENCODED) @@ -41,7 +47,7 @@ public KakaoTokenResponsed getKakaoTokenResponse(String code){ return response; } - private LinkedMultiValueMap createBody(String code) { + private LinkedMultiValueMap createLoginBody(String code) { var body = new LinkedMultiValueMap(); body.add("grant_type", "authorization_code"); body.add("client_id", kakaoProperties.clientId()); @@ -50,21 +56,30 @@ private LinkedMultiValueMap createBody(String code) { return body; } + /** + * 로그인을한다. + * @param accessToken 카카오 엑세스 토큰 + * @param refreshToken 카카오 리프레시 토큰 + * @return jwt토큰 + */ @Transactional - public String Login(String accessToken, String refreshToken){ + public String login(String accessToken, String refreshToken){ var uri = "https://kapi.kakao.com/v2/user/me"; var response = restClient.get() .uri(URI.create(uri)) .header("Authorization", "Bearer " + accessToken) .retrieve() .body(KakaoUserInfo.class); + String userName = response.kakaoAccount().profile().nickname(); String kakaoId = response.id()+""; Optional user = userRepository.findByKakaoId(kakaoId); if(user.isEmpty()){ - User newUser = User.builder().kakaoId(kakaoId). - accessToken(accessToken). - refreshToken(refreshToken). - build(); + User newUser = User.builder().kakaoId(kakaoId) + .userName(userName) + .userImgUrl("") + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); userRepository.save(newUser); return jwtUtil.createToken(newUser); } @@ -72,5 +87,27 @@ public String Login(String accessToken, String refreshToken){ return jwtUtil.createToken(user.get()); } + /** + * 카카오 로그아웃 수행 + * @param userId + * @return logoutResponseDto + */ + @Transactional + public LogoutResponseDto logout(Long userId){ + User user = userRepository.findById(userId) + .orElseThrow(()-> new UserNotFoundException("확인되지 않은 유저 입니다.")); + + var uri = "https://kapi.kakao.com/v1/user/logout"; + var response = restClient.post() + .uri(URI.create(uri)) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .header("Authorization", "Bearer " + user.getAccessToken()) + .retrieve() + .body(LogoutResponseDto.class); + user.kakaoTokenUpdate("",""); + return response; + + } + } diff --git a/src/main/java/jeje/work/aeatbe/service/UserService.java b/src/main/java/jeje/work/aeatbe/service/UserService.java index f3e0ae28..5a96590f 100644 --- a/src/main/java/jeje/work/aeatbe/service/UserService.java +++ b/src/main/java/jeje/work/aeatbe/service/UserService.java @@ -7,7 +7,9 @@ import jeje.work.aeatbe.domian.KakaoProperties; import jeje.work.aeatbe.domian.KakaoTokenResponsed; import jeje.work.aeatbe.domian.KakaoUserInfo; +import jeje.work.aeatbe.dto.user.UserInfoResponseDto; import jeje.work.aeatbe.entity.User; +import jeje.work.aeatbe.exception.UserNotFoundException; import jeje.work.aeatbe.repository.UserRepository; import jeje.work.aeatbe.utility.JwtUtil; import lombok.RequiredArgsConstructor; @@ -26,7 +28,11 @@ public class UserService { private final KakaoService kakaoService; - + /** + * 카카오 id로 유저 id(pk)를 반환 + * @param kakaoId + * @return Long 유저의 id + */ public Long getUserId(String kakaoId){ Optional user = userRepository.findByKakaoId(kakaoId); if(user.isPresent()){ @@ -35,15 +41,32 @@ public Long getUserId(String kakaoId){ return null; } - public String createToken(User user) { - return jwtUtil.createToken(user); - } - + /** + * 주어진 jwt토큰을 검증하여 이미 있는 유저인지 확인한다. + * @param token + * @return boolean 이미 존재하는 유저인지 + */ public boolean validateToken(String token) { String kakaoId = jwtUtil.getKakaoId(token); return userRepository.findByKakaoId(kakaoId).isPresent(); } + /** + * 유저 정보를 반환한다, + * @param userId + * @return UserInfoResponseDto + */ + public UserInfoResponseDto getUserInfo(Long userId){ + User user = userRepository.findById(userId) + .orElseThrow(()-> new UserNotFoundException("잘못된 유저입니다.")); + return UserInfoResponseDto.builder() + .id(user.getId()) + .userName(user.getUserName()) + .userImageUrl(user.getUserImgUrl()) + .build(); + + } + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e7a58945..3f04fd37 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -34,5 +34,7 @@ jwt: kakao: client_id: 4d3d60da75b24635e63c62f780ab3ea9 - redirect_url: http://localhost:8080/api/users/callback + redirect_url: https://kakao-team2-fe.vercel.app/login/redirect auth_url: https://kauth.kakao.com/oauth/authorize + logout_url: https://kauth.kakao.com/oauth/logout + logout_redirect_url: http://localhost:8080/api/users/logoutWithKakao/callback