Skip to content

Commit

Permalink
Merge pull request #106 from Team-HMH/feat/#100-get-kakao-social-acce…
Browse files Browse the repository at this point in the history
…ss-token

feat - 테스트용 카카오 소셜 액세스 토큰 발급 기능 구현
  • Loading branch information
kseysh authored Mar 11, 2024
2 parents 56a29c0 + ccec31b commit 22c8b3c
Show file tree
Hide file tree
Showing 18 changed files with 129 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -14,6 +15,7 @@
import sopt.org.hmh.domain.user.dto.request.SocialSignUpRequest;
import sopt.org.hmh.domain.user.service.UserService;
import sopt.org.hmh.global.auth.UserId;
import sopt.org.hmh.global.auth.social.SocialAccessTokenResponse;
import sopt.org.hmh.global.common.response.BaseResponse;
import sopt.org.hmh.global.common.response.EmptyJsonResponse;

Expand Down Expand Up @@ -90,4 +92,13 @@ public ResponseEntity<BaseResponse<?>> orderWithdraw(@UserId final Long userId)
.status(UserSuccess.WITHDRAW_SUCCESS.getHttpStatus())
.body(BaseResponse.success(UserSuccess.WITHDRAW_SUCCESS, new EmptyJsonResponse()));
}

@GetMapping("/social/token/kakao")
public ResponseEntity<BaseResponse<SocialAccessTokenResponse>> orderGetKakaoAccessToken(
@RequestParam("code") final String code
) {
return ResponseEntity
.status(UserSuccess.GET_SOCIAL_ACCESS_TOKEN_SUCCESS.getHttpStatus())
.body(BaseResponse.success(UserSuccess.GET_SOCIAL_ACCESS_TOKEN_SUCCESS, userService.getSocialAccessTokenByAuthorizationCode(code)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum UserSuccess implements SuccessBase {
GET_USER_INFO_SUCCESS(HttpStatus.OK, "유저의 정보를 불러오는데에 성공했습니다."),
GET_USER_POINT_SUCCESS(HttpStatus.OK, "유저의 포인트 정보를 불러오는데에 성공했습니다."),
WITHDRAW_SUCCESS(HttpStatus.OK, "회원 탈퇴를 성공하였습니다."),
GET_SOCIAL_ACCESS_TOKEN_SUCCESS(HttpStatus.OK, "소셜 액세스 토큰 발급을 성공했습니다."),

// 201 CREATED
SIGNUP_SUCCESS(HttpStatus.CREATED, "회원 가입에 성공했습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import sopt.org.hmh.global.auth.jwt.exception.JwtException;
import sopt.org.hmh.global.auth.redis.TokenService;
import sopt.org.hmh.global.auth.social.SocialPlatform;
import sopt.org.hmh.global.auth.social.SocialAccessTokenResponse;
import sopt.org.hmh.global.auth.social.apple.fegin.AppleOAuthProvider;
import sopt.org.hmh.global.auth.social.kakao.fegin.KakaoLoginService;

Expand Down Expand Up @@ -197,4 +198,9 @@ public void deleteExpiredUser(LocalDateTime currentDate) {
userRepository.deleteAllById(expiredUserList);
challengeService.deleteChallengeRelatedByUserId(expiredUserList);
}

public SocialAccessTokenResponse getSocialAccessTokenByAuthorizationCode(String code) {
return kakaoLoginService.getKakaoAccessToken(code);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package sopt.org.hmh.global.auth.social;

import lombok.Builder;

@Builder
public record SocialAccessTokenResponse(
String socialAccessToken
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import sopt.org.hmh.global.auth.social.apple.request.ApplePublicKeys;
import sopt.org.hmh.global.auth.social.apple.response.ApplePublicKeysResponse;

@FeignClient(name = "appleFeignClient", url = "${oauth2.apple.base-url}")
public interface AppleFeignClient {

@GetMapping("/auth/keys")
ApplePublicKeys getApplePublicKeys();
ApplePublicKeysResponse getApplePublicKeys();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.util.Map;
import sopt.org.hmh.global.auth.jwt.exception.JwtError;
import sopt.org.hmh.global.auth.jwt.exception.JwtException;
import sopt.org.hmh.global.auth.social.apple.request.ApplePublicKeys;
import sopt.org.hmh.global.auth.social.apple.response.ApplePublicKeysResponse;

@RequiredArgsConstructor
@Component
Expand All @@ -19,8 +19,8 @@ public class AppleOAuthProvider {

public String getApplePlatformId(String identityToken) {
Map<String, String> headers = appleIdentityTokenParser.parseHeaders(identityToken);
ApplePublicKeys applePublicKeys = appleFeignClient.getApplePublicKeys();
PublicKey publicKey = applePublicKeyGenerator.generatePublicKeyWithApplePublicKeys(headers, applePublicKeys);
ApplePublicKeysResponse applePublicKeysResponse = appleFeignClient.getApplePublicKeys();
PublicKey publicKey = applePublicKeyGenerator.generatePublicKeyWithApplePublicKeys(headers, applePublicKeysResponse);
Claims claims = appleIdentityTokenParser.parseWithPublicKeyAndGetClaims(identityToken, publicKey);
validateClaims(claims);
return claims.getSubject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@
import java.math.BigInteger;
import sopt.org.hmh.global.auth.jwt.exception.JwtError;
import sopt.org.hmh.global.auth.jwt.exception.JwtException;
import sopt.org.hmh.global.auth.social.apple.request.ApplePublicKey;
import sopt.org.hmh.global.auth.social.apple.request.ApplePublicKeys;
import sopt.org.hmh.global.auth.social.apple.response.ApplePublicKeyResponse;
import sopt.org.hmh.global.auth.social.apple.response.ApplePublicKeysResponse;

@Component
public class ApplePublicKeyGenerator {
public PublicKey generatePublicKeyWithApplePublicKeys(Map<String, String> headers, ApplePublicKeys applePublicKeys) {
ApplePublicKey applePublicKey = applePublicKeys
public PublicKey generatePublicKeyWithApplePublicKeys(Map<String, String> headers, ApplePublicKeysResponse applePublicKeysResponse) {
ApplePublicKeyResponse applePublicKeyResponse = applePublicKeysResponse
.getMatchesKey(headers.get("alg"), headers.get("kid"));

byte[] nBytes = Base64.getUrlDecoder().decode(applePublicKey.n());
byte[] eBytes = Base64.getUrlDecoder().decode(applePublicKey.e());
byte[] nBytes = Base64.getUrlDecoder().decode(applePublicKeyResponse.n());
byte[] eBytes = Base64.getUrlDecoder().decode(applePublicKeyResponse.e());

RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(
new BigInteger(1, nBytes), new BigInteger(1, eBytes));

try {
KeyFactory keyFactory = KeyFactory.getInstance(applePublicKey.kty());
KeyFactory keyFactory = KeyFactory.getInstance(applePublicKeyResponse.kty());
return keyFactory.generatePublic(rsaPublicKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException exception) {
throw new JwtException(JwtError.UNABLE_TO_CREATE_APPLE_PUBLIC_KEY);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package sopt.org.hmh.global.auth.social.apple.request;
package sopt.org.hmh.global.auth.social.apple.response;

public record ApplePublicKey(
public record ApplePublicKeyResponse(
String kty,
String kid,
String use,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package sopt.org.hmh.global.auth.social.apple.response;

import java.util.List;
import lombok.Getter;
import sopt.org.hmh.global.auth.jwt.exception.JwtError;
import sopt.org.hmh.global.auth.jwt.exception.JwtException;

@Getter
public class ApplePublicKeysResponse {
private List<ApplePublicKeyResponse> keys;

public ApplePublicKeyResponse getMatchesKey(String alg, String kid) {
return keys.stream()
.filter(applePublicKeyResponse -> applePublicKeyResponse.alg().equals(alg) && applePublicKeyResponse.kid().equals(kid))
.findFirst()
.orElseThrow(() -> new JwtException(JwtError.INVALID_IDENTITY_TOKEN));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package sopt.org.hmh.global.auth.social.kakao.fegin;

import sopt.org.hmh.global.auth.social.kakao.response.KakaoTokenResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "kakaoAuthApiClient", url = "https://kauth.kakao.com")
public interface KakaoAuthFeignClient {

@PostMapping(value = "/oauth/token", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
KakaoTokenResponse getOAuth2AccessToken(
@RequestParam("grant_type") String grantType,
@RequestParam("client_id") String clientId,
@RequestParam("redirect_uri") String redirectUri,
@RequestParam("code") String code
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import sopt.org.hmh.global.auth.social.kakao.request.KakaoUserRequest;
import sopt.org.hmh.global.auth.social.kakao.response.KakaoUserResponse;

@FeignClient(name = "kakaoApiClient", url = "${oauth2.kakao.base-url}")
public interface KakaoFeignClient {

@GetMapping(value = "/v2/user/me")
KakaoUserRequest getUserInformation(@RequestHeader(HttpHeaders.AUTHORIZATION) final String accessToken);
KakaoUserResponse getUserInformation(@RequestHeader(HttpHeaders.AUTHORIZATION) final String accessToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,38 @@

import feign.FeignException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import sopt.org.hmh.domain.user.domain.User;
import sopt.org.hmh.global.auth.jwt.exception.JwtError;
import sopt.org.hmh.global.auth.jwt.exception.JwtException;
import sopt.org.hmh.global.auth.social.kakao.request.KakaoUserRequest;
import sopt.org.hmh.global.auth.social.SocialAccessTokenResponse;
import sopt.org.hmh.global.auth.social.kakao.response.KakaoTokenResponse;
import sopt.org.hmh.global.auth.social.kakao.response.KakaoUserResponse;

@Service
@Transactional
@RequiredArgsConstructor
public class KakaoLoginService {

private final KakaoFeignClient kakaoFeignClient;
private final KakaoAuthFeignClient kakaoAuthFeignClient;

@Value("${oauth2.kakao.client-id}")
private String CLIENT_ID;
@Value("${oauth2.kakao.authorization-grant-type}")
private String GRANT_TYPE;
@Value("${oauth2.kakao.redirect-uri}")
private String REDIRECT_URL;

/**
* 카카오 Acess Token으로 유저의 소셜 Id 불러오는 함수
*/
public String getSocialIdByKakao(final String socialAccessToken) {
return String.valueOf(getKakaoUserRequest(socialAccessToken).id());
}

/**
* 카카오 Access Token으로 유저 정보 업데이트
*/
public void updateUserInfoByKakao(User loginUser, final String socialAccessToken) {
KakaoUserRequest userRequest = getKakaoUserRequest(socialAccessToken);
KakaoUserResponse userRequest = getKakaoUserRequest(socialAccessToken);

try {
String nickname = userRequest.kakaoAccount().profile().nickname();
Expand All @@ -45,11 +50,25 @@ public void updateUserInfoByKakao(User loginUser, final String socialAccessToken
}
}

private KakaoUserRequest getKakaoUserRequest(final String socialAccessToken) {
private KakaoUserResponse getKakaoUserRequest(final String socialAccessToken) {
try {
return kakaoFeignClient.getUserInformation(socialAccessToken);
} catch (FeignException exception) {
throw new JwtException(JwtError.INVALID_SOCIAL_ACCESS_TOKEN);
}
}

public SocialAccessTokenResponse getKakaoAccessToken(String code) {

KakaoTokenResponse kakaoTokenResponse = kakaoAuthFeignClient.getOAuth2AccessToken(
GRANT_TYPE,
CLIENT_ID,
REDIRECT_URL,
code
);

return SocialAccessTokenResponse.builder()
.socialAccessToken(kakaoTokenResponse.accessToken())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sopt.org.hmh.global.auth.social.kakao.request;
package sopt.org.hmh.global.auth.social.kakao.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package sopt.org.hmh.global.auth.social.kakao.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record KakaoTokenResponse(
String accessToken
) {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sopt.org.hmh.global.auth.social.kakao.request;
package sopt.org.hmh.global.auth.social.kakao.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package sopt.org.hmh.global.auth.social.kakao.request;
package sopt.org.hmh.global.auth.social.kakao.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record KakaoUserRequest(
public record KakaoUserResponse(
Long id,
KakaoAccount kakaoAccount
) {
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/sopt/org/hmh/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public class SecurityConfig {
private final JwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint;

private static final String[] AUTH_WHITELIST = {
"/", "/error", "/health", "/api/v1/dummy/**",
// Global
"/health",
"/error",

// Swagger
"/swagger-ui/**",
Expand All @@ -38,6 +40,10 @@ public class SecurityConfig {
"/api/v1/user/login",
"/api/v1/user/reissue",
"/api/v1/user/signup",
"/api/v1/user/social/token/kakao",

// etc
"/api/v1/dummy/**",
};

@Bean
Expand Down

0 comments on commit 22c8b3c

Please sign in to comment.