From c77f16d1a32d9768e5467e0b7f2ee659f3739e93 Mon Sep 17 00:00:00 2001 From: joon Date: Sat, 11 May 2024 04:08:35 +0900 Subject: [PATCH] =?UTF-8?q?:recycle:=20Refactor:=20jwt=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EA=B8=B0=EA=B0=84=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?Refresh=20Token=20=EC=BF=A0=ED=82=A4=EB=A1=9C=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiPayload/code/status/ErrorStatus.java | 1 + .../apiPayload/code/status/SuccessStatus.java | 1 + .../provider/TokenProvider.java | 4 +- .../converter/member/MemberConverter.java | 4 +- .../MemberService/MemberCommandService.java | 11 ++- .../MemberCommandServiceImpl.java | 80 ++++++++++++++++--- .../web/controller/MemberController.java | 50 ++++++------ .../web/dto/member/MemberResponseDTO.java | 5 +- 8 files changed, 111 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/umc/TheGoods/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/umc/TheGoods/apiPayload/code/status/ErrorStatus.java index 653ee3e9..99f8a72e 100644 --- a/src/main/java/com/umc/TheGoods/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/umc/TheGoods/apiPayload/code/status/ErrorStatus.java @@ -61,6 +61,7 @@ public enum ErrorStatus implements BaseErrorCode { MEMBER_NOT_OWNER(HttpStatus.NOT_ACCEPTABLE, "MEMBER4011", "해당 회원이 아닙니다."), MEMBER_CONTACT_NOT_FOUND(HttpStatus.NOT_FOUND,"MEMBER4012", "연락 가능 시간을 조회할 수 없습니다."), MEMBER_EMAIL_DUPLICATED(HttpStatus.BAD_REQUEST, "MEMBER4013", "중복된 이메일입니다."), + MEMBER_EMAIL_INCORRECT(HttpStatus.BAD_REQUEST, "MEMBER4014", "잘못된 이메일입니다."), //JWT diff --git a/src/main/java/com/umc/TheGoods/apiPayload/code/status/SuccessStatus.java b/src/main/java/com/umc/TheGoods/apiPayload/code/status/SuccessStatus.java index 579c0397..670af41e 100644 --- a/src/main/java/com/umc/TheGoods/apiPayload/code/status/SuccessStatus.java +++ b/src/main/java/com/umc/TheGoods/apiPayload/code/status/SuccessStatus.java @@ -22,6 +22,7 @@ public enum SuccessStatus implements BaseCode { MEMBER_DECLARE_DELETE(HttpStatus.OK, "MEMBER2008", "신고 삭제 성공했습니다."), MEMBER_CONTACT_SUCCESS(HttpStatus.OK, "MEMBER2009", "연락가능 시간 변경 성공입니다."), + //POST POST_FOLLOW_SUCCESS(HttpStatus.OK, "POST2001", "팔로우 성공입니다."), POST_DELETE_FOLLOW_SUCCESS(HttpStatus.OK, "POST2002", "팔로우 취소 성공입니다."), diff --git a/src/main/java/com/umc/TheGoods/config/springSecurity/provider/TokenProvider.java b/src/main/java/com/umc/TheGoods/config/springSecurity/provider/TokenProvider.java index c938f33d..85e7106f 100644 --- a/src/main/java/com/umc/TheGoods/config/springSecurity/provider/TokenProvider.java +++ b/src/main/java/com/umc/TheGoods/config/springSecurity/provider/TokenProvider.java @@ -47,7 +47,7 @@ public enum TokenType { public TokenProvider(@Value("${jwt.token.secret}") String secretKey) { this.secret = secretKey; - this.accessTokenValidityInMilliseconds = 1000 * 60 * 30L; + this.accessTokenValidityInMilliseconds = 1000 * 60 * 60* 6L; } @Override @@ -82,7 +82,7 @@ public String createAccessToken(Long memberId, String memberRole, String email, public String createRefreshToken(Collection authorities) { //2주 - long tokenValidTime = 60 * 60 * 24 * 14 * 1000L; + long tokenValidTime = 60 * 60 * 24 * 30 * 1000L; long now = (new Date()).getTime(); Date validity = new Date(now + tokenValidTime); diff --git a/src/main/java/com/umc/TheGoods/converter/member/MemberConverter.java b/src/main/java/com/umc/TheGoods/converter/member/MemberConverter.java index 3dedcf68..a0d44ac0 100644 --- a/src/main/java/com/umc/TheGoods/converter/member/MemberConverter.java +++ b/src/main/java/com/umc/TheGoods/converter/member/MemberConverter.java @@ -67,9 +67,9 @@ public static MemberResponseDTO.LogoutResultDTO toLogoutResultDTO(Member member) .build(); } - public static MemberResponseDTO.NewTokenDTO toNewTokenDTO(String accessToken, String refreshToken) { + public static MemberResponseDTO.NewTokenDTO toNewTokenDTO(String accessToken, LocalDateTime time) { return MemberResponseDTO.NewTokenDTO.builder() - .refreshToken(refreshToken) + .accessExpireTime(time) .accessToken(accessToken) .build(); } diff --git a/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandService.java b/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandService.java index 4926fc78..f67a643b 100644 --- a/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandService.java +++ b/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandService.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; +import com.umc.TheGoods.apiPayload.ApiResponse; import com.umc.TheGoods.domain.member.Auth; import com.umc.TheGoods.domain.member.Member; import com.umc.TheGoods.domain.mypage.Account; @@ -12,6 +13,8 @@ import com.umc.TheGoods.web.dto.member.MemberResponseDTO; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; + public interface MemberCommandService { @@ -21,7 +24,7 @@ public interface MemberCommandService { Member join(MemberRequestDTO.JoinDTO request); - MemberResponseDTO.LoginResultDTO login(MemberRequestDTO.LoginDTO request); + MemberResponseDTO.LoginResultDTO login(MemberRequestDTO.LoginDTO request, HttpServletResponse response); void logout(String accessToken, Member member); String regenerateAccessToken(RefreshToken refreshToken); @@ -39,13 +42,13 @@ public interface MemberCommandService { Boolean confirmEmailAuth(MemberRequestDTO.EmailAuthConfirmDTO request); - String emailAuthCreateJWT(MemberRequestDTO.EmailAuthConfirmDTO request); + MemberResponseDTO.LoginResultDTO emailAuthCreateJWT(MemberRequestDTO.EmailAuthConfirmDTO request, HttpServletResponse response); Boolean updatePassword(MemberRequestDTO.PasswordUpdateDTO request, Member member); - String kakaoAuth(String code); + ApiResponse kakaoAuth(String code, HttpServletResponse response); - String naverAuth(String code, String state); + ApiResponse naverAuth(String code, String state, HttpServletResponse response); Member profileModify(MultipartFile profile, String nickname, String introduce, Member member); diff --git a/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandServiceImpl.java b/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandServiceImpl.java index fcea5c00..55ba836e 100644 --- a/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandServiceImpl.java +++ b/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandServiceImpl.java @@ -3,6 +3,7 @@ import antlr.Token; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.umc.TheGoods.apiPayload.ApiResponse; import com.umc.TheGoods.apiPayload.code.status.ErrorStatus; import com.umc.TheGoods.apiPayload.exception.handler.MemberHandler; import com.umc.TheGoods.config.MailConfig; @@ -44,6 +45,8 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @@ -159,7 +162,7 @@ public Member join(MemberRequestDTO.JoinDTO request) { * @return */ @Override - public MemberResponseDTO.LoginResultDTO login(MemberRequestDTO.LoginDTO request) { + public MemberResponseDTO.LoginResultDTO login(MemberRequestDTO.LoginDTO request, HttpServletResponse response) { //email 없음 Member selectedMember = memberRepository.findByEmail(request.getEmail()) @@ -173,10 +176,20 @@ public MemberResponseDTO.LoginResultDTO login(MemberRequestDTO.LoginDTO request) throw new MemberHandler(ErrorStatus.MEMBER_PASSWORD_ERROR); } + // refresh 생성후 쿠키로 만들고 response 헤더에 담기 + RefreshToken refreshToken = redisService.generateRefreshToken(request.getEmail()); + Cookie cookie = new Cookie("refreshToken",refreshToken.getToken()); + cookie.setHttpOnly(true); + response.addCookie(cookie); + + LocalDateTime currentDateTime = LocalDateTime.now(); + LocalDateTime accessExpireTime = currentDateTime.plusHours(6); + + return MemberResponseDTO.LoginResultDTO.builder() .accessToken(redisService.saveLoginStatus(selectedMember.getId(), tokenProvider.createAccessToken(selectedMember.getId(), selectedMember.getMemberRole().toString() , request.getEmail(), Arrays.asList(new SimpleGrantedAuthority("USER"))))) - .refreshToken(redisService.generateRefreshToken(request.getEmail())) + .accessExpireTime(accessExpireTime) .build(); } @@ -382,7 +395,7 @@ public Boolean confirmEmailAuth(MemberRequestDTO.EmailAuthConfirmDTO request) { } @Override - public String emailAuthCreateJWT(MemberRequestDTO.EmailAuthConfirmDTO request) { + public MemberResponseDTO.LoginResultDTO emailAuthCreateJWT(MemberRequestDTO.EmailAuthConfirmDTO request, HttpServletResponse response) { Member member = memberRepository.findByEmail(request.getEmail()) .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); @@ -390,7 +403,21 @@ public String emailAuthCreateJWT(MemberRequestDTO.EmailAuthConfirmDTO request) { List roles = new ArrayList<>(); roles.add("ROLE_USER"); - return tokenProvider.createAccessToken(member.getId(), member.getMemberRole().toString() , member.getEmail(), Arrays.asList(new SimpleGrantedAuthority("USER"))); + // refresh 생성후 쿠키로 만들고 response 헤더에 담기 + RefreshToken refreshToken = redisService.generateRefreshToken(request.getEmail()); + Cookie cookie = new Cookie("refreshToken",refreshToken.getToken()); + cookie.setHttpOnly(true); + response.addCookie(cookie); + + LocalDateTime currentDateTime = LocalDateTime.now(); + LocalDateTime accessExpireTime = currentDateTime.plusHours(6); + + return MemberResponseDTO.LoginResultDTO.builder() + .accessToken(redisService.saveLoginStatus(member.getId(), tokenProvider.createAccessToken(member.getId(), member.getMemberRole().toString() , request.getEmail(), Arrays.asList(new SimpleGrantedAuthority("USER"))))) + .accessExpireTime(accessExpireTime) + .build(); + + } @Override @@ -407,7 +434,7 @@ public Boolean updatePassword(MemberRequestDTO.PasswordUpdateDTO request, Member @Override @Transactional - public String kakaoAuth(String code) { + public ApiResponse kakaoAuth(String code, HttpServletResponse res) { String jwt; @@ -486,21 +513,40 @@ public String kakaoAuth(String code) { Optional member = memberRepository.findByPhone(phone); + LocalDateTime currentDateTime = LocalDateTime.now(); + LocalDateTime accessExpireTime = currentDateTime.plusHours(6); + + + if (member.isPresent()) { if(!this.checkDeregister(member.orElseThrow())){ throw new MemberHandler(ErrorStatus.MEMBER_INACTIVATE); } - return tokenProvider.createAccessToken(member.get().getId(), member.get().getMemberRole().toString() , member.get().getEmail(), Arrays.asList(new SimpleGrantedAuthority("USER"))); + + // refresh 생성후 쿠키로 만들고 response 헤더에 담기 + RefreshToken refreshToken = redisService.generateRefreshToken(member.get().getEmail()); + Cookie cookie = new Cookie("refreshToken",refreshToken.getToken()); + cookie.setHttpOnly(true); + res.addCookie(cookie); + + + return ApiResponse.onSuccess(MemberResponseDTO.LoginResultDTO.builder() + .accessToken(redisService.saveLoginStatus(member.get().getId(), tokenProvider.createAccessToken(member.get().getId(), member.get().getMemberRole().toString() , member.get().getEmail(), Arrays.asList(new SimpleGrantedAuthority("USER"))))) + .accessExpireTime(accessExpireTime) + .build()); } - return phone + kakaoProfile.getKakao_account().email; + return ApiResponse.onFailure("MEMBER401", "로그인 실패 회원가입 필요", MemberConverter.toSocialJoinResultDTO(phone, kakaoProfile.getKakao_account().email)); + + + } @Override @Transactional - public String naverAuth(String code, String state) { + public ApiResponse naverAuth(String code, String state, HttpServletResponse res) { RestTemplate rt = new RestTemplate(); @@ -571,17 +617,31 @@ public String naverAuth(String code, String state) { Optional member = memberRepository.findByPhone(phone); + LocalDateTime currentDateTime = LocalDateTime.now(); + LocalDateTime accessExpireTime = currentDateTime.plusHours(6); + if (member.isPresent()) { if(!this.checkDeregister(member.orElseThrow())){ throw new MemberHandler(ErrorStatus.MEMBER_INACTIVATE); } - return tokenProvider.createAccessToken(member.get().getId(), member.get().getMemberRole().toString() , member.get().getEmail(), Arrays.asList(new SimpleGrantedAuthority("USER"))); + // refresh 생성후 쿠키로 만들고 response 헤더에 담기 + RefreshToken refreshToken = redisService.generateRefreshToken(member.get().getEmail()); + Cookie cookie = new Cookie("refreshToken",refreshToken.getToken()); + cookie.setHttpOnly(true); + res.addCookie(cookie); + + return ApiResponse.onSuccess(MemberResponseDTO.LoginResultDTO.builder() + .accessToken(redisService.saveLoginStatus(member.get().getId(), tokenProvider.createAccessToken(member.get().getId(), member.get().getMemberRole().toString() , member.get().getEmail(), Arrays.asList(new SimpleGrantedAuthority("USER"))))) + .accessExpireTime(accessExpireTime) + .build()); } - return phone + naverProfile.getResponse().email; + + + return ApiResponse.onFailure("MEMBER401","로그인 실패 회원가입 필요",MemberConverter.toSocialJoinResultDTO(phone, naverProfile.getResponse().email)); } @Override diff --git a/src/main/java/com/umc/TheGoods/web/controller/MemberController.java b/src/main/java/com/umc/TheGoods/web/controller/MemberController.java index cdd5d000..a1aa326f 100644 --- a/src/main/java/com/umc/TheGoods/web/controller/MemberController.java +++ b/src/main/java/com/umc/TheGoods/web/controller/MemberController.java @@ -41,8 +41,11 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -74,10 +77,10 @@ public ApiResponse join(@RequestBody @Valid Mem //password @PostMapping("/login") @Operation(summary = "로그인 API", description = "request 파라미터 : 이메일, 비밀번호(String)") - public ApiResponse login(@RequestBody MemberRequestDTO.LoginDTO request) { + public ApiResponse login(@RequestBody MemberRequestDTO.LoginDTO request, HttpServletResponse response) { - return ApiResponse.onSuccess(memberCommandService.login(request)); + return ApiResponse.onSuccess(memberCommandService.login(request,response)); } @Operation(summary = "로그아웃 API", description = "로그아웃 API 입니다.") @@ -92,10 +95,20 @@ public ApiResponse logout(Authentication auth @Operation(summary = "리프레쉬 토큰을 이용해 accessToken 재발급 API ️", description = "리프레쉬 토큰으로 accessToken 재발급하는 API입니다.") @PostMapping("/token/regenerate") - public ApiResponse getNewToken(@RequestBody MemberRequestDTO.RefreshTokenDTO request) { + public ApiResponse getNewToken(@RequestBody MemberRequestDTO.RefreshTokenDTO request, + HttpServletResponse response) { RefreshToken newRefreshToken = redisService.reGenerateRefreshToken(request); String accessToken = memberCommandService.regenerateAccessToken(newRefreshToken); - return ApiResponse.onSuccess(MemberConverter.toNewTokenDTO(accessToken, newRefreshToken.getToken())); + + LocalDateTime currentDateTime = LocalDateTime.now(); + LocalDateTime accessExpireTime = currentDateTime.plusHours(6); + + Cookie cookie = new Cookie("refreshToken", newRefreshToken.getToken()); + cookie.setHttpOnly(true); + + response.addCookie(cookie); + + return ApiResponse.onSuccess(MemberConverter.toNewTokenDTO(accessToken,accessExpireTime)); } @PostMapping("/jwt/test") @@ -170,16 +183,16 @@ public ApiResponse emailAuthSend(@Requ @PostMapping("email/auth/verify") @Operation(summary = "비밀번호 찾기에서 사용되는 email 인증 코드 검증 api", description = "request: 이메일, 코드 response: 인증완료 true") - public ApiResponse emailAuth(@RequestBody MemberRequestDTO.EmailAuthConfirmDTO request) { + public ApiResponse emailAuth(@RequestBody MemberRequestDTO.EmailAuthConfirmDTO request, HttpServletResponse response) { Boolean checkEmail = memberCommandService.confirmEmailAuth(request); if (checkEmail == true) { - String jwt = memberCommandService.emailAuthCreateJWT(request); - return ApiResponse.onSuccess(MemberConverter.toEmailAuthConfirmResultDTO(checkEmail, jwt)); + + return ApiResponse.onSuccess(memberCommandService.emailAuthCreateJWT(request,response)); } - return ApiResponse.onSuccess(MemberConverter.toEmailAuthConfirmResultDTO(checkEmail, null)); + return ApiResponse.onFailure(ErrorStatus.MEMBER_EMAIL_INCORRECT.getCode(),ErrorStatus.MEMBER_EMAIL_INCORRECT.getMessage(), null); } @@ -201,35 +214,22 @@ public ApiResponse updatePassword(@Re @GetMapping("/kakao/callback") @Operation(summary = "카카오 소셜 로그인 api", description = "callback 용도 api여서 swagger에서 test 안됩니다") - public ApiResponse kakaoCallback(@RequestParam String code) { + public ApiResponse kakaoCallback(@RequestParam String code, HttpServletResponse response) { - String result = memberCommandService.kakaoAuth(code); + return memberCommandService.kakaoAuth(code, response); //반환한 카카오 프로필에서 기존 회원이면 jwt 토큰 반환 아니면 회원가입 진행 //토큰 반환은 쉽지만 회원가입 로직으로 보내야하면 false 반환해서 회원가입 진행하도록하기 - if (result.startsWith("010")) { - String phone = result.substring(0, 11); - String email = result.substring(11); - return ApiResponse.onFailure("로그인 실패", "회원가입 필요", MemberConverter.toSocialJoinResultDTO(phone, email)); - } - return ApiResponse.onSuccess(MemberConverter.toSocialLoginResultDTO(result)); } @GetMapping("/naver/callback") @Operation(summary = "네이버 소셜 로그인 api", description = "callback 용도 api여서 swagger에서 test 안됩니다") - public ApiResponse naverCallback(@RequestParam String code, String state) { + public ApiResponse naverCallback(@RequestParam String code, String state, HttpServletResponse response) { - String result = memberCommandService.naverAuth(code, state); - - if (result.startsWith("010")) { - String phone = result.substring(0, 11); - String email = result.substring(11); - return ApiResponse.onFailure("로그인 실패", "회원가입 필요", MemberConverter.toSocialJoinResultDTO(phone, email)); - } + return memberCommandService.naverAuth(code, state, response); - return ApiResponse.onSuccess(MemberConverter.toSocialLoginResultDTO(result)); } diff --git a/src/main/java/com/umc/TheGoods/web/dto/member/MemberResponseDTO.java b/src/main/java/com/umc/TheGoods/web/dto/member/MemberResponseDTO.java index 242f99d2..9ad1827d 100644 --- a/src/main/java/com/umc/TheGoods/web/dto/member/MemberResponseDTO.java +++ b/src/main/java/com/umc/TheGoods/web/dto/member/MemberResponseDTO.java @@ -44,7 +44,8 @@ public static class JoinResultDTO { @AllArgsConstructor public static class LoginResultDTO { String accessToken; - RefreshToken refreshToken; + LocalDateTime accessExpireTime; + } @Builder @@ -241,7 +242,7 @@ public static class AccountDTO { @NoArgsConstructor public static class NewTokenDTO{ private String accessToken; - private String refreshToken; + private LocalDateTime accessExpireTime; }