From a54487472b21111646f4537bd236dae02e0c5fef Mon Sep 17 00:00:00 2001 From: clean2001 Date: Sat, 6 Jul 2024 16:15:41 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20kakao=20oauth=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + build.gradle | 6 +- layer-api/build.gradle | 0 .../java/org/layer/api/LayerApplication.java | 12 --- .../org/layer/auth/api/AuthController.java | 66 ++++++++++++++- .../dto/controller/ReissueTokenResponse.java | 11 +++ .../auth/dto/controller/SignInResponse.java | 16 ++++ .../service/ReissueTokenServiceResponse.java | 10 +++ .../dto/service/SignInServiceResponse.java | 14 ++++ .../layer/auth/exception/AuthException.java | 5 ++ .../java/org/layer/auth/oauth/SocialType.java | 6 ++ .../org/layer/auth/service/AuthService.java | 81 +++++++++++++++++++ .../org/layer/auth/service/JwtService.java | 10 +-- .../org/layer/config/AuthValueConfig.java | 4 + .../java/org/layer/config/SecurityConfig.java | 2 + layer-api/src/main/resources/application.yml | 5 -- .../org/layer/oauth/config/OAuthConfig.java | 36 +++++++++ .../service/KakaoAccountServiceResponse.java | 4 + .../KakaoGetMemberInfoServiceResponse.java | 5 ++ .../service/KakaoTokenServiceResponse.java | 33 ++++++++ .../org/layer/oauth/service/KakaoService.java | 69 ++++++++++++++++ 21 files changed, 372 insertions(+), 27 deletions(-) delete mode 100644 layer-api/build.gradle delete mode 100644 layer-api/src/main/java/org/layer/api/LayerApplication.java create mode 100644 layer-api/src/main/java/org/layer/auth/dto/controller/ReissueTokenResponse.java create mode 100644 layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java create mode 100644 layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java create mode 100644 layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java create mode 100644 layer-api/src/main/java/org/layer/auth/exception/AuthException.java create mode 100644 layer-api/src/main/java/org/layer/auth/oauth/SocialType.java create mode 100644 layer-api/src/main/java/org/layer/auth/service/AuthService.java delete mode 100644 layer-api/src/main/resources/application.yml create mode 100644 layer-external/src/main/java/org/layer/oauth/config/OAuthConfig.java create mode 100644 layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java create mode 100644 layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java create mode 100644 layer-external/src/main/java/org/layer/oauth/dto/service/KakaoTokenServiceResponse.java create mode 100644 layer-external/src/main/java/org/layer/oauth/service/KakaoService.java diff --git a/.gitignore b/.gitignore index f09c2913..0021dbf4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ # auth layer-api/src/main/resources/application-auth.properties +layer-external/src/main/resources/application-oauth.properties +layer-external/src/main/resources/application-oauth.yaml +layer-external/src/main/resources/application.yaml +layer-api/src/main/resources/application.yaml HELP.md .gradle diff --git a/build.gradle b/build.gradle index 0ca8d814..e739132e 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,9 @@ subprojects { testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' + } test { @@ -60,8 +63,7 @@ project(":layer-api") { implementation project(path: ':layer-domain') implementation project(path: ':layer-external') - implementation 'org.springframework.boot:spring-boot-starter' - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-actuator' //== jwt ==// diff --git a/layer-api/build.gradle b/layer-api/build.gradle deleted file mode 100644 index e69de29b..00000000 diff --git a/layer-api/src/main/java/org/layer/api/LayerApplication.java b/layer-api/src/main/java/org/layer/api/LayerApplication.java deleted file mode 100644 index 3829b296..00000000 --- a/layer-api/src/main/java/org/layer/api/LayerApplication.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.layer.api; - - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class LayerApplication { - public static void main(String[] args) { - SpringApplication.run(LayerApplication.class, args); - } -} \ No newline at end of file diff --git a/layer-api/src/main/java/org/layer/auth/api/AuthController.java b/layer-api/src/main/java/org/layer/auth/api/AuthController.java index c05ca7b4..5f29eded 100644 --- a/layer-api/src/main/java/org/layer/auth/api/AuthController.java +++ b/layer-api/src/main/java/org/layer/auth/api/AuthController.java @@ -1,17 +1,28 @@ package org.layer.auth.api; import lombok.RequiredArgsConstructor; +import org.layer.auth.dto.controller.ReissueTokenResponse; +import org.layer.auth.dto.controller.SignInRequest; +import org.layer.auth.dto.controller.SignInResponse; +import org.layer.auth.dto.service.ReissueTokenServiceResponse; +import org.layer.auth.dto.service.SignInServiceResponse; import org.layer.auth.jwt.JwtToken; +import org.layer.auth.oauth.SocialType; +import org.layer.auth.service.AuthService; import org.layer.auth.service.JwtService; import org.layer.member.MemberRole; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.layer.oauth.service.KakaoService; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RequiredArgsConstructor @RestController public class AuthController { private final JwtService jwtService; + private final AuthService authService; + private final KakaoService kakaoService; // 테스트용 임시 컨트롤러입니다. (토큰 없이 접속 가능) // "/create-token?id=멤버아이디" uri로 get 요청을 보내면 토큰이 발급됩니다. @@ -25,4 +36,53 @@ public JwtToken authTest(@RequestParam("id") Long memberId) { public String authTest() { return "인증 성공"; } + + @GetMapping("/test") + public String test() { + + return "==="; + } + + @GetMapping("/oauth/kakao") + public Object kakaoLogin(@RequestParam(value = "code", required = false) String code, + @RequestParam(value = "error", required = false) String error, + @RequestParam(value = "error_description", required = false) String error_description, + @RequestParam(value = "state", required = false) String state) { + + String accessToken = kakaoService.getToken(code); + return authService.getMemberInfo(SocialType.KAKAO, accessToken); + } + + + + // 로그인 TODO: 리턴타입 ResponseEntity>.. 이런 식으로 변경 + @PostMapping("/sign-in") + public ResponseEntity signIn(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody SocialType socialType) { + SignInServiceResponse signInServiceResponse = authService.signIn(socialAccessToken, socialType); + return new ResponseEntity<>(SignInResponse.of(signInServiceResponse), HttpStatus.OK); + } + + + // 로그아웃 TODO: 리턴타입 ResponseEntity.. 으로 변경 + @PostMapping("/sign-out") + public ResponseEntity signOut(@RequestBody Long memberId) { + authService.signOut(memberId); + return new ResponseEntity<>(HttpStatus.OK); + } + + // 회원 탈퇴 + @PostMapping("/withdraw") + // TODO: 리턴 타입, 리턴 데이터 수정 필요 + public ResponseEntity withdraw(@RequestBody Long memberId) { + authService.withdraw(memberId); + return new ResponseEntity<>(HttpStatus.OK); // TODO: 리턴 객체 수정 필요 + } + + // 토큰 재발급 + @PostMapping("/reissue-token") + public ResponseEntity reissueToken(@RequestBody Long memberId) { + return new ResponseEntity<>( + ReissueTokenResponse.of(authService.reissueToken(memberId)), + HttpStatus.CREATED); + } } diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/ReissueTokenResponse.java b/layer-api/src/main/java/org/layer/auth/dto/controller/ReissueTokenResponse.java new file mode 100644 index 00000000..65d573ee --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/dto/controller/ReissueTokenResponse.java @@ -0,0 +1,11 @@ +package org.layer.auth.dto.controller; + +import org.layer.auth.dto.service.ReissueTokenServiceResponse; + +public record ReissueTokenResponse(Long memberId, String accessToken, String refreshToken){ + public static ReissueTokenResponse of(ReissueTokenServiceResponse rtsr) { + return new ReissueTokenResponse(rtsr.memberId(), + rtsr.jwtToken().getAccessToken(), + rtsr.jwtToken().getRefreshToken()); + } +} diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java b/layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java new file mode 100644 index 00000000..4dde210a --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java @@ -0,0 +1,16 @@ +package org.layer.auth.dto.controller; + +import org.layer.auth.dto.service.SignInServiceResponse; +import org.layer.auth.jwt.JwtToken; +import org.layer.member.Member; +import org.layer.member.MemberRole; + +public record SignInResponse(Long memberId, String accessToken, String refreshToken, MemberRole memberRole) { + public static SignInResponse of(SignInServiceResponse signInServiceResponse) { + return new SignInResponse(signInServiceResponse.memberId(), + signInServiceResponse.accessToken(), + signInServiceResponse.refreshToken(), + signInServiceResponse.memberRole() + ); + } +} diff --git a/layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java b/layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java new file mode 100644 index 00000000..9f9fa761 --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java @@ -0,0 +1,10 @@ +package org.layer.auth.dto.service; + +import org.layer.auth.jwt.JwtToken; +import org.layer.member.Member; + +public record ReissueTokenServiceResponse(Long memberId, JwtToken jwtToken) { + public static ReissueTokenServiceResponse of(Member member, JwtToken jwtToken) { + return new ReissueTokenServiceResponse(member.getId(), jwtToken); + } +} diff --git a/layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java b/layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java new file mode 100644 index 00000000..bd597063 --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java @@ -0,0 +1,14 @@ +package org.layer.auth.dto.service; + +import org.layer.auth.jwt.JwtToken; +import org.layer.member.Member; +import org.layer.member.MemberRole; + +public record SignInServiceResponse(Long memberId, String accessToken, String refreshToken, MemberRole memberRole) { + public static SignInServiceResponse of(Member member, JwtToken jwtToken) { + return new SignInServiceResponse(member.getId(), + jwtToken.getAccessToken(), + jwtToken.getRefreshToken(), + member.getMemberRole()); + } +} diff --git a/layer-api/src/main/java/org/layer/auth/exception/AuthException.java b/layer-api/src/main/java/org/layer/auth/exception/AuthException.java new file mode 100644 index 00000000..d3e21b9c --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/exception/AuthException.java @@ -0,0 +1,5 @@ +package org.layer.auth.exception; + +// OAuth를 위해 임시로 만들어놓은 Exception입니다. +public class AuthException extends RuntimeException { +} diff --git a/layer-api/src/main/java/org/layer/auth/oauth/SocialType.java b/layer-api/src/main/java/org/layer/auth/oauth/SocialType.java new file mode 100644 index 00000000..cec7e77b --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/oauth/SocialType.java @@ -0,0 +1,6 @@ +package org.layer.auth.oauth; + +public enum SocialType { + KAKAO, + GOOGLE; +} diff --git a/layer-api/src/main/java/org/layer/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/auth/service/AuthService.java new file mode 100644 index 00000000..f3b262cd --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/service/AuthService.java @@ -0,0 +1,81 @@ +package org.layer.auth.service; + +import lombok.RequiredArgsConstructor; +import org.layer.auth.dto.service.ReissueTokenServiceResponse; +import org.layer.auth.dto.service.SignInServiceResponse; +import org.layer.auth.exception.AuthException; +//import org.layer.member.model.SocialType; +import org.layer.auth.jwt.JwtToken; +import org.layer.auth.oauth.SocialType; +import org.layer.member.Member; +import org.layer.oauth.dto.service.KakaoAccountServiceResponse; +import org.layer.oauth.service.KakaoService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class AuthService { + private final KakaoService kakaoLoginService; + private final JwtService jwtService; + + + // 소셜 로그인 + // TODO: SocialType 위치에 대한 고민. domain이 적절한지, 아니면 api 모듈에 있는게 적절한지 + @Transactional + public SignInServiceResponse signIn(final String socialAccessToken, final SocialType socialType) { + String memberEmail = getMemberInfo(socialType, socialAccessToken).email(); + + // TODO: DB 조회 후 없으면 저장 + // + + Member signedMember = getMemberByEmail(memberEmail); + JwtToken jwtToken = jwtService.issueToken(signedMember.getId(), signedMember.getMemberRole()); + + return SignInServiceResponse.of(signedMember, jwtToken); + } + + // 로그아웃 + public void signOut(final Long memberId) { + // TODO: 현재 유저와 memberId가 동일한지 확인 + jwtService.deleteRefreshToken(memberId); + } + + + // 회원 탈퇴 + public void withdraw(final Long memberId) { + // TODO: member 도메인에서 del_yn 바꾸기 + } + + // (리프레시 토큰을 받았을 때) 토큰 재발급 + public ReissueTokenServiceResponse reissueToken(final Long memberId) { + // TODO: DB에서 멤버 객체 찾아오기 + Member member = new Member(); // TODO: DB에서 찾아온 객체로 바꿀것 + return ReissueTokenServiceResponse.of(member, + jwtService.issueToken(member.getId(), member.getMemberRole())); + } + + + //== private methods ==// + + // member email 리턴 + // TODO: 리턴 타입, 접근 제어자 수정 필요 + public KakaoAccountServiceResponse getMemberInfo(SocialType socialType, String socialAccessToken) { + return switch (socialType) { + case KAKAO -> kakaoLoginService.getMemberInfo(socialAccessToken); + case GOOGLE -> null; + default -> throw new AuthException(); + }; + } + + private Member getMemberByEmail(final String email) { + // TODO: DB에서 Member를 찾아오는 코드 + return null; + } + + private boolean memberExists(SocialType socialType, String email) { + // TODO: DB에서 socialType과 email로 Member를 찾아오는 코드 + return false; + } + +} diff --git a/layer-api/src/main/java/org/layer/auth/service/JwtService.java b/layer-api/src/main/java/org/layer/auth/service/JwtService.java index 344ec137..da40c5d4 100644 --- a/layer-api/src/main/java/org/layer/auth/service/JwtService.java +++ b/layer-api/src/main/java/org/layer/auth/service/JwtService.java @@ -24,7 +24,7 @@ public JwtToken issueToken(Long memberId, MemberRole memberRole) { String accessToken = jwtProvider.createToken(MemberAuthentication.create(memberId, memberRole), ACCESS_TOKEN_EXPIRATION_TIME); String refreshToken = jwtProvider.createToken(MemberAuthentication.create(memberId, memberRole), REFRESH_TOKEN_EXPIRATION_TIME); - saveRefreshTokenToRedis(memberId, memberRole, refreshToken); + saveRefreshTokenToRedis(memberId, refreshToken); return JwtToken.builder() .accessToken(accessToken) @@ -32,8 +32,8 @@ public JwtToken issueToken(Long memberId, MemberRole memberRole) { .build(); } - private void saveRefreshTokenToRedis(Long memberId, MemberRole memberRole, String refreshToken) { - redisTemplate.opsForValue().set(refreshToken, memberId, Duration.ofDays(14)); + private void saveRefreshTokenToRedis(Long memberId, String refreshToken) { + redisTemplate.opsForValue().set(memberId.toString(), refreshToken, Duration.ofDays(14)); } private Long getMemberIdFromRefreshToken(String refreshToken) throws TokenException { @@ -46,8 +46,8 @@ private Long getMemberIdFromRefreshToken(String refreshToken) throws TokenExcept return memberId; } - public void deleteRefreshToken(String refreshToken) { - redisTemplate.delete(refreshToken); + public void deleteRefreshToken(Long memberId) { + redisTemplate.delete(memberId.toString()); } } diff --git a/layer-api/src/main/java/org/layer/config/AuthValueConfig.java b/layer-api/src/main/java/org/layer/config/AuthValueConfig.java index 2fc59b4d..c4c219d8 100644 --- a/layer-api/src/main/java/org/layer/config/AuthValueConfig.java +++ b/layer-api/src/main/java/org/layer/config/AuthValueConfig.java @@ -16,8 +16,12 @@ public class AuthValueConfig { @Value("${jwt.secret}") private String JWT_SECRET; + public static final Long ACCESS_TOKEN_EXPIRATION_TIME = 1000 * 60 * 30L; // 30분 public static final Long REFRESH_TOKEN_EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 14L; // 2주 + public static final String AUTHORIZATION = "Authorization"; + public static final String KAKAO_URI = "https://kapi.kakao.com/v2/user/me"; + @PostConstruct 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 7912aa45..c235a396 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,8 @@ private void setHttp(HttpSecurity http) throws Exception { ).authorizeHttpRequests(authorizeRequest -> authorizeRequest .requestMatchers(new AntPathRequestMatcher("/create-token")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/oauth/kakao")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/test")).permitAll() .anyRequest().authenticated() ); } diff --git a/layer-api/src/main/resources/application.yml b/layer-api/src/main/resources/application.yml deleted file mode 100644 index 3cacfbb1..00000000 --- a/layer-api/src/main/resources/application.yml +++ /dev/null @@ -1,5 +0,0 @@ -spring: - data: - redis: - host: localhost - port: 6379 \ No newline at end of file diff --git a/layer-external/src/main/java/org/layer/oauth/config/OAuthConfig.java b/layer-external/src/main/java/org/layer/oauth/config/OAuthConfig.java new file mode 100644 index 00000000..c4379187 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/config/OAuthConfig.java @@ -0,0 +1,36 @@ +package org.layer.oauth.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +@Getter +@PropertySource("classpath:application-oauth.properties") +@Configuration +public class OAuthConfig { + public static final String AUTHORIZATION = "Authorization"; + public static final String KAKAO_URI = "https://kapi.kakao.com/v2/user/me"; + public static final String TOKEN_PREFIX = "Bearer "; + + @Value("${kakao.login.api_key}") + private String kakaoLoginApiKey; + + @Value("${kakao.login.redirect_uri}") + private String redirectUri; + + @Value("${kakao.login.uri.code}") + private String codeReqeustUri; + + @Value("${kakao.login.uri.base}") + private String kakaoAuthBaseUri; + + @Value("${kakao.login.uri.token}") + private String tokenRequestUri; + + @Value("${kakao.api.uri.base}") + private String kakaoApiBaseUri; + + @Value("${kakao.api.uri.user}") + private String kakaoApiUserInfoRequestUri; + +} diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java new file mode 100644 index 00000000..fd6d7083 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java @@ -0,0 +1,4 @@ +package org.layer.oauth.dto.service; + +public record KakaoAccountServiceResponse(String name, String email) { +} diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java new file mode 100644 index 00000000..59400a72 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java @@ -0,0 +1,5 @@ +package org.layer.oauth.dto.service; + +public record KakaoGetMemberInfoServiceResponse(KakaoAccountServiceResponse kakao_account) { + +} diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoTokenServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoTokenServiceResponse.java new file mode 100644 index 00000000..9d245b44 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoTokenServiceResponse.java @@ -0,0 +1,33 @@ +package org.layer.oauth.dto.service; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +@Getter +public class KakaoTokenServiceResponse { + + /** + * tokenType: bearer로 고정 + */ + @JsonProperty("token_type") + private String tokenType; + @JsonProperty("access_token") + private String accessToken; + /** + * 액세스 토큰과 ID 토큰의 만료 시간(초) + */ + @JsonProperty("expires_in") + private Integer expiresIn; + @JsonProperty("refresh_token") + private String refreshToken; + /** + * 리프레시 토큰 만료 시간(초) + */ + @JsonProperty("refresh_token_expires_in") + private Integer refreshTokenExpiresIn; + + @JsonProperty("id_token") + private String idToken; + @JsonProperty("scope") + private String scope; +} \ No newline at end of file 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 new file mode 100644 index 00000000..e009da32 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/service/KakaoService.java @@ -0,0 +1,69 @@ +package org.layer.oauth.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.layer.oauth.config.OAuthConfig; +import org.layer.oauth.dto.service.KakaoAccountServiceResponse; +import org.layer.oauth.dto.service.KakaoGetMemberInfoServiceResponse; +import org.springframework.http.HttpStatusCode; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; + +import java.util.Map; + +import static org.layer.oauth.config.OAuthConfig.*; + + +@Slf4j +@RequiredArgsConstructor +@Service +public class KakaoService { + private final OAuthConfig oAuthConfig; + + // TODO: getMemberInfo 리턴 타입 수정 필요 + public KakaoAccountServiceResponse getMemberInfo(final String accessToken) { + KakaoGetMemberInfoServiceResponse response = null; + try { + RestClient restClient = RestClient.create(); + response = restClient.get() + .uri(KAKAO_URI) + .header(AUTHORIZATION, TOKEN_PREFIX + accessToken) + .header("Content-type", "application/x-www-form-urlencoded;charset=utf-8") + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, + (kakaoRequest, kakaoResponse) -> { + throw new RuntimeException(); // TODO: 수정 필요 + }) + .body(KakaoGetMemberInfoServiceResponse.class); + } catch(Exception e) { + throw e; // TODO: Exception 수정 필요 + } + + assert response != null; + return response.kakao_account(); + } + + //== 이건 프론트에서..? TODO: 지우기 ==// + public String getToken(String code) { + // 토큰 요청 데이터 + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "authorization_code"); + params.add("client_id", oAuthConfig.getKakaoLoginApiKey()); + params.add("redirect_uri", oAuthConfig.getRedirectUri()); + params.add("code", code); + + + Map response = RestClient.create(oAuthConfig.getKakaoAuthBaseUri()) + .post() + .uri(oAuthConfig.getTokenRequestUri()) + .body(params) + .header("Content-type", "application/x-www-form-urlencoded;charset=utf-8") //요청 헤더 + .retrieve() + .body(Map.class); + + assert response != null; + return (String) response.get("access_token"); + } +} From 7e7f49969dcdd1e0a576e3caaec72ff62a38c970 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Sun, 7 Jul 2024 15:13:40 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20kakao=20oauth=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 +- .../layer/api/controller/HelloController.java | 6 +- .../org/layer/auth/api/AuthController.java | 34 +++++--- .../auth/dto/controller/SignInRequest.java | 7 ++ .../auth/dto/controller/SignInResponse.java | 5 +- .../auth/dto/controller/SignUpRequest.java | 9 +++ .../auth/dto/controller/SignUpResponse.java | 22 +++++ .../service/ReissueTokenServiceResponse.java | 2 +- .../dto/service/SignInServiceResponse.java | 4 +- .../dto/service/SignUpServiceResponse.java | 21 +++++ .../auth/jwt/JwtAuthenticationFilter.java | 6 +- .../layer/auth/jwt/MemberAuthentication.java | 2 +- .../java/org/layer/auth/jwt/SecurityUtil.java | 23 ++++++ .../java/org/layer/auth/oauth/SocialType.java | 6 -- .../org/layer/auth/service/AuthService.java | 81 ++++++++++++------- .../org/layer/auth/service/JwtService.java | 4 +- .../java/org/layer/config/RedisConfig.java | 2 +- .../java/org/layer/config/SecurityConfig.java | 3 + .../layer/member/service/MemberService.java | 43 ++++++++++ .../org/layer/member/service/MemberUtil.java | 28 +++++++ .../common/exception/MemberExceptionType.java | 5 +- .../layer/domain/member/entity/Member.java | 14 ++++ .../member/repository/MemberRepository.java | 5 ++ layer-external/build.gradle | 3 + .../service/KakaoAccountServiceResponse.java | 2 +- .../KakaoGetMemberInfoServiceResponse.java | 5 +- .../service/MemberInfoServiceResponse.java | 6 ++ .../org/layer/oauth/service/KakaoService.java | 14 +++- 28 files changed, 289 insertions(+), 79 deletions(-) create mode 100644 layer-api/src/main/java/org/layer/auth/dto/controller/SignInRequest.java create mode 100644 layer-api/src/main/java/org/layer/auth/dto/controller/SignUpRequest.java create mode 100644 layer-api/src/main/java/org/layer/auth/dto/controller/SignUpResponse.java create mode 100644 layer-api/src/main/java/org/layer/auth/dto/service/SignUpServiceResponse.java create mode 100644 layer-api/src/main/java/org/layer/auth/jwt/SecurityUtil.java delete mode 100644 layer-api/src/main/java/org/layer/auth/oauth/SocialType.java create mode 100644 layer-api/src/main/java/org/layer/member/service/MemberService.java create mode 100644 layer-api/src/main/java/org/layer/member/service/MemberUtil.java create mode 100644 layer-external/src/main/java/org/layer/oauth/dto/service/MemberInfoServiceResponse.java diff --git a/build.gradle b/build.gradle index 8e4353a1..7584b2eb 100644 --- a/build.gradle +++ b/build.gradle @@ -76,8 +76,12 @@ project(":layer-api") { // redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + + // jpa + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + runtimeOnly 'com.mysql:mysql-connector-j' } jar.enabled = false diff --git a/layer-api/src/main/java/org/layer/api/controller/HelloController.java b/layer-api/src/main/java/org/layer/api/controller/HelloController.java index f6a1ed28..fc0b89fd 100644 --- a/layer-api/src/main/java/org/layer/api/controller/HelloController.java +++ b/layer-api/src/main/java/org/layer/api/controller/HelloController.java @@ -1,7 +1,7 @@ package org.layer.api.controller; import lombok.RequiredArgsConstructor; -import org.layer.common.model.Sample; +//import org.layer.common.model.Sample; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -11,8 +11,4 @@ @RequestMapping("/api/test") public class HelloController { - @GetMapping("") - public Sample test(){ - return new Sample("hello"); - } } diff --git a/layer-api/src/main/java/org/layer/auth/api/AuthController.java b/layer-api/src/main/java/org/layer/auth/api/AuthController.java index 5f29eded..b7c6f0b4 100644 --- a/layer-api/src/main/java/org/layer/auth/api/AuthController.java +++ b/layer-api/src/main/java/org/layer/auth/api/AuthController.java @@ -1,19 +1,18 @@ package org.layer.auth.api; import lombok.RequiredArgsConstructor; -import org.layer.auth.dto.controller.ReissueTokenResponse; -import org.layer.auth.dto.controller.SignInRequest; -import org.layer.auth.dto.controller.SignInResponse; +import org.layer.auth.dto.controller.*; import org.layer.auth.dto.service.ReissueTokenServiceResponse; import org.layer.auth.dto.service.SignInServiceResponse; +import org.layer.auth.dto.service.SignUpServiceResponse; import org.layer.auth.jwt.JwtToken; -import org.layer.auth.oauth.SocialType; import org.layer.auth.service.AuthService; import org.layer.auth.service.JwtService; -import org.layer.member.MemberRole; +import org.layer.domain.member.entity.MemberRole; +import org.layer.domain.member.entity.SocialType; +import org.layer.oauth.dto.service.KakaoGetMemberInfoServiceResponse; import org.layer.oauth.service.KakaoService; import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -50,20 +49,30 @@ public Object kakaoLogin(@RequestParam(value = "code", required = false) String @RequestParam(value = "state", required = false) String state) { String accessToken = kakaoService.getToken(code); - return authService.getMemberInfo(SocialType.KAKAO, accessToken); - } + return accessToken; +// KakaoGetMemberInfoServiceResponse memberInfo = authService.getMemberInfo(SocialType.KAKAO, accessToken); + + // 여기서 sign-in 으로 요청보내기 + } - // 로그인 TODO: 리턴타입 ResponseEntity>.. 이런 식으로 변경 + // 로그인 @PostMapping("/sign-in") - public ResponseEntity signIn(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody SocialType socialType) { - SignInServiceResponse signInServiceResponse = authService.signIn(socialAccessToken, socialType); + 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); } + // 회원가입 => 소셜로그인 했는데 유효한 유저가 없을 때 이름 입력하고 회원가입하는 과정 + @PostMapping("/sign-up") + public ResponseEntity signUp(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignUpRequest signUpRequest) { + SignUpServiceResponse signUpServiceResponse = authService.signUp(socialAccessToken, signUpRequest); + return new ResponseEntity<>(SignUpResponse.of(signUpServiceResponse), HttpStatus.CREATED); + } + - // 로그아웃 TODO: 리턴타입 ResponseEntity.. 으로 변경 + // 로그아웃 @PostMapping("/sign-out") public ResponseEntity signOut(@RequestBody Long memberId) { authService.signOut(memberId); @@ -72,7 +81,6 @@ public ResponseEntity signOut(@RequestBody Long memberId) { // 회원 탈퇴 @PostMapping("/withdraw") - // TODO: 리턴 타입, 리턴 데이터 수정 필요 public ResponseEntity withdraw(@RequestBody Long memberId) { authService.withdraw(memberId); return new ResponseEntity<>(HttpStatus.OK); // TODO: 리턴 객체 수정 필요 diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/SignInRequest.java b/layer-api/src/main/java/org/layer/auth/dto/controller/SignInRequest.java new file mode 100644 index 00000000..8b1750b2 --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/dto/controller/SignInRequest.java @@ -0,0 +1,7 @@ +package org.layer.auth.dto.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.layer.domain.member.entity.SocialType; + +public record SignInRequest(@JsonProperty("social_type") SocialType socialType) { +} diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java b/layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java index 4dde210a..f87586db 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java +++ b/layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java @@ -1,9 +1,8 @@ package org.layer.auth.dto.controller; import org.layer.auth.dto.service.SignInServiceResponse; -import org.layer.auth.jwt.JwtToken; -import org.layer.member.Member; -import org.layer.member.MemberRole; +import org.layer.domain.member.entity.MemberRole; + public record SignInResponse(Long memberId, String accessToken, String refreshToken, MemberRole memberRole) { public static SignInResponse of(SignInServiceResponse signInServiceResponse) { diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpRequest.java b/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpRequest.java new file mode 100644 index 00000000..e87f2599 --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpRequest.java @@ -0,0 +1,9 @@ +package org.layer.auth.dto.controller; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import org.layer.domain.member.entity.SocialType; + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public record SignUpRequest(SocialType socialType, String name) { +} diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpResponse.java b/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpResponse.java new file mode 100644 index 00000000..cba99f51 --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpResponse.java @@ -0,0 +1,22 @@ +package org.layer.auth.dto.controller; + +import org.layer.auth.dto.service.SignUpServiceResponse; +import org.layer.domain.member.entity.Member; +import org.layer.domain.member.entity.MemberRole; +import org.layer.domain.member.entity.SocialType; + +public record SignUpResponse(Long memberId, + String name, + String email, + MemberRole memberRole, + String SocialId, + SocialType socialType) { + public static SignUpResponse of(SignUpServiceResponse signUpServiceResponse) { + return new SignUpResponse(signUpServiceResponse.memberId(), + signUpServiceResponse.name(), + signUpServiceResponse.email(), + signUpServiceResponse.memberRole(), + signUpServiceResponse.socialId(), + signUpServiceResponse.socialType()); + } +} diff --git a/layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java b/layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java index 9f9fa761..148f677b 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java +++ b/layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java @@ -1,7 +1,7 @@ package org.layer.auth.dto.service; import org.layer.auth.jwt.JwtToken; -import org.layer.member.Member; +import org.layer.domain.member.entity.Member; public record ReissueTokenServiceResponse(Long memberId, JwtToken jwtToken) { public static ReissueTokenServiceResponse of(Member member, JwtToken jwtToken) { diff --git a/layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java b/layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java index bd597063..abf973aa 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java +++ b/layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java @@ -1,8 +1,8 @@ package org.layer.auth.dto.service; import org.layer.auth.jwt.JwtToken; -import org.layer.member.Member; -import org.layer.member.MemberRole; +import org.layer.domain.member.entity.Member; +import org.layer.domain.member.entity.MemberRole; public record SignInServiceResponse(Long memberId, String accessToken, String refreshToken, MemberRole memberRole) { public static SignInServiceResponse of(Member member, JwtToken jwtToken) { diff --git a/layer-api/src/main/java/org/layer/auth/dto/service/SignUpServiceResponse.java b/layer-api/src/main/java/org/layer/auth/dto/service/SignUpServiceResponse.java new file mode 100644 index 00000000..f169f209 --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/dto/service/SignUpServiceResponse.java @@ -0,0 +1,21 @@ +package org.layer.auth.dto.service; + +import org.layer.domain.member.entity.Member; +import org.layer.domain.member.entity.MemberRole; +import org.layer.domain.member.entity.SocialType; + +public record SignUpServiceResponse(Long memberId, + String name, + String email, + MemberRole memberRole, + String socialId, + SocialType socialType) { + public static SignUpServiceResponse of(Member member) { + return new SignUpServiceResponse(member.getId(), + member.getName(), + member.getEmail(), + member.getMemberRole(), + member.getSocialId(), + member.getSocialType()); + } +} diff --git a/layer-api/src/main/java/org/layer/auth/jwt/JwtAuthenticationFilter.java b/layer-api/src/main/java/org/layer/auth/jwt/JwtAuthenticationFilter.java index 11dd806a..8f510632 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/JwtAuthenticationFilter.java +++ b/layer-api/src/main/java/org/layer/auth/jwt/JwtAuthenticationFilter.java @@ -1,23 +1,19 @@ package org.layer.auth.jwt; -import com.nimbusds.oauth2.sdk.Role; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.layer.member.Member; -import org.layer.member.MemberRole; +import org.layer.domain.member.entity.MemberRole; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -import java.util.LinkedHashMap; import java.util.List; @Slf4j diff --git a/layer-api/src/main/java/org/layer/auth/jwt/MemberAuthentication.java b/layer-api/src/main/java/org/layer/auth/jwt/MemberAuthentication.java index a8fd36e5..95521630 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/MemberAuthentication.java +++ b/layer-api/src/main/java/org/layer/auth/jwt/MemberAuthentication.java @@ -2,7 +2,7 @@ import lombok.Builder; import lombok.RequiredArgsConstructor; -import org.layer.member.MemberRole; +import org.layer.domain.member.entity.MemberRole; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; diff --git a/layer-api/src/main/java/org/layer/auth/jwt/SecurityUtil.java b/layer-api/src/main/java/org/layer/auth/jwt/SecurityUtil.java new file mode 100644 index 00000000..c69e277c --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/jwt/SecurityUtil.java @@ -0,0 +1,23 @@ +package org.layer.auth.jwt; + +import org.layer.common.exception.BaseCustomException; +import org.layer.common.exception.ExceptionType; +import org.layer.common.exception.MemberExceptionType; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import static org.layer.common.exception.MemberExceptionType.UNAUTHORIZED_USER; + +@Component +public class SecurityUtil { + public Long getCurrentMemberId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + try { + return Long.parseLong(authentication.getName()); + } catch(Exception e) { + throw new BaseCustomException(UNAUTHORIZED_USER); + } + } +} diff --git a/layer-api/src/main/java/org/layer/auth/oauth/SocialType.java b/layer-api/src/main/java/org/layer/auth/oauth/SocialType.java deleted file mode 100644 index cec7e77b..00000000 --- a/layer-api/src/main/java/org/layer/auth/oauth/SocialType.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.layer.auth.oauth; - -public enum SocialType { - KAKAO, - GOOGLE; -} diff --git a/layer-api/src/main/java/org/layer/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/auth/service/AuthService.java index f3b262cd..acf02e44 100644 --- a/layer-api/src/main/java/org/layer/auth/service/AuthService.java +++ b/layer-api/src/main/java/org/layer/auth/service/AuthService.java @@ -1,56 +1,77 @@ package org.layer.auth.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.layer.auth.dto.controller.SignInRequest; +import org.layer.auth.dto.controller.SignUpRequest; import org.layer.auth.dto.service.ReissueTokenServiceResponse; import org.layer.auth.dto.service.SignInServiceResponse; +import org.layer.auth.dto.service.SignUpServiceResponse; import org.layer.auth.exception.AuthException; -//import org.layer.member.model.SocialType; import org.layer.auth.jwt.JwtToken; -import org.layer.auth.oauth.SocialType; -import org.layer.member.Member; -import org.layer.oauth.dto.service.KakaoAccountServiceResponse; +import org.layer.common.exception.BaseCustomException; +import org.layer.domain.member.entity.Member; +import org.layer.domain.member.entity.SocialType; +import org.layer.member.service.MemberService; +import org.layer.member.service.MemberUtil; +import org.layer.oauth.dto.service.KakaoGetMemberInfoServiceResponse; +import org.layer.oauth.dto.service.MemberInfoServiceResponse; import org.layer.oauth.service.KakaoService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import static org.layer.common.exception.MemberExceptionType.FORBIDDEN; + +@Slf4j +@Transactional(readOnly = true) @RequiredArgsConstructor @Service public class AuthService { private final KakaoService kakaoLoginService; private final JwtService jwtService; + private final MemberService memberService; + private final MemberUtil memberUtil; - - // 소셜 로그인 - // TODO: SocialType 위치에 대한 고민. domain이 적절한지, 아니면 api 모듈에 있는게 적절한지 - @Transactional + //== 로그인 ==// public SignInServiceResponse signIn(final String socialAccessToken, final SocialType socialType) { - String memberEmail = getMemberInfo(socialType, socialAccessToken).email(); + MemberInfoServiceResponse signedMember = getMemberInfo(socialType, socialAccessToken); - // TODO: DB 조회 후 없으면 저장 - // + // DB에서 회원 찾기. 없다면 Exception 발생 => 이름 입력 창으로 + Member member = memberService.findMemberBySocialIdAndSocialType(signedMember.socialId(), socialType); + JwtToken jwtToken = jwtService.issueToken(member.getId(), member.getMemberRole()); + return SignInServiceResponse.of(member, jwtToken); + } - Member signedMember = getMemberByEmail(memberEmail); - JwtToken jwtToken = jwtService.issueToken(signedMember.getId(), signedMember.getMemberRole()); + //== 회원가입(이름을 입력 받기) ==// + public SignUpServiceResponse signUp(final String socialAccessToken, final SignUpRequest signUpRequest) { + MemberInfoServiceResponse memberInfo = getMemberInfo(signUpRequest.socialType(), socialAccessToken); - return SignInServiceResponse.of(signedMember, jwtToken); + // DB에 회원 저장 + Member member = memberService.saveMember(signUpRequest, memberInfo); + return SignUpServiceResponse.of(member); } - // 로그아웃 + //== 로그아웃 ==// public void signOut(final Long memberId) { - // TODO: 현재 유저와 memberId가 동일한지 확인 + // 현재 로그인된 사용자와 memberId가 일치하는지 확인 => 일치하지 않으면 Exception + isValidMember(memberId); jwtService.deleteRefreshToken(memberId); } - // 회원 탈퇴 + //== 회원 탈퇴 ==// public void withdraw(final Long memberId) { - // TODO: member 도메인에서 del_yn 바꾸기 + // TODO: member 도메인에서 del_yn 바꾸기 => Member entitiy에 추가,,? + } - // (리프레시 토큰을 받았을 때) 토큰 재발급 + //== (리프레시 토큰을 받았을 때) 토큰 재발급 ==// public ReissueTokenServiceResponse reissueToken(final Long memberId) { - // TODO: DB에서 멤버 객체 찾아오기 - Member member = new Member(); // TODO: DB에서 찾아온 객체로 바꿀것 + // 현재 로그인된 사용자와 memberId가 일치하는지 확인 + isValidMember(memberId); + + // 시큐리티 컨텍스트에서 member 찾아오기 + Member member = memberUtil.getCurrentMember(); return ReissueTokenServiceResponse.of(member, jwtService.issueToken(member.getId(), member.getMemberRole())); } @@ -58,9 +79,8 @@ public ReissueTokenServiceResponse reissueToken(final Long memberId) { //== private methods ==// - // member email 리턴 - // TODO: 리턴 타입, 접근 제어자 수정 필요 - public KakaoAccountServiceResponse getMemberInfo(SocialType socialType, String socialAccessToken) { + // MemberInfoServiceResponse: socialId, socialType, email + private MemberInfoServiceResponse getMemberInfo(SocialType socialType, String socialAccessToken) { return switch (socialType) { case KAKAO -> kakaoLoginService.getMemberInfo(socialAccessToken); case GOOGLE -> null; @@ -68,14 +88,13 @@ public KakaoAccountServiceResponse getMemberInfo(SocialType socialType, String s }; } - private Member getMemberByEmail(final String email) { - // TODO: DB에서 Member를 찾아오는 코드 - return null; - } - private boolean memberExists(SocialType socialType, String email) { - // TODO: DB에서 socialType과 email로 Member를 찾아오는 코드 - return false; + // 현재 로그인 된 사용자와 해당 멤버 아이디가 일치하는지 확인 + private void isValidMember(Long memberId) { + Member currentMember = memberUtil.getCurrentMember(); + if(!currentMember.getId().equals(memberId)) { + throw new BaseCustomException(FORBIDDEN); + } } } diff --git a/layer-api/src/main/java/org/layer/auth/service/JwtService.java b/layer-api/src/main/java/org/layer/auth/service/JwtService.java index da40c5d4..9190688d 100644 --- a/layer-api/src/main/java/org/layer/auth/service/JwtService.java +++ b/layer-api/src/main/java/org/layer/auth/service/JwtService.java @@ -3,10 +3,8 @@ import lombok.RequiredArgsConstructor; import org.layer.auth.exception.TokenException; import org.layer.auth.jwt.*; -import org.layer.config.AuthValueConfig; -import org.layer.member.MemberRole; +import org.layer.domain.member.entity.MemberRole; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import java.time.Duration; 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 a33b7dbe..c7d7d060 100644 --- a/layer-api/src/main/java/org/layer/config/RedisConfig.java +++ b/layer-api/src/main/java/org/layer/config/RedisConfig.java @@ -1,6 +1,6 @@ package org.layer.config; -import org.layer.member.Member; +import org.layer.domain.member.entity.Member; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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 c235a396..c620315a 100644 --- a/layer-api/src/main/java/org/layer/config/SecurityConfig.java +++ b/layer-api/src/main/java/org/layer/config/SecurityConfig.java @@ -37,6 +37,9 @@ private void setHttp(HttpSecurity http) throws Exception { authorizeRequest .requestMatchers(new AntPathRequestMatcher("/create-token")).permitAll() .requestMatchers(new AntPathRequestMatcher("/oauth/kakao")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/sign-in")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/reissue-token")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/sign-up")).permitAll() .requestMatchers(new AntPathRequestMatcher("/test")).permitAll() .anyRequest().authenticated() ); diff --git a/layer-api/src/main/java/org/layer/member/service/MemberService.java b/layer-api/src/main/java/org/layer/member/service/MemberService.java new file mode 100644 index 00000000..867c60e8 --- /dev/null +++ b/layer-api/src/main/java/org/layer/member/service/MemberService.java @@ -0,0 +1,43 @@ +package org.layer.member.service; + +import lombok.RequiredArgsConstructor; +import org.layer.auth.dto.controller.SignUpRequest; +import org.layer.common.exception.BaseCustomException; +import org.layer.common.exception.MemberExceptionType; +import org.layer.domain.member.entity.Member; +import org.layer.domain.member.entity.SocialType; +import org.layer.domain.member.repository.MemberRepository; +import org.layer.oauth.dto.service.MemberInfoServiceResponse; +import org.springframework.stereotype.Service; + +import static org.layer.domain.member.entity.MemberRole.USER; + +@RequiredArgsConstructor +@Service +public class MemberService { + private final MemberRepository memberRepository; + public Member findMemberById(Long memberId) { + return memberRepository.findById(memberId).orElse(null); + } + + // 소셜 아이디와 소셜 타입으로 멤버 찾기. 멤버가 없으면 Exception + public Member findMemberBySocialIdAndSocialType(String socialId, SocialType socialType) { + return memberRepository.findBySocialIdAndSocialType(socialId, socialType) + .orElseThrow(() -> new BaseCustomException(MemberExceptionType.NOT_FOUND_USER)); + } + + public Member saveMember(SignUpRequest signUpRequest, MemberInfoServiceResponse memberInfo) { + Member member = Member.builder() + .name(signUpRequest.name()) + .memberRole(USER) + .email(memberInfo.email()) + .socialId(memberInfo.socialId()) + .socialType(memberInfo.socialType()) + .build(); + + memberRepository.save(member); + + return member; + } + +} diff --git a/layer-api/src/main/java/org/layer/member/service/MemberUtil.java b/layer-api/src/main/java/org/layer/member/service/MemberUtil.java new file mode 100644 index 00000000..7f063dce --- /dev/null +++ b/layer-api/src/main/java/org/layer/member/service/MemberUtil.java @@ -0,0 +1,28 @@ +package org.layer.member.service; + +import lombok.RequiredArgsConstructor; +import org.layer.auth.jwt.SecurityUtil; +import org.layer.common.exception.BaseCustomException; +import org.layer.common.exception.MemberExceptionType; +import org.layer.domain.member.entity.Member; +import org.layer.domain.member.repository.MemberRepository; +import org.springframework.stereotype.Component; + +import static org.layer.common.exception.MemberExceptionType.*; +@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)); + } +} 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 1536d6ee..e2ec0dc8 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 @@ -9,7 +9,10 @@ public enum MemberExceptionType implements ExceptionType { /** * 400 */ - NOT_FOUND_USER(HttpStatus.NOT_FOUND, "유효한 유저를 찾지 못했습니다."); + NOT_FOUND_USER(HttpStatus.NOT_FOUND, "유효한 유저를 찾지 못했습니다."), + UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "로그인되지 않은 사용자입니다"), + FORBIDDEN(HttpStatus.FORBIDDEN, "권한이 없습니다."), + FAIL_TO_AUTH(HttpStatus.BAD_REQUEST, "인증에 실패했습니다."); private final HttpStatus status; diff --git a/layer-domain/src/main/java/org/layer/domain/member/entity/Member.java b/layer-domain/src/main/java/org/layer/domain/member/entity/Member.java index 7ab0ec66..3696ab7a 100644 --- a/layer-domain/src/main/java/org/layer/domain/member/entity/Member.java +++ b/layer-domain/src/main/java/org/layer/domain/member/entity/Member.java @@ -8,9 +8,12 @@ import jakarta.persistence.Id; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; + @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -33,4 +36,15 @@ public class Member { @NotNull private String socialId; + + + @Builder(access = AccessLevel.PUBLIC) + private Member(String name,String email, MemberRole memberRole, + SocialType socialType, String socialId) { + this.name = name; + this.email = email; + this.memberRole = memberRole; + this.socialType = socialType; + this.socialId = socialId; + } } 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 a733e798..d3648db8 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 @@ -1,8 +1,13 @@ package org.layer.domain.member.repository; +import jakarta.validation.constraints.NotNull; import org.layer.domain.member.entity.Member; +import org.layer.domain.member.entity.SocialType; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface MemberRepository extends JpaRepository { + Optional findBySocialIdAndSocialType(@NotNull String socialId, @NotNull SocialType socialType); } diff --git a/layer-external/build.gradle b/layer-external/build.gradle index e69de29b..17ff2c7c 100644 --- a/layer-external/build.gradle +++ b/layer-external/build.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation project(path: ':layer-domain') +} \ No newline at end of file diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java index fd6d7083..a998288e 100644 --- a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java @@ -1,4 +1,4 @@ package org.layer.oauth.dto.service; -public record KakaoAccountServiceResponse(String name, String email) { +public record KakaoAccountServiceResponse(String email) { } diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java index 59400a72..f295c5e8 100644 --- a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java @@ -1,5 +1,8 @@ package org.layer.oauth.dto.service; -public record KakaoGetMemberInfoServiceResponse(KakaoAccountServiceResponse kakao_account) { +public record KakaoGetMemberInfoServiceResponse(String id, KakaoAccountServiceResponse kakao_account) { + public static KakaoGetMemberInfoServiceResponse of(String id, KakaoAccountServiceResponse kakao_account) { + return new KakaoGetMemberInfoServiceResponse(id, kakao_account); + } } diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/MemberInfoServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/MemberInfoServiceResponse.java new file mode 100644 index 00000000..d8318687 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/MemberInfoServiceResponse.java @@ -0,0 +1,6 @@ +package org.layer.oauth.dto.service; + +import org.layer.domain.member.entity.SocialType; + +public record MemberInfoServiceResponse(String socialId, SocialType socialType, String email) { +} 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 e009da32..fe4e6afa 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 @@ -2,9 +2,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.layer.common.exception.BaseCustomException; +import org.layer.common.exception.MemberExceptionType; +import org.layer.domain.member.entity.SocialType; import org.layer.oauth.config.OAuthConfig; import org.layer.oauth.dto.service.KakaoAccountServiceResponse; import org.layer.oauth.dto.service.KakaoGetMemberInfoServiceResponse; +import org.layer.oauth.dto.service.MemberInfoServiceResponse; import org.springframework.http.HttpStatusCode; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; @@ -13,6 +17,8 @@ import java.util.Map; +import static org.layer.common.exception.MemberExceptionType.FAIL_TO_AUTH; +import static org.layer.domain.member.entity.SocialType.KAKAO; import static org.layer.oauth.config.OAuthConfig.*; @@ -23,7 +29,7 @@ public class KakaoService { private final OAuthConfig oAuthConfig; // TODO: getMemberInfo 리턴 타입 수정 필요 - public KakaoAccountServiceResponse getMemberInfo(final String accessToken) { + public MemberInfoServiceResponse getMemberInfo(final String accessToken) { KakaoGetMemberInfoServiceResponse response = null; try { RestClient restClient = RestClient.create(); @@ -34,15 +40,15 @@ public KakaoAccountServiceResponse getMemberInfo(final String accessToken) { .retrieve() .onStatus(HttpStatusCode::is4xxClientError, (kakaoRequest, kakaoResponse) -> { - throw new RuntimeException(); // TODO: 수정 필요 + throw new BaseCustomException(FAIL_TO_AUTH); }) .body(KakaoGetMemberInfoServiceResponse.class); } catch(Exception e) { - throw e; // TODO: Exception 수정 필요 + throw new BaseCustomException(FAIL_TO_AUTH); } assert response != null; - return response.kakao_account(); + return new MemberInfoServiceResponse(response.id(), KAKAO, response.kakao_account().email()); } //== 이건 프론트에서..? TODO: 지우기 ==// From 16a3275355c0c858f96b545485f06b90317b5981 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Sun, 7 Jul 2024 16:08:01 +0900 Subject: [PATCH 03/12] =?UTF-8?q?chore:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20api=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/layer/auth/api/AuthController.java | 36 +------------------ .../java/org/layer/config/SecurityConfig.java | 9 ++--- 2 files changed, 4 insertions(+), 41 deletions(-) diff --git a/layer-api/src/main/java/org/layer/auth/api/AuthController.java b/layer-api/src/main/java/org/layer/auth/api/AuthController.java index 0892c8bf..23ba25ac 100644 --- a/layer-api/src/main/java/org/layer/auth/api/AuthController.java +++ b/layer-api/src/main/java/org/layer/auth/api/AuthController.java @@ -19,46 +19,12 @@ import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor +@RequestMapping("/api/auth") @RestController public class AuthController { - private final JwtService jwtService; private final AuthService authService; private final KakaoService kakaoService; - // 테스트용 임시 컨트롤러입니다. (토큰 없이 접속 가능) - // "/create-token?id=멤버아이디" uri로 get 요청을 보내면 토큰이 발급됩니다. - @GetMapping("/create-token") - public JwtToken authTest(@RequestParam("id") Long memberId) { - return jwtService.issueToken(memberId, MemberRole.USER); - } - - // header에 액세스 토큰을 넣어 요청을 보내면 인증됩니다. - @GetMapping("/authentication-test") - public String authTest() { - return "인증 성공"; - } - - @GetMapping("/test") - public String test() { - - return "==="; - } - - @GetMapping("/oauth/kakao") - public Object kakaoLogin(@RequestParam(value = "code", required = false) String code, - @RequestParam(value = "error", required = false) String error, - @RequestParam(value = "error_description", required = false) String error_description, - @RequestParam(value = "state", required = false) String state) { - - String accessToken = kakaoService.getToken(code); - return accessToken; -// KakaoGetMemberInfoServiceResponse memberInfo = authService.getMemberInfo(SocialType.KAKAO, accessToken); - - // 여기서 sign-in 으로 요청보내기 - - - } - // 로그인 @PostMapping("/sign-in") public ResponseEntity signIn(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignInRequest signInRequest) { 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 c620315a..64c812a9 100644 --- a/layer-api/src/main/java/org/layer/config/SecurityConfig.java +++ b/layer-api/src/main/java/org/layer/config/SecurityConfig.java @@ -35,12 +35,9 @@ private void setHttp(HttpSecurity http) throws Exception { session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ).authorizeHttpRequests(authorizeRequest -> authorizeRequest - .requestMatchers(new AntPathRequestMatcher("/create-token")).permitAll() - .requestMatchers(new AntPathRequestMatcher("/oauth/kakao")).permitAll() - .requestMatchers(new AntPathRequestMatcher("/sign-in")).permitAll() - .requestMatchers(new AntPathRequestMatcher("/reissue-token")).permitAll() - .requestMatchers(new AntPathRequestMatcher("/sign-up")).permitAll() - .requestMatchers(new AntPathRequestMatcher("/test")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-in")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/api/auth/reissue-token")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-up")).permitAll() .anyRequest().authenticated() ); } From e1d88320e5b1d848a1842c0bf5aefdcd06937e68 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Sun, 7 Jul 2024 20:03:01 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=EA=B5=AC=EA=B8=80=20=EB=A1=9C?= =?UTF-8?q?=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 --- .gitignore | 1 + .../org/layer/auth/api/AuthController.java | 17 +++- .../org/layer/auth/service/AuthService.java | 10 +-- .../java/org/layer/config/SecurityConfig.java | 2 + .../layer/oauth/config/GoogleOAuthConfig.java | 25 ++++++ ...OAuthConfig.java => KakaoOAuthConfig.java} | 5 +- .../oauth/dto/controller/TokenRequestDto.java | 6 ++ .../GoogleGetMemberInfoServiceResponse.java | 7 ++ .../KakaoAccountServiceResponse.java | 2 +- .../KakaoGetMemberInfoServiceResponse.java | 2 +- .../KakaoTokenServiceResponse.java | 2 +- .../layer/oauth/service/GoogleService.java | 79 +++++++++++++++++++ .../org/layer/oauth/service/KakaoService.java | 20 ++--- 13 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 layer-external/src/main/java/org/layer/oauth/config/GoogleOAuthConfig.java rename layer-external/src/main/java/org/layer/oauth/config/{OAuthConfig.java => KakaoOAuthConfig.java} (86%) create mode 100644 layer-external/src/main/java/org/layer/oauth/dto/controller/TokenRequestDto.java create mode 100644 layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleGetMemberInfoServiceResponse.java rename layer-external/src/main/java/org/layer/oauth/dto/service/{ => kakao}/KakaoAccountServiceResponse.java (58%) rename layer-external/src/main/java/org/layer/oauth/dto/service/{ => kakao}/KakaoGetMemberInfoServiceResponse.java (87%) rename layer-external/src/main/java/org/layer/oauth/dto/service/{ => kakao}/KakaoTokenServiceResponse.java (94%) create mode 100644 layer-external/src/main/java/org/layer/oauth/service/GoogleService.java diff --git a/.gitignore b/.gitignore index ff3f6263..1e3c0c70 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ layer-external/src/main/resources/application-oauth.properties layer-external/src/main/resources/application-oauth.yaml layer-external/src/main/resources/application.yaml layer-api/src/main/resources/application.yaml +layer-external/src/main/resources/application.yml HELP.md diff --git a/layer-api/src/main/java/org/layer/auth/api/AuthController.java b/layer-api/src/main/java/org/layer/auth/api/AuthController.java index 23ba25ac..962cb6ec 100644 --- a/layer-api/src/main/java/org/layer/auth/api/AuthController.java +++ b/layer-api/src/main/java/org/layer/auth/api/AuthController.java @@ -1,6 +1,7 @@ package org.layer.auth.api; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.layer.auth.dto.controller.*; import org.layer.auth.dto.service.ReissueTokenServiceResponse; import org.layer.auth.dto.service.SignInServiceResponse; @@ -10,6 +11,8 @@ import org.layer.auth.service.JwtService; import org.layer.domain.member.entity.MemberRole; +import org.layer.oauth.dto.controller.TokenRequestDto; +import org.layer.oauth.service.GoogleService; import org.layer.oauth.service.KakaoService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,12 +21,13 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +@Slf4j @RequiredArgsConstructor @RequestMapping("/api/auth") @RestController public class AuthController { private final AuthService authService; - private final KakaoService kakaoService; + private final GoogleService googleService; // 로그인 @PostMapping("/sign-in") @@ -61,4 +65,15 @@ public ResponseEntity reissueToken(@RequestBody Long membe ReissueTokenResponse.of(authService.reissueToken(memberId)), HttpStatus.CREATED); } + + +// //== 구글 테스트용 api ==// +// @GetMapping("oauth2/google") +// public String googleTest(@RequestParam("code") String code) { +// log.info("code : " + code); +// String token = googleService.getToken(code); +// return token; +// } + + } diff --git a/layer-api/src/main/java/org/layer/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/auth/service/AuthService.java index 37eafe0e..0ca677c4 100644 --- a/layer-api/src/main/java/org/layer/auth/service/AuthService.java +++ b/layer-api/src/main/java/org/layer/auth/service/AuthService.java @@ -2,7 +2,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.layer.auth.dto.controller.SignInRequest; import org.layer.auth.dto.controller.SignUpRequest; import org.layer.auth.dto.service.ReissueTokenServiceResponse; import org.layer.auth.dto.service.SignInServiceResponse; @@ -14,8 +13,8 @@ import org.layer.domain.member.entity.SocialType; import org.layer.member.service.MemberService; import org.layer.member.service.MemberUtil; -import org.layer.oauth.dto.service.KakaoGetMemberInfoServiceResponse; import org.layer.oauth.dto.service.MemberInfoServiceResponse; +import org.layer.oauth.service.GoogleService; import org.layer.oauth.service.KakaoService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,7 +25,8 @@ @RequiredArgsConstructor @Service public class AuthService { - private final KakaoService kakaoLoginService; + private final KakaoService kakaoService; + private final GoogleService googleService; private final JwtService jwtService; private final MemberService memberService; private final MemberUtil memberUtil; @@ -89,8 +89,8 @@ public ReissueTokenServiceResponse reissueToken(final Long memberId) { // MemberInfoServiceResponse: socialId, socialType, email private MemberInfoServiceResponse getMemberInfo(SocialType socialType, String socialAccessToken) { return switch (socialType) { - case KAKAO -> kakaoLoginService.getMemberInfo(socialAccessToken); - case GOOGLE -> null; + case KAKAO -> kakaoService.getMemberInfo(socialAccessToken); + case GOOGLE -> googleService.getMemberInfo(socialAccessToken); default -> throw new AuthException(); }; } 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 64c812a9..5f84cdea 100644 --- a/layer-api/src/main/java/org/layer/config/SecurityConfig.java +++ b/layer-api/src/main/java/org/layer/config/SecurityConfig.java @@ -38,6 +38,8 @@ private void setHttp(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-in")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/reissue-token")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-up")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/google/code")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/google")).permitAll() .anyRequest().authenticated() ); } diff --git a/layer-external/src/main/java/org/layer/oauth/config/GoogleOAuthConfig.java b/layer-external/src/main/java/org/layer/oauth/config/GoogleOAuthConfig.java new file mode 100644 index 00000000..7cbc4f3e --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/config/GoogleOAuthConfig.java @@ -0,0 +1,25 @@ +package org.layer.oauth.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Getter +@Configuration +public class GoogleOAuthConfig { + public static final String AUTHORIZATION = "Authorization"; + public static final String TOKEN_PREFIX = "Bearer "; + public static final String GOOGLE_CODE_URI = "https://accounts.google.com/o/oauth2/v2/auth"; + public static final String GOOGLE_TOKEN_URI = "https://oauth2.googleapis.com/token"; + public static final String GOOGLE_USER_INFO_URI = "https://www.googleapis.com/userinfo/v2/me"; + + + @Value("${google.login.client_id}") + private String googleClientId; + + @Value("${google.login.client_secret}") + private String googleClientSecret; + + @Value("${google.login.redirect_uri}") + private String googleRedirectUri; +} diff --git a/layer-external/src/main/java/org/layer/oauth/config/OAuthConfig.java b/layer-external/src/main/java/org/layer/oauth/config/KakaoOAuthConfig.java similarity index 86% rename from layer-external/src/main/java/org/layer/oauth/config/OAuthConfig.java rename to layer-external/src/main/java/org/layer/oauth/config/KakaoOAuthConfig.java index c4379187..e39fe55c 100644 --- a/layer-external/src/main/java/org/layer/oauth/config/OAuthConfig.java +++ b/layer-external/src/main/java/org/layer/oauth/config/KakaoOAuthConfig.java @@ -3,11 +3,10 @@ import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; + @Getter -@PropertySource("classpath:application-oauth.properties") @Configuration -public class OAuthConfig { +public class KakaoOAuthConfig { public static final String AUTHORIZATION = "Authorization"; public static final String KAKAO_URI = "https://kapi.kakao.com/v2/user/me"; public static final String TOKEN_PREFIX = "Bearer "; diff --git a/layer-external/src/main/java/org/layer/oauth/dto/controller/TokenRequestDto.java b/layer-external/src/main/java/org/layer/oauth/dto/controller/TokenRequestDto.java new file mode 100644 index 00000000..6880753f --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/dto/controller/TokenRequestDto.java @@ -0,0 +1,6 @@ +package org.layer.oauth.dto.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record TokenRequestDto(@JsonProperty("access_token") String accessToken) { +} 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 new file mode 100644 index 00000000..eb09b963 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleGetMemberInfoServiceResponse.java @@ -0,0 +1,7 @@ +package org.layer.oauth.dto.service.google; + +import org.layer.oauth.dto.service.kakao.KakaoAccountServiceResponse; +import org.layer.oauth.dto.service.kakao.KakaoGetMemberInfoServiceResponse; + +public record GoogleGetMemberInfoServiceResponse(String id, String email) { +} diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/kakao/KakaoAccountServiceResponse.java similarity index 58% rename from layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java rename to layer-external/src/main/java/org/layer/oauth/dto/service/kakao/KakaoAccountServiceResponse.java index a998288e..d09c6970 100644 --- a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoAccountServiceResponse.java +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/kakao/KakaoAccountServiceResponse.java @@ -1,4 +1,4 @@ -package org.layer.oauth.dto.service; +package org.layer.oauth.dto.service.kakao; public record KakaoAccountServiceResponse(String email) { } diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/kakao/KakaoGetMemberInfoServiceResponse.java similarity index 87% rename from layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java rename to layer-external/src/main/java/org/layer/oauth/dto/service/kakao/KakaoGetMemberInfoServiceResponse.java index f295c5e8..77b9de42 100644 --- a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoGetMemberInfoServiceResponse.java +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/kakao/KakaoGetMemberInfoServiceResponse.java @@ -1,4 +1,4 @@ -package org.layer.oauth.dto.service; +package org.layer.oauth.dto.service.kakao; public record KakaoGetMemberInfoServiceResponse(String id, KakaoAccountServiceResponse kakao_account) { public static KakaoGetMemberInfoServiceResponse of(String id, KakaoAccountServiceResponse kakao_account) { diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoTokenServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/kakao/KakaoTokenServiceResponse.java similarity index 94% rename from layer-external/src/main/java/org/layer/oauth/dto/service/KakaoTokenServiceResponse.java rename to layer-external/src/main/java/org/layer/oauth/dto/service/kakao/KakaoTokenServiceResponse.java index 9d245b44..c77e4b6f 100644 --- a/layer-external/src/main/java/org/layer/oauth/dto/service/KakaoTokenServiceResponse.java +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/kakao/KakaoTokenServiceResponse.java @@ -1,4 +1,4 @@ -package org.layer.oauth.dto.service; +package org.layer.oauth.dto.service.kakao; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; 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 new file mode 100644 index 00000000..c21e5ab0 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/service/GoogleService.java @@ -0,0 +1,79 @@ +package org.layer.oauth.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.layer.common.exception.BaseCustomException; +import org.layer.oauth.config.GoogleOAuthConfig; +import org.layer.oauth.dto.controller.TokenRequestDto; +import org.layer.oauth.dto.service.MemberInfoServiceResponse; +import org.layer.oauth.dto.service.google.GoogleGetMemberInfoServiceResponse; +import org.layer.oauth.dto.service.kakao.KakaoGetMemberInfoServiceResponse; +import org.springframework.http.HttpStatusCode; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.layer.common.exception.MemberExceptionType.FAIL_TO_AUTH; +import static org.layer.domain.member.entity.SocialType.GOOGLE; +import static org.layer.oauth.config.GoogleOAuthConfig.*; +import static org.springframework.http.MediaType.APPLICATION_JSON; + +@Slf4j +@RequiredArgsConstructor +@Service +public class GoogleService { + private final GoogleOAuthConfig googleOAuthConfig; + + //== 액세스 토큰으로 사용자 정보 가져오기 ==// + public MemberInfoServiceResponse getMemberInfo(final String accessToken) { + GoogleGetMemberInfoServiceResponse response = null; + try { + RestClient restClient = RestClient.create(); + response = restClient.get() + .uri(GOOGLE_USER_INFO_URI) + .header(AUTHORIZATION, TOKEN_PREFIX + accessToken) + .header("Content-type", "application/x-www-form-urlencoded;charset=utf-8") + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, + (googleRequest, googleResponse) -> { + throw new BaseCustomException(FAIL_TO_AUTH); + }) + .body(GoogleGetMemberInfoServiceResponse.class); + } catch(Exception e) { + throw new BaseCustomException(FAIL_TO_AUTH); + } + + assert response != null; + return new MemberInfoServiceResponse(response.id(), GOOGLE, response.email()); + } + + + //== 코드로 액세스 토큰을 받는 메서드 TODO: 삭제하기==// + public String getToken(String code) { + log.info("redirect uri: {}", googleOAuthConfig.getGoogleRedirectUri()); + // 토큰 요청 데이터 + String uri = UriComponentsBuilder.fromOriginHeader(GOOGLE_TOKEN_URI) + .toUriString(); + + Map params = new LinkedHashMap<>(); + params.put("client_id", googleOAuthConfig.getGoogleClientId()); + params.put("client_secret", googleOAuthConfig.getGoogleClientSecret()); + params.put("code", code); + params.put("grant_type", "authorization_code"); + params.put("redirect_uri", googleOAuthConfig.getGoogleRedirectUri()); + + + TokenRequestDto response = RestClient.create(GOOGLE_TOKEN_URI) + .post() + .contentType(APPLICATION_JSON) + .body(params) + .retrieve() + .body(TokenRequestDto.class); + + assert response != null; + return response.accessToken(); + } +} 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 fe4e6afa..f34a19c7 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 @@ -3,11 +3,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.layer.common.exception.BaseCustomException; -import org.layer.common.exception.MemberExceptionType; -import org.layer.domain.member.entity.SocialType; -import org.layer.oauth.config.OAuthConfig; -import org.layer.oauth.dto.service.KakaoAccountServiceResponse; -import org.layer.oauth.dto.service.KakaoGetMemberInfoServiceResponse; +import org.layer.oauth.config.KakaoOAuthConfig; +import org.layer.oauth.dto.service.kakao.KakaoGetMemberInfoServiceResponse; import org.layer.oauth.dto.service.MemberInfoServiceResponse; import org.springframework.http.HttpStatusCode; import org.springframework.stereotype.Service; @@ -19,16 +16,15 @@ import static org.layer.common.exception.MemberExceptionType.FAIL_TO_AUTH; import static org.layer.domain.member.entity.SocialType.KAKAO; -import static org.layer.oauth.config.OAuthConfig.*; +import static org.layer.oauth.config.KakaoOAuthConfig.*; @Slf4j @RequiredArgsConstructor @Service public class KakaoService { - private final OAuthConfig oAuthConfig; + private final KakaoOAuthConfig kakaoOAuthConfig; - // TODO: getMemberInfo 리턴 타입 수정 필요 public MemberInfoServiceResponse getMemberInfo(final String accessToken) { KakaoGetMemberInfoServiceResponse response = null; try { @@ -56,14 +52,14 @@ public String getToken(String code) { // 토큰 요청 데이터 MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", "authorization_code"); - params.add("client_id", oAuthConfig.getKakaoLoginApiKey()); - params.add("redirect_uri", oAuthConfig.getRedirectUri()); + params.add("client_id", kakaoOAuthConfig.getKakaoLoginApiKey()); + params.add("redirect_uri", kakaoOAuthConfig.getRedirectUri()); params.add("code", code); - Map response = RestClient.create(oAuthConfig.getKakaoAuthBaseUri()) + Map response = RestClient.create(kakaoOAuthConfig.getKakaoAuthBaseUri()) .post() - .uri(oAuthConfig.getTokenRequestUri()) + .uri(kakaoOAuthConfig.getTokenRequestUri()) .body(params) .header("Content-type", "application/x-www-form-urlencoded;charset=utf-8") //요청 헤더 .retrieve() From ce6317b870664bafcbff279ba33fffa7669b71b5 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Sun, 7 Jul 2024 20:26:28 +0900 Subject: [PATCH 05/12] =?UTF-8?q?chore:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/layer/auth/api/AuthController.java | 12 +-------- .../layer/oauth/service/GoogleService.java | 27 ------------------- .../org/layer/oauth/service/KakaoService.java | 21 --------------- 3 files changed, 1 insertion(+), 59 deletions(-) diff --git a/layer-api/src/main/java/org/layer/auth/api/AuthController.java b/layer-api/src/main/java/org/layer/auth/api/AuthController.java index 962cb6ec..86e1903b 100644 --- a/layer-api/src/main/java/org/layer/auth/api/AuthController.java +++ b/layer-api/src/main/java/org/layer/auth/api/AuthController.java @@ -65,15 +65,5 @@ public ResponseEntity reissueToken(@RequestBody Long membe ReissueTokenResponse.of(authService.reissueToken(memberId)), HttpStatus.CREATED); } - - -// //== 구글 테스트용 api ==// -// @GetMapping("oauth2/google") -// public String googleTest(@RequestParam("code") String code) { -// log.info("code : " + code); -// String token = googleService.getToken(code); -// return token; -// } - - + } 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 c21e5ab0..54b94063 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 @@ -49,31 +49,4 @@ public MemberInfoServiceResponse getMemberInfo(final String accessToken) { assert response != null; return new MemberInfoServiceResponse(response.id(), GOOGLE, response.email()); } - - - //== 코드로 액세스 토큰을 받는 메서드 TODO: 삭제하기==// - public String getToken(String code) { - log.info("redirect uri: {}", googleOAuthConfig.getGoogleRedirectUri()); - // 토큰 요청 데이터 - String uri = UriComponentsBuilder.fromOriginHeader(GOOGLE_TOKEN_URI) - .toUriString(); - - Map params = new LinkedHashMap<>(); - params.put("client_id", googleOAuthConfig.getGoogleClientId()); - params.put("client_secret", googleOAuthConfig.getGoogleClientSecret()); - params.put("code", code); - params.put("grant_type", "authorization_code"); - params.put("redirect_uri", googleOAuthConfig.getGoogleRedirectUri()); - - - TokenRequestDto response = RestClient.create(GOOGLE_TOKEN_URI) - .post() - .contentType(APPLICATION_JSON) - .body(params) - .retrieve() - .body(TokenRequestDto.class); - - assert response != null; - return response.accessToken(); - } } 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 f34a19c7..065c50ce 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 @@ -47,25 +47,4 @@ public MemberInfoServiceResponse getMemberInfo(final String accessToken) { return new MemberInfoServiceResponse(response.id(), KAKAO, response.kakao_account().email()); } - //== 이건 프론트에서..? TODO: 지우기 ==// - public String getToken(String code) { - // 토큰 요청 데이터 - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("grant_type", "authorization_code"); - params.add("client_id", kakaoOAuthConfig.getKakaoLoginApiKey()); - params.add("redirect_uri", kakaoOAuthConfig.getRedirectUri()); - params.add("code", code); - - - Map response = RestClient.create(kakaoOAuthConfig.getKakaoAuthBaseUri()) - .post() - .uri(kakaoOAuthConfig.getTokenRequestUri()) - .body(params) - .header("Content-type", "application/x-www-form-urlencoded;charset=utf-8") //요청 헤더 - .retrieve() - .body(Map.class); - - assert response != null; - return (String) response.get("access_token"); - } } From 432af76e941d6f223ebd2d9c5b9a8f4f16acfc48 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Sun, 7 Jul 2024 20:43:13 +0900 Subject: [PATCH 06/12] =?UTF-8?q?chore:=20TokenException,=20AuthException?= =?UTF-8?q?=20->=20BaseCustomException=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layer/auth/exception/AuthException.java | 5 ---- .../auth/exception/AuthExceptionType.java | 29 ++++++++++++++++++ .../layer/auth/exception/TokenException.java | 5 ---- .../auth/exception/TokenExceptionType.java | 30 +++++++++++++++++++ .../java/org/layer/auth/jwt/JwtValidator.java | 24 +++++++++++---- .../org/layer/auth/service/AuthService.java | 6 ++-- .../org/layer/auth/service/JwtService.java | 7 +++-- .../common/exception/MemberExceptionType.java | 1 - 8 files changed, 85 insertions(+), 22 deletions(-) delete mode 100644 layer-api/src/main/java/org/layer/auth/exception/AuthException.java create mode 100644 layer-api/src/main/java/org/layer/auth/exception/AuthExceptionType.java delete mode 100644 layer-api/src/main/java/org/layer/auth/exception/TokenException.java create mode 100644 layer-api/src/main/java/org/layer/auth/exception/TokenExceptionType.java diff --git a/layer-api/src/main/java/org/layer/auth/exception/AuthException.java b/layer-api/src/main/java/org/layer/auth/exception/AuthException.java deleted file mode 100644 index d3e21b9c..00000000 --- a/layer-api/src/main/java/org/layer/auth/exception/AuthException.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.layer.auth.exception; - -// OAuth를 위해 임시로 만들어놓은 Exception입니다. -public class AuthException extends RuntimeException { -} diff --git a/layer-api/src/main/java/org/layer/auth/exception/AuthExceptionType.java b/layer-api/src/main/java/org/layer/auth/exception/AuthExceptionType.java new file mode 100644 index 00000000..aae3f623 --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/exception/AuthExceptionType.java @@ -0,0 +1,29 @@ +package org.layer.auth.exception; + +import lombok.RequiredArgsConstructor; +import org.layer.common.exception.ExceptionType; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum AuthExceptionType implements ExceptionType { + + /** + * 400 + */ + INVALID_SOCIAL_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 방식의 로그인입니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, "권한이 없습니다."); + + + private final HttpStatus status; + private final String message; + + @Override + public HttpStatus httpStatus() { + return status; + } + + @Override + public String message() { + return message; + } +} diff --git a/layer-api/src/main/java/org/layer/auth/exception/TokenException.java b/layer-api/src/main/java/org/layer/auth/exception/TokenException.java deleted file mode 100644 index 62e1e27d..00000000 --- a/layer-api/src/main/java/org/layer/auth/exception/TokenException.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.layer.auth.exception; - -// 임시로 만들어놓은 Exception입니다 -public class TokenException extends RuntimeException { -} diff --git a/layer-api/src/main/java/org/layer/auth/exception/TokenExceptionType.java b/layer-api/src/main/java/org/layer/auth/exception/TokenExceptionType.java new file mode 100644 index 00000000..65f5bcfb --- /dev/null +++ b/layer-api/src/main/java/org/layer/auth/exception/TokenExceptionType.java @@ -0,0 +1,30 @@ +package org.layer.auth.exception; + +import lombok.RequiredArgsConstructor; +import org.layer.common.exception.ExceptionType; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum TokenExceptionType implements ExceptionType { + + /** + * 400 + */ + INVALID_TOKEN(HttpStatus.BAD_REQUEST, "token이 유효하지 않습니다."), + INVALID_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "refresh token이 유효하지 않습니다."), + INVALID_ACCESS_TOKEN(HttpStatus.BAD_REQUEST, "access token이 유효하지 않습니다."); + + + private final HttpStatus status; + private final String message; + + @Override + public HttpStatus httpStatus() { + return status; + } + + @Override + public String message() { + return message; + } +} diff --git a/layer-api/src/main/java/org/layer/auth/jwt/JwtValidator.java b/layer-api/src/main/java/org/layer/auth/jwt/JwtValidator.java index 099c8e23..1673483c 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/JwtValidator.java +++ b/layer-api/src/main/java/org/layer/auth/jwt/JwtValidator.java @@ -4,12 +4,14 @@ import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.layer.auth.exception.TokenException; +import org.layer.auth.exception.TokenExceptionType; +import org.layer.common.exception.BaseCustomException; import org.springframework.stereotype.Component; import java.util.LinkedHashMap; import java.util.List; +import static org.layer.auth.exception.TokenExceptionType.*; import static org.layer.auth.jwt.JwtValidationType.*; @Slf4j @@ -22,18 +24,30 @@ public JwtValidationType validateToken(String token) { try { getClaims(token); return VALID_JWT; - } catch(TokenException e) { + } catch(Exception e) { return INVALID_JWT; } } public long getMemberIdFromToken(String token) { - Claims claims = getClaims(token); + Claims claims; + try { + claims = getClaims(token); + } catch(Exception e) { + throw new BaseCustomException(INVALID_TOKEN); + } + return Long.parseLong(claims.get("memberId").toString()); } - public List getRoleFromToken(String token) throws TokenException { - Claims claims = getClaims(token); + public List getRoleFromToken(String token) { + Claims claims; + try { + claims = getClaims(token); + } catch(Exception e) { + throw new BaseCustomException(INVALID_TOKEN); + } + return (List) (claims.get("role")); } diff --git a/layer-api/src/main/java/org/layer/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/auth/service/AuthService.java index 0ca677c4..70060df2 100644 --- a/layer-api/src/main/java/org/layer/auth/service/AuthService.java +++ b/layer-api/src/main/java/org/layer/auth/service/AuthService.java @@ -6,7 +6,7 @@ import org.layer.auth.dto.service.ReissueTokenServiceResponse; import org.layer.auth.dto.service.SignInServiceResponse; import org.layer.auth.dto.service.SignUpServiceResponse; -import org.layer.auth.exception.AuthException; +import org.layer.auth.exception.AuthExceptionType; import org.layer.auth.jwt.JwtToken; import org.layer.common.exception.BaseCustomException; import org.layer.domain.member.entity.Member; @@ -19,7 +19,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static org.layer.common.exception.MemberExceptionType.FORBIDDEN; +import static org.layer.auth.exception.AuthExceptionType.*; @Slf4j @RequiredArgsConstructor @@ -91,7 +91,7 @@ private MemberInfoServiceResponse getMemberInfo(SocialType socialType, String so return switch (socialType) { case KAKAO -> kakaoService.getMemberInfo(socialAccessToken); case GOOGLE -> googleService.getMemberInfo(socialAccessToken); - default -> throw new AuthException(); + default -> throw new BaseCustomException(INVALID_SOCIAL_TYPE); }; } diff --git a/layer-api/src/main/java/org/layer/auth/service/JwtService.java b/layer-api/src/main/java/org/layer/auth/service/JwtService.java index 9190688d..74b1650a 100644 --- a/layer-api/src/main/java/org/layer/auth/service/JwtService.java +++ b/layer-api/src/main/java/org/layer/auth/service/JwtService.java @@ -1,8 +1,8 @@ package org.layer.auth.service; import lombok.RequiredArgsConstructor; -import org.layer.auth.exception.TokenException; import org.layer.auth.jwt.*; +import org.layer.common.exception.BaseCustomException; import org.layer.domain.member.entity.MemberRole; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -10,6 +10,7 @@ import java.time.Duration; import java.util.Objects; +import static org.layer.auth.exception.TokenExceptionType.INVALID_REFRESH_TOKEN; import static org.layer.config.AuthValueConfig.*; @RequiredArgsConstructor @@ -34,12 +35,12 @@ private void saveRefreshTokenToRedis(Long memberId, String refreshToken) { redisTemplate.opsForValue().set(memberId.toString(), refreshToken, Duration.ofDays(14)); } - private Long getMemberIdFromRefreshToken(String refreshToken) throws TokenException { + private Long getMemberIdFromRefreshToken(String refreshToken) { Long memberId = null; try { memberId = Long.parseLong((String) Objects.requireNonNull(redisTemplate.opsForValue().get(refreshToken))); } catch(Exception e) { - throw new TokenException(); + throw new BaseCustomException(INVALID_REFRESH_TOKEN); } return memberId; } 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 a38e88e1..29b67706 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 @@ -11,7 +11,6 @@ public enum MemberExceptionType implements ExceptionType { */ NOT_FOUND_USER(HttpStatus.NOT_FOUND, "유효한 유저를 찾지 못했습니다."), UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "로그인되지 않은 사용자입니다"), - FORBIDDEN(HttpStatus.FORBIDDEN, "권한이 없습니다."), FAIL_TO_AUTH(HttpStatus.BAD_REQUEST, "인증에 실패했습니다."), NOT_A_NEW_MEMBER(HttpStatus.BAD_REQUEST, "이미 가입된 회원입니다."); From 0ceba863efcb0e3646a710efd3d125527b5935ac Mon Sep 17 00:00:00 2001 From: clean2001 Date: Sun, 7 Jul 2024 23:02:44 +0900 Subject: [PATCH 07/12] =?UTF-8?q?chore:=20google=20oauth=20test=EC=9A=A9?= =?UTF-8?q?=20api=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/layer/auth/api/AuthController.java | 7 +++++- .../java/org/layer/config/SecurityConfig.java | 1 - .../layer/oauth/service/GoogleService.java | 25 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/layer-api/src/main/java/org/layer/auth/api/AuthController.java b/layer-api/src/main/java/org/layer/auth/api/AuthController.java index 86e1903b..59145126 100644 --- a/layer-api/src/main/java/org/layer/auth/api/AuthController.java +++ b/layer-api/src/main/java/org/layer/auth/api/AuthController.java @@ -65,5 +65,10 @@ public ResponseEntity reissueToken(@RequestBody Long membe ReissueTokenResponse.of(authService.reissueToken(memberId)), HttpStatus.CREATED); } - + + //== test용 API 액세스 토큰 발급 ==// + @GetMapping("oauth2/google") + public String googleTest(@RequestParam("code") String code) { + return googleService.getToken(code); + } } 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 5f84cdea..1335013b 100644 --- a/layer-api/src/main/java/org/layer/config/SecurityConfig.java +++ b/layer-api/src/main/java/org/layer/config/SecurityConfig.java @@ -38,7 +38,6 @@ private void setHttp(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-in")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/reissue-token")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-up")).permitAll() - .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/google/code")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/google")).permitAll() .anyRequest().authenticated() ); 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 54b94063..8a953c3f 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 @@ -49,4 +49,29 @@ public MemberInfoServiceResponse getMemberInfo(final String accessToken) { assert response != null; return new MemberInfoServiceResponse(response.id(), GOOGLE, response.email()); } + + public String getToken(String code) { + log.info("redirect uri: {}", googleOAuthConfig.getGoogleRedirectUri()); + // 토큰 요청 데이터 + String uri = UriComponentsBuilder.fromOriginHeader(GOOGLE_TOKEN_URI) + .toUriString(); + + Map params = new LinkedHashMap<>(); + params.put("client_id", googleOAuthConfig.getGoogleClientId()); + params.put("client_secret", googleOAuthConfig.getGoogleClientSecret()); + params.put("code", code); + params.put("grant_type", "authorization_code"); + params.put("redirect_uri", googleOAuthConfig.getGoogleRedirectUri()); + + + TokenRequestDto response = RestClient.create(GOOGLE_TOKEN_URI) + .post() + .contentType(APPLICATION_JSON) + .body(params) + .retrieve() + .body(TokenRequestDto.class); + + assert response != null; + return response.accessToken(); + } } From 6c2c589afa8b61d794ec2e24304b29ad018a6008 Mon Sep 17 00:00:00 2001 From: clean2001 Date: Sun, 7 Jul 2024 23:26:32 +0900 Subject: [PATCH 08/12] =?UTF-8?q?chore:=20SecurityConfig=20setHttp=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20kakao=20test=20api=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/layer/auth/api/AuthController.java | 15 ++++++++----- .../org/layer/auth/service/AuthService.java | 8 +++++++ .../org/layer/auth/service/JwtService.java | 1 + .../java/org/layer/config/SecurityConfig.java | 1 + .../oauth/dto/controller/TokenRequestDto.java | 6 ----- .../google/GoogleTokenServiceResponse.java | 6 +++++ .../layer/oauth/service/GoogleService.java | 7 +++--- .../org/layer/oauth/service/KakaoService.java | 22 +++++++++++++++++++ 8 files changed, 50 insertions(+), 16 deletions(-) delete mode 100644 layer-external/src/main/java/org/layer/oauth/dto/controller/TokenRequestDto.java create mode 100644 layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleTokenServiceResponse.java diff --git a/layer-api/src/main/java/org/layer/auth/api/AuthController.java b/layer-api/src/main/java/org/layer/auth/api/AuthController.java index 59145126..80823671 100644 --- a/layer-api/src/main/java/org/layer/auth/api/AuthController.java +++ b/layer-api/src/main/java/org/layer/auth/api/AuthController.java @@ -3,15 +3,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.layer.auth.dto.controller.*; -import org.layer.auth.dto.service.ReissueTokenServiceResponse; import org.layer.auth.dto.service.SignInServiceResponse; import org.layer.auth.dto.service.SignUpServiceResponse; -import org.layer.auth.jwt.JwtToken; import org.layer.auth.service.AuthService; -import org.layer.auth.service.JwtService; -import org.layer.domain.member.entity.MemberRole; -import org.layer.oauth.dto.controller.TokenRequestDto; import org.layer.oauth.service.GoogleService; import org.layer.oauth.service.KakaoService; import org.springframework.http.HttpStatus; @@ -28,6 +23,7 @@ public class AuthController { private final AuthService authService; private final GoogleService googleService; + private final KakaoService kakaoService; // 로그인 @PostMapping("/sign-in") @@ -66,9 +62,16 @@ public ResponseEntity reissueToken(@RequestBody Long membe HttpStatus.CREATED); } - //== test용 API 액세스 토큰 발급 ==// + //== google OAuth2 test용 API 액세스 토큰 발급 ==// @GetMapping("oauth2/google") public String googleTest(@RequestParam("code") String code) { return googleService.getToken(code); } + + //== kakao OAuth2 test용 API 액세스 토큰 발급 ==// + @GetMapping("oauth2/kakao") + public Object kakaoLogin(@RequestParam(value = "code", required = false) String code) { + return kakaoService.getToken(code); + } + } diff --git a/layer-api/src/main/java/org/layer/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/auth/service/AuthService.java index 70060df2..f7260170 100644 --- a/layer-api/src/main/java/org/layer/auth/service/AuthService.java +++ b/layer-api/src/main/java/org/layer/auth/service/AuthService.java @@ -13,11 +13,18 @@ import org.layer.domain.member.entity.SocialType; import org.layer.member.service.MemberService; import org.layer.member.service.MemberUtil; +import org.layer.oauth.config.KakaoOAuthConfig; import org.layer.oauth.dto.service.MemberInfoServiceResponse; import org.layer.oauth.service.GoogleService; import org.layer.oauth.service.KakaoService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.client.RestClient; + +import java.util.Map; import static org.layer.auth.exception.AuthExceptionType.*; @@ -109,4 +116,5 @@ private void isNewMember(SocialType socialType, String socialId) { memberService.checkIsNewMember(socialId, socialType); } + } diff --git a/layer-api/src/main/java/org/layer/auth/service/JwtService.java b/layer-api/src/main/java/org/layer/auth/service/JwtService.java index 74b1650a..1c2bcbd2 100644 --- a/layer-api/src/main/java/org/layer/auth/service/JwtService.java +++ b/layer-api/src/main/java/org/layer/auth/service/JwtService.java @@ -1,6 +1,7 @@ package org.layer.auth.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.layer.auth.jwt.*; import org.layer.common.exception.BaseCustomException; import org.layer.domain.member.entity.MemberRole; 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 1335013b..f01580e1 100644 --- a/layer-api/src/main/java/org/layer/config/SecurityConfig.java +++ b/layer-api/src/main/java/org/layer/config/SecurityConfig.java @@ -39,6 +39,7 @@ private void setHttp(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/api/auth/reissue-token")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-up")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/google")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/kakao")).permitAll() .anyRequest().authenticated() ); } diff --git a/layer-external/src/main/java/org/layer/oauth/dto/controller/TokenRequestDto.java b/layer-external/src/main/java/org/layer/oauth/dto/controller/TokenRequestDto.java deleted file mode 100644 index 6880753f..00000000 --- a/layer-external/src/main/java/org/layer/oauth/dto/controller/TokenRequestDto.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.layer.oauth.dto.controller; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public record TokenRequestDto(@JsonProperty("access_token") String accessToken) { -} diff --git a/layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleTokenServiceResponse.java b/layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleTokenServiceResponse.java new file mode 100644 index 00000000..937a25e2 --- /dev/null +++ b/layer-external/src/main/java/org/layer/oauth/dto/service/google/GoogleTokenServiceResponse.java @@ -0,0 +1,6 @@ +package org.layer.oauth.dto.service.google; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record GoogleTokenServiceResponse(@JsonProperty("access_token") String accessToken) { +} 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 8a953c3f..15d3619f 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 @@ -4,10 +4,9 @@ import lombok.extern.slf4j.Slf4j; import org.layer.common.exception.BaseCustomException; import org.layer.oauth.config.GoogleOAuthConfig; -import org.layer.oauth.dto.controller.TokenRequestDto; +import org.layer.oauth.dto.service.google.GoogleTokenServiceResponse; import org.layer.oauth.dto.service.MemberInfoServiceResponse; import org.layer.oauth.dto.service.google.GoogleGetMemberInfoServiceResponse; -import org.layer.oauth.dto.service.kakao.KakaoGetMemberInfoServiceResponse; import org.springframework.http.HttpStatusCode; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; @@ -64,12 +63,12 @@ public String getToken(String code) { params.put("redirect_uri", googleOAuthConfig.getGoogleRedirectUri()); - TokenRequestDto response = RestClient.create(GOOGLE_TOKEN_URI) + GoogleTokenServiceResponse response = RestClient.create(GOOGLE_TOKEN_URI) .post() .contentType(APPLICATION_JSON) .body(params) .retrieve() - .body(TokenRequestDto.class); + .body(GoogleTokenServiceResponse.class); assert response != null; return response.accessToken(); 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 065c50ce..1d7a696c 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 @@ -47,4 +47,26 @@ public MemberInfoServiceResponse getMemberInfo(final String accessToken) { return new MemberInfoServiceResponse(response.id(), KAKAO, response.kakao_account().email()); } + //== 프론트에서 처리해주는 부분 TODO: 추후 삭제하기 ==// + public String getToken(String code) { + // 토큰 요청 데이터 + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "authorization_code"); + params.add("client_id", kakaoOAuthConfig.getKakaoLoginApiKey()); + params.add("redirect_uri", kakaoOAuthConfig.getRedirectUri()); + params.add("code", code); + + + Map response = RestClient.create(kakaoOAuthConfig.getKakaoAuthBaseUri()) + .post() + .uri(kakaoOAuthConfig.getTokenRequestUri()) + .body(params) + .header("Content-type", "application/x-www-form-urlencoded;charset=utf-8") //요청 헤더 + .retrieve() + .body(Map.class); + + assert response != null; + return (String) response.get("access_token"); + } + } From c2b7cab7f75d7a07ab51f875ccf15e31da0a7c91 Mon Sep 17 00:00:00 2001 From: Raymond Date: Mon, 8 Jul 2024 03:38:02 +0900 Subject: [PATCH 09/12] =?UTF-8?q?refactor:=20=EB=AA=A8=EB=93=88=EC=9C=84?= =?UTF-8?q?=EC=B9=98,=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++ .../layer/api/controller/HelloController.java | 14 ----- .../java/org/layer/auth/jwt/JwtToken.java | 16 ----- .../exception/GlobalExceptionHandler.java | 2 - .../java/org/layer/config/SecurityConfig.java | 6 +- .../java/org/layer/config/SwaggerConfig.java | 60 +++++++++++++++++++ .../main/java/org/layer/config/WebConfig.java | 41 +++++++++++++ .../auth/controller}/AuthController.java | 23 +++---- .../controller/dto}/ReissueTokenResponse.java | 4 +- .../auth/controller/dto}/SignInRequest.java | 2 +- .../auth/controller/dto}/SignInResponse.java | 4 +- .../auth/controller/dto}/SignUpRequest.java | 2 +- .../auth/controller/dto}/SignUpResponse.java | 5 +- .../auth/exception/AuthException.java | 2 +- .../auth/exception/TokenException.java | 2 +- .../auth/service/AuthService.java | 19 +++--- .../dto}/ReissueTokenServiceResponse.java | 4 +- .../service/dto}/SignInServiceResponse.java | 4 +- .../service/dto}/SignUpServiceResponse.java | 2 +- .../jwt/JwtAuthenticationFilter.java | 2 +- .../{auth => domain}/jwt/JwtProvider.java | 2 +- .../java/org/layer/domain/jwt/JwtToken.java | 13 ++++ .../jwt/JwtValidationType.java | 4 +- .../{auth => domain}/jwt/JwtValidator.java | 11 ++-- .../jwt/MemberAuthentication.java | 2 +- .../{auth => domain}/jwt/RefreshToken.java | 2 +- .../jwt/SecretKeyFactory.java | 9 +-- .../{auth => domain}/jwt/SecurityUtil.java | 4 +- .../jwt}/service/JwtService.java | 11 ++-- .../member/service/MemberService.java | 4 +- .../member/service/MemberUtil.java | 7 +-- .../org/layer/domain/space/entity/Space.java | 5 ++ 32 files changed, 183 insertions(+), 109 deletions(-) delete mode 100644 layer-api/src/main/java/org/layer/api/controller/HelloController.java delete mode 100644 layer-api/src/main/java/org/layer/auth/jwt/JwtToken.java create mode 100644 layer-api/src/main/java/org/layer/config/SwaggerConfig.java create mode 100644 layer-api/src/main/java/org/layer/config/WebConfig.java rename layer-api/src/main/java/org/layer/{auth/api => domain/auth/controller}/AuthController.java (74%) rename layer-api/src/main/java/org/layer/{auth/dto/controller => domain/auth/controller/dto}/ReissueTokenResponse.java (74%) rename layer-api/src/main/java/org/layer/{auth/dto/controller => domain/auth/controller/dto}/SignInRequest.java (80%) rename layer-api/src/main/java/org/layer/{auth/dto/controller => domain/auth/controller/dto}/SignInResponse.java (81%) rename layer-api/src/main/java/org/layer/{auth/dto/controller => domain/auth/controller/dto}/SignUpRequest.java (86%) rename layer-api/src/main/java/org/layer/{auth/dto/controller => domain/auth/controller/dto}/SignUpResponse.java (85%) rename layer-api/src/main/java/org/layer/{ => domain}/auth/exception/AuthException.java (74%) rename layer-api/src/main/java/org/layer/{ => domain}/auth/exception/TokenException.java (72%) rename layer-api/src/main/java/org/layer/{ => domain}/auth/service/AuthService.java (88%) rename layer-api/src/main/java/org/layer/{auth/dto/service => domain/auth/service/dto}/ReissueTokenServiceResponse.java (78%) rename layer-api/src/main/java/org/layer/{auth/dto/service => domain/auth/service/dto}/SignInServiceResponse.java (85%) rename layer-api/src/main/java/org/layer/{auth/dto/service => domain/auth/service/dto}/SignUpServiceResponse.java (94%) rename layer-api/src/main/java/org/layer/{auth => domain}/jwt/JwtAuthenticationFilter.java (98%) rename layer-api/src/main/java/org/layer/{auth => domain}/jwt/JwtProvider.java (96%) create mode 100644 layer-api/src/main/java/org/layer/domain/jwt/JwtToken.java rename layer-api/src/main/java/org/layer/{auth => domain}/jwt/JwtValidationType.java (52%) rename layer-api/src/main/java/org/layer/{auth => domain}/jwt/JwtValidator.java (82%) rename layer-api/src/main/java/org/layer/{auth => domain}/jwt/MemberAuthentication.java (96%) rename layer-api/src/main/java/org/layer/{auth => domain}/jwt/RefreshToken.java (92%) rename layer-api/src/main/java/org/layer/{auth => domain}/jwt/SecretKeyFactory.java (76%) rename layer-api/src/main/java/org/layer/{auth => domain}/jwt/SecurityUtil.java (83%) rename layer-api/src/main/java/org/layer/{auth => domain/jwt}/service/JwtService.java (81%) rename layer-api/src/main/java/org/layer/{ => domain}/member/service/MemberService.java (95%) rename layer-api/src/main/java/org/layer/{ => domain}/member/service/MemberUtil.java (81%) diff --git a/build.gradle b/build.gradle index 547f5ac0..f9568545 100644 --- a/build.gradle +++ b/build.gradle @@ -81,6 +81,9 @@ project(":layer-api") { // jpa implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + //Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + runtimeOnly 'com.mysql:mysql-connector-j' } @@ -94,6 +97,7 @@ project(":layer-common") { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' } } diff --git a/layer-api/src/main/java/org/layer/api/controller/HelloController.java b/layer-api/src/main/java/org/layer/api/controller/HelloController.java deleted file mode 100644 index d716d63c..00000000 --- a/layer-api/src/main/java/org/layer/api/controller/HelloController.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.layer.api.controller; - -import lombok.RequiredArgsConstructor; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/test") -public class HelloController { - -} diff --git a/layer-api/src/main/java/org/layer/auth/jwt/JwtToken.java b/layer-api/src/main/java/org/layer/auth/jwt/JwtToken.java deleted file mode 100644 index f9888165..00000000 --- a/layer-api/src/main/java/org/layer/auth/jwt/JwtToken.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.layer.auth.jwt; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class JwtToken { - private final String accessToken; - private final String refreshToken; - - @Builder - public JwtToken(String accessToken, String refreshToken) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - } -} \ No newline at end of file diff --git a/layer-api/src/main/java/org/layer/common/exception/GlobalExceptionHandler.java b/layer-api/src/main/java/org/layer/common/exception/GlobalExceptionHandler.java index b4eb496d..d94ec47e 100644 --- a/layer-api/src/main/java/org/layer/common/exception/GlobalExceptionHandler.java +++ b/layer-api/src/main/java/org/layer/common/exception/GlobalExceptionHandler.java @@ -1,8 +1,6 @@ package org.layer.common.exception; import lombok.extern.slf4j.Slf4j; -import org.layer.common.exception.BaseCustomException; -import org.layer.common.exception.ExceptionType; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; 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 5f84cdea..4da930a9 100644 --- a/layer-api/src/main/java/org/layer/config/SecurityConfig.java +++ b/layer-api/src/main/java/org/layer/config/SecurityConfig.java @@ -2,7 +2,7 @@ import lombok.RequiredArgsConstructor; -import org.layer.auth.jwt.JwtAuthenticationFilter; +import org.layer.domain.jwt.JwtAuthenticationFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -40,6 +40,7 @@ private void setHttp(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/api/auth/sign-up")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/google/code")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/auth/oauth2/google")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/api/test")).permitAll() .anyRequest().authenticated() ); } @@ -51,7 +52,4 @@ private void permitSwaggerUri(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/swagger-ui/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/docs/**")).permitAll()); } - - - } diff --git a/layer-api/src/main/java/org/layer/config/SwaggerConfig.java b/layer-api/src/main/java/org/layer/config/SwaggerConfig.java new file mode 100644 index 00000000..ce007325 --- /dev/null +++ b/layer-api/src/main/java/org/layer/config/SwaggerConfig.java @@ -0,0 +1,60 @@ +package org.layer.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.layer.common.annotation.MemberId; +import org.springdoc.core.customizers.OperationCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.HandlerMethod; + +import java.util.Arrays; + +@Configuration +public class SwaggerConfig { + + SecurityScheme apiAuth = new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("authorization-token"); + + SecurityRequirement addSecurityItem = new SecurityRequirement() + .addList("authorization-token"); + + @Bean + public OpenAPI openAPI(){ + var info = new Info(); + info.title("Layer API"); + info.description("Layer API 문서에요."); + info.contact( + new Contact() + .email("teamkb.dpm@gmail.com") + .name("떡잎마을방범대") + ); + info.license(new License().name("MIT")); + return new OpenAPI() + .components(new Components() + .addSecuritySchemes("authorization-token", apiAuth) + ) + .addSecurityItem(addSecurityItem) + .info(info); + } + + @Bean + public OperationCustomizer customizeOperation() { + return (operation, handlerMethod) -> { + HandlerMethod method = (HandlerMethod) handlerMethod; + method.getMethodParameters(); + method.getMethodParameters(); + if (Arrays.stream(method.getMethodParameters()).anyMatch(param -> param.hasParameterAnnotation(MemberId.class))) { + operation.getParameters().removeIf(param -> "memberId".equals(param.getName())); + } + return operation; + }; + } +} diff --git a/layer-api/src/main/java/org/layer/config/WebConfig.java b/layer-api/src/main/java/org/layer/config/WebConfig.java new file mode 100644 index 00000000..e573e066 --- /dev/null +++ b/layer-api/src/main/java/org/layer/config/WebConfig.java @@ -0,0 +1,41 @@ +package org.layer.config; + +import lombok.RequiredArgsConstructor; +import org.layer.resolver.MemberIdResolver; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + @Value("${webmvc.cors.allowedOrigins}") + private String allowedOrigins; + + private final MemberIdResolver memberIdResolver; + + + @Bean + public WebMvcConfigurer corsConfigurer(){ + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins(allowedOrigins.split(",")) + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); + } + }; + } + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(memberIdResolver); + } +} diff --git a/layer-api/src/main/java/org/layer/auth/api/AuthController.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java similarity index 74% rename from layer-api/src/main/java/org/layer/auth/api/AuthController.java rename to layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java index 962cb6ec..079c5d91 100644 --- a/layer-api/src/main/java/org/layer/auth/api/AuthController.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java @@ -1,25 +1,15 @@ -package org.layer.auth.api; +package org.layer.domain.auth.controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.layer.auth.dto.controller.*; -import org.layer.auth.dto.service.ReissueTokenServiceResponse; -import org.layer.auth.dto.service.SignInServiceResponse; -import org.layer.auth.dto.service.SignUpServiceResponse; -import org.layer.auth.jwt.JwtToken; -import org.layer.auth.service.AuthService; -import org.layer.auth.service.JwtService; -import org.layer.domain.member.entity.MemberRole; - -import org.layer.oauth.dto.controller.TokenRequestDto; +import org.layer.domain.auth.controller.dto.*; +import org.layer.domain.auth.service.AuthService; +import org.layer.domain.auth.service.dto.SignInServiceResponse; +import org.layer.domain.auth.service.dto.SignUpServiceResponse; import org.layer.oauth.service.GoogleService; -import org.layer.oauth.service.KakaoService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; @Slf4j @RequiredArgsConstructor @@ -38,7 +28,8 @@ public ResponseEntity signIn(@RequestHeader("Authorization") fin // 회원가입 => 소셜로그인 했는데 유효한 유저가 없을 때 이름 입력하고 회원가입하는 과정 @PostMapping("/sign-up") - public ResponseEntity signUp(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignUpRequest signUpRequest) { + public ResponseEntity signUp(@RequestHeader(value = "authorizatio") final String socialAccessToken, @RequestBody final SignUpRequest signUpRequest) { + log.info("{} <<< socialAccessToken",socialAccessToken); SignUpServiceResponse signUpServiceResponse = authService.signUp(socialAccessToken, signUpRequest); return new ResponseEntity<>(SignUpResponse.of(signUpServiceResponse), HttpStatus.CREATED); } diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/ReissueTokenResponse.java b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/ReissueTokenResponse.java similarity index 74% rename from layer-api/src/main/java/org/layer/auth/dto/controller/ReissueTokenResponse.java rename to layer-api/src/main/java/org/layer/domain/auth/controller/dto/ReissueTokenResponse.java index 65d573ee..220a593b 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/controller/ReissueTokenResponse.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/ReissueTokenResponse.java @@ -1,6 +1,6 @@ -package org.layer.auth.dto.controller; +package org.layer.domain.auth.controller.dto; -import org.layer.auth.dto.service.ReissueTokenServiceResponse; +import org.layer.domain.auth.service.dto.ReissueTokenServiceResponse; public record ReissueTokenResponse(Long memberId, String accessToken, String refreshToken){ public static ReissueTokenResponse of(ReissueTokenServiceResponse rtsr) { diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/SignInRequest.java b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignInRequest.java similarity index 80% rename from layer-api/src/main/java/org/layer/auth/dto/controller/SignInRequest.java rename to layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignInRequest.java index 8b1750b2..2f6f149d 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/controller/SignInRequest.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignInRequest.java @@ -1,4 +1,4 @@ -package org.layer.auth.dto.controller; +package org.layer.domain.auth.controller.dto; import com.fasterxml.jackson.annotation.JsonProperty; import org.layer.domain.member.entity.SocialType; diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignInResponse.java similarity index 81% rename from layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java rename to layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignInResponse.java index f87586db..157fe559 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/controller/SignInResponse.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignInResponse.java @@ -1,6 +1,6 @@ -package org.layer.auth.dto.controller; +package org.layer.domain.auth.controller.dto; -import org.layer.auth.dto.service.SignInServiceResponse; +import org.layer.domain.auth.service.dto.SignInServiceResponse; import org.layer.domain.member.entity.MemberRole; diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpRequest.java b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignUpRequest.java similarity index 86% rename from layer-api/src/main/java/org/layer/auth/dto/controller/SignUpRequest.java rename to layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignUpRequest.java index e87f2599..dabb82b6 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpRequest.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignUpRequest.java @@ -1,4 +1,4 @@ -package org.layer.auth.dto.controller; +package org.layer.domain.auth.controller.dto; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; diff --git a/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpResponse.java b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignUpResponse.java similarity index 85% rename from layer-api/src/main/java/org/layer/auth/dto/controller/SignUpResponse.java rename to layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignUpResponse.java index cba99f51..abba1bf2 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/controller/SignUpResponse.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/dto/SignUpResponse.java @@ -1,7 +1,6 @@ -package org.layer.auth.dto.controller; +package org.layer.domain.auth.controller.dto; -import org.layer.auth.dto.service.SignUpServiceResponse; -import org.layer.domain.member.entity.Member; +import org.layer.domain.auth.service.dto.SignUpServiceResponse; import org.layer.domain.member.entity.MemberRole; import org.layer.domain.member.entity.SocialType; diff --git a/layer-api/src/main/java/org/layer/auth/exception/AuthException.java b/layer-api/src/main/java/org/layer/domain/auth/exception/AuthException.java similarity index 74% rename from layer-api/src/main/java/org/layer/auth/exception/AuthException.java rename to layer-api/src/main/java/org/layer/domain/auth/exception/AuthException.java index d3e21b9c..444bea52 100644 --- a/layer-api/src/main/java/org/layer/auth/exception/AuthException.java +++ b/layer-api/src/main/java/org/layer/domain/auth/exception/AuthException.java @@ -1,4 +1,4 @@ -package org.layer.auth.exception; +package org.layer.domain.auth.exception; // OAuth를 위해 임시로 만들어놓은 Exception입니다. public class AuthException extends RuntimeException { diff --git a/layer-api/src/main/java/org/layer/auth/exception/TokenException.java b/layer-api/src/main/java/org/layer/domain/auth/exception/TokenException.java similarity index 72% rename from layer-api/src/main/java/org/layer/auth/exception/TokenException.java rename to layer-api/src/main/java/org/layer/domain/auth/exception/TokenException.java index 62e1e27d..cbd72038 100644 --- a/layer-api/src/main/java/org/layer/auth/exception/TokenException.java +++ b/layer-api/src/main/java/org/layer/domain/auth/exception/TokenException.java @@ -1,4 +1,4 @@ -package org.layer.auth.exception; +package org.layer.domain.auth.exception; // 임시로 만들어놓은 Exception입니다 public class TokenException extends RuntimeException { diff --git a/layer-api/src/main/java/org/layer/auth/service/AuthService.java b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java similarity index 88% rename from layer-api/src/main/java/org/layer/auth/service/AuthService.java rename to layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java index 0ca677c4..27d7b306 100644 --- a/layer-api/src/main/java/org/layer/auth/service/AuthService.java +++ b/layer-api/src/main/java/org/layer/domain/auth/service/AuthService.java @@ -1,18 +1,19 @@ -package org.layer.auth.service; +package org.layer.domain.auth.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.layer.auth.dto.controller.SignUpRequest; -import org.layer.auth.dto.service.ReissueTokenServiceResponse; -import org.layer.auth.dto.service.SignInServiceResponse; -import org.layer.auth.dto.service.SignUpServiceResponse; -import org.layer.auth.exception.AuthException; -import org.layer.auth.jwt.JwtToken; import org.layer.common.exception.BaseCustomException; +import org.layer.domain.auth.controller.dto.SignUpRequest; +import org.layer.domain.auth.exception.AuthException; +import org.layer.domain.auth.service.dto.ReissueTokenServiceResponse; +import org.layer.domain.auth.service.dto.SignInServiceResponse; +import org.layer.domain.auth.service.dto.SignUpServiceResponse; +import org.layer.domain.jwt.JwtToken; +import org.layer.domain.jwt.service.JwtService; import org.layer.domain.member.entity.Member; import org.layer.domain.member.entity.SocialType; -import org.layer.member.service.MemberService; -import org.layer.member.service.MemberUtil; +import org.layer.domain.member.service.MemberService; +import org.layer.domain.member.service.MemberUtil; import org.layer.oauth.dto.service.MemberInfoServiceResponse; import org.layer.oauth.service.GoogleService; import org.layer.oauth.service.KakaoService; diff --git a/layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java b/layer-api/src/main/java/org/layer/domain/auth/service/dto/ReissueTokenServiceResponse.java similarity index 78% rename from layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java rename to layer-api/src/main/java/org/layer/domain/auth/service/dto/ReissueTokenServiceResponse.java index 148f677b..7c4f26c1 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/service/ReissueTokenServiceResponse.java +++ b/layer-api/src/main/java/org/layer/domain/auth/service/dto/ReissueTokenServiceResponse.java @@ -1,6 +1,6 @@ -package org.layer.auth.dto.service; +package org.layer.domain.auth.service.dto; -import org.layer.auth.jwt.JwtToken; +import org.layer.domain.jwt.JwtToken; import org.layer.domain.member.entity.Member; public record ReissueTokenServiceResponse(Long memberId, JwtToken jwtToken) { diff --git a/layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java b/layer-api/src/main/java/org/layer/domain/auth/service/dto/SignInServiceResponse.java similarity index 85% rename from layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java rename to layer-api/src/main/java/org/layer/domain/auth/service/dto/SignInServiceResponse.java index abf973aa..37b5da9f 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/service/SignInServiceResponse.java +++ b/layer-api/src/main/java/org/layer/domain/auth/service/dto/SignInServiceResponse.java @@ -1,6 +1,6 @@ -package org.layer.auth.dto.service; +package org.layer.domain.auth.service.dto; -import org.layer.auth.jwt.JwtToken; +import org.layer.domain.jwt.JwtToken; import org.layer.domain.member.entity.Member; import org.layer.domain.member.entity.MemberRole; diff --git a/layer-api/src/main/java/org/layer/auth/dto/service/SignUpServiceResponse.java b/layer-api/src/main/java/org/layer/domain/auth/service/dto/SignUpServiceResponse.java similarity index 94% rename from layer-api/src/main/java/org/layer/auth/dto/service/SignUpServiceResponse.java rename to layer-api/src/main/java/org/layer/domain/auth/service/dto/SignUpServiceResponse.java index f169f209..d32a3d39 100644 --- a/layer-api/src/main/java/org/layer/auth/dto/service/SignUpServiceResponse.java +++ b/layer-api/src/main/java/org/layer/domain/auth/service/dto/SignUpServiceResponse.java @@ -1,4 +1,4 @@ -package org.layer.auth.dto.service; +package org.layer.domain.auth.service.dto; import org.layer.domain.member.entity.Member; import org.layer.domain.member.entity.MemberRole; diff --git a/layer-api/src/main/java/org/layer/auth/jwt/JwtAuthenticationFilter.java b/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java similarity index 98% rename from layer-api/src/main/java/org/layer/auth/jwt/JwtAuthenticationFilter.java rename to layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java index 8f510632..7ad7c959 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/JwtAuthenticationFilter.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/JwtAuthenticationFilter.java @@ -1,4 +1,4 @@ -package org.layer.auth.jwt; +package org.layer.domain.jwt; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; diff --git a/layer-api/src/main/java/org/layer/auth/jwt/JwtProvider.java b/layer-api/src/main/java/org/layer/domain/jwt/JwtProvider.java similarity index 96% rename from layer-api/src/main/java/org/layer/auth/jwt/JwtProvider.java rename to layer-api/src/main/java/org/layer/domain/jwt/JwtProvider.java index f8283da1..5f1848e8 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/JwtProvider.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/JwtProvider.java @@ -1,4 +1,4 @@ -package org.layer.auth.jwt; +package org.layer.domain.jwt; import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; diff --git a/layer-api/src/main/java/org/layer/domain/jwt/JwtToken.java b/layer-api/src/main/java/org/layer/domain/jwt/JwtToken.java new file mode 100644 index 00000000..33554177 --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/jwt/JwtToken.java @@ -0,0 +1,13 @@ +package org.layer.domain.jwt; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class JwtToken { + private final String accessToken; + private final String refreshToken; +} \ No newline at end of file diff --git a/layer-api/src/main/java/org/layer/auth/jwt/JwtValidationType.java b/layer-api/src/main/java/org/layer/domain/jwt/JwtValidationType.java similarity index 52% rename from layer-api/src/main/java/org/layer/auth/jwt/JwtValidationType.java rename to layer-api/src/main/java/org/layer/domain/jwt/JwtValidationType.java index 55369baa..8307defb 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/JwtValidationType.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/JwtValidationType.java @@ -1,7 +1,7 @@ -package org.layer.auth.jwt; +package org.layer.domain.jwt; public enum JwtValidationType { VALID_JWT, - INVALID_JWT; + INVALID_JWT } diff --git a/layer-api/src/main/java/org/layer/auth/jwt/JwtValidator.java b/layer-api/src/main/java/org/layer/domain/jwt/JwtValidator.java similarity index 82% rename from layer-api/src/main/java/org/layer/auth/jwt/JwtValidator.java rename to layer-api/src/main/java/org/layer/domain/jwt/JwtValidator.java index 099c8e23..f7b8a66f 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/JwtValidator.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/JwtValidator.java @@ -1,17 +1,14 @@ -package org.layer.auth.jwt; +package org.layer.domain.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.layer.auth.exception.TokenException; +import org.layer.domain.auth.exception.TokenException; import org.springframework.stereotype.Component; -import java.util.LinkedHashMap; import java.util.List; -import static org.layer.auth.jwt.JwtValidationType.*; - @Slf4j @RequiredArgsConstructor @Component @@ -21,9 +18,9 @@ public class JwtValidator { public JwtValidationType validateToken(String token) { try { getClaims(token); - return VALID_JWT; + return JwtValidationType.VALID_JWT; } catch(TokenException e) { - return INVALID_JWT; + return JwtValidationType.INVALID_JWT; } } diff --git a/layer-api/src/main/java/org/layer/auth/jwt/MemberAuthentication.java b/layer-api/src/main/java/org/layer/domain/jwt/MemberAuthentication.java similarity index 96% rename from layer-api/src/main/java/org/layer/auth/jwt/MemberAuthentication.java rename to layer-api/src/main/java/org/layer/domain/jwt/MemberAuthentication.java index 8d07eda4..4d8fb4c0 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/MemberAuthentication.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/MemberAuthentication.java @@ -1,4 +1,4 @@ -package org.layer.auth.jwt; +package org.layer.domain.jwt; import lombok.Builder; import org.layer.domain.member.entity.MemberRole; diff --git a/layer-api/src/main/java/org/layer/auth/jwt/RefreshToken.java b/layer-api/src/main/java/org/layer/domain/jwt/RefreshToken.java similarity index 92% rename from layer-api/src/main/java/org/layer/auth/jwt/RefreshToken.java rename to layer-api/src/main/java/org/layer/domain/jwt/RefreshToken.java index d66e9749..073d3f29 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/RefreshToken.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/RefreshToken.java @@ -1,4 +1,4 @@ -package org.layer.auth.jwt; +package org.layer.domain.jwt; import lombok.Builder; import lombok.Getter; diff --git a/layer-api/src/main/java/org/layer/auth/jwt/SecretKeyFactory.java b/layer-api/src/main/java/org/layer/domain/jwt/SecretKeyFactory.java similarity index 76% rename from layer-api/src/main/java/org/layer/auth/jwt/SecretKeyFactory.java rename to layer-api/src/main/java/org/layer/domain/jwt/SecretKeyFactory.java index e6483470..56f4627d 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/SecretKeyFactory.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/SecretKeyFactory.java @@ -1,15 +1,12 @@ -package org.layer.auth.jwt; +package org.layer.domain.jwt; import io.jsonwebtoken.security.Keys; -import java.util.Base64; -import javax.crypto.SecretKey; - import lombok.RequiredArgsConstructor; import org.layer.config.AuthValueConfig; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; +import javax.crypto.SecretKey; + import static java.util.Base64.getEncoder; @RequiredArgsConstructor diff --git a/layer-api/src/main/java/org/layer/auth/jwt/SecurityUtil.java b/layer-api/src/main/java/org/layer/domain/jwt/SecurityUtil.java similarity index 83% rename from layer-api/src/main/java/org/layer/auth/jwt/SecurityUtil.java rename to layer-api/src/main/java/org/layer/domain/jwt/SecurityUtil.java index c69e277c..2b92e1e7 100644 --- a/layer-api/src/main/java/org/layer/auth/jwt/SecurityUtil.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/SecurityUtil.java @@ -1,8 +1,6 @@ -package org.layer.auth.jwt; +package org.layer.domain.jwt; import org.layer.common.exception.BaseCustomException; -import org.layer.common.exception.ExceptionType; -import org.layer.common.exception.MemberExceptionType; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; diff --git a/layer-api/src/main/java/org/layer/auth/service/JwtService.java b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java similarity index 81% rename from layer-api/src/main/java/org/layer/auth/service/JwtService.java rename to layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java index 9190688d..9f069845 100644 --- a/layer-api/src/main/java/org/layer/auth/service/JwtService.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java @@ -1,8 +1,10 @@ -package org.layer.auth.service; +package org.layer.domain.jwt.service; import lombok.RequiredArgsConstructor; -import org.layer.auth.exception.TokenException; -import org.layer.auth.jwt.*; +import org.layer.domain.auth.exception.TokenException; +import org.layer.domain.jwt.JwtProvider; +import org.layer.domain.jwt.JwtToken; +import org.layer.domain.jwt.MemberAuthentication; import org.layer.domain.member.entity.MemberRole; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -10,7 +12,8 @@ import java.time.Duration; import java.util.Objects; -import static org.layer.config.AuthValueConfig.*; +import static org.layer.config.AuthValueConfig.ACCESS_TOKEN_EXPIRATION_TIME; +import static org.layer.config.AuthValueConfig.REFRESH_TOKEN_EXPIRATION_TIME; @RequiredArgsConstructor @Service diff --git a/layer-api/src/main/java/org/layer/member/service/MemberService.java b/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java similarity index 95% rename from layer-api/src/main/java/org/layer/member/service/MemberService.java rename to layer-api/src/main/java/org/layer/domain/member/service/MemberService.java index baa54609..f29f9005 100644 --- a/layer-api/src/main/java/org/layer/member/service/MemberService.java +++ b/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java @@ -1,9 +1,9 @@ -package org.layer.member.service; +package org.layer.domain.member.service; import lombok.RequiredArgsConstructor; -import org.layer.auth.dto.controller.SignUpRequest; import org.layer.common.exception.BaseCustomException; import org.layer.common.exception.MemberExceptionType; +import org.layer.domain.auth.controller.dto.SignUpRequest; import org.layer.domain.member.entity.Member; import org.layer.domain.member.entity.SocialType; import org.layer.domain.member.repository.MemberRepository; diff --git a/layer-api/src/main/java/org/layer/member/service/MemberUtil.java b/layer-api/src/main/java/org/layer/domain/member/service/MemberUtil.java similarity index 81% rename from layer-api/src/main/java/org/layer/member/service/MemberUtil.java rename to layer-api/src/main/java/org/layer/domain/member/service/MemberUtil.java index 7f063dce..00569d31 100644 --- a/layer-api/src/main/java/org/layer/member/service/MemberUtil.java +++ b/layer-api/src/main/java/org/layer/domain/member/service/MemberUtil.java @@ -1,14 +1,13 @@ -package org.layer.member.service; +package org.layer.domain.member.service; import lombok.RequiredArgsConstructor; -import org.layer.auth.jwt.SecurityUtil; import org.layer.common.exception.BaseCustomException; -import org.layer.common.exception.MemberExceptionType; +import org.layer.domain.jwt.SecurityUtil; import org.layer.domain.member.entity.Member; import org.layer.domain.member.repository.MemberRepository; import org.springframework.stereotype.Component; -import static org.layer.common.exception.MemberExceptionType.*; +import static org.layer.common.exception.MemberExceptionType.NOT_FOUND_USER; @RequiredArgsConstructor @Component public class MemberUtil { diff --git a/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java b/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java index d911f252..750de297 100644 --- a/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java +++ b/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java @@ -32,4 +32,9 @@ public class Space { private Long leaderId; private Long defaultFormId; + + /** + * Form Relationid + */ + private Long formId; } From e05a2f6ffc33e225741c6fa9bf67225c5499d622 Mon Sep 17 00:00:00 2001 From: Raymond Date: Mon, 8 Jul 2024 15:04:04 +0900 Subject: [PATCH 10/12] =?UTF-8?q?refactor:=20=EC=98=A4=ED=83=80=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=EB=90=9C=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/layer/domain/auth/controller/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 079c5d91..4407f397 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 @@ -28,7 +28,7 @@ public ResponseEntity signIn(@RequestHeader("Authorization") fin // 회원가입 => 소셜로그인 했는데 유효한 유저가 없을 때 이름 입력하고 회원가입하는 과정 @PostMapping("/sign-up") - public ResponseEntity signUp(@RequestHeader(value = "authorizatio") final String socialAccessToken, @RequestBody final SignUpRequest signUpRequest) { + public ResponseEntity signUp(@RequestHeader(value = "Authorization") final String socialAccessToken, @RequestBody final SignUpRequest signUpRequest) { log.info("{} <<< socialAccessToken",socialAccessToken); SignUpServiceResponse signUpServiceResponse = authService.signUp(socialAccessToken, signUpRequest); return new ResponseEntity<>(SignUpResponse.of(signUpServiceResponse), HttpStatus.CREATED); From ee6d5555d6fcdcd7d2cbf70e9d8784f5fed8f0ce Mon Sep 17 00:00:00 2001 From: Raymond Date: Tue, 9 Jul 2024 00:21:44 +0900 Subject: [PATCH 11/12] =?UTF-8?q?refactor:=20=EC=BD=94=EB=A9=98=ED=8A=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../layer/domain/auth/exception/AuthException.java | 5 ----- .../org/layer/domain/auth/service/AuthService.java | 4 ++-- .../main/java/org/layer/domain/jwt/JwtValidator.java | 11 +++++++---- .../{auth => jwt}/exception/TokenException.java | 5 ++++- .../java/org/layer/domain/jwt/service/JwtService.java | 2 +- .../java/org/layer/domain/space/entity/Space.java | 2 -- 7 files changed, 15 insertions(+), 16 deletions(-) delete mode 100644 layer-api/src/main/java/org/layer/domain/auth/exception/AuthException.java rename layer-api/src/main/java/org/layer/domain/{auth => jwt}/exception/TokenException.java (71%) diff --git a/build.gradle b/build.gradle index f9568545..044a1d04 100644 --- a/build.gradle +++ b/build.gradle @@ -97,7 +97,7 @@ project(":layer-common") { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' +// implementation 'org.springframework.boot:spring-boot-starter-data-jpa' } } diff --git a/layer-api/src/main/java/org/layer/domain/auth/exception/AuthException.java b/layer-api/src/main/java/org/layer/domain/auth/exception/AuthException.java deleted file mode 100644 index 444bea52..00000000 --- a/layer-api/src/main/java/org/layer/domain/auth/exception/AuthException.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.layer.domain.auth.exception; - -// OAuth를 위해 임시로 만들어놓은 Exception입니다. -public class AuthException extends RuntimeException { -} 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 27d7b306..5673b7ef 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 @@ -4,11 +4,11 @@ import lombok.extern.slf4j.Slf4j; import org.layer.common.exception.BaseCustomException; import org.layer.domain.auth.controller.dto.SignUpRequest; -import org.layer.domain.auth.exception.AuthException; import org.layer.domain.auth.service.dto.ReissueTokenServiceResponse; import org.layer.domain.auth.service.dto.SignInServiceResponse; import org.layer.domain.auth.service.dto.SignUpServiceResponse; import org.layer.domain.jwt.JwtToken; +import org.layer.domain.jwt.exception.TokenException; import org.layer.domain.jwt.service.JwtService; import org.layer.domain.member.entity.Member; import org.layer.domain.member.entity.SocialType; @@ -92,7 +92,7 @@ private MemberInfoServiceResponse getMemberInfo(SocialType socialType, String so return switch (socialType) { case KAKAO -> kakaoService.getMemberInfo(socialAccessToken); case GOOGLE -> googleService.getMemberInfo(socialAccessToken); - default -> throw new AuthException(); + default -> throw new TokenException(); }; } 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 f7b8a66f..c3399696 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 @@ -4,11 +4,14 @@ import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.layer.domain.auth.exception.TokenException; +import org.layer.domain.jwt.exception.TokenException; import org.springframework.stereotype.Component; import java.util.List; +import static org.layer.domain.jwt.JwtValidationType.INVALID_JWT; +import static org.layer.domain.jwt.JwtValidationType.VALID_JWT; + @Slf4j @RequiredArgsConstructor @Component @@ -18,9 +21,9 @@ public class JwtValidator { public JwtValidationType validateToken(String token) { try { getClaims(token); - return JwtValidationType.VALID_JWT; - } catch(TokenException e) { - return JwtValidationType.INVALID_JWT; + return VALID_JWT; + } catch(Exception e) { + return INVALID_JWT; } } diff --git a/layer-api/src/main/java/org/layer/domain/auth/exception/TokenException.java b/layer-api/src/main/java/org/layer/domain/jwt/exception/TokenException.java similarity index 71% rename from layer-api/src/main/java/org/layer/domain/auth/exception/TokenException.java rename to layer-api/src/main/java/org/layer/domain/jwt/exception/TokenException.java index cbd72038..ad2b91f5 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/exception/TokenException.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/exception/TokenException.java @@ -1,4 +1,7 @@ -package org.layer.domain.auth.exception; +package org.layer.domain.jwt.exception; + + + // 임시로 만들어놓은 Exception입니다 public class TokenException extends RuntimeException { 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 9f069845..0b8f2d81 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,10 +1,10 @@ package org.layer.domain.jwt.service; import lombok.RequiredArgsConstructor; -import org.layer.domain.auth.exception.TokenException; import org.layer.domain.jwt.JwtProvider; import org.layer.domain.jwt.JwtToken; import org.layer.domain.jwt.MemberAuthentication; +import org.layer.domain.jwt.exception.TokenException; import org.layer.domain.member.entity.MemberRole; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; diff --git a/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java b/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java index 750de297..3c10bd58 100644 --- a/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java +++ b/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java @@ -31,8 +31,6 @@ public class Space { @NotNull private Long leaderId; - private Long defaultFormId; - /** * Form Relationid */ From 6cfdfd8055059518da7b5bc36b39072e0b2517ab Mon Sep 17 00:00:00 2001 From: Raymond Date: Tue, 9 Jul 2024 00:49:23 +0900 Subject: [PATCH 12/12] =?UTF-8?q?refactor:=20=EC=BD=94=EB=A9=98=ED=8A=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/layer/common/annotation/MemberId.java | 11 +++++ .../org/layer/resolver/MemberIdResolver.java | 45 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 layer-api/src/main/java/org/layer/common/annotation/MemberId.java create mode 100644 layer-api/src/main/java/org/layer/resolver/MemberIdResolver.java diff --git a/layer-api/src/main/java/org/layer/common/annotation/MemberId.java b/layer-api/src/main/java/org/layer/common/annotation/MemberId.java new file mode 100644 index 00000000..6bcda6df --- /dev/null +++ b/layer-api/src/main/java/org/layer/common/annotation/MemberId.java @@ -0,0 +1,11 @@ +package org.layer.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface MemberId { +} diff --git a/layer-api/src/main/java/org/layer/resolver/MemberIdResolver.java b/layer-api/src/main/java/org/layer/resolver/MemberIdResolver.java new file mode 100644 index 00000000..10fb2b3c --- /dev/null +++ b/layer-api/src/main/java/org/layer/resolver/MemberIdResolver.java @@ -0,0 +1,45 @@ +package org.layer.resolver; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.layer.common.annotation.MemberId; +import org.layer.common.exception.BaseCustomException; +import org.springframework.core.MethodParameter; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import java.util.Optional; + +import static org.layer.common.exception.MemberExceptionType.UNAUTHORIZED_USER; + +@Component +@RequiredArgsConstructor +@Slf4j +public class MemberIdResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + // 1. 어노테이션 체크 + var annotation = parameter.hasParameterAnnotation(MemberId.class); + + // 2. 파라미터의 타입 체크 + var parameterType = parameter.getParameterType().equals(Long.class); + + return (annotation && parameterType); + } + + @Override + public Long resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + try{ + var securityContext = SecurityContextHolder.getContext(); + var memberId = Optional.ofNullable(securityContext).map(it -> it.getAuthentication().getName()).orElseThrow(() -> new BaseCustomException(UNAUTHORIZED_USER)); + + return Long.parseLong(memberId); + } catch(Exception e) { + throw new BaseCustomException(UNAUTHORIZED_USER); + } + } +}