diff --git a/src/main/java/com/kcy/fitapet/FitapetApplication.java b/src/main/java/com/kcy/fitapet/FitapetApplication.java index 6887ea22..5052773b 100644 --- a/src/main/java/com/kcy/fitapet/FitapetApplication.java +++ b/src/main/java/com/kcy/fitapet/FitapetApplication.java @@ -5,6 +5,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import java.util.TimeZone; diff --git a/src/main/java/com/kcy/fitapet/domain/care/domain/Care.java b/src/main/java/com/kcy/fitapet/domain/care/domain/Care.java index 5594cbd9..b5cd9ced 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/domain/Care.java +++ b/src/main/java/com/kcy/fitapet/domain/care/domain/Care.java @@ -15,7 +15,7 @@ @Entity @Table(name = "CARE") @NoArgsConstructor(access = AccessLevel.PROTECTED) -@ToString(of = {"careName", "dtype"}) +@ToString(of = {"careName"}) @Getter public class Care extends Auditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 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 89f4962f..2cd7acc9 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 @@ -5,7 +5,7 @@ import com.kcy.fitapet.domain.model.Auditable; import com.kcy.fitapet.domain.notification.domain.Notification; import com.kcy.fitapet.domain.notification.type.NotificationType; -import com.kcy.fitapet.domain.oauth.domain.OAuthID; +import com.kcy.fitapet.domain.oauth.domain.OauthAccount; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.ColumnDefault; @@ -45,7 +45,7 @@ public class Member extends Auditable { private NotificationSetting notificationSetting; @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) - private Set oauthIDs = new HashSet<>(); + private Set oauthIDs = new HashSet<>(); @OneToMany(mappedBy = "from", cascade = CascadeType.ALL) private Set fromMemberNickname = new HashSet<>(); @OneToMany(mappedBy = "to", cascade = CascadeType.ALL) 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 index 6c78d31a..40581ce8 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java @@ -35,7 +35,7 @@ public void signIn( @PostMapping("/{id}") @PreAuthorize("isAnonymous()") public void signUp( - @PathVariable("id") String id, + @PathVariable("id") Long id, @RequestParam("provider") ProviderType provider, @RequestBody @Valid OauthSignUpReq req ) { 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 index 95a44019..5f31a4bf 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/domain/OauthAccount.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/domain/OauthAccount.java @@ -17,7 +17,7 @@ public class OauthAccount extends Auditable { private Long id; @Column(name = "oauth_id") - private Long OauthId; + private Long oauthId; @Convert(converter = ProviderTypeConverter.class) private ProviderType provider; private String email; @@ -27,17 +27,17 @@ public class OauthAccount extends Auditable { private Member member; @Builder - public OauthAccount(Long id, Long OauthId, ProviderType provider, String email, Member member) { + public OauthAccount(Long id, Long oauthId, ProviderType provider, String email, Member member) { this.id = id; - this.OauthId = OauthId; + this.oauthId = oauthId; this.provider = provider; this.email = email; this.member = member; } - public static OauthAccount of(Long OauthId, ProviderType provider, String email, Member member) { + public static OauthAccount of(Long oauthId, ProviderType provider, String email, Member member) { return OauthAccount.builder() - .OauthId(OauthId) + .oauthId(oauthId) .provider(provider) .email(email) .member(member) 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 index 4d64f1de..d4bab6e5 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignInReq.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignInReq.java @@ -4,7 +4,7 @@ public record OauthSignInReq( @NotEmpty - String id, + Long id, @NotEmpty String id_token, @NotEmpty diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/exception/OauthException.java b/src/main/java/com/kcy/fitapet/domain/oauth/exception/OauthException.java index a0dc580d..67a7e83a 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/exception/OauthException.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/exception/OauthException.java @@ -10,6 +10,7 @@ public enum OauthException implements StatusCode { /* BAD REQUEST */ INVALID_PROVIDER(HttpStatus.BAD_REQUEST, "유효하지 않은 제공자입니다."), + INVALID_OAUTH_ID(HttpStatus.BAD_REQUEST, "ID와 제공자가 일치하지 않습니다."), /* FORBIDDEN */ NOT_FOUND_MEMBER(HttpStatus.FORBIDDEN, "존재하지 않는 회원입니다."); diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java index 760880bb..6e3623f3 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java @@ -1,10 +1,12 @@ package com.kcy.fitapet.domain.oauth.service.component; import com.kcy.fitapet.domain.member.domain.Member; +import com.kcy.fitapet.domain.oauth.exception.OauthException; import com.kcy.fitapet.domain.oauth.service.module.OauthApplicationConfigHelper; import com.kcy.fitapet.domain.oauth.service.module.OauthClientHelper; import com.kcy.fitapet.domain.oauth.service.module.OauthSearchService; import com.kcy.fitapet.domain.oauth.type.ProviderType; +import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; import com.kcy.fitapet.global.common.security.jwt.JwtUtil; import com.kcy.fitapet.global.common.security.jwt.dto.Jwt; import com.kcy.fitapet.global.common.security.jwt.dto.JwtUserInfo; @@ -13,6 +15,7 @@ import com.kcy.fitapet.global.common.security.oauth.OauthOIDCHelper; import com.kcy.fitapet.global.common.security.oauth.dto.OIDCDecodePayload; import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKeyResponse; +import com.kcy.fitapet.global.common.security.oauth.kakao.KakaoOauthClient; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -25,18 +28,17 @@ public class OauthService { private final OauthSearchService oauthSearchService; private final OauthOIDCHelper oauthOIDCHelper; - private final OauthClientHelper oauthClientHelper; private final OauthApplicationConfigHelper oauthApplicationConfigHelper; + private final OauthClientHelper oauthClientHelper; private final JwtUtil jwtUtil; - @Transactional public void signUpByOIDC() { } @Transactional - public Jwt signInByOIDC(String id, String idToken, ProviderType provider, String nonce) { + public Jwt signInByOIDC(Long id, String idToken, ProviderType provider, String nonce) { OauthClient oauthClient = oauthClientHelper.getOauthClient(provider); OIDCPublicKeyResponse oidcPublicKeyResponse = oauthClient.getOIDCPublicKey(); OauthApplicationConfig oauthApplicationConfig = oauthApplicationConfigHelper.getOauthApplicationConfig(provider); @@ -45,8 +47,10 @@ public Jwt signInByOIDC(String id, String idToken, ProviderType provider, String idToken, oauthApplicationConfig.getAuthorizationUri(), oauthApplicationConfig.getClientId(), nonce, oidcPublicKeyResponse); - if (oauthSearchService.isExistMember(Long.parseLong(payload.sub()), provider)) { - Member member = oauthSearchService.findMemberByOauthIdAndProvider(Long.parseLong(payload.sub()), provider); + isValidRequestId(id, Long.parseLong(payload.sub())); + + if (oauthSearchService.isExistMember(id, provider)) { + Member member = oauthSearchService.findMemberByOauthIdAndProvider(id, provider); return generateToken(JwtUserInfo.from(member)); } else { return null; @@ -63,10 +67,18 @@ public void signUpByCode() { } + private void isValidRequestId(Long id, Long sub) { + if (!id.equals(sub)) { + throw new GlobalErrorException(OauthException.INVALID_OAUTH_ID); + } + } + private Jwt generateToken(JwtUserInfo jwtUserInfo) { return Jwt.builder() .accessToken(jwtUtil.generateAccessToken(jwtUserInfo)) .refreshToken(jwtUtil.generateRefreshToken(jwtUserInfo)) .build(); } + + } diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthClient.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthClient.java index 74870334..ac5b0942 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthClient.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthClient.java @@ -1,6 +1,7 @@ package com.kcy.fitapet.global.common.security.oauth; import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKeyResponse; +import org.springframework.cloud.openfeign.FeignClient; public interface OauthClient { OIDCPublicKeyResponse getOIDCPublicKey(); 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 index 4a9fa0d1..1f07bb16 100644 --- 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 @@ -3,7 +3,6 @@ import com.kcy.fitapet.global.common.security.oauth.dto.OIDCDecodePayload; import org.springframework.stereotype.Component; -@Component public interface OauthOIDCProvider { /** * ID Token의 header에서 kid를 추출하는 메서드 diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProviderImpl.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProviderImpl.java index b6f4574d..c8e2d9c0 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProviderImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProviderImpl.java @@ -6,6 +6,7 @@ import com.kcy.fitapet.global.common.security.jwt.exception.JwtErrorCodeUtil; import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; import java.math.BigInteger; import java.security.Key; @@ -16,6 +17,7 @@ import java.util.Base64; @Slf4j +@Component public class OauthOIDCProviderImpl implements OauthOIDCProvider { private final String KID = "kid"; private final String RSA = "RSA"; diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoApplicationConfig.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoApplicationConfig.java index 4316b89b..e60bfb28 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoApplicationConfig.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoApplicationConfig.java @@ -3,6 +3,7 @@ import com.kcy.fitapet.global.common.security.oauth.OauthApplicationConfig; import jakarta.validation.constraints.NotEmpty; import lombok.Getter; +import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.stereotype.Component; @@ -11,9 +12,10 @@ import java.util.List; @Getter +@Setter @Validated @Component -@ConfigurationProperties(prefix = "kakao") +@ConfigurationProperties(prefix = "oauth2.client.provider.kakao") @ConfigurationPropertiesBinding public class KakaoApplicationConfig implements OauthApplicationConfig { @NotEmpty 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 index 97740f26..97f8e4cb 100644 --- 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 @@ -9,7 +9,7 @@ @FeignClient( name = "KakaoOauthClient", - url = "${security.oauth2.client.provider.authorization-uri}", + url = "${oauth2.client.provider.kakao.authorization-uri}", configuration = KakaoOauthConfig.class ) public interface KakaoOauthClient extends OauthClient { diff --git a/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java b/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java index 71f04c43..a69285d7 100644 --- a/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java @@ -1,9 +1,13 @@ package com.kcy.fitapet.global.config; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FeignAutoConfiguration; import org.springframework.context.annotation.Configuration; @Configuration @EnableFeignClients(basePackages = "com.kcy.fitapet") +@ImportAutoConfiguration({FeignAutoConfiguration.class, HttpClientConfiguration.class}) 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 8e0c74ce..6d69b3a3 100644 --- a/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java @@ -1,10 +1,14 @@ package com.kcy.fitapet.global.config; import com.kcy.fitapet.global.common.redis.sms.SmsCertification; +import com.kcy.fitapet.global.config.feign.OidcCacheManager; +import com.kcy.fitapet.global.config.feign.RedisCacheConnectionFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -21,6 +25,7 @@ @Configuration @EnableRedisRepositories +@EnableCaching public class RedisConfig { @Value("${spring.data.redis.host}") private String host; @@ -28,6 +33,26 @@ public class RedisConfig { private int port; @Bean + @Primary + public CacheManager redisCacheManager( + @RedisCacheConnectionFactory RedisConnectionFactory cf) { + RedisCacheConfiguration redisCacheConfiguration = + RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer())) + .entryTtl(Duration.ofHours(1L)); + + return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf) + .cacheDefaults(redisCacheConfiguration) + .build(); + } + + @Bean + @RedisCacheConnectionFactory public RedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port); // config.setPassword(); // redis 패스워드 설정 시, 주석 해제 @@ -46,6 +71,7 @@ public RedisTemplate redisTemplate() { } @Bean + @OidcCacheManager public CacheManager oidcCacheManger(RedisConnectionFactory cf) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith( diff --git a/src/main/java/com/kcy/fitapet/global/config/feign/OidcCacheManager.java b/src/main/java/com/kcy/fitapet/global/config/feign/OidcCacheManager.java new file mode 100644 index 00000000..403e8caf --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/config/feign/OidcCacheManager.java @@ -0,0 +1,13 @@ +package com.kcy.fitapet.global.config.feign; + +import org.springframework.beans.factory.annotation.Qualifier; + +import java.lang.annotation.*; + +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, + ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier("oidcCacheManager") +public @interface OidcCacheManager { +} diff --git a/src/main/java/com/kcy/fitapet/global/config/feign/RedisCacheConnectionFactory.java b/src/main/java/com/kcy/fitapet/global/config/feign/RedisCacheConnectionFactory.java new file mode 100644 index 00000000..0c02b36f --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/config/feign/RedisCacheConnectionFactory.java @@ -0,0 +1,13 @@ +package com.kcy.fitapet.global.config.feign; + +import org.springframework.beans.factory.annotation.Qualifier; + +import java.lang.annotation.*; + +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, + ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier("redisCacheConnectionFactory") +public @interface RedisCacheConnectionFactory { +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2113df1a..f41ee622 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -38,21 +38,18 @@ spring: resources: add-mappings: false - 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 +oauth2: + client: + provider: + kakao: + authorization-uri: ${KAKAO_AUTH_URI} + client-id: ${KAKAO_CLIENT_ID} + client-secret: ${KAKAO_CLIENT_SECRET} + client-name: Kakao + scope: + - profile_nickname + - profile_image + - account_email feign: