diff --git a/growthookServer/build.gradle b/growthookServer/build.gradle index 31d8766..9037e91 100644 --- a/growthookServer/build.gradle +++ b/growthookServer/build.gradle @@ -47,6 +47,8 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + + implementation 'com.google.code.gson:gson:2.10.1' } tasks.named('bootBuildImage') { diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java index e51a494..d80e439 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java @@ -9,6 +9,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -18,6 +19,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "AciontPlan - 액션플랜 관련 API",description = "AcitonPlan APi Documnet") public class ActionPlanController { diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java index f34ccf1..eaa95db 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java @@ -18,5 +18,6 @@ public class ActionPlanGetResponseDto { private Boolean isFinished; - private Boolean isReviewed; + private Boolean hasReview; + } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java index 6bdb694..e7f1c3c 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java @@ -17,5 +17,6 @@ public class DoingActionPlanGetResponseDto { private Long seedId; - private Boolean isReviewed; + private Boolean hasReview; + } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java index a044521..eb9eef2 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java @@ -17,5 +17,6 @@ public class FinishedActionPlanGetResponseDto { private Long seedId; - private Boolean isReviewed; + private Boolean hasReview; + } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/cave/controller/CaveController.java b/growthookServer/src/main/java/com/example/growthookserver/api/cave/controller/CaveController.java index e601e16..08db317 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/cave/controller/CaveController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/cave/controller/CaveController.java @@ -9,6 +9,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -18,6 +19,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "Cave - 동굴 관련 API", description = "Cave API Document") public class CaveController { private final CaveService caveService; diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/controller/AuthController.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/controller/AuthController.java new file mode 100644 index 0000000..17bff6b --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/controller/AuthController.java @@ -0,0 +1,53 @@ +package com.example.growthookserver.api.member.auth.controller; + +import com.example.growthookserver.api.member.auth.dto.Request.AuthRequestDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthResponseDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthTokenResponseDto; +import com.example.growthookserver.api.member.auth.service.AuthService; +import com.example.growthookserver.common.config.jwt.JwtTokenProvider; +import com.example.growthookserver.common.response.ApiResponse; +import com.example.growthookserver.common.response.SuccessStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/auth") +@RequiredArgsConstructor +@Tag(name = "Auth - 인증/인가 관련 API", description = "Auth API Document") +public class AuthController { + private final AuthService authService; + private final JwtTokenProvider jwtTokenProvider; + + @PostMapping() + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "SocialLogin", description = "소셜 로그인 API입니다.") + public ApiResponse socialLogin(@RequestBody AuthRequestDto authRequestDto) throws NoSuchAlgorithmException, InvalidKeySpecException { + + AuthResponseDto responseDto = authService.socialLogin(authRequestDto); + return ApiResponse.success(SuccessStatus.SIGNIN_SUCCESS, responseDto); + + } + + @GetMapping("/token") + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "TokenRefresh", description = "토큰 재발급 API입니다.") + public ApiResponse getNewToken(HttpServletRequest request) { + String accessToken = (String) request.getAttribute("newAccessToken"); + String refreshToken = jwtTokenProvider.resolveRefreshToken(request); + + return ApiResponse.success(SuccessStatus.GET_NEW_TOKEN_SUCCESS, authService.getNewToken(accessToken, refreshToken)); + } +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java new file mode 100644 index 0000000..5c919d5 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java @@ -0,0 +1,14 @@ +package com.example.growthookserver.api.member.auth.dto.Request; + +import static lombok.AccessLevel.PROTECTED; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = PROTECTED) +public class AuthRequestDto { + private String socialPlatform; + private String socialToken; + private String userName; +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthResponseDto.java new file mode 100644 index 0000000..a881d36 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthResponseDto.java @@ -0,0 +1,20 @@ +package com.example.growthookserver.api.member.auth.dto.Response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor(staticName = "of") +public class AuthResponseDto { + + private String nickname; + + private Long memberId; + + private String accessToken; + + private String refreshToken; + +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java new file mode 100644 index 0000000..773159f --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java @@ -0,0 +1,14 @@ +package com.example.growthookserver.api.member.auth.dto.Response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor(staticName = "of") +public class AuthTokenResponseDto { + private String accessToken; + + private String refreshToken; +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java new file mode 100644 index 0000000..3323558 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java @@ -0,0 +1,13 @@ +package com.example.growthookserver.api.member.auth.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class SocialInfoDto { + private String id; + private String nickname; + private String email; + private String profileImage; +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AppleAuthService.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AppleAuthService.java new file mode 100644 index 0000000..5ce8093 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AppleAuthService.java @@ -0,0 +1,126 @@ +package com.example.growthookserver.api.member.auth.service; + +import com.example.growthookserver.api.member.auth.dto.SocialInfoDto; +import com.example.growthookserver.common.exception.BaseException; +import com.google.gson.*; +import com.example.growthookserver.common.exception.UnAuthorizedException; +import com.example.growthookserver.common.response.ErrorStatus; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.net.HttpURLConnection; +import java.net.URL; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.Base64; +import java.util.Objects; +import lombok.RequiredArgsConstructor; + +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AppleAuthService { + /** + * 1. apple로 부터 공개키 3개 가져옴 + * 2. 내가 클라에서 가져온 token String과 비교해서 써야할 공개키 확인 (kid,alg 값 같은 것) + * 3. 그 공개키 재료들로 공개키 만들고, 이 공개키로 JWT토큰 부분의 바디 부분의 decode하면 유저 정보 + */ + public SocialInfoDto login(String socialAccessToken, String userName) { + return getAppleSocialData(socialAccessToken, userName); + } + + private JsonArray getApplePublicKeys() { + StringBuffer result = new StringBuffer(); + try { + URL url = new URL("https://appleid.apple.com/auth/keys"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + + String line = ""; + + while ((line = br.readLine()) != null) { + result.append(line); + } + JsonObject keys = (JsonObject) JsonParser.parseString(result.toString()); + return (JsonArray) keys.get("keys"); // 1. 공개키 가져오기 + } catch (IOException e) { + throw new UnAuthorizedException(ErrorStatus.FAILED_TO_VALIDATE_APPLE_LOGIN.getMessage()); + } + } + + private SocialInfoDto getAppleSocialData(String socialAccessToken, String userName) { + try { + JsonArray publicKeyList = getApplePublicKeys(); + PublicKey publicKey = makePublicKey(socialAccessToken, publicKeyList); + + Claims userInfo = Jwts.parserBuilder() + .setSigningKey(publicKey) + .build() + .parseClaimsJws(socialAccessToken.substring(7)) + .getBody(); + + JsonObject userInfoObject = (JsonObject) JsonParser.parseString(new Gson().toJson(userInfo)); + String appleId = userInfoObject.get("sub").getAsString(); + String email = userInfoObject.get("email").getAsString(); + + return new SocialInfoDto(appleId, userName, email, null); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "애플 계정 데이터 가공 실패"); + } + } + + private PublicKey makePublicKey(String identityToken, JsonArray publicKeyList) throws NoSuchAlgorithmException, InvalidKeySpecException { + JsonObject selectedObject = null; + + String[] decodeArray = identityToken.split("\\."); + String header = new String(Base64.getDecoder().decode(decodeArray[0].substring(7))); + + JsonElement kid = ((JsonObject) JsonParser.parseString(header)).get("kid"); + JsonElement alg = ((JsonObject) JsonParser.parseString(header)).get("alg"); + + for (JsonElement publicKey : publicKeyList) { + JsonObject publicKeyObject = publicKey.getAsJsonObject(); + JsonElement publicKid = publicKeyObject.get("kid"); + JsonElement publicAlg = publicKeyObject.get("alg"); + + if (Objects.equals(kid, publicKid) && Objects.equals(alg, publicAlg)) { + selectedObject = publicKeyObject; + break; + } + } + + if (selectedObject == null) { + throw new InvalidKeySpecException("공개키를 찾을 수 없습니다."); + } + + return getPublicKey(selectedObject); + } + + private PublicKey getPublicKey(JsonObject object) throws NoSuchAlgorithmException, InvalidKeySpecException { + String nStr = object.get("n").toString(); + String eStr = object.get("e").toString(); + + byte[] nBytes = Base64.getUrlDecoder().decode(nStr.substring(1, nStr.length() - 1)); + byte[] eBytes = Base64.getUrlDecoder().decode(eStr.substring(1, eStr.length() - 1)); + + BigInteger n = new BigInteger(1, nBytes); + BigInteger e = new BigInteger(1, eBytes); + + + RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + return publicKey; + } + +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AuthService.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AuthService.java new file mode 100644 index 0000000..f46d36e --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AuthService.java @@ -0,0 +1,13 @@ +package com.example.growthookserver.api.member.auth.service; + +import com.example.growthookserver.api.member.auth.dto.Request.AuthRequestDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthResponseDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthTokenResponseDto; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +public interface AuthService { + AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchAlgorithmException, InvalidKeySpecException; + + AuthTokenResponseDto getNewToken(String accessToken, String refreshToken); +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java new file mode 100644 index 0000000..647c533 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java @@ -0,0 +1,98 @@ +package com.example.growthookserver.api.member.auth.service.Impl; + +import com.example.growthookserver.api.member.auth.SocialPlatform; +import com.example.growthookserver.api.member.auth.dto.Request.AuthRequestDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthResponseDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthTokenResponseDto; + +import com.example.growthookserver.api.member.auth.dto.SocialInfoDto; +import com.example.growthookserver.api.member.auth.service.AppleAuthService; +import com.example.growthookserver.api.member.auth.service.AuthService; +import com.example.growthookserver.api.member.auth.service.KakaoAuthService; + +import com.example.growthookserver.api.member.domain.Member; +import com.example.growthookserver.api.member.repository.MemberRepository; +import com.example.growthookserver.common.config.jwt.JwtTokenProvider; +import com.example.growthookserver.common.config.jwt.UserAuthentication; +import com.example.growthookserver.common.exception.BadRequestException; +import com.example.growthookserver.common.response.ErrorStatus; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService { + private final JwtTokenProvider jwtTokenProvider; + private final KakaoAuthService kakaoAuthService; + private final AppleAuthService appleAuthService; + private final MemberRepository memberRepository; + + @Override + @Transactional + public AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchAlgorithmException, InvalidKeySpecException { + + if (authRequestDto.getSocialPlatform() == null || authRequestDto.getSocialToken() == null) { + throw new BadRequestException(ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION.getMessage()); + } + + try { + SocialPlatform socialPlatform = SocialPlatform.valueOf(authRequestDto.getSocialPlatform()); + + SocialInfoDto socialData = getSocialData(socialPlatform, authRequestDto.getSocialToken(), + authRequestDto.getUserName()); + + String refreshToken = jwtTokenProvider.generateRefreshToken(); + + Boolean isExistUser = memberRepository.existsBySocialId(socialData.getId()); + + // 신규 유저 저장 + if (!isExistUser.booleanValue()) { + Member member = Member.builder() + .nickname(socialData.getNickname()) + .email(socialData.getEmail()) + .socialPlatform(socialPlatform) + .socialId(socialData.getId()) + .profileImage(socialData.getProfileImage()) + .build(); + + memberRepository.save(member); + + member.updateRefreshToken(refreshToken); + } + else memberRepository.findMemberBySocialIdOrThrow(socialData.getId()).updateRefreshToken(refreshToken); + + // socialId를 통해서 등록된 유저 찾기 + Member signedMember = memberRepository.findMemberBySocialIdOrThrow(socialData.getId()); + + String accessToken = jwtTokenProvider.generateAccessToken(signedMember.getId()); + + return AuthResponseDto.of(signedMember.getNickname(), signedMember.getId(), accessToken, signedMember.getRefreshToken()); + + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException(ErrorStatus.ANOTHER_ACCESS_TOKEN.getMessage()); + } + } + + @Override + @Transactional + public AuthTokenResponseDto getNewToken(String accessToken, String refreshToken) { + return AuthTokenResponseDto.of(accessToken,refreshToken); + } + + private SocialInfoDto getSocialData(SocialPlatform socialPlatform, String socialAccessToken, String userName) { + + switch (socialPlatform) { + case KAKAO: + return kakaoAuthService.login(socialAccessToken); + case APPLE: + return appleAuthService.login(socialAccessToken, userName); + default: + throw new IllegalArgumentException(ErrorStatus.ANOTHER_ACCESS_TOKEN.getMessage()); + } + } +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java new file mode 100644 index 0000000..0374827 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java @@ -0,0 +1,55 @@ +package com.example.growthookserver.api.member.auth.service; + +import com.example.growthookserver.api.member.auth.dto.SocialInfoDto; +import com.example.growthookserver.common.exception.BaseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@RequiredArgsConstructor +@Component +public class KakaoAuthService { + + //이 login은 카카오 서버에 AccessToken으로 접속할때의 login + public SocialInfoDto login(String socialAccessToken) { + return getKakaoSocialData(socialAccessToken); + } + + private SocialInfoDto getKakaoSocialData(String socialAccessToken) { + + try{ + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", socialAccessToken); + HttpEntity httpEntity = new HttpEntity<>(headers); + ResponseEntity responseData = restTemplate.postForEntity("https://kapi.kakao.com/v2/user/me", httpEntity, String.class); + ObjectMapper objectMapper = new ObjectMapper(); + + JsonNode jsonNode = objectMapper.readTree(responseData.getBody()); + + String nickname = jsonNode.get("kakao_account").get("profile").get("nickname").asText(); + String profileImage; + try { + profileImage = jsonNode.get("kakao_account").get("profile").get("profile_image_url").asText(); + } catch (NullPointerException e) { + profileImage = null; + } + String email = jsonNode.get("kakao_account").get("email").asText(); + String kakaoId = jsonNode.get("id").asText(); + + return new SocialInfoDto(kakaoId, nickname, email, profileImage); + } catch (JsonProcessingException e) { + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "카카오 계정 데이터 가공 실패"); + } + + } + +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/controller/MemberController.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/controller/MemberController.java index f8b1fca..29961bd 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/controller/MemberController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/controller/MemberController.java @@ -7,6 +7,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -20,6 +21,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "Member - 유저 관련 API", description = "Member API Document") public class MemberController { diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java index 3ce450c..912c65f 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java @@ -23,13 +23,13 @@ public class Member extends BaseTimeEntity { @Column private String email; + @Column + private String profileImage; + @Enumerated(value = EnumType.STRING) @Column(name = "social_platform") private SocialPlatform socialPlatform; - @Column(name = "is_new_member") - private Boolean isNewMember; - @Column(name = "used_ssuk") private Integer usedSsuk; @@ -43,16 +43,16 @@ public class Member extends BaseTimeEntity { private String refreshToken; @Builder - public Member(String nickname, String email, SocialPlatform socialPlatform, Boolean isNewMember, Integer usedSsuk, Integer gatheredSsuk) { + public Member(String nickname, String email, SocialPlatform socialPlatform, String socialId, String profileImage) { this.nickname = nickname; this.email = email; this.socialPlatform = socialPlatform; - this.isNewMember = isNewMember; - this.usedSsuk = usedSsuk; - this.gatheredSsuk = gatheredSsuk; + this.socialId = socialId; + this.profileImage = profileImage; + this.usedSsuk = 0; + this.gatheredSsuk = 0; } - @Builder public void incrementGatheredSsuk() { this.gatheredSsuk = (this.gatheredSsuk == null ? 0 : this.gatheredSsuk) + 1; } @@ -61,4 +61,8 @@ public void useSsuck() { this.gatheredSsuk--; this.usedSsuk++; } + + public void updateRefreshToken (String refreshToken) { + this.refreshToken = refreshToken; + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/dto/response/MemberDetailGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/dto/response/MemberDetailGetResponseDto.java index 5e413f7..777658e 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/dto/response/MemberDetailGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/dto/response/MemberDetailGetResponseDto.java @@ -11,4 +11,5 @@ public class MemberDetailGetResponseDto { private String nickname; private String email; + private String profileImage; } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java index c662aa9..b2ad88f 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java @@ -1,7 +1,9 @@ package com.example.growthookserver.api.member.repository; import com.example.growthookserver.api.member.domain.Member; +import com.example.growthookserver.common.exception.BadRequestException; import com.example.growthookserver.common.exception.NotFoundException; +import com.example.growthookserver.common.exception.UnAuthorizedException; import com.example.growthookserver.common.response.ErrorStatus; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,8 +12,23 @@ public interface MemberRepository extends JpaRepository { Optional findMemberById(Long id); + boolean existsBySocialId(String socialId); + Optional findByRefreshToken(String refreshToken); + + Optional findBySocialId(String socialId); + default Member findMemberByIdOrThrow(Long memberId){ return findMemberById(memberId) .orElseThrow(() -> new NotFoundException(ErrorStatus.NOT_FOUND_MEMBER.getMessage())); } + + default Member findMemberBySocialIdOrThrow(String socialId) { + return findBySocialId(socialId) + .orElseThrow(() -> new BadRequestException(ErrorStatus.INVALID_MEMBER.getMessage())); + } + + default Member findByRefreshTokenOrThrow(String refreshToken) { + return findByRefreshToken(refreshToken) + .orElseThrow(() -> new UnAuthorizedException(ErrorStatus.INVALID_MEMBER.getMessage())); + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/service/MemberServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/service/MemberServiceImpl.java index a657370..311e087 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/service/MemberServiceImpl.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/service/MemberServiceImpl.java @@ -19,7 +19,8 @@ public class MemberServiceImpl implements MemberService{ @Override public MemberDetailGetResponseDto getMemberProfile(Long memberId) { Member member = memberRepository.findMemberByIdOrThrow(memberId); - return MemberDetailGetResponseDto.of(member.getNickname(), member.getEmail()); + return MemberDetailGetResponseDto.of(member.getNickname(), member.getEmail(), + member.getProfileImage()); } @Override diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/review/controller/ReviewController.java b/growthookServer/src/main/java/com/example/growthookserver/api/review/controller/ReviewController.java index 74f34e3..4b174fe 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/review/controller/ReviewController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/review/controller/ReviewController.java @@ -6,6 +6,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -21,6 +22,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "Review - 리뷰 관련 API", description = "Review API Document") public class ReviewController { diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/seed/controller/SeedController.java b/growthookServer/src/main/java/com/example/growthookserver/api/seed/controller/SeedController.java index 0c4b4dc..75a8f4c 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/seed/controller/SeedController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/seed/controller/SeedController.java @@ -12,6 +12,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.util.List; @@ -22,6 +23,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "Seed - 인사이트 관련 API", description = "Seed API Document") public class SeedController { diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java index dd11568..5de7896 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java @@ -1,19 +1,32 @@ package com.example.growthookserver.common.config; +import com.example.growthookserver.common.config.jwt.JwtAuthenticationEntryPoint; +import com.example.growthookserver.common.config.jwt.JwtAuthenticationFilter; +import com.example.growthookserver.common.config.jwt.JwtTokenProvider; +import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { + private final JwtTokenProvider jwtTokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private static final String[] SWAGGER_URL = { "/swagger-resources/**", "/favicon.ico", @@ -25,22 +38,31 @@ public class SecurityConfig { "/swagger-ui/swagger-ui.css", }; + private static final String[] AUTH_WHITELIST = { + "/api/v1/auth", + "/health", + "/profile", + "/actuator/**" + }; + @Bean @Profile("dev") SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ - // XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler(); -// http -// .csrf((csrf) -> csrf -// .csrfTokenRequestHandler(requestHandler) -// ) -// .authorizeRequests() -// .anyRequest().permitAll(); - http - .csrf().disable() - .httpBasic().disable() - .authorizeHttpRequests() - .anyRequest().permitAll(); - + http.csrf((csrfConfig) -> csrfConfig.disable()) + .cors(Customizer.withDefaults()) + .sessionManagement( + (sessionManagement) -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests( + authorize -> authorize + .requestMatchers(SWAGGER_URL).permitAll() + .requestMatchers(AUTH_WHITELIST).permitAll() + .anyRequest().authenticated()) + .addFilterBefore( + new JwtAuthenticationFilter(this.jwtTokenProvider, this.jwtAuthenticationEntryPoint), + UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(exceptionHandling -> exceptionHandling + .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)); return http.build(); } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/SwaggerConfig.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/SwaggerConfig.java index c38becf..93ea42a 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/SwaggerConfig.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/SwaggerConfig.java @@ -1,7 +1,9 @@ package com.example.growthookserver.common.config; import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityScheme; import lombok.RequiredArgsConstructor; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; @@ -13,6 +15,12 @@ version = "v1")) @RequiredArgsConstructor @Configuration +@SecurityScheme( + name = "JWT Authentication", + type = SecuritySchemeType.HTTP, + bearerFormat = "JWT", + scheme = "bearer" +) public class SwaggerConfig { @Bean diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..fa0ee8b --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,34 @@ +package com.example.growthookserver.common.config.jwt; + +import com.example.growthookserver.common.response.ApiResponse; +import com.example.growthookserver.common.response.ErrorStatus; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper mapper = new ObjectMapper(); + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + setResponse(response, HttpStatus.UNAUTHORIZED, ErrorStatus.UNAUTHORIZED_TOKEN); + } + + + public void setResponse(HttpServletResponse response, HttpStatus statusCode, ErrorStatus status) throws IOException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + ApiResponse apiResponse = ApiResponse.fail(statusCode.value(), status.getMessage()); + response.getWriter().println(mapper.writeValueAsString(apiResponse)); + } + +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..217c0a8 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,82 @@ +package com.example.growthookserver.common.config.jwt; + +import com.example.growthookserver.common.exception.UnAuthorizedException; +import com.example.growthookserver.common.response.ErrorStatus; +import io.jsonwebtoken.Claims; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; + +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + + private static final String ISSUE_TOKEN_API_URL = "/api/v1/auth/token"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain chain) throws ServletException, IOException { + try { + String accessToken = jwtTokenProvider.resolveToken(request); + if (ISSUE_TOKEN_API_URL.equals(request.getRequestURI())) { + String refreshToken = jwtTokenProvider.resolveRefreshToken(request); + + if (jwtTokenProvider.validateToken(refreshToken) == JwtExceptionType.EMPTY_JWT || jwtTokenProvider.validateToken(accessToken) == JwtExceptionType.EMPTY_JWT) { + jwtAuthenticationEntryPoint.setResponse(response, HttpStatus.BAD_REQUEST, ErrorStatus.NO_TOKEN); + return; + } else if (jwtTokenProvider.validateToken(accessToken) == JwtExceptionType.EXPIRED_JWT_TOKEN) { + if (jwtTokenProvider.validateToken(refreshToken) == JwtExceptionType.EXPIRED_JWT_TOKEN) { + // access, refresh 둘 다 만료 + jwtAuthenticationEntryPoint.setResponse(response, HttpStatus.UNAUTHORIZED, ErrorStatus.SIGNIN_REQUIRED); + return; + } else if (jwtTokenProvider.validateToken(refreshToken) == JwtExceptionType.VALID_JWT_TOKEN) { + // 토큰 재발급 + Long memberId = jwtTokenProvider.validateMemberRefreshToken(refreshToken); + + String newAccessToken = jwtTokenProvider.generateAccessToken(memberId); + + setAuthentication(newAccessToken); + request.setAttribute("newAccessToken", newAccessToken); + } + } else if (jwtTokenProvider.validateToken(accessToken) == JwtExceptionType.VALID_JWT_TOKEN) { + jwtAuthenticationEntryPoint.setResponse(response, HttpStatus.UNAUTHORIZED, ErrorStatus.VALID_ACCESS_TOKEN); + return; + } else { + throw new UnAuthorizedException(ErrorStatus.UNAUTHORIZED_TOKEN.getMessage()); + } + } + else { + JwtExceptionType jwtException = jwtTokenProvider.validateToken(accessToken); + + if (accessToken != null) { + // 토큰 검증 + if (jwtException == JwtExceptionType.VALID_JWT_TOKEN) { + setAuthentication(accessToken); + } + } + } + } catch (Exception e) { + throw new UnAuthorizedException(ErrorStatus.UNAUTHORIZED_TOKEN.getMessage()); + } + + chain.doFilter(request, response); + } + + private void setAuthentication(String token) { + Claims claims = jwtTokenProvider.getAccessTokenPayload(token); + Authentication authentication = new UserAuthentication(Long.valueOf(String.valueOf(claims.get("id"))), null, null); + SecurityContextHolder.getContext().setAuthentication(authentication); + } +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtExceptionType.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtExceptionType.java new file mode 100644 index 0000000..6464d58 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtExceptionType.java @@ -0,0 +1,10 @@ +package com.example.growthookserver.common.config.jwt; + +public enum JwtExceptionType { + VALID_JWT_TOKEN, // 유효한 JWT + INVALID_JWT_SIGNATURE, // 유효하지 않은 서명 + INVALID_JWT_TOKEN, // 유효하지 않은 토큰 + EXPIRED_JWT_TOKEN, // 만료된 토큰 + UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰 + EMPTY_JWT // 빈 JWT +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java new file mode 100644 index 0000000..42f5490 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java @@ -0,0 +1,128 @@ +package com.example.growthookserver.common.config.jwt; + +import com.example.growthookserver.api.member.domain.Member; +import com.example.growthookserver.api.member.repository.MemberRepository; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.UnsupportedJwtException; +import jakarta.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Date; +import javax.crypto.spec.SecretKeySpec; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtTokenProvider { + + private final MemberRepository memberRepository; + + @Value("${jwt.secret}") + private String secretKey; + + @Value("${jwt.access-token.expire-length}") + private Long accessTokenExpireLength; + + @Value("${jwt.refresh-token.expire-length}") + private Long refreshTokenExpireLength; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String REFRESH_AUTHORIZATION_HEADER = "refreshToken"; + + public String generateAccessToken(Long memberId) { + Date now = new Date(); + Date expiration = new Date(now.getTime() + accessTokenExpireLength); + + final Claims claims = Jwts.claims() + .setIssuedAt(now) + .setExpiration(expiration); + + claims.put("id", memberId); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setClaims(claims) + .signWith(getSignKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public String generateRefreshToken() { + Date now = new Date(); + Date expiration = new Date(now.getTime() + refreshTokenExpireLength); + + return Jwts.builder() + .setIssuedAt(now) + .setExpiration(expiration) + .signWith(getSignKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public Claims getAccessTokenPayload(String token) { + return Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody(); + } + + public String resolveToken(HttpServletRequest request) { + + String header = request.getHeader(AUTHORIZATION_HEADER); + + if (header == null || !header.startsWith("Bearer ")) { + return null; + } else { + return header.split(" ")[1]; + } + } + + public String resolveRefreshToken(HttpServletRequest request) { + String header = request.getHeader(REFRESH_AUTHORIZATION_HEADER); + + if (header == null || !header.startsWith("Bearer ")) { + return null; + } else { + return header.split(" ")[1]; + } + } + + public JwtExceptionType validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody(); + return JwtExceptionType.VALID_JWT_TOKEN; + } catch (io.jsonwebtoken.security.SignatureException exception) { + log.error("잘못된 JWT 서명을 가진 토큰입니다."); + return JwtExceptionType.INVALID_JWT_SIGNATURE; + } catch (MalformedJwtException exception) { + log.error("잘못된 JWT 토큰입니다."); + return JwtExceptionType.INVALID_JWT_TOKEN; + } catch (ExpiredJwtException exception) { + log.error("만료된 JWT 토큰입니다."); + return JwtExceptionType.EXPIRED_JWT_TOKEN; + } catch (UnsupportedJwtException exception) { + log.error("지원하지 않는 JWT 토큰입니다."); + return JwtExceptionType.UNSUPPORTED_JWT_TOKEN; + } catch (IllegalArgumentException exception) { + log.error("JWT Claims가 비어있습니다."); + return JwtExceptionType.EMPTY_JWT; + } + } + + private Key getSignKey() { + byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); + return new SecretKeySpec(keyBytes, "HmacSHA256"); + } + + public Long validateMemberRefreshToken(String refreshToken) { + Member member = memberRepository.findByRefreshTokenOrThrow(refreshToken); + return member.getId(); + } +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/UserAuthentication.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/UserAuthentication.java new file mode 100644 index 0000000..12cfacb --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/UserAuthentication.java @@ -0,0 +1,16 @@ +package com.example.growthookserver.common.config.jwt; + + +import java.util.Collection; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +// UsernamePasswordAuthenticationToken: 사용자의 인증 정보 저장하고 전달 +public class UserAuthentication extends UsernamePasswordAuthenticationToken { + + // 사용자 인증 객체 생성 + public UserAuthentication(Object principal, Object credentials, + Collection authorities) { + super(principal, credentials, authorities); + } +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/response/ErrorStatus.java b/growthookServer/src/main/java/com/example/growthookserver/common/response/ErrorStatus.java index a63ca96..8c79e44 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/response/ErrorStatus.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/response/ErrorStatus.java @@ -25,6 +25,7 @@ public enum ErrorStatus { KAKAO_UNAUTHORIZED_USER("카카오 로그인 실패. 만료되었거나 잘못된 카카오 토큰입니다."), SIGNIN_REQUIRED("access, refreshToken 모두 만료되었습니다. 재로그인이 필요합니다."), VALID_ACCESS_TOKEN("아직 유효한 accessToken 입니다."), + FAILED_TO_VALIDATE_APPLE_LOGIN("애플 로그인 실패"), /** * 404 NOT_FOUND @@ -40,7 +41,8 @@ public enum ErrorStatus { * 500 SERVER_ERROR */ INTERNAL_SERVER_ERROR("예상치 못한 서버 에러가 발생했습니다."), - BAD_GATEWAY_EXCEPTION("일시적인 에러가 발생하였습니다.\n잠시 후 다시 시도해주세요!"); + BAD_GATEWAY_EXCEPTION("일시적인 에러가 발생하였습니다.\n잠시 후 다시 시도해주세요!"), + ; private final String message;