From 0ff7e32294ca7c6e97e2f3df91bf24f61d08cd66 Mon Sep 17 00:00:00 2001 From: You Jung <80906691+JangYouJung@users.noreply.github.com> Date: Sun, 14 Jul 2024 01:57:03 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Feature:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 7 ---- .../server/auth/service/AuthService.java | 7 ++-- .../server/chat/service/WebSocketService.java | 2 +- .../server/config/TokenProvider.java | 6 +-- .../server/domain/entity/Account.java | 2 + .../domain/repository/AccountRepository.java | 6 +-- .../party/PartyParticipantRepository.java | 5 +++ .../repository/party/PartyRepository.java | 3 ++ .../user/controller/UserController.java | 17 ++++++++ .../server/user/service/UserService.java | 42 +++++++++++++++++++ .../meltingpot/server/util/ResponseCode.java | 6 +++ 11 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/main/java/meltingpot/server/auth/controller/AuthController.java b/src/main/java/meltingpot/server/auth/controller/AuthController.java index b2361df..25c834c 100644 --- a/src/main/java/meltingpot/server/auth/controller/AuthController.java +++ b/src/main/java/meltingpot/server/auth/controller/AuthController.java @@ -111,11 +111,4 @@ public ResponseEntity> reissueToken( return ResponseData.toResponseEntity(ResponseCode.INVALID_REFRESH_TOKEN, null); } } - - - // 비밀번호 재설정 - - // 탈퇴 - - } diff --git a/src/main/java/meltingpot/server/auth/service/AuthService.java b/src/main/java/meltingpot/server/auth/service/AuthService.java index 3732892..cf4eb44 100644 --- a/src/main/java/meltingpot/server/auth/service/AuthService.java +++ b/src/main/java/meltingpot/server/auth/service/AuthService.java @@ -107,6 +107,7 @@ public AccountResponseDto signup(SignupRequestDto signupRequest) { .gender(Gender.valueOf(signupRequest.gender())) .birth(signupRequest.birth()) .nationality(signupRequest.nationality()) + .isQuit(false) .build(); account.setProfileImages(signupRequest.profileImages().stream().map( @@ -152,7 +153,7 @@ public AccountResponseDto signin(SigninServiceDto serviceDto){ TokenDto tokenDto = tokenProvider.generateTokenDto(authentication); // 4. RefreshToken 저장 - Account account = accountRepository.findByUsername(authentication.getName()) + Account account = accountRepository.findByUsernameAndIsQuitIsFalse(authentication.getName()) .orElseThrow(() -> new ResourceNotFoundException(ResponseCode.ACCOUNT_NOT_FOUND)); RefreshToken refreshToken = RefreshToken.builder() .account(account) @@ -201,7 +202,7 @@ public FileUploadResponse generateImageUploadUrl() { // 로그인 유저 정보 반환 to @CurrentUser @Transactional(readOnly = true) public Account getUserInfo(){ - return accountRepository.findByUsernameAndDeletedAtIsNull(SecurityUtil.getCurrentUserName()) + return accountRepository.findByUsernameAndIsQuitIsFalse(SecurityUtil.getCurrentUserName()) .orElseThrow(() -> new ResourceNotFoundException(ResponseCode.ACCOUNT_NOT_FOUND)); } @@ -209,7 +210,7 @@ public Account getUserInfo(){ @Override @Transactional(readOnly = true) public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - Account account = accountRepository.findByUsername(username) + Account account = accountRepository.findByUsernameAndIsQuitIsFalse(username) .orElseThrow(() -> new UsernameNotFoundException(username)); return new AccountUser(account); } diff --git a/src/main/java/meltingpot/server/chat/service/WebSocketService.java b/src/main/java/meltingpot/server/chat/service/WebSocketService.java index ea784cf..ce36563 100644 --- a/src/main/java/meltingpot/server/chat/service/WebSocketService.java +++ b/src/main/java/meltingpot/server/chat/service/WebSocketService.java @@ -55,7 +55,7 @@ public ChatMessage createChatMessage(String sessionId, ChatMessageSendRequest ch Party party = partyRepository.findByChatRoomId(chatRoom.getId()) .orElseThrow(() -> new ResourceNotFoundException(PARTY_NOT_FOUND)); - Account account = accountRepository.findByUsername(socketSessionRepository.findBySessionId(sessionId).getUsername()) + Account account = accountRepository.findByUsernameAndIsQuitIsFalse(socketSessionRepository.findBySessionId(sessionId).getUsername()) .orElseThrow(() -> new ResourceNotFoundException(ACCOUNT_NOT_FOUND)); Role role = (party.getAccount().getId().equals(account.getId())) diff --git a/src/main/java/meltingpot/server/config/TokenProvider.java b/src/main/java/meltingpot/server/config/TokenProvider.java index 91a84a6..4d13c65 100644 --- a/src/main/java/meltingpot/server/config/TokenProvider.java +++ b/src/main/java/meltingpot/server/config/TokenProvider.java @@ -90,7 +90,7 @@ public TokenDto generateReissuedTokenDto(String accessToken) { // accessToken에서 username 추출 String username = parseClaims(accessToken).getSubject(); // username으로 account 조회 - Account account = accountRepository.findByUsername(username) + Account account = accountRepository.findByUsernameAndIsQuitIsFalse(username) .orElseThrow(() -> new ResourceNotFoundException(ResponseCode.ACCOUNT_NOT_FOUND)); // account에서 account_roles -> authorities로 변환 String authorities = account.toAuthStringList().stream().collect(Collectors.joining(",")); @@ -126,7 +126,7 @@ public TokenDto generateReissuedTokenDto(String accessToken) { // 저장되어있는 RefreshToken의 account와 접속한 계정이 동일한지 확인 public Boolean validRefreshToken(String refreshToken, String accessToken) { String username = parseClaims(accessToken).getSubject(); - Account account = accountRepository.findByUsername(username) + Account account = accountRepository.findByUsernameAndIsQuitIsFalse(username) .orElseThrow(() -> new ResourceNotFoundException(ResponseCode.ACCOUNT_NOT_FOUND)); RefreshToken matchRefreshToken = refreshTokenRepository.findByTokenValue(refreshToken) @@ -141,7 +141,7 @@ public Boolean validRefreshToken(String refreshToken, String accessToken) { // 재발급한 RefreshToken 저장 public void updateRefreshToken(String accessToken, String newRefreshToken) { String username = parseClaims(accessToken).getSubject(); - Account account = accountRepository.findByUsername(username) + Account account = accountRepository.findByUsernameAndIsQuitIsFalse(username) .orElseThrow(() -> new ResourceNotFoundException(ResponseCode.ACCOUNT_NOT_FOUND)); // 재발급한 refresh token 저장 diff --git a/src/main/java/meltingpot/server/domain/entity/Account.java b/src/main/java/meltingpot/server/domain/entity/Account.java index 989d1d4..fc2f8c0 100644 --- a/src/main/java/meltingpot/server/domain/entity/Account.java +++ b/src/main/java/meltingpot/server/domain/entity/Account.java @@ -59,6 +59,8 @@ public class Account extends BaseEntity { private LocalDateTime deletedAt; + private Boolean isQuit; // 탈퇴 여부 + @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, fetch = FetchType.EAGER) @Builder.Default private List profileImages = new ArrayList<>(); diff --git a/src/main/java/meltingpot/server/domain/repository/AccountRepository.java b/src/main/java/meltingpot/server/domain/repository/AccountRepository.java index 87e16d7..8767719 100644 --- a/src/main/java/meltingpot/server/domain/repository/AccountRepository.java +++ b/src/main/java/meltingpot/server/domain/repository/AccountRepository.java @@ -4,14 +4,10 @@ import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; -import javax.swing.text.html.Option; import java.util.Optional; public interface AccountRepository extends JpaRepository { - @EntityGraph(attributePaths = {"accountRoles"}) - Optional findByUsername(String name); - - Optional findByUsernameAndDeletedAtIsNull(String currentUserName); + Optional findByUsernameAndIsQuitIsFalse(String name); boolean existsByUsername(String username); diff --git a/src/main/java/meltingpot/server/domain/repository/party/PartyParticipantRepository.java b/src/main/java/meltingpot/server/domain/repository/party/PartyParticipantRepository.java index 079246a..8aec54f 100644 --- a/src/main/java/meltingpot/server/domain/repository/party/PartyParticipantRepository.java +++ b/src/main/java/meltingpot/server/domain/repository/party/PartyParticipantRepository.java @@ -1,15 +1,20 @@ package meltingpot.server.domain.repository.party; import meltingpot.server.domain.entity.Account; +import meltingpot.server.domain.entity.party.Party; import meltingpot.server.domain.entity.party.PartyParticipant; import meltingpot.server.domain.entity.party.enums.ParticipantStatus; import meltingpot.server.domain.entity.party.enums.PartyStatus; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.EnumSet; +import java.util.List; import java.util.Optional; public interface PartyParticipantRepository extends JpaRepository { int countByParty_PartyStatusAndParticipantStatusAndAccount(PartyStatus partyStatus, ParticipantStatus participantStatus, Account account); + List findAllByAccount(Account account); + Optional findAllByAccountId(Long userId); } diff --git a/src/main/java/meltingpot/server/domain/repository/party/PartyRepository.java b/src/main/java/meltingpot/server/domain/repository/party/PartyRepository.java index 2a82a8e..2692d72 100644 --- a/src/main/java/meltingpot/server/domain/repository/party/PartyRepository.java +++ b/src/main/java/meltingpot/server/domain/repository/party/PartyRepository.java @@ -10,6 +10,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.EnumSet; import java.util.Optional; public interface PartyRepository extends JpaRepository, JpaSpecificationExecutor { @@ -17,6 +18,8 @@ public interface PartyRepository extends JpaRepository, JpaSpeci Party findByAccountAndPartyStatus(Account account, PartyStatus status); + boolean existsByAccountAndPartyStatusIn(Account account, EnumSet statuses); + int countByAccountAndPartyStatus(Account account, PartyStatus status); @Query("SELECT DISTINCT p FROM Party p LEFT JOIN p.partyParticipants pp WHERE (p.account = :account OR pp.account = :account) AND p.deletedAt IS NULL ORDER BY p.createdAt DESC") diff --git a/src/main/java/meltingpot/server/user/controller/UserController.java b/src/main/java/meltingpot/server/user/controller/UserController.java index 0b41a70..ea10723 100644 --- a/src/main/java/meltingpot/server/user/controller/UserController.java +++ b/src/main/java/meltingpot/server/user/controller/UserController.java @@ -176,4 +176,21 @@ public ResponseEntity>> readUsersParti } } + //탈퇴 + @DeleteMapping("") + @Operation(summary="마이페이지 회원 탈퇴", description="회원 삭제가 아닌 소프트 마킹으로 구현했습니다." ) + @ApiResponses(value = { + @ApiResponse(responseCode = "OK", description = "회원 탈퇴 성공"), + @ApiResponse(responseCode = "BAD_REQUEST", description = "예정된 파티가 있는 경우") + }) + public ResponseEntity deleteAccount( + @CurrentUser Account account + ){ + try{ + return ResponseData.toResponseEntity(userService.deleteAccount(account)); + } + catch (NoSuchElementException e){ + return ResponseData.toResponseEntity(ResponseCode.ACCOUNT_DELETE_FAIL); + } + } } diff --git a/src/main/java/meltingpot/server/user/service/UserService.java b/src/main/java/meltingpot/server/user/service/UserService.java index 98fbfe8..04fb240 100644 --- a/src/main/java/meltingpot/server/user/service/UserService.java +++ b/src/main/java/meltingpot/server/user/service/UserService.java @@ -4,7 +4,9 @@ import lombok.RequiredArgsConstructor; import meltingpot.server.domain.entity.Account; import meltingpot.server.domain.entity.AccountProfileImage; +import meltingpot.server.domain.entity.enums.Gender; import meltingpot.server.domain.entity.party.Party; +import meltingpot.server.domain.entity.party.PartyParticipant; import meltingpot.server.util.Constants; import meltingpot.server.domain.entity.comment.Comment; import meltingpot.server.domain.entity.party.enums.ParticipantStatus; @@ -228,6 +230,8 @@ public SliceResponse readUsersComments(Long userId, Integer pa return new SliceResponse<>(postSlice); } + // 마이페이지 사용자 파티 참여/주최 내역 + @Transactional public SliceResponse readUsersParties(Long userId, Integer page) { Account account = accountRepository.findById(userId).orElseThrow(() -> new NoSuchElementException("계정을 찾을 수 없습니다")); PageRequest pageRequest = PageRequest.of(page, Constants.PAGE_DEFAULT_SIZE, Sort.by("createdAt").descending()); @@ -237,4 +241,42 @@ public SliceResponse readUsersParties(Long userId, Integer page) .map(party -> PartyResponse.of(party))); } + + // 회원 탈퇴 + @Transactional + public ResponseCode deleteAccount(Account account) { + + EnumSet plannedPartyStatus = EnumSet.of(PartyStatus.RECRUIT_SCHEDULED, PartyStatus.RECRUIT_OPEN, PartyStatus.RECRUIT_CLOSED, PartyStatus.RUNNING); + + // 주최 중인 파티 있는지 확인 + if(partyRepository.existsByAccountAndPartyStatusIn(account, plannedPartyStatus)){ + return ResponseCode.PARTY_HOST_ACCOUNT_DELETE_DENIED; + } + + // 참여 중인 파티 있는지 확인 + boolean hasActiveParty = partyParticipantRepository.findAllByAccount(account).stream() + .map(participant -> participant.getParty().getPartyStatus()) + .anyMatch(plannedPartyStatus::contains); + + if (hasActiveParty) { + return ResponseCode.PARTY_PARTICIPANT_ACCOUNT_DELETE_DENIED; + } + + account.setUsername(""); + account.setLanguages(new ArrayList<>()); + account.setName("UNKNOWN"); + account.setPassword(""); + account.setGender(Gender.UNKNOWN); + account.setBirth(null); + account.setBio("This account is deleted"); + account.setNationality(""); + account.setIsQuit(true); + + // 프로필 이미지 삭제 + for(AccountProfileImage image : account.getProfileImages()){ + deleteProfileImage(account, image.getId()); + } + + return ResponseCode.ACCOUNT_DELETE_SUCCESS; + } } diff --git a/src/main/java/meltingpot/server/util/ResponseCode.java b/src/main/java/meltingpot/server/util/ResponseCode.java index 1b5ea0f..263c921 100644 --- a/src/main/java/meltingpot/server/util/ResponseCode.java +++ b/src/main/java/meltingpot/server/util/ResponseCode.java @@ -17,6 +17,7 @@ public enum ResponseCode { MAIL_VERIFICATION_SEND_SUCCESS(OK, "이메일 인증번호 전송 성공"), MAIL_VERIFICATION_CHECK_SUCCESS(OK, "인증번호가 일치합니다"), MAIL_AVAILABLE(OK, "사용 가능한 이메일입니다"), + ACCOUNT_DELETE_SUCCESS(OK,"회원 탈퇴 성공"), PARTY_FETCH_SUCCESS(OK, "파티 정보 불러오기 성공"), PARTY_SEARCH_SUCCESS(OK, "파티 검색 성공"), @@ -99,6 +100,11 @@ public enum ResponseCode { READ_USERS_COMMENTS_FAIL(BAD_REQUEST, "사용자가 댓글을 작성한 게시글 불러오기 실패"), READ_USERS_PARTIES_FAIL(BAD_REQUEST, "사용자가 참여한 파티 불러오기 실패"), + ACCOUNT_DELETE_FAIL(BAD_REQUEST,"회원 탈퇴 실패"), + PARTY_HOST_ACCOUNT_DELETE_DENIED(BAD_REQUEST,"파티가 예정된 주최자는 탈퇴할 수 없습니다."), + PARTY_PARTICIPANT_ACCOUNT_DELETE_DENIED(BAD_REQUEST,"파티가 예정된 참여자는 탈퇴할 수 없습니다."), + + /* 401 UNAUTHORIZED : 인증되지 않은 사용자 */ INVALID_AUTH_TOKEN(UNAUTHORIZED, "권한 정보가 없는 토큰입니다"), INVALID_ACCOUNT(UNAUTHORIZED, "계정이 비활성화 되었습니다"), From 5c903ba0a71448a56a24eead1c80592d81e31dce Mon Sep 17 00:00:00 2001 From: You Jung <80906691+JangYouJung@users.noreply.github.com> Date: Sun, 21 Jul 2024 01:01:03 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Feature:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 8 + .../auth/controller/AuthController.java | 44 +++- .../controller/dto/OAuthSignInRequestDto.java | 10 + .../controller/dto/OAuthSignupRequestDto.java | 21 ++ .../server/auth/oauth/OAuthUserDetails.java | 58 ++++++ .../auth/oauth/kakao/KaKaoTokenDto.java | 10 + .../server/auth/oauth/kakao/KakaoDto.java | 13 ++ .../server/auth/oauth/kakao/KakaoService.java | 110 ++++++++++ .../server/auth/service/AuthService.java | 5 +- .../server/auth/service/OAuthService.java | 196 ++++++++++++++++++ .../service/dto/OAuthSignInResponseDto.java | 13 ++ .../server/domain/entity/Account.java | 4 +- .../server/domain/entity/MsgEntity.java | 17 ++ .../server/domain/entity/enums/OAuthType.java | 5 + .../server/exception/ExceptionAdvice.java | 15 ++ .../meltingpot/server/util/ErrorCode.java | 2 +- .../meltingpot/server/util/SecurityUtil.java | 2 - src/main/resources/application.yml | 22 ++ 18 files changed, 546 insertions(+), 9 deletions(-) create mode 100644 src/main/java/meltingpot/server/auth/controller/dto/OAuthSignInRequestDto.java create mode 100644 src/main/java/meltingpot/server/auth/controller/dto/OAuthSignupRequestDto.java create mode 100644 src/main/java/meltingpot/server/auth/oauth/OAuthUserDetails.java create mode 100644 src/main/java/meltingpot/server/auth/oauth/kakao/KaKaoTokenDto.java create mode 100644 src/main/java/meltingpot/server/auth/oauth/kakao/KakaoDto.java create mode 100644 src/main/java/meltingpot/server/auth/oauth/kakao/KakaoService.java create mode 100644 src/main/java/meltingpot/server/auth/service/OAuthService.java create mode 100644 src/main/java/meltingpot/server/auth/service/dto/OAuthSignInResponseDto.java create mode 100644 src/main/java/meltingpot/server/domain/entity/MsgEntity.java create mode 100644 src/main/java/meltingpot/server/domain/entity/enums/OAuthType.java create mode 100644 src/main/java/meltingpot/server/exception/ExceptionAdvice.java diff --git a/build.gradle b/build.gradle index 8939082..facd766 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,7 @@ dependencies { // mail implementation 'org.springframework.boot:spring-boot-starter-mail:3.0.5' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' // JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' @@ -53,10 +54,17 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-core:2.16.1' implementation 'org.springframework.boot:spring-boot-starter-webflux' + if (isAppleSilicon()) { runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.94.Final:osx-aarch_64") } + //Apple Login + implementation 'com.nimbusds:nimbus-jose-jwt:3.10' + + //Json + implementation 'com.googlecode.json-simple:json-simple:1.1.1' + developmentOnly 'org.springframework.boot:spring-boot-devtools' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/meltingpot/server/auth/controller/AuthController.java b/src/main/java/meltingpot/server/auth/controller/AuthController.java index 25c834c..0c9346b 100644 --- a/src/main/java/meltingpot/server/auth/controller/AuthController.java +++ b/src/main/java/meltingpot/server/auth/controller/AuthController.java @@ -31,9 +31,10 @@ public class AuthController { private final AuthService authService; + private final OAuthService oAuthService; private final Logger logger = LoggerFactory.getLogger(this.getClass()); - // 회원 가입 + // 일반 회원 가입 @PostMapping("signup") @Operation(summary="회원가입", description="회원가입 API 입니다.\n 회원가입 성공시 자동 로그인되어 AccessToken이 반환됩니다. " ) @ApiResponses(value = { @@ -56,6 +57,28 @@ public ResponseEntity> signup( } } + // SNS 회원 가입 + @PostMapping("/signup/oauth") + @Operation(summary="SNS 회원가입", description="SNS 회원가입 API 입니다.\n" ) + @ApiResponses(value = { + @ApiResponse(responseCode = "CREATED", description = "회원가입 성공"), + @ApiResponse(responseCode = "BAD_REQUEST", description = "회원가입 실패") + }) + public ResponseEntity oauthSignup( + @RequestBody @Valid OAuthSignupRequestDto request + ){ + try{ + return ResponseData.toResponseEntity(oAuthService.oauthSignup(request)); + + }catch ( AuthException e ){ + return ResponseData.toResponseEntity( e.getResponseCode()); + }catch ( IllegalArgumentException e ){ + return ResponseData.toResponseEntity( e.getResponseCode()); + } + } + + + // 프로필 이미지 URL 생성 @GetMapping("/image-url") @Operation(summary = "회원가입 프로필 이미지 URL 생성", description = "프로필 이미지 업로드를 위한 URL을 생성합니다. 생성된 URL에 PUT으로 이미지를 업로드 한 뒤 key를 회원가입에 첨부해주세요.") @@ -86,6 +109,25 @@ public ResponseEntity> signin( } } + // SNS 로그인 + @PostMapping("signin/oauth") + @Operation(summary="SNS 로그인", description="SNS 로그인 API 입니다" ) + public ResponseEntity> SNSLogin( + @RequestBody @Valid OAuthSignInRequestDto request + ){ + try{ + OAuthSignInResponseDto data = oAuthService.SNSLogin(request); + return ResponseData.toResponseEntity(ResponseCode.SIGNIN_SUCCESS, data); + + }catch( ResourceNotFoundException e ){ + return ResponseData.toResponseEntity(ResponseCode.ACCOUNT_NOT_FOUND, null); + }catch ( InvalidTokenException e ){ + return ResponseData.toResponseEntity(ResponseCode.REFRESH_TOKEN_NOT_FOUND, null); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + // 로그아웃 @GetMapping("signout") diff --git a/src/main/java/meltingpot/server/auth/controller/dto/OAuthSignInRequestDto.java b/src/main/java/meltingpot/server/auth/controller/dto/OAuthSignInRequestDto.java new file mode 100644 index 0000000..c5cd0d6 --- /dev/null +++ b/src/main/java/meltingpot/server/auth/controller/dto/OAuthSignInRequestDto.java @@ -0,0 +1,10 @@ +package meltingpot.server.auth.controller.dto; + +import meltingpot.server.domain.entity.enums.OAuthType; + +public record OAuthSignInRequestDto( + OAuthType type, + String code, + String push_token +) { +} diff --git a/src/main/java/meltingpot/server/auth/controller/dto/OAuthSignupRequestDto.java b/src/main/java/meltingpot/server/auth/controller/dto/OAuthSignupRequestDto.java new file mode 100644 index 0000000..4b47153 --- /dev/null +++ b/src/main/java/meltingpot/server/auth/controller/dto/OAuthSignupRequestDto.java @@ -0,0 +1,21 @@ +package meltingpot.server.auth.controller.dto; + +import lombok.Builder; +import meltingpot.server.domain.entity.enums.OAuthType; + +import java.time.LocalDate; +import java.util.List; + +@Builder +public record OAuthSignupRequestDto( + OAuthType OauthType, + String email, + String name, + String gender, + LocalDate birth, + String nationality, + List languages, + List profileImages, + String pushToken +) { +} diff --git a/src/main/java/meltingpot/server/auth/oauth/OAuthUserDetails.java b/src/main/java/meltingpot/server/auth/oauth/OAuthUserDetails.java new file mode 100644 index 0000000..8365b87 --- /dev/null +++ b/src/main/java/meltingpot/server/auth/oauth/OAuthUserDetails.java @@ -0,0 +1,58 @@ +package meltingpot.server.auth.oauth; + +import meltingpot.server.domain.entity.Account; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +public class OAuthUserDetails implements UserDetails { + + private final Account account; + + public OAuthUserDetails(Account account){ + this.account = account; + } + + @Override + public Collection getAuthorities() { + List grantedAuthorities = new ArrayList<>(); + grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER")); + return grantedAuthorities; + } + + @Override + public String getPassword() { + return this.account.getPassword(); + } + + @Override + public String getUsername() { + return this.account.getName(); + } + + //계정 만료 여부 + @Override + public boolean isAccountNonExpired() { + return true; + } + //계정 잠김 여부 + @Override + public boolean isAccountNonLocked() { + return true; + } + //계정 정보 변경 필요 여부 + @Override + public boolean isCredentialsNonExpired() { + return true; + } + //계정 활성화 여부 + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/meltingpot/server/auth/oauth/kakao/KaKaoTokenDto.java b/src/main/java/meltingpot/server/auth/oauth/kakao/KaKaoTokenDto.java new file mode 100644 index 0000000..1538024 --- /dev/null +++ b/src/main/java/meltingpot/server/auth/oauth/kakao/KaKaoTokenDto.java @@ -0,0 +1,10 @@ +package meltingpot.server.auth.oauth.kakao; + +import lombok.Builder; + +@Builder +public record KaKaoTokenDto ( + String accessToken, + String refreshToken +){ +} diff --git a/src/main/java/meltingpot/server/auth/oauth/kakao/KakaoDto.java b/src/main/java/meltingpot/server/auth/oauth/kakao/KakaoDto.java new file mode 100644 index 0000000..5c0780c --- /dev/null +++ b/src/main/java/meltingpot/server/auth/oauth/kakao/KakaoDto.java @@ -0,0 +1,13 @@ +package meltingpot.server.auth.oauth.kakao; + +import lombok.Builder; +import lombok.Data; +@Builder +@Data +public class KakaoDto { + + private long id; + private String email; + private String nickname; + +} \ No newline at end of file diff --git a/src/main/java/meltingpot/server/auth/oauth/kakao/KakaoService.java b/src/main/java/meltingpot/server/auth/oauth/kakao/KakaoService.java new file mode 100644 index 0000000..08e9822 --- /dev/null +++ b/src/main/java/meltingpot/server/auth/oauth/kakao/KakaoService.java @@ -0,0 +1,110 @@ +package meltingpot.server.auth.oauth.kakao; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +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.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +@Service +public class KakaoService { + + @Value("${spring.security.oauth2.client.registration.kakao.client-id}") + private String KAKAO_CLIENT_ID; + + @Value("${spring.security.oauth2.client.registration.kakao.client-secret}") + private String KAKAO_CLIENT_SECRET; + + @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}") + private String redirect_uri; + + private final static String KAKAO_AUTH_URI = "https://kauth.kakao.com"; + private final static String KAKAO_API_URI = "https://kapi.kakao.com"; + + public String getKakaoLogin(String redirect_uri) { // 프론트 구현부 + return KAKAO_AUTH_URI + "/oauth/authorize" + + "?client_id=" + KAKAO_CLIENT_ID + + "&redirect_uri=" + redirect_uri + + "&response_type=code"; + } + + public KaKaoTokenDto getKakaoInfo(String code) throws Exception { + if (code == null) throw new Exception("Failed get authorization code"); + + String accessToken = ""; + String refreshToken = ""; + + try { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-type", "application/x-www-form-urlencoded"); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type" , "authorization_code"); + params.add("client_id" , KAKAO_CLIENT_ID); + params.add("client_secret", KAKAO_CLIENT_SECRET); + params.add("code" , code); + params.add("redirect_uri" , redirect_uri); + + RestTemplate restTemplate = new RestTemplate(); + HttpEntity> httpEntity = new HttpEntity<>(params, headers); + + ResponseEntity response = restTemplate.exchange( + KAKAO_AUTH_URI + "/oauth/token", + HttpMethod.POST, + httpEntity, + String.class + ); + + JSONParser jsonParser = new JSONParser(); + JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody()); + + accessToken = (String) jsonObj.get("access_token"); + refreshToken = (String) jsonObj.get("refresh_token"); + } catch (Exception e) { + throw new Exception("KAKAO API call failed"); + } + + return KaKaoTokenDto.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + + public KakaoDto getUserInfoWithToken(String accessToken) throws Exception { + //HttpHeader 생성 + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + accessToken); + headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); + + //HttpHeader 담기 + RestTemplate rt = new RestTemplate(); + HttpEntity> httpEntity = new HttpEntity<>(headers); + ResponseEntity response = rt.exchange( + KAKAO_API_URI + "/v2/user/me", + HttpMethod.POST, + httpEntity, + String.class + ); + + //Response 데이터 파싱 + JSONParser jsonParser = new JSONParser(); + JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody()); + JSONObject account = (JSONObject) jsonObj.get("kakao_account"); + JSONObject profile = (JSONObject) account.get("profile"); + + long id = (long) jsonObj.get("id"); + String email = String.valueOf(account.get("email")); + String nickname = String.valueOf(profile.get("nickname")); + + return KakaoDto.builder() + .id(id) + .email(email) + .nickname(nickname).build(); + } +} \ No newline at end of file diff --git a/src/main/java/meltingpot/server/auth/service/AuthService.java b/src/main/java/meltingpot/server/auth/service/AuthService.java index cf4eb44..0c4e667 100644 --- a/src/main/java/meltingpot/server/auth/service/AuthService.java +++ b/src/main/java/meltingpot/server/auth/service/AuthService.java @@ -2,9 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import meltingpot.server.auth.controller.dto.ProfileImageRequestDto; -import meltingpot.server.auth.controller.dto.ReissueTokenResponseDto; -import meltingpot.server.auth.controller.dto.SignupRequestDto; +import meltingpot.server.auth.controller.dto.*; import meltingpot.server.domain.entity.*; import meltingpot.server.domain.entity.enums.Gender; import meltingpot.server.domain.repository.AccountPushTokenRepository; @@ -13,7 +11,6 @@ import meltingpot.server.config.TokenProvider; import meltingpot.server.domain.repository.RefreshTokenRepository; import meltingpot.server.domain.repository.AccountRepository; -import meltingpot.server.auth.controller.dto.AccountResponseDto; import meltingpot.server.auth.service.dto.SigninServiceDto; import meltingpot.server.exception.IllegalArgumentException; import meltingpot.server.util.AccountUser; diff --git a/src/main/java/meltingpot/server/auth/service/OAuthService.java b/src/main/java/meltingpot/server/auth/service/OAuthService.java new file mode 100644 index 0000000..082d086 --- /dev/null +++ b/src/main/java/meltingpot/server/auth/service/OAuthService.java @@ -0,0 +1,196 @@ +package meltingpot.server.auth.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import meltingpot.server.auth.controller.dto.OAuthSignInRequestDto; +import meltingpot.server.auth.controller.dto.OAuthSignupRequestDto; +import meltingpot.server.auth.controller.dto.ProfileImageRequestDto; +import meltingpot.server.auth.oauth.OAuthUserDetails; +import meltingpot.server.auth.oauth.kakao.KaKaoTokenDto; +import meltingpot.server.auth.oauth.kakao.KakaoDto; +import meltingpot.server.auth.oauth.kakao.KakaoService; +import meltingpot.server.auth.service.dto.OAuthSignInResponseDto; +import meltingpot.server.config.TokenProvider; +import meltingpot.server.domain.entity.*; +import meltingpot.server.domain.entity.enums.Gender; +import meltingpot.server.domain.entity.enums.OAuthType; +import meltingpot.server.domain.repository.AccountPushTokenRepository; +import meltingpot.server.domain.repository.AccountRepository; +import meltingpot.server.domain.repository.RefreshTokenRepository; +import meltingpot.server.exception.AuthException; +import meltingpot.server.exception.IllegalArgumentException; +import meltingpot.server.exception.ResourceNotFoundException; +import meltingpot.server.util.ResponseCode; +import meltingpot.server.util.TokenDto; +import meltingpot.server.util.r2.FileService; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; + +@Slf4j +@RequiredArgsConstructor +@Service +@EnableWebSecurity +public class OAuthService { + private final AccountRepository accountRepository; + private final KakaoService kakaoService; + private final TokenProvider tokenProvider; + private final RefreshTokenRepository refreshTokenRepository; + private final AccountPushTokenRepository accountPushTokenRepository; + + + // SNS 회원 가입 + public ResponseCode oauthSignup(OAuthSignupRequestDto signupRequest) { + // 프로필 사진 개수 확인 + if(signupRequest.profileImages().isEmpty()){ + throw new AuthException(ResponseCode.PROFILE_IMAGE_LESS_THAN_ONE); + } + if(signupRequest.profileImages().size()>4){ + throw new AuthException(ResponseCode.PROFILE_IMAGE_MORE_THAN_FOUR); + } + + // 프로필 이미지 썸네일 지정 여부 확인 + boolean thumbnail_check = false; + for(ProfileImageRequestDto image : signupRequest.profileImages()){ + if (image.isThumbnail()) { + if(thumbnail_check) throw new IllegalArgumentException(ResponseCode.THUMBNAIL_IS_DUPLICATED); + thumbnail_check = true; + } + } + if(!thumbnail_check) throw new AuthException(ResponseCode.THUMBNAIL_NOT_FOUND); + + // 시퀀스 모두 다른지 확인 + Set sequences = new HashSet<>(); + for (ProfileImageRequestDto profileImage : signupRequest.profileImages()) { + if (!sequences.add(profileImage.getSequence())) { + throw new IllegalArgumentException(ResponseCode.PROFILE_IMAGE_SEQUENCE_IS_DUPLICATED); + } + } + + // 성별 유효성 확인 + boolean gender_check = false; + for( Gender gender : Gender.values()){ + if(gender.toString().equals(signupRequest.gender())) gender_check = true; + } + if(!gender_check) throw new IllegalArgumentException(ResponseCode.INVALID_GENDER_IS_PROVIDED); + + + Account account = Account.builder() + .username(signupRequest.email()) + .name(signupRequest.name()) + .password("") + .gender(Gender.valueOf(signupRequest.gender())) + .birth(signupRequest.birth()) + .nationality(signupRequest.nationality()) + .isQuit(false) + .oAuthType(signupRequest.OauthType()) + .build(); + + account.setProfileImages(signupRequest.profileImages().stream().map( + (image) -> AccountProfileImage.builder() + .account(account) + .imageKey(image.getImageKey()) + .isThumbnail(image.isThumbnail()) + .sequence(image.getSequence()) + .imageOriginalName("") + .build()).toList() + ); + + account.setLanguages(signupRequest.languages().stream().map( + (language) -> AccountLanguage.builder() + .account(account) + .language(language) + .build()).toList() + ); + + accountRepository.save(account); + + return ResponseCode.SIGNUP_SUCCESS; + + } + + @Transactional + public OAuthSignInResponseDto SNSLogin(OAuthSignInRequestDto request) throws Exception { + + if(request.type() == OAuthType.KAKAO) { + // 카카오 토큰 가져오기 + KaKaoTokenDto tokenDto = kakaoService.getKakaoInfo(request.code()); + + // 카카오 유저 정보 가져오기 + KakaoDto kakaoDto = kakaoService.getUserInfoWithToken(tokenDto.accessToken()); + + // 이미 가입한 회원인지 확인 + Optional account = accountRepository.findByUsernameAndIsQuitIsFalse(kakaoDto.getEmail()); + if (account.isEmpty()) { + + // 회원 가입이 필요한 경우 + return OAuthSignInResponseDto.builder() + .register_required(true) + .accessToken(tokenDto.accessToken()) + .refreshToken(tokenDto.refreshToken()) + .nickName(kakaoDto.getNickname()) + .email(kakaoDto.getEmail()) + .build(); + + } else { + + // 발급 받은 토큰 Spring Security Context에 저장 + OAuthUserDetails oAuthUserDetails= new OAuthUserDetails(account.get()); + Authentication authentication = new UsernamePasswordAuthenticationToken(oAuthUserDetails, null, oAuthUserDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + + // 인증 정보를 기반으로 JWT 토큰 생성 + TokenDto jwtTokenDto = tokenProvider.generateTokenDto(authentication); + + // RefreshToken 저장 + RefreshToken refreshToken = RefreshToken.builder() + .account(account.get()) + .tokenValue(jwtTokenDto.getRefreshToken()) + .build(); + + refreshTokenRepository.save(refreshToken); + + if (!accountPushTokenRepository.existsAccountPushByAccountAndToken(account.get(), request.push_token())) { + AccountPushToken accountPushToken = AccountPushToken.builder() + .account(account.get()) + .token(request.push_token()) + .build(); + + accountPushTokenRepository.save(accountPushToken); + } + + //인증된 Authentication를 SecurityContext에 저장 + SecurityContextHolder.getContext().setAuthentication(authentication); + + + return OAuthSignInResponseDto.builder() + .register_required(false) + .accessToken(jwtTokenDto.getAccessToken()) + .refreshToken(jwtTokenDto.getRefreshToken()) + .nickName(kakaoDto.getNickname()) + .email(kakaoDto.getEmail()) + .build(); + } + } + else { + throw new NoSuchElementException(); + } + + } + + + + + + +} diff --git a/src/main/java/meltingpot/server/auth/service/dto/OAuthSignInResponseDto.java b/src/main/java/meltingpot/server/auth/service/dto/OAuthSignInResponseDto.java new file mode 100644 index 0000000..f95500e --- /dev/null +++ b/src/main/java/meltingpot/server/auth/service/dto/OAuthSignInResponseDto.java @@ -0,0 +1,13 @@ +package meltingpot.server.auth.service.dto; + +import lombok.Builder; + +@Builder +public record OAuthSignInResponseDto( + String accessToken, + String refreshToken, + String email, + String nickName, + boolean register_required +) { +} diff --git a/src/main/java/meltingpot/server/domain/entity/Account.java b/src/main/java/meltingpot/server/domain/entity/Account.java index fc2f8c0..6521063 100644 --- a/src/main/java/meltingpot/server/domain/entity/Account.java +++ b/src/main/java/meltingpot/server/domain/entity/Account.java @@ -8,6 +8,7 @@ import meltingpot.server.domain.entity.comment.CommentImage; import meltingpot.server.domain.entity.common.BaseEntity; import meltingpot.server.domain.entity.enums.Gender; +import meltingpot.server.domain.entity.enums.OAuthType; import meltingpot.server.domain.entity.post.Post; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; @@ -42,7 +43,6 @@ public class Account extends BaseEntity { @NotNull private String name; - @NotNull private String password; @NotNull @@ -61,6 +61,8 @@ public class Account extends BaseEntity { private Boolean isQuit; // 탈퇴 여부 + private OAuthType oAuthType; + @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, fetch = FetchType.EAGER) @Builder.Default private List profileImages = new ArrayList<>(); diff --git a/src/main/java/meltingpot/server/domain/entity/MsgEntity.java b/src/main/java/meltingpot/server/domain/entity/MsgEntity.java new file mode 100644 index 0000000..2fb131d --- /dev/null +++ b/src/main/java/meltingpot/server/domain/entity/MsgEntity.java @@ -0,0 +1,17 @@ +package meltingpot.server.domain.entity; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MsgEntity { + + private String msg; + private Object result; + + public MsgEntity(String msg, Object result) { + this.msg = msg; + this.result = result; + } +} \ No newline at end of file diff --git a/src/main/java/meltingpot/server/domain/entity/enums/OAuthType.java b/src/main/java/meltingpot/server/domain/entity/enums/OAuthType.java new file mode 100644 index 0000000..b132801 --- /dev/null +++ b/src/main/java/meltingpot/server/domain/entity/enums/OAuthType.java @@ -0,0 +1,5 @@ +package meltingpot.server.domain.entity.enums; + +public enum OAuthType { + NONE, KAKAO, GOOGLE, APPLE +} diff --git a/src/main/java/meltingpot/server/exception/ExceptionAdvice.java b/src/main/java/meltingpot/server/exception/ExceptionAdvice.java new file mode 100644 index 0000000..d8c55da --- /dev/null +++ b/src/main/java/meltingpot/server/exception/ExceptionAdvice.java @@ -0,0 +1,15 @@ +package meltingpot.server.exception; + +import meltingpot.server.domain.entity.MsgEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ExceptionAdvice { + @ExceptionHandler(Exception.class) + protected ResponseEntity globalException(Exception e) { + return ResponseEntity.badRequest() + .body(new MsgEntity(e.getMessage(), "")); + } +} \ No newline at end of file diff --git a/src/main/java/meltingpot/server/util/ErrorCode.java b/src/main/java/meltingpot/server/util/ErrorCode.java index 9247f2e..a77d232 100644 --- a/src/main/java/meltingpot/server/util/ErrorCode.java +++ b/src/main/java/meltingpot/server/util/ErrorCode.java @@ -2,7 +2,7 @@ public enum ErrorCode { INVALID_MESSAGE(400, "Invalid message format."), - INVALID_TOKEN(401, "Invalid token."); + INVALID_TOKEN(401, "Invalid code."); private final int status; private final String message; diff --git a/src/main/java/meltingpot/server/util/SecurityUtil.java b/src/main/java/meltingpot/server/util/SecurityUtil.java index 4754649..a21d590 100644 --- a/src/main/java/meltingpot/server/util/SecurityUtil.java +++ b/src/main/java/meltingpot/server/util/SecurityUtil.java @@ -6,8 +6,6 @@ @Slf4j public class SecurityUtil { - // SecurityContext 에 유저 정보가 저장되는 시점 - // Request 가 들어올 때 JwtFilter 의 doFilter 에서 저장 public static String getCurrentUserName() { final Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5469230..100bf05 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -21,6 +21,28 @@ spring: mail.smtp.auth: true mail.smtp.starttls.enable: true default-encoding: UTF-8 + security: + oauth2: + client: + registration: + kakao: + client-id: ${KAKAO_CLIENT_ID} + client-secret: ${KAKAO_CLIENT_SECRET} + scope: + - account_email + - profile_nickname + authorization-grant-type: authorization_code + redirect-uri: ${KAKAO_REDIRECT_URI} + client-name: Kakao + client-authentication-method: client_secret_post + + 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 + springdoc: swagger-ui: From d8433c4b183c808356d89771b085f0dfdb8f82f5 Mon Sep 17 00:00:00 2001 From: You Jung <80906691+JangYouJung@users.noreply.github.com> Date: Sun, 21 Jul 2024 01:01:48 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Refactor:=20AuthController=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/meltingpot/server/auth/controller/AuthController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/meltingpot/server/auth/controller/AuthController.java b/src/main/java/meltingpot/server/auth/controller/AuthController.java index 0c9346b..14d0286 100644 --- a/src/main/java/meltingpot/server/auth/controller/AuthController.java +++ b/src/main/java/meltingpot/server/auth/controller/AuthController.java @@ -6,6 +6,8 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import meltingpot.server.auth.controller.dto.*; +import meltingpot.server.auth.service.OAuthService; +import meltingpot.server.auth.service.dto.OAuthSignInResponseDto; import meltingpot.server.exception.AuthException; import meltingpot.server.exception.DuplicateException; import meltingpot.server.exception.IllegalArgumentException; From d6b7b9cde9107fa59d289e4e00a800360c99beb6 Mon Sep 17 00:00:00 2001 From: You Jung <80906691+JangYouJung@users.noreply.github.com> Date: Mon, 22 Jul 2024 00:26:01 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Refactor:=20WebSocketService=20=EC=9B=90?= =?UTF-8?q?=EC=83=81=EB=B3=B5=EA=B7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meltingpot/server/chat/service/WebSocketService.java | 2 +- .../server/domain/repository/AccountRepository.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/meltingpot/server/chat/service/WebSocketService.java b/src/main/java/meltingpot/server/chat/service/WebSocketService.java index ce36563..ea784cf 100644 --- a/src/main/java/meltingpot/server/chat/service/WebSocketService.java +++ b/src/main/java/meltingpot/server/chat/service/WebSocketService.java @@ -55,7 +55,7 @@ public ChatMessage createChatMessage(String sessionId, ChatMessageSendRequest ch Party party = partyRepository.findByChatRoomId(chatRoom.getId()) .orElseThrow(() -> new ResourceNotFoundException(PARTY_NOT_FOUND)); - Account account = accountRepository.findByUsernameAndIsQuitIsFalse(socketSessionRepository.findBySessionId(sessionId).getUsername()) + Account account = accountRepository.findByUsername(socketSessionRepository.findBySessionId(sessionId).getUsername()) .orElseThrow(() -> new ResourceNotFoundException(ACCOUNT_NOT_FOUND)); Role role = (party.getAccount().getId().equals(account.getId())) diff --git a/src/main/java/meltingpot/server/domain/repository/AccountRepository.java b/src/main/java/meltingpot/server/domain/repository/AccountRepository.java index 8767719..a3230c6 100644 --- a/src/main/java/meltingpot/server/domain/repository/AccountRepository.java +++ b/src/main/java/meltingpot/server/domain/repository/AccountRepository.java @@ -7,9 +7,10 @@ import java.util.Optional; public interface AccountRepository extends JpaRepository { - Optional findByUsernameAndIsQuitIsFalse(String name); + Optional findByUsernameAndIsQuitIsFalse(String username); + Optional findByUsername(String name); boolean existsByUsername(String username); - Account findByIdAndDeletedAtIsNull(Long id); + Account findByIdAndIsQuitIsFalse(Long id); } From 53d27cfa1609883d5eefd99ec744885645dcdd8c Mon Sep 17 00:00:00 2001 From: You Jung <80906691+JangYouJung@users.noreply.github.com> Date: Mon, 22 Jul 2024 00:27:15 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feature:=20OAuth=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 10 +-- .../server/auth/service/AuthService.java | 2 + .../server/auth/service/OAuthService.java | 88 +++++++++++-------- .../service/dto/OAuthSignInResponseDto.java | 6 +- .../server/user/service/UserService.java | 2 +- .../meltingpot/server/util/ResponseCode.java | 2 + 6 files changed, 64 insertions(+), 46 deletions(-) diff --git a/src/main/java/meltingpot/server/auth/controller/AuthController.java b/src/main/java/meltingpot/server/auth/controller/AuthController.java index 14d0286..5f216b2 100644 --- a/src/main/java/meltingpot/server/auth/controller/AuthController.java +++ b/src/main/java/meltingpot/server/auth/controller/AuthController.java @@ -66,16 +66,16 @@ public ResponseEntity> signup( @ApiResponse(responseCode = "CREATED", description = "회원가입 성공"), @ApiResponse(responseCode = "BAD_REQUEST", description = "회원가입 실패") }) - public ResponseEntity oauthSignup( + public ResponseEntity> oauthSignup( @RequestBody @Valid OAuthSignupRequestDto request ){ try{ - return ResponseData.toResponseEntity(oAuthService.oauthSignup(request)); + return ResponseData.toResponseEntity(ResponseCode.OAUTH_SIGNUP_SUCCESS, oAuthService.oauthSignup(request)); }catch ( AuthException e ){ - return ResponseData.toResponseEntity( e.getResponseCode()); + return ResponseData.toResponseEntity( e.getResponseCode(), null); }catch ( IllegalArgumentException e ){ - return ResponseData.toResponseEntity( e.getResponseCode()); + return ResponseData.toResponseEntity( e.getResponseCode(), null); } } @@ -119,7 +119,7 @@ public ResponseEntity> SNSLogin( ){ try{ OAuthSignInResponseDto data = oAuthService.SNSLogin(request); - return ResponseData.toResponseEntity(ResponseCode.SIGNIN_SUCCESS, data); + return ResponseData.toResponseEntity(ResponseCode.OAUTH_SIGNIN_SUCCESS, data); }catch( ResourceNotFoundException e ){ return ResponseData.toResponseEntity(ResponseCode.ACCOUNT_NOT_FOUND, null); diff --git a/src/main/java/meltingpot/server/auth/service/AuthService.java b/src/main/java/meltingpot/server/auth/service/AuthService.java index 0c4e667..94f56af 100644 --- a/src/main/java/meltingpot/server/auth/service/AuthService.java +++ b/src/main/java/meltingpot/server/auth/service/AuthService.java @@ -5,6 +5,7 @@ import meltingpot.server.auth.controller.dto.*; import meltingpot.server.domain.entity.*; import meltingpot.server.domain.entity.enums.Gender; +import meltingpot.server.domain.entity.enums.OAuthType; import meltingpot.server.domain.repository.AccountPushTokenRepository; import meltingpot.server.domain.repository.MailVerificationRepository; import meltingpot.server.exception.*; @@ -105,6 +106,7 @@ public AccountResponseDto signup(SignupRequestDto signupRequest) { .birth(signupRequest.birth()) .nationality(signupRequest.nationality()) .isQuit(false) + .oAuthType(OAuthType.NONE) .build(); account.setProfileImages(signupRequest.profileImages().stream().map( diff --git a/src/main/java/meltingpot/server/auth/service/OAuthService.java b/src/main/java/meltingpot/server/auth/service/OAuthService.java index 082d086..59a39a4 100644 --- a/src/main/java/meltingpot/server/auth/service/OAuthService.java +++ b/src/main/java/meltingpot/server/auth/service/OAuthService.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import meltingpot.server.auth.controller.dto.AccountResponseDto; import meltingpot.server.auth.controller.dto.OAuthSignInRequestDto; import meltingpot.server.auth.controller.dto.OAuthSignupRequestDto; import meltingpot.server.auth.controller.dto.ProfileImageRequestDto; @@ -50,7 +51,9 @@ public class OAuthService { // SNS 회원 가입 - public ResponseCode oauthSignup(OAuthSignupRequestDto signupRequest) { + @Transactional + public OAuthSignInResponseDto oauthSignup(OAuthSignupRequestDto signupRequest) { + // 프로필 사진 개수 확인 if(signupRequest.profileImages().isEmpty()){ throw new AuthException(ResponseCode.PROFILE_IMAGE_LESS_THAN_ONE); @@ -115,7 +118,13 @@ public ResponseCode oauthSignup(OAuthSignupRequestDto signupRequest) { accountRepository.save(account); - return ResponseCode.SIGNUP_SUCCESS; + + return OAuthSignInResponseDto.builder(). + register_required(false) + .nickName(account.getName()) + .email(account.getUsername()) + .tokenDto(setSecurityContext(account, signupRequest.pushToken())) + .build(); } @@ -136,61 +145,66 @@ public OAuthSignInResponseDto SNSLogin(OAuthSignInRequestDto request) throws Exc // 회원 가입이 필요한 경우 return OAuthSignInResponseDto.builder() .register_required(true) - .accessToken(tokenDto.accessToken()) - .refreshToken(tokenDto.refreshToken()) .nickName(kakaoDto.getNickname()) .email(kakaoDto.getEmail()) + .tokenDto(null) .build(); } else { - // 발급 받은 토큰 Spring Security Context에 저장 - OAuthUserDetails oAuthUserDetails= new OAuthUserDetails(account.get()); - Authentication authentication = new UsernamePasswordAuthenticationToken(oAuthUserDetails, null, oAuthUserDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - - // 인증 정보를 기반으로 JWT 토큰 생성 - TokenDto jwtTokenDto = tokenProvider.generateTokenDto(authentication); - - // RefreshToken 저장 - RefreshToken refreshToken = RefreshToken.builder() - .account(account.get()) - .tokenValue(jwtTokenDto.getRefreshToken()) - .build(); - - refreshTokenRepository.save(refreshToken); - - if (!accountPushTokenRepository.existsAccountPushByAccountAndToken(account.get(), request.push_token())) { - AccountPushToken accountPushToken = AccountPushToken.builder() - .account(account.get()) - .token(request.push_token()) - .build(); - - accountPushTokenRepository.save(accountPushToken); - } - - //인증된 Authentication를 SecurityContext에 저장 - SecurityContextHolder.getContext().setAuthentication(authentication); - - - return OAuthSignInResponseDto.builder() - .register_required(false) - .accessToken(jwtTokenDto.getAccessToken()) - .refreshToken(jwtTokenDto.getRefreshToken()) + return OAuthSignInResponseDto.builder(). + register_required(false) .nickName(kakaoDto.getNickname()) .email(kakaoDto.getEmail()) + .tokenDto(setSecurityContext(account.get(), request.push_token())) .build(); } } +// else if(request.type() == OAuthType.APPLE) { +// +// } +// else if(request.type() == OAuthType.GOOGLE) { +// +// } else { throw new NoSuchElementException(); } } + @Transactional + public TokenDto setSecurityContext(Account account, String pushToken ){ + + OAuthUserDetails oAuthUserDetails= new OAuthUserDetails(account); + Authentication authentication = new UsernamePasswordAuthenticationToken(oAuthUserDetails, null, oAuthUserDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + + // 인증 정보를 기반으로 JWT 토큰 생성 + TokenDto jwtTokenDto = tokenProvider.generateTokenDto(authentication); + // RefreshToken 저장 + RefreshToken refreshToken = RefreshToken.builder() + .account(account) + .tokenValue(jwtTokenDto.getRefreshToken()) + .build(); + refreshTokenRepository.save(refreshToken); + // PushToken 저장 + if (!accountPushTokenRepository.existsAccountPushByAccountAndToken(account, pushToken)) { + AccountPushToken accountPushToken = AccountPushToken.builder() + .account(account) + .token(pushToken) + .build(); + accountPushTokenRepository.save(accountPushToken); + } + + //인증된 Authentication를 SecurityContext에 저장 + SecurityContextHolder.getContext().setAuthentication(authentication); + + return jwtTokenDto; + + } } diff --git a/src/main/java/meltingpot/server/auth/service/dto/OAuthSignInResponseDto.java b/src/main/java/meltingpot/server/auth/service/dto/OAuthSignInResponseDto.java index f95500e..f11eb80 100644 --- a/src/main/java/meltingpot/server/auth/service/dto/OAuthSignInResponseDto.java +++ b/src/main/java/meltingpot/server/auth/service/dto/OAuthSignInResponseDto.java @@ -1,13 +1,13 @@ package meltingpot.server.auth.service.dto; import lombok.Builder; +import meltingpot.server.util.TokenDto; @Builder public record OAuthSignInResponseDto( - String accessToken, - String refreshToken, + boolean register_required, String email, String nickName, - boolean register_required + TokenDto tokenDto ) { } diff --git a/src/main/java/meltingpot/server/user/service/UserService.java b/src/main/java/meltingpot/server/user/service/UserService.java index 04fb240..61ffc6f 100644 --- a/src/main/java/meltingpot/server/user/service/UserService.java +++ b/src/main/java/meltingpot/server/user/service/UserService.java @@ -90,7 +90,7 @@ public UserResponseDto updateProfileBio(Account account, UpdateBioRequestDto ser @Transactional public List readProfileImages( long accountId ) { - Account account = accountRepository.findByIdAndDeletedAtIsNull(accountId); + Account account = accountRepository.findByIdAndIsQuitIsFalse(accountId); if(account == null) throw new NoSuchElementException(); List accountProfileImages = accountProfileImageRepository.findAllByAccountAndDeletedAtIsNull(account); diff --git a/src/main/java/meltingpot/server/util/ResponseCode.java b/src/main/java/meltingpot/server/util/ResponseCode.java index 263c921..bfc6cf0 100644 --- a/src/main/java/meltingpot/server/util/ResponseCode.java +++ b/src/main/java/meltingpot/server/util/ResponseCode.java @@ -12,6 +12,7 @@ public enum ResponseCode { /* 200 OK : 요청 성공 */ SIGNIN_SUCCESS(OK, "로그인 성공"), + OAUTH_SIGNIN_SUCCESS(OK, "SNS 로그인 성공"), SIGNOUT_SUCCESS(OK, "로그아웃 성공"), REISSUE_TOKEN_SUCCESS(OK, "토큰 재발급 성공"), MAIL_VERIFICATION_SEND_SUCCESS(OK, "이메일 인증번호 전송 성공"), @@ -60,6 +61,7 @@ public enum ResponseCode { PARTY_REPORT_SUCCESS(CREATED, "파티 신고 성공"), PARTY_CREATE_SUCCESS(CREATED, "파티 생성 성공"), IMAGE_URL_GENERATE_SUCCESS(CREATED, "이미지 URL 생성 성공"), + OAUTH_SIGNUP_SUCCESS(CREATED, "SNS 회원가입 성공"), /* 400 BAD_REQUEST : 잘못된 요청 */