diff --git a/build.gradle b/build.gradle index 0df2d15f..d6bfc728 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,11 @@ dependencies { runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' + // feign + implementation platform("org.springframework.cloud:spring-cloud-dependencies:2021.0.5") + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + implementation 'io.github.openfeign:feign-okhttp' + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' implementation 'mysql:mysql-connector-java:8.0.28' diff --git a/src/main/java/com/kcy/fitapet/domain/member/domain/Member.java b/src/main/java/com/kcy/fitapet/domain/member/domain/Member.java index 6c835f2c..89f4962f 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/domain/Member.java +++ b/src/main/java/com/kcy/fitapet/domain/member/domain/Member.java @@ -4,9 +4,8 @@ import com.kcy.fitapet.domain.member.type.converter.RoleTypeConverter; import com.kcy.fitapet.domain.model.Auditable; import com.kcy.fitapet.domain.notification.domain.Notification; -import com.kcy.fitapet.domain.member.type.NotificationSetting; import com.kcy.fitapet.domain.notification.type.NotificationType; -import com.kcy.fitapet.domain.oauth2.domain.OAuthID; +import com.kcy.fitapet.domain.oauth.domain.OAuthID; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.ColumnDefault; diff --git a/src/main/java/com/kcy/fitapet/domain/member/type/NotificationSetting.java b/src/main/java/com/kcy/fitapet/domain/member/domain/NotificationSetting.java similarity index 97% rename from src/main/java/com/kcy/fitapet/domain/member/type/NotificationSetting.java rename to src/main/java/com/kcy/fitapet/domain/member/domain/NotificationSetting.java index 91b81c8c..2b23eb97 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/type/NotificationSetting.java +++ b/src/main/java/com/kcy/fitapet/domain/member/domain/NotificationSetting.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.domain.member.type; +package com.kcy.fitapet.domain.member.domain; import com.kcy.fitapet.domain.notification.type.NotificationType; import jakarta.persistence.*; diff --git a/src/main/java/com/kcy/fitapet/domain/member/dto/account/AccountProfileRes.java b/src/main/java/com/kcy/fitapet/domain/member/dto/account/AccountProfileRes.java index 07f30be7..3518bb97 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/dto/account/AccountProfileRes.java +++ b/src/main/java/com/kcy/fitapet/domain/member/dto/account/AccountProfileRes.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.member.dto.account; import com.kcy.fitapet.domain.member.domain.Member; -import com.kcy.fitapet.domain.member.type.NotificationSetting; +import com.kcy.fitapet.domain.member.domain.NotificationSetting; import com.kcy.fitapet.global.common.util.bind.Dto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; diff --git a/src/main/java/com/kcy/fitapet/domain/member/dto/auth/SignUpReq.java b/src/main/java/com/kcy/fitapet/domain/member/dto/auth/SignUpReq.java index aaf0e088..595050f1 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/dto/auth/SignUpReq.java +++ b/src/main/java/com/kcy/fitapet/domain/member/dto/auth/SignUpReq.java @@ -1,7 +1,6 @@ package com.kcy.fitapet.domain.member.dto.auth; import com.kcy.fitapet.domain.member.domain.Member; -import com.kcy.fitapet.domain.member.type.NotificationSetting; import com.kcy.fitapet.domain.member.type.RoleType; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java index bba950e3..43763f01 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java @@ -12,8 +12,8 @@ import com.kcy.fitapet.global.common.response.code.StatusCode; import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; import com.kcy.fitapet.global.common.util.jwt.JwtUtil; -import com.kcy.fitapet.global.common.util.jwt.entity.JwtUserInfo; -import com.kcy.fitapet.global.common.util.jwt.entity.SmsAuthInfo; +import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.util.jwt.dto.SmsAuthInfo; import com.kcy.fitapet.global.common.redis.forbidden.ForbiddenTokenService; import com.kcy.fitapet.global.common.redis.refresh.RefreshToken; import com.kcy.fitapet.global.common.redis.refresh.RefreshTokenService; diff --git a/src/main/java/com/kcy/fitapet/domain/notification/domain/Notification.java b/src/main/java/com/kcy/fitapet/domain/notification/domain/Notification.java index f35f5390..943bd43f 100644 --- a/src/main/java/com/kcy/fitapet/domain/notification/domain/Notification.java +++ b/src/main/java/com/kcy/fitapet/domain/notification/domain/Notification.java @@ -3,7 +3,7 @@ import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.domain.model.Auditable; import com.kcy.fitapet.domain.notification.type.NotificationType; -import com.kcy.fitapet.domain.notification.type.NotificationTypeConverter; +import com.kcy.fitapet.domain.notification.type.converter.NotificationTypeConverter; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.ColumnDefault; diff --git a/src/main/java/com/kcy/fitapet/domain/notification/type/NotificationTypeConverter.java b/src/main/java/com/kcy/fitapet/domain/notification/type/converter/NotificationTypeConverter.java similarity index 88% rename from src/main/java/com/kcy/fitapet/domain/notification/type/NotificationTypeConverter.java rename to src/main/java/com/kcy/fitapet/domain/notification/type/converter/NotificationTypeConverter.java index 649ddf32..7b21c1ed 100644 --- a/src/main/java/com/kcy/fitapet/domain/notification/type/NotificationTypeConverter.java +++ b/src/main/java/com/kcy/fitapet/domain/notification/type/converter/NotificationTypeConverter.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.domain.notification.type; +package com.kcy.fitapet.domain.notification.type.converter; import com.kcy.fitapet.domain.notification.type.NotificationType; import com.kcy.fitapet.global.common.util.converter.AbstractLegacyEnumAttributeConverter; diff --git a/src/main/java/com/kcy/fitapet/domain/notification/type/NotificationTypeQueryConverter.java b/src/main/java/com/kcy/fitapet/domain/notification/type/converter/NotificationTypeQueryConverter.java similarity index 75% rename from src/main/java/com/kcy/fitapet/domain/notification/type/NotificationTypeQueryConverter.java rename to src/main/java/com/kcy/fitapet/domain/notification/type/converter/NotificationTypeQueryConverter.java index d5a0ebaa..7cb265cc 100644 --- a/src/main/java/com/kcy/fitapet/domain/notification/type/NotificationTypeQueryConverter.java +++ b/src/main/java/com/kcy/fitapet/domain/notification/type/converter/NotificationTypeQueryConverter.java @@ -1,5 +1,6 @@ -package com.kcy.fitapet.domain.notification.type; +package com.kcy.fitapet.domain.notification.type.converter; +import com.kcy.fitapet.domain.notification.type.NotificationType; import org.springframework.core.convert.converter.Converter; public class NotificationTypeQueryConverter implements Converter { diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/api/OAuthApi.java b/src/main/java/com/kcy/fitapet/domain/oauth/api/OAuthApi.java new file mode 100644 index 00000000..c389e8cf --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/api/OAuthApi.java @@ -0,0 +1,45 @@ +package com.kcy.fitapet.domain.oauth.api; + +import com.kcy.fitapet.domain.member.dto.auth.SignInReq; +import com.kcy.fitapet.domain.oauth.dto.OauthSignInReq; +import com.kcy.fitapet.domain.oauth.dto.OauthSignUpReq; +import com.kcy.fitapet.domain.oauth.service.OAuthService; +import com.kcy.fitapet.domain.oauth.type.ProviderType; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "OAuth API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/auth/oauth") +@Slf4j +public class OAuthApi { + private final OAuthService oAuthService; + + @PostMapping("") + @PreAuthorize("isAnonymous()") + public void signIn( + @RequestParam("provider") ProviderType provider, + @RequestBody @Valid OauthSignInReq req + ) { + if (ProviderType.NAVER.equals(provider)) { + + } else { + + } + } + + @PostMapping("/{id}") + @PreAuthorize("isAnonymous()") + public void signUp( + @PathVariable("id") String id, + @RequestParam("provider") ProviderType provider, + @RequestBody @Valid OauthSignUpReq req + ) { + + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/dao/OAuthRepository.java b/src/main/java/com/kcy/fitapet/domain/oauth/dao/OAuthRepository.java new file mode 100644 index 00000000..0e048bf2 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/dao/OAuthRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.domain.oauth.dao; + +import com.kcy.fitapet.domain.oauth.api.OAuthApi; +import com.kcy.fitapet.global.common.repository.ExtendedRepository; + +public interface OAuthRepository extends ExtendedRepository { +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/domain/OAuthAccount.java b/src/main/java/com/kcy/fitapet/domain/oauth/domain/OAuthAccount.java new file mode 100644 index 00000000..df953a56 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/domain/OAuthAccount.java @@ -0,0 +1,45 @@ +package com.kcy.fitapet.domain.oauth.domain; + +import com.kcy.fitapet.domain.member.domain.Member; +import com.kcy.fitapet.domain.model.Auditable; +import com.kcy.fitapet.domain.oauth.type.ProviderType; +import com.kcy.fitapet.domain.oauth.type.converter.ProviderTypeConverter; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "OAUTH") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString(of = {"id", "provider"}) +public class OAuthAccount extends Auditable { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "oauth_id") + private Long OAuthID; + @Convert(converter = ProviderTypeConverter.class) + private ProviderType provider; + private String email; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @Builder + public OAuthAccount(Long id, Long OAuthID, ProviderType provider, String email, Member member) { + this.id = id; + this.OAuthID = OAuthID; + this.provider = provider; + this.email = email; + this.member = member; + } + + public static OAuthAccount of(Long OAuthID, ProviderType provider, String email, Member member) { + return OAuthAccount.builder() + .OAuthID(OAuthID) + .provider(provider) + .email(email) + .member(member) + .build(); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignInReq.java b/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignInReq.java new file mode 100644 index 00000000..8aaf6f69 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignInReq.java @@ -0,0 +1,11 @@ +package com.kcy.fitapet.domain.oauth.dto; + +import jakarta.validation.constraints.NotEmpty; + +public record OauthSignInReq( + @NotEmpty + String id, + @NotEmpty + String id_token +) { +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignUpReq.java b/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignUpReq.java new file mode 100644 index 00000000..f1002a3b --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignUpReq.java @@ -0,0 +1,13 @@ +package com.kcy.fitapet.domain.oauth.dto; + +import jakarta.validation.constraints.NotBlank; + +public record OauthSignUpReq( + @NotBlank + String phone, + @NotBlank + String name, + @NotBlank + String uid +) { +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/OAuthService.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/OAuthService.java new file mode 100644 index 00000000..95e25b28 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/OAuthService.java @@ -0,0 +1,34 @@ +package com.kcy.fitapet.domain.oauth.service; + +import com.kcy.fitapet.domain.oauth.dao.OAuthRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class OAuthService { + private final OAuthRepository oAuthRepository; + + @Transactional + public void signUpByOIDC() { + + } + + @Transactional + public void signInByOIDC() { + + } + + @Transactional + public void signInByCode() { + + } + + @Transactional + public void signUpByCode() { + + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/type/ProviderType.java b/src/main/java/com/kcy/fitapet/domain/oauth/type/ProviderType.java new file mode 100644 index 00000000..d78eac8a --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/type/ProviderType.java @@ -0,0 +1,47 @@ +package com.kcy.fitapet.domain.oauth.type; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.kcy.fitapet.global.common.util.converter.LegacyCommonType; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Map; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toMap; + +@RequiredArgsConstructor +public enum ProviderType implements LegacyCommonType { + KAKAO("1", "카카오"), + GOOGLE("2", "구글"), + NAVER("2", "네이버"), + APPLE("3", "애플"); + + private final String code; + private final String type; + + private static final Map stringToEnum = + Stream.of(values()).collect(toMap(Object::toString, e -> e)); + + + @Override + public String getCode() { + return code; + } + + @JsonValue + public String getType() { + return type; + } + + @JsonCreator + public static ProviderType fromString(String type) { + return stringToEnum.get(type.toUpperCase()); + } + + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/type/converter/ProviderTypeConverter.java b/src/main/java/com/kcy/fitapet/domain/oauth/type/converter/ProviderTypeConverter.java new file mode 100644 index 00000000..b2a93a00 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/type/converter/ProviderTypeConverter.java @@ -0,0 +1,12 @@ +package com.kcy.fitapet.domain.oauth.type.converter; + +import com.kcy.fitapet.domain.oauth.type.ProviderType; +import com.kcy.fitapet.global.common.util.converter.AbstractLegacyEnumAttributeConverter; + +public class ProviderTypeConverter extends AbstractLegacyEnumAttributeConverter { + private static final String ENUM_NAME = "제공자"; + + public ProviderTypeConverter() { + super(ProviderType.class, false, ENUM_NAME); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/type/converter/ProviderTypeQueryConverter.java b/src/main/java/com/kcy/fitapet/domain/oauth/type/converter/ProviderTypeQueryConverter.java new file mode 100644 index 00000000..8177140a --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/type/converter/ProviderTypeQueryConverter.java @@ -0,0 +1,15 @@ +package com.kcy.fitapet.domain.oauth.type.converter; + +import com.kcy.fitapet.domain.oauth.type.ProviderType; +import org.springframework.core.convert.converter.Converter; + +public class ProviderTypeQueryConverter implements Converter { + @Override + public ProviderType convert(String source) { + try { + return ProviderType.valueOf(source.toUpperCase()); + } catch (IllegalArgumentException e) { + throw null; + } + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth2/api/OAuthApi.java b/src/main/java/com/kcy/fitapet/domain/oauth2/api/OAuthApi.java deleted file mode 100644 index ee1851de..00000000 --- a/src/main/java/com/kcy/fitapet/domain/oauth2/api/OAuthApi.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.kcy.fitapet.domain.oauth2.api; - -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Tag(name = "OAuth API") -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/auth/oauth") -@Slf4j -public class OAuthApi { -} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth2/domain/OAuthID.java b/src/main/java/com/kcy/fitapet/domain/oauth2/domain/OAuthID.java deleted file mode 100644 index e05cf31e..00000000 --- a/src/main/java/com/kcy/fitapet/domain/oauth2/domain/OAuthID.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.kcy.fitapet.domain.oauth2.domain; - -import com.kcy.fitapet.domain.member.domain.Member; -import com.kcy.fitapet.domain.model.Auditable; -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Table(name = "OAUTH_ID") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@ToString(of = {"id", "provider"}) -public class OAuthID extends Auditable { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "access_token") - private String accessToken; - private String provider; - @Column(name = "expired_time") - private Long expired_time; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id") - private Member member; - - @Builder - private OAuthID(String accessToken, String provider, Long expired_time) { - this.accessToken = accessToken; - this.provider = provider; - this.expired_time = expired_time; - } - - public static OAuthID of(String accessToken, String provider, Long expired_time) { - return OAuthID.builder() - .accessToken(accessToken) - .provider(provider) - .expired_time(expired_time) - .build(); - } -} diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenServiceImpl.java b/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenServiceImpl.java index 0292685d..1db59951 100644 --- a/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenServiceImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenServiceImpl.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.global.common.redis.refresh; import com.kcy.fitapet.global.common.util.jwt.JwtUtil; -import com.kcy.fitapet.global.common.util.jwt.entity.JwtUserInfo; +import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/kcy/fitapet/global/common/security/authentication/CustomUserDetails.java b/src/main/java/com/kcy/fitapet/global/common/security/authentication/CustomUserDetails.java index 6bb3e662..a3c053ba 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/authentication/CustomUserDetails.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/authentication/CustomUserDetails.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.domain.member.type.RoleType; -import com.kcy.fitapet.global.common.util.jwt.entity.JwtUserInfo; +import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; import lombok.Builder; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; diff --git a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java index 9d788ae3..62ab2eb4 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java @@ -3,7 +3,7 @@ import com.kcy.fitapet.global.common.security.authentication.UserDetailServiceImpl; import com.kcy.fitapet.global.common.util.cookie.CookieUtil; import com.kcy.fitapet.global.common.util.jwt.JwtUtil; -import com.kcy.fitapet.global.common.util.jwt.entity.JwtUserInfo; +import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; import com.kcy.fitapet.global.common.redis.forbidden.ForbiddenTokenService; diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCHelper.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCHelper.java new file mode 100644 index 00000000..3e0225b5 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCHelper.java @@ -0,0 +1,28 @@ +package com.kcy.fitapet.global.common.security.oauth; + +import com.kcy.fitapet.global.common.security.oauth.dto.OIDCDecodePayload; +import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKey; +import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKeyResponse; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Helper; + +@Helper +@RequiredArgsConstructor +public class OauthOIDCHelper { + private final OauthOIDCProvider oauthOIDCProvider; + + public OIDCDecodePayload getPayloadFromIdToken(String token, String iss, String aud, OIDCPublicKeyResponse response) { + String kid = getKidFromUnsignedIdToken(token, iss, aud); + + OIDCPublicKey key = response.getKeys().stream() + .filter(k -> k.kid().equals(kid)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No matching key found")); + + return oauthOIDCProvider.getOIDCTokenBody(token, key.n(), key.e()); + } + + private String getKidFromUnsignedIdToken(String token, String iss, String aud) { + return oauthOIDCProvider.getKidFromUnsignedTokenHeader(token, iss, aud); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProvider.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProvider.java new file mode 100644 index 00000000..be35d966 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProvider.java @@ -0,0 +1,12 @@ +package com.kcy.fitapet.global.common.security.oauth; + +import com.kcy.fitapet.global.common.security.oauth.dto.OIDCDecodePayload; +import org.springframework.stereotype.Component; + +@Component +public interface OauthOIDCProvider { + + String getKidFromUnsignedTokenHeader(String token, String iss, String aud); + + OIDCDecodePayload getOIDCTokenBody(String token, String modulus, String exponent); +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/dto/OIDCDecodePayload.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/dto/OIDCDecodePayload.java new file mode 100644 index 00000000..7d7b4746 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/dto/OIDCDecodePayload.java @@ -0,0 +1,12 @@ +package com.kcy.fitapet.global.common.security.oauth.dto; + +public record OIDCDecodePayload( + /* issuer */ + String iss, + /* client id */ + String aud, + /* aouth provider account unique id */ + String sub, + String email +) { +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/dto/OIDCPublicKey.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/dto/OIDCPublicKey.java new file mode 100644 index 00000000..1c3a28af --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/dto/OIDCPublicKey.java @@ -0,0 +1,11 @@ +package com.kcy.fitapet.global.common.security.oauth.dto; + +public record OIDCPublicKey( + String kid, + String kty, + String alg, + String use, + String n, + String e +) { +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/dto/OIDCPublicKeyResponse.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/dto/OIDCPublicKeyResponse.java new file mode 100644 index 00000000..c68aceca --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/dto/OIDCPublicKeyResponse.java @@ -0,0 +1,12 @@ +package com.kcy.fitapet.global.common.security.oauth.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +public class OIDCPublicKeyResponse { + List keys; +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoOauthClient.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoOauthClient.java new file mode 100644 index 00000000..e6e540b4 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoOauthClient.java @@ -0,0 +1,18 @@ +package com.kcy.fitapet.global.common.security.oauth.kakao; + +import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKeyResponse; +import com.kcy.fitapet.global.config.feign.KakaoOauthConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +@FeignClient( + name = "KakaoOauthClient", + url = "${security.oauth2.client.provider.authorization-uri}", + configuration = KakaoOauthConfig.class +) +public interface KakaoOauthClient { + @Cacheable(value = "KakaoOauth", cacheManager = "oidcCacheManger") + @GetMapping("/.well-knowm/jwks.json") + OIDCPublicKeyResponse getKakaoOIDCPublicKey(); +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KauthErrorDecoder.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KauthErrorDecoder.java new file mode 100644 index 00000000..b37165db --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KauthErrorDecoder.java @@ -0,0 +1,11 @@ +package com.kcy.fitapet.global.common.security.oauth.kakao; + +import feign.Response; +import feign.codec.ErrorDecoder; + +public class KauthErrorDecoder implements ErrorDecoder { + @Override + public Exception decode(String methodKey, Response response) { + return null; + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/dto/KakaoKauthErrorResponse.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/dto/KakaoKauthErrorResponse.java new file mode 100644 index 00000000..c143d301 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/dto/KakaoKauthErrorResponse.java @@ -0,0 +1,32 @@ +package com.kcy.fitapet.global.common.security.oauth.kakao.dto; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.kcy.fitapet.global.common.response.code.ErrorCode; +import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; +import feign.Response; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.io.IOException; +import java.io.InputStream; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class KakaoKauthErrorResponse { + private String error; + private String errorCode; + private String errorDescription; + + public static KakaoKauthErrorResponse from(Response response) { + try (InputStream bodyIs = response.body().asInputStream()) { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(bodyIs, KakaoKauthErrorResponse.class); + } catch (IOException e) { + throw new GlobalErrorException(ErrorCode.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth2/OAuth2Provider.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth2/OAuth2Provider.java deleted file mode 100644 index 700d1364..00000000 --- a/src/main/java/com/kcy/fitapet/global/common/security/oauth2/OAuth2Provider.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.kcy.fitapet.global.common.security.oauth2; - -public interface OAuth2Provider { - -} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java index 32aa995d..89a1249a 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java @@ -1,11 +1,10 @@ package com.kcy.fitapet.global.common.util.jwt; -import com.kcy.fitapet.global.common.util.jwt.entity.JwtUserInfo; -import com.kcy.fitapet.global.common.util.jwt.entity.SmsAuthInfo; +import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.util.jwt.dto.SmsAuthInfo; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; import java.time.LocalDateTime; -import java.util.Date; public interface JwtUtil { /** diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java index f9b2bf9c..4f6c482b 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java @@ -3,8 +3,8 @@ import com.kcy.fitapet.domain.member.type.RoleType; import com.kcy.fitapet.global.common.util.DateUtil; import com.kcy.fitapet.global.common.util.jwt.exception.JwtErrorCodeUtil; -import com.kcy.fitapet.global.common.util.jwt.entity.JwtUserInfo; -import com.kcy.fitapet.global.common.util.jwt.entity.SmsAuthInfo; +import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.util.jwt.dto.SmsAuthInfo; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; import io.jsonwebtoken.Claims; diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/OauthOIDCProviderImpl.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/OauthOIDCProviderImpl.java new file mode 100644 index 00000000..4d63df16 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/OauthOIDCProviderImpl.java @@ -0,0 +1,88 @@ +package com.kcy.fitapet.global.common.util.jwt; + +import com.kcy.fitapet.global.common.security.oauth.dto.OIDCDecodePayload; +import com.kcy.fitapet.global.common.security.oauth.OauthOIDCProvider; +import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; +import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.util.jwt.exception.JwtErrorCodeUtil; +import io.jsonwebtoken.*; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigInteger; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.Base64; + +@Slf4j +public class OauthOIDCProviderImpl implements OauthOIDCProvider { + private final String KID = "kid"; + private final String RSA = "RSA"; + + @Override + public String getKidFromUnsignedTokenHeader(String token, String iss, String aud) { + return (String) getUnsignedTokenClaims(token, iss, aud).getHeader().get(KID); + } + + @Override + public OIDCDecodePayload getOIDCTokenBody(String token, String modulus, String exponent) { + Claims body = getOIDCTokenJws(token, modulus, exponent).getBody(); + + return new OIDCDecodePayload( + body.getIssuer(), + body.getAudience(), + body.getSubject(), + body.get("email", String.class)); + } + + private Jwt getUnsignedTokenClaims(String token, String iss, String aud) { + try { + return Jwts.parserBuilder() + .requireAudience(aud) + .requireIssuer(iss) + .build() + .parseClaimsJwt(getUnsignedToken(token)); + } catch (JwtException e) { + final AuthErrorCode errorCode = JwtErrorCodeUtil.determineErrorCode(e, AuthErrorCode.FAILED_AUTHENTICATION); + + log.warn("Error code : {}, Error - {}, {}", errorCode, e.getClass(), e.getMessage()); + throw new AuthErrorException(errorCode, e.toString()); + } + } + + private String getUnsignedToken(String token){ + String[] splitToken = token.split("\\."); + if (splitToken.length != 3) throw new AuthErrorException(AuthErrorCode.INVALID_TOKEN, "Invalid token"); + return splitToken[0] + "." + splitToken[1] + "."; + } + + private Jws getOIDCTokenJws(String token, String modulus, String exponent) { + try { + return Jwts.parserBuilder() + .setSigningKey(getRSAPublicKey(modulus, exponent)) + .build() + .parseClaimsJws(token); + } catch (JwtException e) { + final AuthErrorCode errorCode = JwtErrorCodeUtil.determineErrorCode(e, AuthErrorCode.FAILED_AUTHENTICATION); + + log.warn("Error code : {}, Error - {}, {}", errorCode, e.getClass(), e.getMessage()); + throw new AuthErrorException(errorCode, e.toString()); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + log.warn("Error - {}, {}", e.getClass(), e.getMessage()); + throw new AuthErrorException(AuthErrorCode.INVALID_TOKEN, e.toString()); + } + } + + private Key getRSAPublicKey(String modulus, String exponent) throws NoSuchAlgorithmException, InvalidKeySpecException { + KeyFactory keyFactory = KeyFactory.getInstance(RSA); + byte[] decodeN = Base64.getUrlDecoder().decode(modulus); + byte[] decodeE = Base64.getUrlDecoder().decode(exponent); + BigInteger n = new BigInteger(1, decodeN); + BigInteger e = new BigInteger(1, decodeE); + + RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e); + return keyFactory.generatePublic(publicKeySpec); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/Jwt.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/Jwt.java new file mode 100644 index 00000000..7395e03a --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/Jwt.java @@ -0,0 +1,10 @@ +package com.kcy.fitapet.global.common.util.jwt.dto; + +public record Jwt( + String accessToken, + String refreshToken +) { + public static Jwt of(String accessToken, String refreshToken) { + return new Jwt(accessToken, refreshToken); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtUserInfo.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/JwtUserInfo.java similarity index 80% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtUserInfo.java rename to src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/JwtUserInfo.java index 173dbab1..c483a8fd 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtUserInfo.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/JwtUserInfo.java @@ -1,7 +1,8 @@ -package com.kcy.fitapet.global.common.util.jwt.entity; +package com.kcy.fitapet.global.common.util.jwt.dto; import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.domain.member.type.RoleType; +import com.kcy.fitapet.global.common.util.jwt.entity.JwtDto; import lombok.Builder; @Builder diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/SmsAuthInfo.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/SmsAuthInfo.java similarity index 69% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/SmsAuthInfo.java rename to src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/SmsAuthInfo.java index fad139d3..ce302697 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/SmsAuthInfo.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/SmsAuthInfo.java @@ -1,5 +1,6 @@ -package com.kcy.fitapet.global.common.util.jwt.entity; +package com.kcy.fitapet.global.common.util.jwt.dto; +import com.kcy.fitapet.global.common.util.jwt.entity.JwtDto; import lombok.Builder; @Builder diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java index d85b3ad4..4142d232 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java @@ -38,6 +38,7 @@ public enum AuthErrorCode implements StatusCode { /** * 500 INTERNAL_SERVER_ERROR: 서버 내부 에러 */ + INVALID_TOKEN(INTERNAL_SERVER_ERROR, "유효하지 않은 토큰입니다"), INVALID_JWT_DTO_FORMAT(INTERNAL_SERVER_ERROR, "서버 내부 에러가 발생했습니다."), UNEXPECTED_ERROR(INTERNAL_SERVER_ERROR, "예상치 못한 에러가 발생했습니다."),; diff --git a/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java b/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java new file mode 100644 index 00000000..f2280e15 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java @@ -0,0 +1,9 @@ +package com.kcy.fitapet.global.config; + +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableFeignClients +public class FeignConfig { +} diff --git a/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java b/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java index ca60b12e..8e0c74ce 100644 --- a/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java @@ -2,8 +2,11 @@ import com.kcy.fitapet.global.common.redis.sms.SmsCertification; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; @@ -11,8 +14,11 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; +import java.time.Duration; + @Configuration @EnableRedisRepositories public class RedisConfig { @@ -38,4 +44,20 @@ public RedisTemplate redisTemplate() { template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } + + @Bean + public CacheManager oidcCacheManger(RedisConnectionFactory cf) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer() + )) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer() + )) + .entryTtl(Duration.ofDays(3)); + + return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf).cacheDefaults(config).build(); + } } \ No newline at end of file diff --git a/src/main/java/com/kcy/fitapet/global/config/WebConfig.java b/src/main/java/com/kcy/fitapet/global/config/WebConfig.java index 5f414939..4f3b12fd 100644 --- a/src/main/java/com/kcy/fitapet/global/config/WebConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/WebConfig.java @@ -1,7 +1,8 @@ package com.kcy.fitapet.global.config; import com.kcy.fitapet.domain.member.type.converter.MemberAttrTypeConverter; -import com.kcy.fitapet.domain.notification.type.NotificationTypeQueryConverter; +import com.kcy.fitapet.domain.notification.type.converter.NotificationTypeQueryConverter; +import com.kcy.fitapet.domain.oauth.type.converter.ProviderTypeQueryConverter; import com.kcy.fitapet.global.common.redis.sms.SmsPrefixConverter; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; @@ -11,9 +12,9 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registrar) { - registrar.addConverter(new SmsPrefixConverter()); registrar.addConverter(new NotificationTypeQueryConverter()); registrar.addConverter(new MemberAttrTypeConverter()); + registrar.addConverter(new ProviderTypeQueryConverter()); } } diff --git a/src/main/java/com/kcy/fitapet/global/config/feign/KakaoOauthConfig.java b/src/main/java/com/kcy/fitapet/global/config/feign/KakaoOauthConfig.java new file mode 100644 index 00000000..a55cb9c9 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/config/feign/KakaoOauthConfig.java @@ -0,0 +1,23 @@ +package com.kcy.fitapet.global.config.feign; + +import com.kcy.fitapet.global.common.security.oauth.kakao.KauthErrorDecoder; +import feign.codec.Encoder; +import feign.codec.ErrorDecoder; +import feign.form.FormEncoder; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +@Import(KauthErrorDecoder.class) +public class KakaoOauthConfig { + @Bean + @ConditionalOnMissingBean(value = ErrorDecoder.class) + public KauthErrorDecoder commonFeignErrorDecoder() { + return new KauthErrorDecoder(); + } + + @Bean + Encoder formEncoder() { + return new FormEncoder(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dbebeceb..2113df1a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -38,55 +38,34 @@ spring: resources: add-mappings: false -# security: -# oauth2: -# client: -# registration: -# naver: -# client-id: ${NAVER_CLIENT_ID} -# client-secret: ${NAVER_CLIENT_SECRET} -# client-name: Naver -# redirect-uri: "http://localhost:8082/oauth2/callback/naver" -# authorization-grant-type: authorization_code -# google: -# client-id: ${GOOGLE_CLIENT_ID} -# client-secret: ${GOOGLE_CLIENT_SECRET} -# client-name: Google -# redirect-uri: "http://localhost:8082/login/oauth2/code/google" -# authorization-grant-type: authorization_code -# kakao: -# client-id: ${KAKAO_CLIENT_ID} -# client-secret: ${KAKAO_CLIENT_SECRET} -# client-name: Kakao -# redirect-uri: "http://localhost:8082/login/oauth2/code/kakao" -# authorization-grant-type: authorization_code -# apple: -# client-id: ${APPLE_CLIENT_ID} -# client-secret: ${APPLE_CLIENT_SECRET} -# client-name: Apple -# redirect-uri: "http://localhost:8082/login/oauth2/code/apple" -# authorization-grant-type: authorization_code -# provider: -# naver: -# authorization-uri: https://nid.naver.com/oauth2.0/authorize -# token-uri: https://nid.naver.com/oauth2.0/token -# user-info-uri: https://openapi.naver.com/v1/nid/me -# user-name-attribute: response -# google: -# authorization-uri: https://accounts.google.com/o/oauth2/v2/auth -# token-uri: https://oauth2.googleapis.com/token -# user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo -# user-name-attribute: sub -# kakao: -# authorization-uri: https://kauth.kakao.com/oauth/authorize -# token-uri: https://kauth.kakao.com/oauth/token -# user-info-uri: https://kapi.kakao.com/v2/user/me -# user-name-attribute: id -# apple: -# authorization-uri: https://appleid.apple.com/auth/authorize -# token-uri: https://appleid.apple.com/auth/token -# user-info-uri: https://appleid.apple.com/auth/userinfo -# user-name-attribute: sub + security: + oauth2: + client: + provider: + kakao: + authorization-uri: ${KAKAO_AUTH_URI} + registration: + kakao: + client-id: ${KAKAO_CLIENT_ID} + client-secret: ${KAKAO_CLIENT_SECRET} + client-name: Kakao + scope: + - profile_nickname + - profile_image + - account_email + + +feign: + okhttp: + enabled: true + client: + config: + default: + connectTimeout: 5000 + readTimeout: 10000 + loggerLevel: full + httpclient: + max-connections: 1000 springdoc: default-consumes-media-type: application/json;charset=UTF-8