From c5412d9adbec2b49b4dff57224f9b85a4b0358b3 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 00:19:28 +0900 Subject: [PATCH 01/11] =?UTF-8?q?style:=20MemberRepository=20NotNull=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/layer/domain/member/repository/MemberRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layer-domain/src/main/java/org/layer/domain/member/repository/MemberRepository.java b/layer-domain/src/main/java/org/layer/domain/member/repository/MemberRepository.java index 61034a45..cc20c5a4 100644 --- a/layer-domain/src/main/java/org/layer/domain/member/repository/MemberRepository.java +++ b/layer-domain/src/main/java/org/layer/domain/member/repository/MemberRepository.java @@ -8,5 +8,5 @@ import java.util.Optional; public interface MemberRepository extends JpaRepository { - Optional findBySocialIdAndSocialType(@NotNull String socialId, @NotNull SocialType socialType); + Optional findBySocialIdAndSocialType(String socialId, SocialType socialType); } From 175e21cf7d9f1fcb69fdfb2f39f8406b90b0d249 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 00:56:37 +0900 Subject: [PATCH 02/11] =?UTF-8?q?style:=20MemberExceptionType=20message=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/layer/common/exception/MemberExceptionType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layer-common/src/main/java/org/layer/common/exception/MemberExceptionType.java b/layer-common/src/main/java/org/layer/common/exception/MemberExceptionType.java index 29b67706..492bd0f9 100644 --- a/layer-common/src/main/java/org/layer/common/exception/MemberExceptionType.java +++ b/layer-common/src/main/java/org/layer/common/exception/MemberExceptionType.java @@ -10,7 +10,7 @@ public enum MemberExceptionType implements ExceptionType { * 400 */ NOT_FOUND_USER(HttpStatus.NOT_FOUND, "유효한 유저를 찾지 못했습니다."), - UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "로그인되지 않은 사용자입니다"), + UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "로그인되지 않은 사용자입니다."), FAIL_TO_AUTH(HttpStatus.BAD_REQUEST, "인증에 실패했습니다."), NOT_A_NEW_MEMBER(HttpStatus.BAD_REQUEST, "이미 가입된 회원입니다."); From 5da115d6295e5ceeb8d08b51eb4e03c6972d9a1f Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 01:02:53 +0900 Subject: [PATCH 03/11] =?UTF-8?q?refactor:=20MemberUtil=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layer/domain/auth/service/AuthService.java | 6 ++---- .../domain/member/service/MemberService.java | 15 +++++++++++++++ .../layer/domain/member/service/MemberUtil.java | 13 +------------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java index 3ee52021..0ae282bd 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java +++ b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java @@ -29,8 +29,6 @@ public class AuthService { private final GoogleService googleService; private final JwtService jwtService; private final MemberService memberService; - private final MemberUtil memberUtil; - //== 로그인 ==// @Transactional public SignInServiceResponse signIn(final String socialAccessToken, final SocialType socialType) { @@ -78,7 +76,7 @@ public ReissueTokenServiceResponse reissueToken(final Long memberId) { isValidMember(memberId); // 시큐리티 컨텍스트에서 member 찾아오기 - Member member = memberUtil.getCurrentMember(); + Member member = memberService.getCurrentMember(); return ReissueTokenServiceResponse.of(member, jwtService.issueToken(member.getId(), member.getMemberRole())); } @@ -98,7 +96,7 @@ private MemberInfoServiceResponse getMemberInfo(SocialType socialType, String so // 현재 로그인 된 사용자와 해당 멤버 아이디가 일치하는지 확인 private void isValidMember(Long memberId) { - Member currentMember = memberUtil.getCurrentMember(); + Member currentMember = memberService.getCurrentMember(); if(!currentMember.getId().equals(memberId)) { throw new BaseCustomException(AuthExceptionType.FORBIDDEN); } diff --git a/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java b/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java index f29f9005..f490cf05 100644 --- a/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java +++ b/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java @@ -4,6 +4,7 @@ import org.layer.common.exception.BaseCustomException; import org.layer.common.exception.MemberExceptionType; import org.layer.domain.auth.controller.dto.SignUpRequest; +import org.layer.domain.jwt.SecurityUtil; import org.layer.domain.member.entity.Member; import org.layer.domain.member.entity.SocialType; import org.layer.domain.member.repository.MemberRepository; @@ -13,11 +14,13 @@ import java.util.Optional; import static org.layer.common.exception.MemberExceptionType.NOT_A_NEW_MEMBER; +import static org.layer.common.exception.MemberExceptionType.NOT_FOUND_USER; import static org.layer.domain.member.entity.MemberRole.USER; @RequiredArgsConstructor @Service public class MemberService { + private final SecurityUtil securityUtil; private final MemberRepository memberRepository; public Member findMemberById(Long memberId) { return memberRepository.findById(memberId).orElse(null); @@ -51,4 +54,16 @@ public Member saveMember(SignUpRequest signUpRequest, MemberInfoServiceResponse return member; } + public Member getCurrentMember() { + return memberRepository + .findById(securityUtil.getCurrentMemberId()) + .orElseThrow(() -> new BaseCustomException(NOT_FOUND_USER)); + } + + public Member getMemberByMemberId(Long memberId) { + return memberRepository. + findById(memberId) + .orElseThrow(() -> new BaseCustomException(NOT_FOUND_USER)); + } + } diff --git a/layer-api/src/main/java/org/layer/domain/member/service/MemberUtil.java b/layer-api/src/main/java/org/layer/domain/member/service/MemberUtil.java index 00569d31..6bf51a98 100644 --- a/layer-api/src/main/java/org/layer/domain/member/service/MemberUtil.java +++ b/layer-api/src/main/java/org/layer/domain/member/service/MemberUtil.java @@ -11,17 +11,6 @@ @RequiredArgsConstructor @Component public class MemberUtil { - private final SecurityUtil securityUtil; - private final MemberRepository memberRepository; - public Member getCurrentMember() { - return memberRepository - .findById(securityUtil.getCurrentMemberId()) - .orElseThrow(() -> new BaseCustomException(NOT_FOUND_USER)); - } - public Member getMemberByMemberId(Long memberId) { - return memberRepository. - findById(memberId) - .orElseThrow(() -> new BaseCustomException(NOT_FOUND_USER)); - } + private final MemberRepository memberRepository; } From 76e09b76188a33dfc020ed32563560092d304b3b Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 02:10:26 +0900 Subject: [PATCH 04/11] =?UTF-8?q?refactor:=20OAuthService=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/layer/oauth/service/GoogleService.java | 2 +- .../main/java/org/layer/oauth/service/KakaoService.java | 2 +- .../main/java/org/layer/oauth/service/OAuthService.java | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 layer-external/src/main/java/org/layer/oauth/service/OAuthService.java diff --git a/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java b/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java index 15d3619f..e879ec97 100644 --- a/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java +++ b/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java @@ -23,7 +23,7 @@ @Slf4j @RequiredArgsConstructor @Service -public class GoogleService { +public class GoogleService implements OAuthService { private final GoogleOAuthConfig googleOAuthConfig; //== 액세스 토큰으로 사용자 정보 가져오기 ==// diff --git a/layer-external/src/main/java/org/layer/oauth/service/KakaoService.java b/layer-external/src/main/java/org/layer/oauth/service/KakaoService.java index 02ee66be..7e1a457d 100644 --- a/layer-external/src/main/java/org/layer/oauth/service/KakaoService.java +++ b/layer-external/src/main/java/org/layer/oauth/service/KakaoService.java @@ -22,7 +22,7 @@ @Slf4j @RequiredArgsConstructor @Service -public class KakaoService { +public class KakaoService implements OAuthService { private final KakaoOAuthConfig kakaoOAuthConfig; public MemberInfoServiceResponse getMemberInfo(final String accessToken) { diff --git a/layer-external/src/main/java/org/layer/oauth/service/OAuthService.java b/layer-external/src/main/java/org/layer/oauth/service/OAuthService.java new file mode 100644 index 00000000..afc1c9f5 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/service/OAuthService.java @@ -0,0 +1,8 @@ +package org.layer.oauth.service; + +import org.layer.oauth.dto.service.MemberInfoServiceResponse; + +public interface OAuthService { + MemberInfoServiceResponse getMemberInfo(final String accessToken); + +} From 54805ff7fbf18e064352dfbdcf1b3e38153e6a26 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 18:58:04 +0900 Subject: [PATCH 05/11] =?UTF-8?q?fix:=20refresh=20token=20=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/layer/config/RedisConfig.java | 2 +- .../layer/domain/auth/controller/AuthApi.java | 29 ++++++++++++++ .../auth/controller/AuthController.java | 8 ++-- .../controller/dto/ReissueTokenRequest.java | 6 +++ .../domain/auth/service/AuthService.java | 11 ++--- .../domain/jwt/JwtAuthenticationFilter.java | 2 +- .../org/layer/domain/jwt/JwtValidator.java | 5 +++ .../layer/domain/jwt/service/JwtService.java | 40 +++++++++++++++++-- .../GoogleGetMemberInfoServiceResponse.java | 2 +- .../layer/oauth/service/GoogleService.java | 1 + 10 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java create mode 100644 layer-api/src/main/java/org/layer/domain/auth/controller/dto/ReissueTokenRequest.java diff --git a/layer-api/src/main/java/org/layer/config/RedisConfig.java b/layer-api/src/main/java/org/layer/config/RedisConfig.java index c7d7d060..e3372349 100644 --- a/layer-api/src/main/java/org/layer/config/RedisConfig.java +++ b/layer-api/src/main/java/org/layer/config/RedisConfig.java @@ -38,7 +38,7 @@ RedisTemplate redisTemplate() { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Member.class)); + redisTemplate.setValueSerializer(new StringRedisSerializer()); return redisTemplate; } diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java new file mode 100644 index 00000000..16691bbc --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java @@ -0,0 +1,29 @@ +package org.layer.domain.auth.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.layer.domain.auth.controller.dto.*; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; + +@Tag(name = "인증", description = "인증 관련 API") +public interface AuthApi { + @Operation(summary = "로그인", description = "소셜 로그인 API(구글, 카카오") + public ResponseEntity signIn(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignInRequest signInRequest); + + @Operation(summary = "로그아웃") + public ResponseEntity signOut(@RequestBody Long memberId); + +// @Operation(summary = "토큰 재발급", description = "member_id를 전달하면 데이터베이스에 리프레시 토큰이 남아있지 확인하고 남아있다면 jwt(access + refresh)를 새로 발급합니다.") +// @ApiResponse(responseCode = "201", description="토큰 재발급 성공", +// content = @Content(mediaType = "application/json"), examples = { +// @ExampleObject(name="토큰 재발급 성공") +// }) + public ResponseEntity reissueToken(@RequestBody ReissueTokenRequest reissueTokenRequest); +} diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java index 15190373..2313b482 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java @@ -20,7 +20,7 @@ @RequiredArgsConstructor @RequestMapping("/api/auth") @RestController -public class AuthController { +public class AuthController implements AuthApi { private final AuthService authService; private final GoogleService googleService; private final KakaoService kakaoService; @@ -28,7 +28,7 @@ public class AuthController { // 로그인 @PostMapping("/sign-in") - public ResponseEntity signIn(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignInRequest signInRequest) { + public ResponseEntity signIn(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignInRequest signInRequest) { SignInServiceResponse signInServiceResponse = authService.signIn(socialAccessToken, signInRequest.socialType()); return new ResponseEntity<>(SignInResponse.of(signInServiceResponse), HttpStatus.OK); } @@ -57,9 +57,9 @@ public ResponseEntity withdraw(@RequestBody Long memberId) { // 토큰 재발급 @PostMapping("/reissue-token") - public ResponseEntity reissueToken(@RequestBody Long memberId) { + public ResponseEntity reissueToken(ReissueTokenRequest reissueTokenRequest) { return new ResponseEntity<>( - ReissueTokenResponse.of(authService.reissueToken(memberId)), + ReissueTokenResponse.of(authService.reissueToken(reissueTokenRequest.memberId())), HttpStatus.CREATED); } diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/dto/ReissueTokenRequest.java b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/ReissueTokenRequest.java new file mode 100644 index 00000000..8826358e --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/ReissueTokenRequest.java @@ -0,0 +1,6 @@ +package org.layer.domain.auth.controller.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ReissueTokenRequest(@JsonProperty("member_id") Long memberId) { +} diff --git a/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java index 0ae282bd..d083eb22 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java +++ b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java @@ -69,16 +69,13 @@ public void withdraw(final Long memberId) { } - //== (리프레시 토큰을 받았을 때) 토큰 재발급 ==// + //== 토큰 재발급. redis 확인 후 재발급 ==// @Transactional public ReissueTokenServiceResponse reissueToken(final Long memberId) { - // 현재 로그인된 사용자와 memberId가 일치하는지 확인 - isValidMember(memberId); + Member member = memberService.getMemberByMemberId(memberId); + JwtToken jwtToken = jwtService.reissueToken(memberId); - // 시큐리티 컨텍스트에서 member 찾아오기 - Member member = memberService.getCurrentMember(); - return ReissueTokenServiceResponse.of(member, - jwtService.issueToken(member.getId(), member.getMemberRole())); + return ReissueTokenServiceResponse.of(member, jwtToken); } diff --git a/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java b/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java index 7ad7c959..7cba447c 100644 --- a/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java @@ -27,7 +27,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String accessToken = getJwtFromRequest(request); - if(isValidToken(accessToken)) { + if(jwtValidator.isValidToken(accessToken)) { Long memberId = jwtValidator.getMemberIdFromToken(accessToken); List role = jwtValidator.getRoleFromToken(accessToken); setAuthenticationToContext(memberId, MemberRole.valueOf(role.get(0))); diff --git a/layer-api/src/main/java/org/layer/domain/jwt/JwtValidator.java b/layer-api/src/main/java/org/layer/domain/jwt/JwtValidator.java index 1b0eaca0..6981c02c 100644 --- a/layer-api/src/main/java/org/layer/domain/jwt/JwtValidator.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/JwtValidator.java @@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j; import org.layer.common.exception.BaseCustomException; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import java.util.List; @@ -46,7 +47,11 @@ public List getRoleFromToken(String token) { throw new BaseCustomException(INVALID_TOKEN); } return (List) (claims.get("role")); + } + // 정상적인 토큰인지 판단하는 메서드 + public boolean isValidToken(String token) { + return StringUtils.hasText(token) && validateToken(token) == JwtValidationType.VALID_JWT; } private Claims getClaims(String token) { diff --git a/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java index 0524b35b..d2cc8ca9 100644 --- a/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java @@ -1,25 +1,29 @@ package org.layer.domain.jwt.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.layer.common.exception.BaseCustomException; -import org.layer.domain.jwt.JwtProvider; -import org.layer.domain.jwt.JwtToken; -import org.layer.domain.jwt.MemberAuthentication; +import org.layer.domain.auth.exception.TokenExceptionType; +import org.layer.domain.jwt.*; import org.layer.domain.member.entity.MemberRole; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.time.Duration; +import java.util.List; import java.util.Objects; import static org.layer.domain.auth.exception.TokenExceptionType.INVALID_REFRESH_TOKEN; import static org.layer.config.AuthValueConfig.ACCESS_TOKEN_EXPIRATION_TIME; import static org.layer.config.AuthValueConfig.REFRESH_TOKEN_EXPIRATION_TIME; +@Slf4j @RequiredArgsConstructor @Service public class JwtService { private final JwtProvider jwtProvider; + private final JwtValidator jwtValidator; private final RedisTemplate redisTemplate; public JwtToken issueToken(Long memberId, MemberRole memberRole) { @@ -34,10 +38,27 @@ public JwtToken issueToken(Long memberId, MemberRole memberRole) { .build(); } + // Jwt 재발급 + public JwtToken reissueToken(Long memberId) { + // 리프레시 토큰 검사 + String refreshToken = getRefreshTokenFromRedis(memberId); + if(!jwtValidator.isValidToken(refreshToken)) { + throw new BaseCustomException(TokenExceptionType.INVALID_TOKEN); // FIXME: TokenException 등으로 변경 필요 + } + + + return issueToken(memberId, getMemberRoleFromRefreshToken(refreshToken)); + } + private void saveRefreshTokenToRedis(Long memberId, String refreshToken) { redisTemplate.opsForValue().set(memberId.toString(), refreshToken, Duration.ofDays(14)); } + private String getRefreshTokenFromRedis(Long memberId) { + log.info("refresh token from redis: {}", redisTemplate.opsForValue().get(memberId.toString())); + return (String) redisTemplate.opsForValue().get(memberId.toString()); + } + private Long getMemberIdFromRefreshToken(String refreshToken) { Long memberId = null; try { @@ -48,8 +69,21 @@ private Long getMemberIdFromRefreshToken(String refreshToken) { return memberId; } + private MemberRole getMemberRoleFromRefreshToken(String refreshToken) { + MemberRole memberRole = null; + try { + List role = jwtValidator.getRoleFromToken(refreshToken); + memberRole = MemberRole.valueOf(role.get(0)); + } catch(Exception e) { + throw new BaseCustomException(INVALID_REFRESH_TOKEN); + } + + log.info("line 81: {}", memberRole); + return memberRole; + } public void deleteRefreshToken(Long memberId) { redisTemplate.delete(memberId.toString()); } + } diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleGetMemberInfoServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleGetMemberInfoServiceResponse.java index eb09b963..27c21b2e 100644 --- a/layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleGetMemberInfoServiceResponse.java +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleGetMemberInfoServiceResponse.java @@ -3,5 +3,5 @@ import org.layer.oauth.dto.service.kakao.KakaoAccountServiceResponse; import org.layer.oauth.dto.service.kakao.KakaoGetMemberInfoServiceResponse; -public record GoogleGetMemberInfoServiceResponse(String id, String email) { +public record GoogleGetMemberInfoServiceResponse(String id, String email, String name) { } diff --git a/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java b/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java index e879ec97..d9ddf6de 100644 --- a/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java +++ b/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java @@ -46,6 +46,7 @@ public MemberInfoServiceResponse getMemberInfo(final String accessToken) { } assert response != null; + log.info("name is {}", response.name()); return new MemberInfoServiceResponse(response.id(), GOOGLE, response.email()); } From dc4bf521f62828a054c20398c8c9911c5733f861 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 19:43:11 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20reissueToken=20swagger=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layer/domain/auth/controller/AuthApi.java | 30 +++++++++++++++---- .../layer/domain/jwt/service/JwtService.java | 3 +- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java index 16691bbc..c576c583 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.layer.domain.auth.controller.dto.*; import org.springframework.http.ResponseEntity; @@ -20,10 +21,29 @@ public interface AuthApi { @Operation(summary = "로그아웃") public ResponseEntity signOut(@RequestBody Long memberId); -// @Operation(summary = "토큰 재발급", description = "member_id를 전달하면 데이터베이스에 리프레시 토큰이 남아있지 확인하고 남아있다면 jwt(access + refresh)를 새로 발급합니다.") -// @ApiResponse(responseCode = "201", description="토큰 재발급 성공", -// content = @Content(mediaType = "application/json"), examples = { -// @ExampleObject(name="토큰 재발급 성공") -// }) + @Operation(summary = "토큰 재발급", description = "member_id를 전달하면 데이터베이스에 리프레시 토큰이 남아있지 확인하고 남아있다면 jwt(access + refresh)를 새로 발급합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "토큰 재발급 성공", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(name="토큰 재발급 성공(리프레시 토큰이 DB에 남아있음, 기한 2주)", value = """ + { + "memberId": 1, + "accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MjA2OTMyOTMsImV4cCI6MTcyMDY5NTA5Mywicm9sZSI6WyJVU0VSIl0sIm1lbWJlcklkIjoxfQ.nt4Tj1jTihS-6U7j2wkzv4VbgzTkhSPWnjBC_yXe_GiOKn3eoJ0i9NuA7Dzw6e4w_B-ab_PHzdrhfzyeVoPJOg", + "refreshToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MjA2OTMyOTMsImV4cCI6MTcyMTkwMjg5Mywicm9sZSI6WyJVU0VSIl0sIm1lbWJlcklkIjoxfQ.MROa3B266VcnQqGHpvu2Lh3JiwexOM4BTYQt_3Tbc7xMY1AwS5Z51oAyVVZdO7wTLDLiUNe73DwR-7HNejWEdA" + } + """ + ) + })), + @ApiResponse(responseCode = "404", description = "토큰 재발급 실패", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(name="토큰 재발급 실패(리프레시 토큰이 DB에 없음)", value = """ + { + "name": "NOT_FOUND_USER", + "message": "유효한 유저를 찾지 못했습니다." + } + """ + ) + })) + }) public ResponseEntity reissueToken(@RequestBody ReissueTokenRequest reissueTokenRequest); } diff --git a/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java index d2cc8ca9..9b644bcc 100644 --- a/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java @@ -51,11 +51,11 @@ public JwtToken reissueToken(Long memberId) { } private void saveRefreshTokenToRedis(Long memberId, String refreshToken) { + log.info("line 54: refresh token: {}", refreshToken); redisTemplate.opsForValue().set(memberId.toString(), refreshToken, Duration.ofDays(14)); } private String getRefreshTokenFromRedis(Long memberId) { - log.info("refresh token from redis: {}", redisTemplate.opsForValue().get(memberId.toString())); return (String) redisTemplate.opsForValue().get(memberId.toString()); } @@ -78,7 +78,6 @@ private MemberRole getMemberRoleFromRefreshToken(String refreshToken) { throw new BaseCustomException(INVALID_REFRESH_TOKEN); } - log.info("line 81: {}", memberRole); return memberRole; } public void deleteRefreshToken(Long memberId) { From 3472c10265add24904ecb9f83a3a28f6f5b0e966 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 21:04:04 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20api=20swagge?= =?UTF-8?q?r=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/layer/config/SecurityConfig.java | 1 + .../layer/domain/auth/controller/AuthApi.java | 93 ++++++++++++++++++- .../auth/controller/AuthController.java | 5 +- .../auth/controller/dto/SignOutRequest.java | 4 + .../domain/auth/service/AuthService.java | 2 +- 5 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignOutRequest.java diff --git a/layer-api/src/main/java/org/layer/config/SecurityConfig.java b/layer-api/src/main/java/org/layer/config/SecurityConfig.java index e9a6c31a..58d24faf 100644 --- a/layer-api/src/main/java/org/layer/config/SecurityConfig.java +++ b/layer-api/src/main/java/org/layer/config/SecurityConfig.java @@ -36,6 +36,7 @@ private void setHttp(HttpSecurity http) throws Exception { ).authorizeHttpRequests(authorizeRequest -> authorizeRequest .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-in")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-out")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/reissue-token")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-up")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/google")).permitAll() diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java index c576c583..2808d280 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; @@ -12,14 +11,84 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; @Tag(name = "인증", description = "인증 관련 API") public interface AuthApi { - @Operation(summary = "로그인", description = "소셜 로그인 API(구글, 카카오") + @Operation(summary = "로그인", description = "소셜 로그인 API(구글, 카카오)") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "로그인 성공", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(name="로그인 성공", value = """ + { + "memberId": 1, + "accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MjA2OTcyMDksImV4cCI6MTcyMDY5OTAwOSwicm9sZSI6WyJVU0VSIl0sIm1lbWJlcklkIjoxfQ.OV-RWbIPZIQlMsPMR0reFHMFq9MNBKQwf7Hw7Uo0QbJPrTEACu0MqSJlv-gMtag1PhBxo7KB5dxEDza6QI06Zw", + "refreshToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MjA2OTcyMTAsImV4cCI6MTcyMTkwNjgxMCwicm9sZSI6WyJVU0VSIl0sIm1lbWJlcklkIjoxfQ.fIVauBlL3GHLrVFJ1YwWb89RFwxa84Cql2WqEu4L258ebPJ04TkAGbqrCt7i-oEKI6dbvv0KDRKXkgDQH18kTA", + "memberRole": "USER" + } + """ + ) + })), + @ApiResponse(responseCode = "400", description = "로그인 실패 - 토큰이 유효하지 않음", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(name="토큰이 유효하지 않음", value = """ + { + "name": "FAIL_TO_AUTH", + "message": "인증에 실패했습니다." + } + """ + ) + })), + @ApiResponse(responseCode = "404", description = "로그인 실패 - 회원이 DB에 없음", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(name="회원이 DB에 없음", value = """ + { + "name": "NOT_FOUND_USER", + "message": "유효한 유저를 찾지 못했습니다." + } + """ + ) + })) + }) public ResponseEntity signIn(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignInRequest signInRequest); - @Operation(summary = "로그아웃") - public ResponseEntity signOut(@RequestBody Long memberId); + @Operation(summary = "회원가입", description = "처음 소셜 로그인 하는 유저가 이름을 입력하는 과정, social_type은 KAKAO, GOOGLE") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "회원가입 성공", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(name="회원 가입 성공. 유저의 정보를 리턴", value = """ + { + "memberId": 1, + "name": "김회고", + "email": "layerkim@kakao.com", + "memberRole": "USER", + "SocialId": "1234567890", + "socialType": "KAKAO" + } + """ + ) + })), + @ApiResponse(responseCode = "400", description = "회원가입 실패", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(name="이미 가입된 회원", value = """ + { + "name": "NOT_A_NEW_MEMBER", + "message": "이미 가입된 회원입니다." + } + """ + ), + @ExampleObject(name="토큰이 유요하지 않음", value = """ + { + "name": "FAIL_TO_AUTH", + "message": "인증에 실패했습니다." + } + """) + })) + }) + public ResponseEntity signUp(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignUpRequest signUpRequest); + + @Operation(summary = "로그아웃", description = "member_id를 전달하면 DB에서 리프레시 토큰을 지웁니다.") + public ResponseEntity signOut(SignOutRequest signOutRequest); @Operation(summary = "토큰 재발급", description = "member_id를 전달하면 데이터베이스에 리프레시 토큰이 남아있지 확인하고 남아있다면 jwt(access + refresh)를 새로 발급합니다.") @ApiResponses({ @@ -34,7 +103,7 @@ public interface AuthApi { """ ) })), - @ApiResponse(responseCode = "404", description = "토큰 재발급 실패", + @ApiResponse(responseCode = "401", description = "토큰 재발급 실패", content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="토큰 재발급 실패(리프레시 토큰이 DB에 없음)", value = """ { @@ -46,4 +115,18 @@ public interface AuthApi { })) }) public ResponseEntity reissueToken(@RequestBody ReissueTokenRequest reissueTokenRequest); + + + + + + // TODO: 토큰 확인용 임시 API 추후 삭제 + @Operation(summary = "[실제 사용 X] 구글 액세스 토큰 받기", description = "서버 쪽에서 토큰을 확인하기 위한 API입니다! (실제 사용 X, 추후 삭제 예정)") + public String googleTest(@RequestParam("code") String code); + + // TODO: 토큰 확인용 임시 API 추후 삭제 + @Operation(summary = "[실제 사용 X] 카카오 액세스 토큰 받기", description = "서버 쪽에서 토큰을 확인하기 위한 API입니다! (실제 사용 X, 추후 삭제 예정)") + public Object kakaoLogin(@RequestParam(value = "code", required = false) String code); + + } diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java index 2313b482..10e22c77 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java @@ -43,8 +43,9 @@ public ResponseEntity signUp(@RequestHeader("Authorization") fin // 로그아웃 @PostMapping("/sign-out") - public ResponseEntity signOut(@RequestBody Long memberId) { - authService.signOut(memberId); + public ResponseEntity signOut(@RequestBody SignOutRequest signOutRequest) { + log.info("memberId: {}", signOutRequest.memberId()); + authService.signOut(signOutRequest.memberId()); return new ResponseEntity<>(HttpStatus.OK); } diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignOutRequest.java b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignOutRequest.java new file mode 100644 index 00000000..6c22f87c --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignOutRequest.java @@ -0,0 +1,4 @@ +package org.layer.domain.auth.controller.dto; + +public record SignOutRequest(Long memberId) { +} diff --git a/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java index d083eb22..cfd1910a 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java +++ b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java @@ -57,7 +57,7 @@ public SignUpServiceResponse signUp(final String socialAccessToken, final SignUp @Transactional public void signOut(final Long memberId) { // 현재 로그인된 사용자와 memberId가 일치하는지 확인 => 일치하지 않으면 Exception - isValidMember(memberId); +// isValidMember(memberId); jwtService.deleteRefreshToken(memberId); } From d28c9813d5b503dc58cc38efec583ed09785d7a5 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 21:09:42 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat:=20AuthApi=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8,=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20@ApiRespons?= =?UTF-8?q?e=EC=97=90=20headers=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layer/domain/auth/controller/AuthApi.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java index 2808d280..2f6e446a 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; @@ -18,6 +19,9 @@ public interface AuthApi { @Operation(summary = "로그인", description = "소셜 로그인 API(구글, 카카오)") @ApiResponses({ @ApiResponse(responseCode = "200", description = "로그인 성공", + headers = { + @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="로그인 성공", value = """ { @@ -30,6 +34,9 @@ public interface AuthApi { ) })), @ApiResponse(responseCode = "400", description = "로그인 실패 - 토큰이 유효하지 않음", + headers = { + @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="토큰이 유효하지 않음", value = """ { @@ -40,6 +47,9 @@ public interface AuthApi { ) })), @ApiResponse(responseCode = "404", description = "로그인 실패 - 회원이 DB에 없음", + headers = { + @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="회원이 DB에 없음", value = """ { @@ -55,6 +65,9 @@ public interface AuthApi { @Operation(summary = "회원가입", description = "처음 소셜 로그인 하는 유저가 이름을 입력하는 과정, social_type은 KAKAO, GOOGLE") @ApiResponses({ @ApiResponse(responseCode = "201", description = "회원가입 성공", + headers = { + @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="회원 가입 성공. 유저의 정보를 리턴", value = """ { @@ -69,6 +82,9 @@ public interface AuthApi { ) })), @ApiResponse(responseCode = "400", description = "회원가입 실패", + headers = { + @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="이미 가입된 회원", value = """ { @@ -117,9 +133,6 @@ public interface AuthApi { public ResponseEntity reissueToken(@RequestBody ReissueTokenRequest reissueTokenRequest); - - - // TODO: 토큰 확인용 임시 API 추후 삭제 @Operation(summary = "[실제 사용 X] 구글 액세스 토큰 받기", description = "서버 쪽에서 토큰을 확인하기 위한 API입니다! (실제 사용 X, 추후 삭제 예정)") public String googleTest(@RequestParam("code") String code); From fc8ee634ed86f7880a6fbdfb3b7482f0c379e110 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 21:44:56 +0900 Subject: [PATCH 09/11] =?UTF-8?q?feat:=20=ED=83=88=ED=87=B4(withdraw)=20ap?= =?UTF-8?q?i=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/layer/config/SecurityConfig.java | 1 - .../layer/domain/auth/controller/AuthApi.java | 25 ++++++++++++------- .../auth/controller/AuthController.java | 7 +++--- .../controller/dto/WithdrawMemberRequest.java | 4 +++ .../domain/auth/service/AuthService.java | 6 ++--- .../domain/jwt/JwtAuthenticationFilter.java | 6 ++++- .../layer/domain/jwt/service/JwtService.java | 1 - .../domain/member/service/MemberService.java | 5 ++++ .../layer/oauth/service/GoogleService.java | 3 +-- 9 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 layer-api/src/main/java/org/layer/domain/auth/controller/dto/WithdrawMemberRequest.java diff --git a/layer-api/src/main/java/org/layer/config/SecurityConfig.java b/layer-api/src/main/java/org/layer/config/SecurityConfig.java index 58d24faf..e9a6c31a 100644 --- a/layer-api/src/main/java/org/layer/config/SecurityConfig.java +++ b/layer-api/src/main/java/org/layer/config/SecurityConfig.java @@ -36,7 +36,6 @@ private void setHttp(HttpSecurity http) throws Exception { ).authorizeHttpRequests(authorizeRequest -> authorizeRequest .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-in")).permitAll() - .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-out")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/reissue-token")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-up")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/google")).permitAll() diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java index 2f6e446a..9bad0ddc 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthApi.java @@ -16,11 +16,11 @@ @Tag(name = "인증", description = "인증 관련 API") public interface AuthApi { - @Operation(summary = "로그인", description = "소셜 로그인 API(구글, 카카오)") + @Operation(summary = "[인증 불필요] 로그인", description = "소셜 로그인 API(구글, 카카오), 헤더에 소셜 액세스 토큰이 필요하며, 자체 jwt 필요없음") @ApiResponses({ @ApiResponse(responseCode = "200", description = "로그인 성공", headers = { - @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + @Header(name = "Authorization", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true) }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="로그인 성공", value = """ @@ -35,7 +35,7 @@ public interface AuthApi { })), @ApiResponse(responseCode = "400", description = "로그인 실패 - 토큰이 유효하지 않음", headers = { - @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + @Header(name = "Authorization", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true) }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="토큰이 유효하지 않음", value = """ @@ -48,7 +48,7 @@ public interface AuthApi { })), @ApiResponse(responseCode = "404", description = "로그인 실패 - 회원이 DB에 없음", headers = { - @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + @Header(name = "Authorization", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true) }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="회원이 DB에 없음", value = """ @@ -62,11 +62,11 @@ public interface AuthApi { }) public ResponseEntity signIn(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignInRequest signInRequest); - @Operation(summary = "회원가입", description = "처음 소셜 로그인 하는 유저가 이름을 입력하는 과정, social_type은 KAKAO, GOOGLE") + @Operation(summary = "[인증 불필요] 회원가입", description = "처음 소셜 로그인 하는 유저가 이름을 입력하는 과정, social_type은 KAKAO, GOOGLE") @ApiResponses({ @ApiResponse(responseCode = "201", description = "회원가입 성공", headers = { - @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + @Header(name = "Authorization", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true) }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="회원 가입 성공. 유저의 정보를 리턴", value = """ @@ -83,7 +83,7 @@ public interface AuthApi { })), @ApiResponse(responseCode = "400", description = "회원가입 실패", headers = { - @Header(name = "Authorization", description = "Bearer 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + @Header(name = "Authorization", description = "소셜 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) }, content = @Content(mediaType = "application/json", examples = { @ExampleObject(name="이미 가입된 회원", value = """ @@ -93,7 +93,7 @@ public interface AuthApi { } """ ), - @ExampleObject(name="토큰이 유요하지 않음", value = """ + @ExampleObject(name="토큰이 유효하지 않음", value = """ { "name": "FAIL_TO_AUTH", "message": "인증에 실패했습니다." @@ -106,7 +106,7 @@ public interface AuthApi { @Operation(summary = "로그아웃", description = "member_id를 전달하면 DB에서 리프레시 토큰을 지웁니다.") public ResponseEntity signOut(SignOutRequest signOutRequest); - @Operation(summary = "토큰 재발급", description = "member_id를 전달하면 데이터베이스에 리프레시 토큰이 남아있지 확인하고 남아있다면 jwt(access + refresh)를 새로 발급합니다.") + @Operation(summary = "[인증 불필요] 토큰 재발급", description = "member_id를 전달하면 데이터베이스에 리프레시 토큰이 남아있지 확인하고 남아있다면 jwt(access + refresh)를 새로 발급합니다.") @ApiResponses({ @ApiResponse(responseCode = "201", description = "토큰 재발급 성공", content = @Content(mediaType = "application/json", examples = { @@ -133,6 +133,13 @@ public interface AuthApi { public ResponseEntity reissueToken(@RequestBody ReissueTokenRequest reissueTokenRequest); + @Operation(summary = "회원 탈퇴", description = "header Authorization에 액세스 토큰과 memberId를 전달하여 회원 탈퇴를 할 수 있습니다.") + @ApiResponse(responseCode = "200", description = "탈퇴 성공", + headers = { + @Header(name = "Authorization", description = "자체 jwt 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true) + }) + public ResponseEntity withdraw(WithdrawMemberRequest withdrawMemberRequest); + // TODO: 토큰 확인용 임시 API 추후 삭제 @Operation(summary = "[실제 사용 X] 구글 액세스 토큰 받기", description = "서버 쪽에서 토큰을 확인하기 위한 API입니다! (실제 사용 X, 추후 삭제 예정)") public String googleTest(@RequestParam("code") String code); diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java index 10e22c77..1a61ed9e 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java @@ -44,16 +44,15 @@ public ResponseEntity signUp(@RequestHeader("Authorization") fin // 로그아웃 @PostMapping("/sign-out") public ResponseEntity signOut(@RequestBody SignOutRequest signOutRequest) { - log.info("memberId: {}", signOutRequest.memberId()); authService.signOut(signOutRequest.memberId()); return new ResponseEntity<>(HttpStatus.OK); } // 회원 탈퇴 @PostMapping("/withdraw") - public ResponseEntity withdraw(@RequestBody Long memberId) { - authService.withdraw(memberId); - return new ResponseEntity<>(HttpStatus.OK); // TODO: 리턴 객체 수정 필요 + public ResponseEntity withdraw(WithdrawMemberRequest withdrawMemberRequest) { + authService.withdraw(withdrawMemberRequest.memberId()); + return new ResponseEntity<>(HttpStatus.OK); } // 토큰 재발급 diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/dto/WithdrawMemberRequest.java b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/WithdrawMemberRequest.java new file mode 100644 index 00000000..70523928 --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/WithdrawMemberRequest.java @@ -0,0 +1,4 @@ +package org.layer.domain.auth.controller.dto; + +public record WithdrawMemberRequest(Long memberId) { +} diff --git a/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java index cfd1910a..02c61978 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java +++ b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java @@ -57,7 +57,7 @@ public SignUpServiceResponse signUp(final String socialAccessToken, final SignUp @Transactional public void signOut(final Long memberId) { // 현재 로그인된 사용자와 memberId가 일치하는지 확인 => 일치하지 않으면 Exception -// isValidMember(memberId); + isValidMember(memberId); jwtService.deleteRefreshToken(memberId); } @@ -65,8 +65,8 @@ public void signOut(final Long memberId) { //== 회원 탈퇴 ==// @Transactional public void withdraw(final Long memberId) { - // TODO: member 도메인에서 del_yn 바꾸기 => Member entitiy에 추가,,? - + // hard delete + memberService.withdrawMember(memberId); } //== 토큰 재발급. redis 확인 후 재발급 ==// diff --git a/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java b/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java index 7cba447c..d9ca3cd1 100644 --- a/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java @@ -44,7 +44,11 @@ private void setAuthenticationToContext(Long memberId, MemberRole memberRole) { // 요청 헤더에서 액세스 토큰을 가져오는 메서드 private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); - return (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) ? bearerToken.replace("Bearer ", ""): null; + String accessToken = null; + if(StringUtils.hasText(bearerToken)) { + accessToken = bearerToken.replace("Bearer ", ""); + } + return accessToken; } // 정상적인 토큰인지 판단하는 메서드 diff --git a/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java index 9b644bcc..c88d1782 100644 --- a/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java @@ -51,7 +51,6 @@ public JwtToken reissueToken(Long memberId) { } private void saveRefreshTokenToRedis(Long memberId, String refreshToken) { - log.info("line 54: refresh token: {}", refreshToken); redisTemplate.opsForValue().set(memberId.toString(), refreshToken, Duration.ofDays(14)); } diff --git a/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java b/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java index f490cf05..a1ef91b4 100644 --- a/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java +++ b/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java @@ -66,4 +66,9 @@ public Member getMemberByMemberId(Long memberId) { .orElseThrow(() -> new BaseCustomException(NOT_FOUND_USER)); } + public void withdrawMember(Long memberId) { + Member currentMember = getCurrentMember(); + memberRepository.delete(currentMember); + } + } diff --git a/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java b/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java index d9ddf6de..158f8481 100644 --- a/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java +++ b/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java @@ -46,12 +46,11 @@ public MemberInfoServiceResponse getMemberInfo(final String accessToken) { } assert response != null; - log.info("name is {}", response.name()); return new MemberInfoServiceResponse(response.id(), GOOGLE, response.email()); } public String getToken(String code) { - log.info("redirect uri: {}", googleOAuthConfig.getGoogleRedirectUri()); +// log.info("redirect uri: {}", googleOAuthConfig.getGoogleRedirectUri()); // 토큰 요청 데이터 String uri = UriComponentsBuilder.fromOriginHeader(GOOGLE_TOKEN_URI) .toUriString(); From 1e02e5251c9524b081d0de69fa12606fdb40c759 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 23:24:27 +0900 Subject: [PATCH 10/11] =?UTF-8?q?chore:=20layer-external=20build.gradle=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + layer-external/build.gradle | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 layer-external/build.gradle diff --git a/build.gradle b/build.gradle index 8368fb37..6ba5a327 100644 --- a/build.gradle +++ b/build.gradle @@ -130,6 +130,7 @@ project(":layer-external"){ dependencies { implementation project(path: ':layer-common') + implementation project(path: ':layer-domain') testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/layer-external/build.gradle b/layer-external/build.gradle deleted file mode 100644 index 17ff2c7c..00000000 --- a/layer-external/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - implementation project(path: ':layer-domain') -} \ No newline at end of file From 6cdeb1aed436aa4e594f39a0be176e17891a675e Mon Sep 17 00:00:00 2001 From: clean2001 Date: Thu, 11 Jul 2024 23:26:10 +0900 Subject: [PATCH 11/11] =?UTF-8?q?chore:=20layer-external,=20layer-common?= =?UTF-8?q?=20build.gradle=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layer-common/build.gradle | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 layer-common/build.gradle diff --git a/layer-common/build.gradle b/layer-common/build.gradle deleted file mode 100644 index e69de29b..00000000