diff --git a/shared-config b/shared-config index 9087e18..81c29ad 160000 --- a/shared-config +++ b/shared-config @@ -1 +1 @@ -Subproject commit 9087e18c9478786763a04abeacae55bdbbbbb8ee +Subproject commit 81c29adb14579b9e4920aa033b37af05b5fd57a5 diff --git a/src/main/kotlin/befly/beflygateway/config/SecurityConfig.kt b/src/main/kotlin/befly/beflygateway/config/SecurityConfig.kt index b0a9c1a..e8dea1b 100644 --- a/src/main/kotlin/befly/beflygateway/config/SecurityConfig.kt +++ b/src/main/kotlin/befly/beflygateway/config/SecurityConfig.kt @@ -5,36 +5,58 @@ import befly.beflygateway.filter.JwtAuthenticationFilter import befly.beflygateway.handler.OAuth2SuccessHandler import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.config.web.server.SecurityWebFiltersOrder import org.springframework.security.config.web.server.ServerHttpSecurity -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.server.SecurityWebFilterChain import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository +import org.springframework.web.cors.CorsConfiguration +import org.springframework.web.cors.reactive.CorsConfigurationSource +import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource + @Configuration @EnableWebFluxSecurity class SecurityConfig ( - private val oAuth2SuccessHandler: OAuth2SuccessHandler, - private val jwtAuthenticationFilter: JwtAuthenticationFilter -){ + private val oAuth2SuccessHandler: OAuth2SuccessHandler, + private val jwtAuthenticationFilter: JwtAuthenticationFilter +) { @Bean fun securityWebFilter(http: ServerHttpSecurity): SecurityWebFilterChain = - http.apply { - cors { it.disable() } - csrf { it.disable() } - formLogin { it.disable() } - httpBasic { it.disable() } - exceptionHandling{ it.authenticationEntryPoint(CustomAuthenticationEntryPoint())} - securityContextRepository(NoOpServerSecurityContextRepository.getInstance()) // STATELESS - authorizeExchange { - it.pathMatchers("/oauth2/**", "/login/**", "/auth/refresh").permitAll() - it.pathMatchers("/swagger-ui/**", "/v3/api-docs/**", "/favicon.ico", "/api/docs", "/api/**").permitAll() - it.anyExchange().authenticated()} - oauth2Login{ - it.authenticationSuccessHandler(oAuth2SuccessHandler) - } - addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION) - }.build() -} \ No newline at end of file + http.apply { + cors { it.configurationSource(corsConfigurationSource()) } // ✅ CORS 활성화 + csrf { it.disable() } + formLogin { it.disable() } + httpBasic { it.disable() } + exceptionHandling { it.authenticationEntryPoint(CustomAuthenticationEntryPoint()) } + securityContextRepository(NoOpServerSecurityContextRepository.getInstance()) + authorizeExchange { + it.pathMatchers( + "/oauth2/**", "/login/**", "/auth/refresh", "/auth/signin", "/auth/signup", + "/swagger-ui/**", "/v3/api-docs/**", "/favicon.ico", "/api/docs", "/api/**" + ).permitAll() + it.pathMatchers(HttpMethod.GET, "/community/**").permitAll() + it.anyExchange().authenticated() + } + oauth2Login { + it.authenticationSuccessHandler(oAuth2SuccessHandler) + } + addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION) + }.build() + + @Bean + fun corsConfigurationSource(): CorsConfigurationSource { + val config = CorsConfiguration().apply { + allowedOrigins = listOf("https://befly.blog", "http://localhost:5173", "https://befly.blog:5173") //도메인 + allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH") // PATCH 추가 + allowedHeaders = listOf("*") + allowCredentials = true + } + + val source = UrlBasedCorsConfigurationSource() + source.registerCorsConfiguration("/**", config) + return source + } +} diff --git a/src/main/kotlin/befly/beflygateway/filter/JwtAuthenticationFilter.kt b/src/main/kotlin/befly/beflygateway/filter/JwtAuthenticationFilter.kt index f9d7d3e..798c565 100644 --- a/src/main/kotlin/befly/beflygateway/filter/JwtAuthenticationFilter.kt +++ b/src/main/kotlin/befly/beflygateway/filter/JwtAuthenticationFilter.kt @@ -28,7 +28,7 @@ class JwtAuthenticationFilter( ): WebFilter { override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono = - PathWhitelistUtil.isWhitelisted(exchange.request.path.toString()) + PathWhitelistUtil.isWhitelisted(exchange.request.method.toString(), exchange.request.path.toString()) .takeIf { it } ?.let {chain.filter(exchange) } ?: run { @@ -38,21 +38,11 @@ class JwtAuthenticationFilter( val userId = jwtProvider.getUserIdFromAccessToken(token) val auth = getAuthentication(userId.toString()) val context = SecurityContextImpl(auth) - return getAuthorizationToServer(userId) - .flatMap { status -> - if (status) { - val mutatedExchange = exchange.mutate() - .request(exchange.request.mutate().header("X-USER-ID", userId.toString()).build()) - .build() - chain.filter(mutatedExchange) - .contextWrite(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(context))) - } - else { - val errorJson = ErrorCode.ACCESS_TOKEN_EXPIRED.toErrorResponse().toJsonBytes() - setErrorResponse(errorJson, exchange.response) - } - } - + val mutatedExchange = exchange.mutate() + .request(exchange.request.mutate().header("X-USER-ID", userId.toString()).build()) + .build() + chain.filter(mutatedExchange) + .contextWrite(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(context))) } ?: run {//access 만료 or 잘못됨 jwtProvider.resolveRefreshToken(exchange.request) ?.takeIf { jwtProvider.validateRefreshToken(it) } @@ -67,17 +57,6 @@ class JwtAuthenticationFilter( } } - private fun getAuthorizationToServer(userId: Long): Mono { - return webClient - .get() - .uri("/auth/exist/user") - .accept(MediaType.ALL) - .header("X-USER-ID", userId.toString()) - .retrieve() - .bodyToMono(AuthResponse::class.java) - .map { it.existStatus } - } - private fun getAuthentication(userId: String):Authentication = UsernamePasswordAuthenticationToken(userId, "", emptyList()) diff --git a/src/main/kotlin/befly/beflygateway/handler/OAuth2SuccessHandler.kt b/src/main/kotlin/befly/beflygateway/handler/OAuth2SuccessHandler.kt index 164299c..c679a6a 100644 --- a/src/main/kotlin/befly/beflygateway/handler/OAuth2SuccessHandler.kt +++ b/src/main/kotlin/befly/beflygateway/handler/OAuth2SuccessHandler.kt @@ -22,7 +22,7 @@ class OAuth2SuccessHandler ( ): ServerAuthenticationSuccessHandler { @Value("\${url.front}") lateinit var FRONT_END_URL: String - + // override fun onAuthenticationSuccess( webFilterExchange: WebFilterExchange?, authentication: Authentication? @@ -52,7 +52,7 @@ class OAuth2SuccessHandler ( .path("/") .build() - val refreshCookie = ResponseCookie.from("refreshToken", "${response.refreshToken}!!") + val refreshCookie = ResponseCookie.from("refreshToken", "${response.refreshToken}") .httpOnly(true) .secure(true) .sameSite("Strict") @@ -61,13 +61,6 @@ class OAuth2SuccessHandler ( .path("/") .build() - webClient - .get() - .uri("/auth/refresh") - .accept(MediaType.ALL) - .header("X-Refresh-Token", refreshToken) - .retrieve() - exchange.response.addCookie(accessCookie) exchange.response.addCookie(refreshCookie) exchange.response.statusCode = HttpStatus.FOUND diff --git a/src/main/kotlin/befly/beflygateway/utils/PathWhitelistUtil.kt b/src/main/kotlin/befly/beflygateway/utils/PathWhitelistUtil.kt index b1f7a59..5658ac9 100644 --- a/src/main/kotlin/befly/beflygateway/utils/PathWhitelistUtil.kt +++ b/src/main/kotlin/befly/beflygateway/utils/PathWhitelistUtil.kt @@ -4,19 +4,29 @@ import org.springframework.util.AntPathMatcher object PathWhitelistUtil { private val matcher = AntPathMatcher() - private val whiteListPatterns = listOf( - "/oauth2/**", - "/login/**", - "/auth/**", - "/swagger-ui/**", - "/v3/api-docs/**", - "/api/docs", - "/api/**", - "/favicon.ico", - "/asdfasdf", + private val whitelistRules: List> = listOf( + "ANY" to "/auth/**", + "ANY" to "/login/**", + "ANY" to "/oauth2/**", + "ANY" to "/swagger-ui/**", + "ANY" to "/v3/api-docs/**", + "ANY" to "/api/**", + "ANY" to "/favicon.ico", // 모든 method 허용할 경우 + "GET" to "/community/**", ) - fun isWhitelisted(path: String): Boolean { - return whiteListPatterns.any { pattern -> matcher.match(pattern, path) } + fun isWhitelisted(method: String, path: String): Boolean { + val isMatched = whitelistRules.any { (ruleMethod, pattern) -> + (ruleMethod == "ANY" || ruleMethod.equals(method, ignoreCase = true)) && + matcher.match(pattern, path) + } + + // 예외 경로 처리: GET /community/notification/** 는 제외 + val isExcluded = method.equals("GET", ignoreCase = true) && ( + matcher.match("/community/notification/**", path) || + matcher.match("/community/**/empathy/check", path) + ) + + return isMatched && !isExcluded } } \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..1a8d0b6 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,56 @@ +spring: + application: + name: befly-gateway-prod + + security: + oauth2: + client: + registration: + kakao: + client-id: 732935614e46722be3537af517f72689 + client-secret: 5teaK9HRw1Wn1B6oBNx0dkc88Cl5s90H + client-authentication-method: client_secret_post + redirect-uri: https://api.befly.blog/login/oauth2/code/kakao + authorization-grant-type: authorization_code + scope: profile_nickname, profile_image + data: + redis: + host: 34.47.101.253 + port: 6379 + username: befly + password: befly0443 + +url: + front: https://befly.blog + back-user: http://user-service.backend.svc.cluster.local + back-community: http://community-service.backend.svc.cluster.local + back-consult: http://consult-service.backend.svc.cluster.local + +jwt: + access: + expire: 604800000 + secret: dskafndnvkclxzvlkcjxvoqewir329014178923hrjedsbjfajdsnv1238491234jh123 + refresh: + prefix: 'refreshToken:' + expire: 604800000 + secret: asdfasdfweqriuweru19237489321749791283vbznxcvbwhenyourcasdfasdfasdfqewr +logging: + level: + org.springframework.security: debug + +server: + port: 8000 + +springdoc: + swagger-ui: + enabled: true + path: /api/docs + urls[0]: + name: user-service + url: /api/user-docs + urls[1]: + name: community-service + url: /api/community-docs + urls[2]: + name: consult-service + url: /api/consult-docs