diff --git a/README.md b/README.md index 143df28c6..cdb534384 100755 --- a/README.md +++ b/README.md @@ -21,19 +21,10 @@ AnDroid + Blossom
**당신만의 매력적인 이미지**로 **움직이는 귀여운 캐릭터 타임캡슐 스킨**을 직접 제작해보세요.
_우리 앱으로 **과거**와 **현재**, **미래**를 연결하는 특별한 경험을 즐기실 수 있습니다._ -## 🎨 Figma -### 작업 중... - - - - - - - - - - - +## 🎨 디자인 + + + ## 🛠 개발 시스템 구성도 @@ -47,8 +38,8 @@ AnDroid + Blossom -## ⚙️ 운용 환경 - +## ⚙️ 운영 환경 + ## 🛢 ERD @@ -57,5 +48,4 @@ AnDroid + Blossom ARchive 위키 ## 🦾 기술 스택 - - + diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java index 9fd7a8826..f71652fc5 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java @@ -6,7 +6,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -15,10 +14,12 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.NegatedRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatchers; import site.timecapsulearchive.core.domain.member.entity.Role; +import site.timecapsulearchive.core.global.security.filter.DefaultAuthenticationFilter; @EnableWebSecurity @Configuration @@ -28,6 +29,7 @@ public class SecurityConfig { private final AuthenticationProvider jwtAuthenticationProvider; private final ObjectMapper objectMapper; private final AccessDeniedHandler accessDeniedHandler; + private final DefaultAuthenticationFilter defaultAuthenticationFilter; @Bean public PasswordEncoder getPasswordEncoder() { @@ -54,6 +56,11 @@ public SecurityFilterChain filterChainWithJwt(final HttpSecurity http) throws Ex ) .exceptionHandling(error -> error.accessDeniedHandler(accessDeniedHandler)); + http.addFilterBefore( + defaultAuthenticationFilter, + UsernamePasswordAuthenticationFilter.class + ); + http.apply( JwtDsl.jwtDsl( jwtAuthenticationProvider, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java index da198085d..72de9db89 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java @@ -13,6 +13,7 @@ public enum ErrorCode { INPUT_INVALID_TYPE_ERROR(400, "GLOBAL-003", "잘못된 입력 타입입니다."), REQUEST_PARAMETER_NOT_FOUND_ERROR(400, "GLOBAL-004", "입력 파라미터가 존재하지 않습니다."), REQUEST_PARAMETER_TYPE_NOT_MATCH_ERROR(400, "GLOBAL-005", "입력 파라미터의 타입이 올바르지 않습니다."), + REQUEST_DEFAULT_KEY_ERROR(400, "GLOBAL-006", "앱에서 발생한 요청이 아닙니다."), //jwt INVALID_TOKEN_ERROR(400, "AUTH-001", "jwt 토큰이 유효하지 않습니다."), diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/filter/DefaultAuthenticationFilter.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/filter/DefaultAuthenticationFilter.java new file mode 100644 index 000000000..8db5276fa --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/filter/DefaultAuthenticationFilter.java @@ -0,0 +1,56 @@ +package site.timecapsulearchive.core.global.security.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +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 lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.ErrorResponse; +import site.timecapsulearchive.core.global.security.property.DefaultKeyProperties; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DefaultAuthenticationFilter extends OncePerRequestFilter { + + private final DefaultKeyProperties defaultKeyProperties; + + @Override + @Order(1) + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + String requestKey = request.getHeader("Default-Key"); + + if (requestKey == null || !requestKey.equals(defaultKeyProperties.defaultKey())) { + log.warn("Invalid default key provided: {}", requestKey); + + final ErrorResponse errorResponse = ErrorResponse.fromErrorCode( + ErrorCode.REQUEST_DEFAULT_KEY_ERROR + ); + + response.setStatus(ErrorCode.REQUEST_DEFAULT_KEY_ERROR.getStatus()); + response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + + response.getWriter().write( + new ObjectMapper().writeValueAsString( + errorResponse + ) + ); + + return; + } + + filterChain.doFilter(request, response); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilter.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilter.java index 3f6b0387f..f5410ab62 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilter.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilter.java @@ -8,6 +8,7 @@ import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationManager; @@ -32,6 +33,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final RequestMatcher notRequireAuthenticationMatcher; @Override + @Order(2) protected void doFilterInternal( final HttpServletRequest request, final HttpServletResponse response, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/property/DefaultKeyProperties.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/property/DefaultKeyProperties.java new file mode 100644 index 000000000..408bd3938 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/property/DefaultKeyProperties.java @@ -0,0 +1,10 @@ +package site.timecapsulearchive.core.global.security.property; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "app") +public record DefaultKeyProperties( + String defaultKey +) { + +} diff --git a/backend/core/src/main/resources/config b/backend/core/src/main/resources/config index 10bd3becb..92430a653 160000 --- a/backend/core/src/main/resources/config +++ b/backend/core/src/main/resources/config @@ -1 +1 @@ -Subproject commit 10bd3becb428fd412e8cec1bcb108b98444a8e3a +Subproject commit 92430a6534f7b84d7e61429f700723f153fef3fe diff --git a/backend/core/src/main/resources/logback-spring.xml b/backend/core/src/main/resources/logback-spring.xml index 4b25a32da..4e3f1d387 100644 --- a/backend/core/src/main/resources/logback-spring.xml +++ b/backend/core/src/main/resources/logback-spring.xml @@ -1,34 +1,41 @@ + - [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - %msg%n - + [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - %msg%n - - ./logs/info.log - - INFO - - - [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n - - - - ./logs/info.%d{yyyy-MM-dd}.%i.log.gz - - - 100MB - - 180 - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + ./logs/info.log + + INFO + + + [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n + + + ./logs/info.%d{yyyy-MM-dd}.%i.log.gz + + 100MB + + 180 + + + + + + + + diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java index 794d15700..b74fa25bd 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; @@ -27,8 +28,10 @@ import site.timecapsulearchive.core.global.api.limit.ApiLimitCheckInterceptor; import site.timecapsulearchive.core.global.api.limit.ApiLimitProperties; import site.timecapsulearchive.core.global.api.limit.ApiUsageCacheRepository; +import site.timecapsulearchive.core.global.security.filter.DefaultAuthenticationFilter; import site.timecapsulearchive.core.global.security.jwt.JwtAuthenticationFilter; import site.timecapsulearchive.core.global.security.jwt.JwtAuthenticationProvider; +import site.timecapsulearchive.core.global.security.property.DefaultKeyProperties; @EnableWebSecurity @TestConfiguration @@ -56,6 +59,7 @@ public SecurityFilterChain filterChainWithJwt(final HttpSecurity http) throws Ex .anyRequest().hasRole(Role.USER.name()) ) .authenticationProvider(jwtAuthenticationProvider()) + .addFilterBefore(testDefaultAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore( jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class @@ -78,6 +82,19 @@ public JwtAuthenticationProvider jwtAuthenticationProvider() { } @Bean + public DefaultKeyProperties testDefaultKeyProperties() { + return new DefaultKeyProperties("testDefaultKey"); + } + + @Bean + @Order(1) + public DefaultAuthenticationFilter testDefaultAuthenticationFilter( + ) { + return new DefaultAuthenticationFilter(testDefaultKeyProperties()); + } + + @Bean + @Order(2) public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(authenticationManager(), new ObjectMapper(), notRequireAuthenticationMatcher()); diff --git a/backend/notification/src/main/resources/config b/backend/notification/src/main/resources/config index 9340cfd0d..9c68d32c6 160000 --- a/backend/notification/src/main/resources/config +++ b/backend/notification/src/main/resources/config @@ -1 +1 @@ -Subproject commit 9340cfd0d1b8c12cb4ad8a7b786e7e6f3ed99a18 +Subproject commit 9c68d32c612fb9014eeb75bc8efb01ac495bdd24