From ea8da0c0c315268e7f86e5d34161380b492551b5 Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Wed, 14 Jan 2026 15:23:50 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat/#5-Swagger=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 ++ .../global/config/SwaggerConfig.java | 43 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java diff --git a/build.gradle b/build.gradle index 6c2b21c4..8c35a658 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,9 @@ dependencies { // mySQL runtimeOnly 'com.mysql:mysql-connector-j' + + // swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' } tasks.named('test') { diff --git a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java new file mode 100644 index 00000000..3bf1f6d8 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java @@ -0,0 +1,43 @@ +package com.example.RealMatch.global.config; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Profile; + + +@OpenAPIDefinition( + servers = { + @io.swagger.v3.oas.annotations.servers.Server(url = "http://localhost:8080"), + @io.swagger.v3.oas.annotations.servers.Server(url = "http://139.150.81.226:8080") + } +) +@Configuration +public class SwaggerConfig { + + @Bean + @Profile("local") + public OpenAPI localOpenAPI() { + return new OpenAPI() + .addServersItem(new Server().url("http://localhost:8080")) + .info(new Info() + .title("๐Ÿ”— RealMatch API (LOCAL)") + .version("1.0.0") + .description("๋กœ์ปฌ ๊ฐœ๋ฐœ์šฉ API ๋ฌธ์„œ")); + } + + @Bean + @Profile("prod") + public OpenAPI prodOpenAPI() { + return new OpenAPI() + // ๋„๋ฉ”์ธ ๊ตฌ๋งคํ•˜๋ฉด url ๋ณ€๊ฒฝํ•˜๊ธฐ + .addServersItem(new Server().url("http://139.150.81.226:8080")) + .info(new Info() + .title("๐Ÿ”— RealMatch API") + .version("1.0.0") + .description("์šด์˜ ์„œ๋ฒ„ API ๋ฌธ์„œ")); + } +} \ No newline at end of file From c24e513e6832a38f8f12bf4f915f6eee520423d3 Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Wed, 14 Jan 2026 17:46:10 +0900 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20swagger=EC=99=80=20spring=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=ED=98=B8=ED=99=98=20=EB=AC=B8=EC=A0=9C?= =?UTF-8?q?=EB=A1=9C=20swagger=20=EB=B2=84=EC=A0=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8c35a658..f490a7e2 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' // swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9' } tasks.named('test') { From a0b987f8112186ed935b6fb16285ed4be5d09625 Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Wed, 14 Jan 2026 18:35:04 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20SpringSecurity=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.java | 73 ++++++++++++++ .../global/config/SwaggerConfig.java | 81 ++++++++++++---- .../global/config/jwt/CustomUserDetails.java | 34 +++++++ .../config/jwt/JwtAuthenticationFilter.java | 63 ++++++++++++ .../global/config/jwt/JwtProvider.java | 95 +++++++++++++++++++ .../global/controller/TestController.java | 56 +++++++++++ .../{ApiResponse.java => CustomResponse.java} | 16 +++- .../advice/CustomAccessDeniedHandler.java | 38 ++++++++ .../advice/CustomAuthEntryPoint.java | 41 ++++++++ .../advice/GlobalExceptionHandler.java | 26 ++--- 10 files changed, 487 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/example/RealMatch/global/config/SecurityConfig.java create mode 100644 src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java create mode 100644 src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java create mode 100644 src/main/java/com/example/RealMatch/global/controller/TestController.java rename src/main/java/com/example/RealMatch/global/presentation/{ApiResponse.java => CustomResponse.java} (54%) create mode 100644 src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java diff --git a/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java b/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java new file mode 100644 index 00000000..88ef1dc5 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java @@ -0,0 +1,73 @@ +package com.example.RealMatch.global.config; + +import com.example.RealMatch.global.config.jwt.JwtAuthenticationFilter; +import com.example.RealMatch.global.presentation.advice.CustomAccessDeniedHandler; +import com.example.RealMatch.global.presentation.advice.CustomAuthEntryPoint; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + private final JwtAuthenticationFilter jwtAuthenticationFilter; + + private static final String[] PERMIT_ALL_URL_ARRAY = { + "/api/v1/*", + "/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**", "/swagger-ui.html" + }; + + private static final String[] REQUEST_AUTHENTICATED_ARRAY = { + "/api/v1/test-auth" + }; + + @Value("${cors.allowed-origin}") + private String allowedOrigin; + @Value("${swagger.server-url}") String prodSwaggerUrl; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAuthEntryPoint customAuthEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) throws Exception{ + http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(csrf -> csrf.disable()) + + .exceptionHandling(exception -> exception + .authenticationEntryPoint(customAuthEntryPoint) // 401 + .accessDeniedHandler(customAccessDeniedHandler) // 403 + ) + + .authorizeHttpRequests(auth -> auth + .requestMatchers(REQUEST_AUTHENTICATED_ARRAY).authenticated() + .requestMatchers(PERMIT_ALL_URL_ARRAY).permitAll() + .anyRequest().denyAll() + ) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource(){ + + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of(allowedOrigin,"http://localhost:8080",prodSwaggerUrl)); + configuration.setAllowedMethods(List.of("GET","POST","PUT","PATCH","DELETE","OPTIONS")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); // ์ฟ ํ‚ค/์ธ์ฆ์ •๋ณด ํฌํ•จ ์š”์ฒญ + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**",configuration); + return source; + } +} + diff --git a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java index 3bf1f6d8..793a0fbe 100644 --- a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java +++ b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java @@ -1,43 +1,88 @@ package com.example.RealMatch.global.config; -import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import io.swagger.v3.oas.models.info.Info; import org.springframework.context.annotation.Profile; +import java.util.List; -@OpenAPIDefinition( - servers = { - @io.swagger.v3.oas.annotations.servers.Server(url = "http://localhost:8080"), - @io.swagger.v3.oas.annotations.servers.Server(url = "http://139.150.81.226:8080") - } -) @Configuration public class SwaggerConfig { + @Value("${swagger.server-url}") String prodSwaggerUrl; + @Bean @Profile("local") public OpenAPI localOpenAPI() { + Info info = new Info() + .title("๐Ÿ”— RealMatch API (LOCAL)") + .version("1.0.0") + .description("ZzicGo API ๋ช…์„ธ์„œ์ž…๋‹ˆ๋‹ค."); + + String jwtSchemeName = "JWT Authentication"; + + io.swagger.v3.oas.models.security.SecurityScheme securityScheme = new io.swagger.v3.oas.models.security.SecurityScheme() + .name("Authorization") + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"); + + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); + + Components components = new Components() + .addSecuritySchemes(jwtSchemeName, securityScheme); + return new OpenAPI() - .addServersItem(new Server().url("http://localhost:8080")) - .info(new Info() - .title("๐Ÿ”— RealMatch API (LOCAL)") - .version("1.0.0") - .description("๋กœ์ปฌ ๊ฐœ๋ฐœ์šฉ API ๋ฌธ์„œ")); + .info(info) + .addSecurityItem(securityRequirement) // โœ… ๋ชจ๋“  API์— ์ „์—ญ์ ์œผ๋กœ ์ธ์ฆ ์ ์šฉ + .components(components) + .servers(List.of( + new Server() + .url("http://localhost:8080") + .description("Local Development Server") + )); } + @Bean @Profile("prod") public OpenAPI prodOpenAPI() { + Info info = new Info() + .title("๐Ÿ”— RealMatch API (PROD)") + .version("1.0.0") + .description("RealMatch Production API ๋ช…์„ธ์„œ์ž…๋‹ˆ๋‹ค."); + + String jwtSchemeName = "JWT Authentication"; + + SecurityScheme securityScheme = new SecurityScheme() + .name("Authorization") + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"); + + SecurityRequirement securityRequirement = + new SecurityRequirement().addList(jwtSchemeName); + + Components components = + new Components().addSecuritySchemes(jwtSchemeName, securityScheme); + return new OpenAPI() - // ๋„๋ฉ”์ธ ๊ตฌ๋งคํ•˜๋ฉด url ๋ณ€๊ฒฝํ•˜๊ธฐ - .addServersItem(new Server().url("http://139.150.81.226:8080")) - .info(new Info() - .title("๐Ÿ”— RealMatch API") - .version("1.0.0") - .description("์šด์˜ ์„œ๋ฒ„ API ๋ฌธ์„œ")); + .info(info) + .addSecurityItem(securityRequirement) + .components(components) + .servers(List.of( + new Server() + .url(prodSwaggerUrl) + .description("Production Server") + )); } + + } \ No newline at end of file diff --git a/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java b/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java new file mode 100644 index 00000000..b106097a --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java @@ -0,0 +1,34 @@ +package com.example.RealMatch.global.config.jwt; + +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + +@Getter +public class CustomUserDetails implements UserDetails { + + private final Long userId; // DB PK + private final String providerId; // ์†Œ์…œ ๊ณ ์œ  ID + private final String role; // USER / ADMIN + + public CustomUserDetails(Long userId, String providerId, String role) { + this.userId = userId; + this.providerId = providerId; + this.role = role; + } + + @Override + public Collection getAuthorities() { + return List.of(() -> "ROLE_" + role); + } + + @Override public String getPassword() { return null; } + @Override public String getUsername() { return providerId; } // ์†Œ์…œ UUID ๊ธฐ์ค€ + @Override public boolean isAccountNonExpired() { return true; } + @Override public boolean isAccountNonLocked() { return true; } + @Override public boolean isCredentialsNonExpired() { return true; } + @Override public boolean isEnabled() { return true; } +} diff --git a/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java b/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java new file mode 100644 index 00000000..022866b8 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,63 @@ +package com.example.RealMatch.global.config.jwt; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtProvider jwtProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + String token = authHeader.substring(7); + + if (!jwtProvider.validateToken(token)) { + filterChain.doFilter(request, response); + return; + } + + Long userId = jwtProvider.getUserId(token); + String providerId = jwtProvider.getProviderId(token); + String role = jwtProvider.getRole(token); + + CustomUserDetails userDetails = + new CustomUserDetails(userId, providerId, role); + + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + filterChain.doFilter(request, response); + } + +} + diff --git a/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java b/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java new file mode 100644 index 00000000..d3da0be7 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java @@ -0,0 +1,95 @@ +package com.example.RealMatch.global.config.jwt; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.util.Base64; +import java.util.Date; + +@Component +public class JwtProvider { + + private final SecretKey secretKey; + private final long accessTokenExpireMillis; + private final long refreshTokenExpireMillis; + + public JwtProvider( + @Value("${jwt.secret}") String secret, + @Value("${jwt.access-expire-ms}") long accessTokenExpireMillis, + @Value("${jwt.refresh-expire-ms}") long refreshTokenExpireMillis + ) { + byte[] keyBytes = Base64.getDecoder().decode(secret); + this.secretKey = Keys.hmacShaKeyFor(keyBytes); +// this.secretKey = Keys.hmacShaKeyFor(secret.getBytes()); + this.accessTokenExpireMillis = accessTokenExpireMillis; + this.refreshTokenExpireMillis = refreshTokenExpireMillis; + } + + // ========================= + // ํ† ํฐ ์ƒ์„ฑ + // ========================= + public String createAccessToken(Long userId, String providerId, String role) { + return createToken(userId, providerId, role, accessTokenExpireMillis); + } + + public String createRefreshToken(Long userId, String providerId, String role) { + return createToken(userId, providerId, role, refreshTokenExpireMillis); + } + + private String createToken(Long userId, String providerId, String role, long expireMillis) { + long now = System.currentTimeMillis(); + + return Jwts.builder() + .subject(String.valueOf(userId)) + .claim("providerId", providerId) + .claim("role", role) + .issuedAt(new Date(now)) + .expiration(new Date(now + expireMillis)) + .signWith(secretKey, SignatureAlgorithm.HS256) + .compact(); + } + + // ========================= + // ํ† ํฐ ๊ฒ€์ฆ + // ========================= + public boolean validateToken(String token) { + try { + parseClaims(token); + return true; + } catch (ExpiredJwtException e) { + return false; // ๋งŒ๋ฃŒ + } catch (JwtException | IllegalArgumentException e) { + return false; // ๋ณ€์กฐ, ์†์ƒ ๋“ฑ + } + } + + // ========================= + // Claims ๊ฐ€์ ธ์˜ค๊ธฐ + // ========================= + public Claims getClaims(String token) { + return parseClaims(token).getPayload(); + } + + public Long getUserId(String token) { + return Long.parseLong(getClaims(token).getSubject()); + } + + public String getProviderId(String token) { + return getClaims(token).get("providerId", String.class); + } + + public String getRole(String token) { + return getClaims(token).get("role", String.class); + } + + private Jws parseClaims(String token) { + return Jwts.parser() + .verifyWith(secretKey) // Key ํƒ€์ž… ๊ฐ•์ œ + .build() + .parseSignedClaims(token); + } +} + diff --git a/src/main/java/com/example/RealMatch/global/controller/TestController.java b/src/main/java/com/example/RealMatch/global/controller/TestController.java new file mode 100644 index 00000000..d89c2f94 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/controller/TestController.java @@ -0,0 +1,56 @@ +package com.example.RealMatch.global.controller; + +import com.example.RealMatch.global.config.jwt.CustomUserDetails; +import com.example.RealMatch.global.presentation.CustomResponse; +import com.example.RealMatch.global.presentation.code.GeneralSuccessCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name="test", description = "ํ…Œ์ŠคํŠธ์šฉ API") +@RestController +@RequestMapping("/api/v1") +public class TestController { + + @Operation(summary = "api ํ…Œ์ŠคํŠธ ํ™•์ธ", + description = """ + ํ…Œ์ŠคํŠธ์šฉ api์ž…๋‹ˆ๋‹ค. + ๋งŒ์•ฝ ์ด api๊ฐ€ ํ†ต๊ณผํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, SecurityConfig์— url์„ ์ถ”๊ฐ€ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. + + ์ธ์ฆ์ด ํ•„์š”์—†๋‹ค๋ฉด, PERMIT_ALL_URL_ARRAY์— ์ถ”๊ฐ€ํ•˜๊ณ , + ์ธ์ฆ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, REQUEST_AUTHENTICATED_ARRAY์— ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”. + """) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "ํ…Œ์ŠคํŠธ ์„ฑ๊ณต") + }) + @GetMapping("/test") + public CustomResponse test() { + String response = "Hello from Spring Boot ๐Ÿ‘‹"; + return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST,response); + } + + @Operation(summary = "api ๊ถŒํ•œ ํ…Œ์ŠคํŠธ ํ™•์ธ", + description = """ + ํ…Œ์ŠคํŠธ์šฉ api์ž…๋‹ˆ๋‹ค. + Swagger์—์„œ Authorize์— ํ† ํฐ์„ ์ž…๋ ฅํ•œ ํ›„ ์‚ฌ์šฉํ•ด์•ผ ์ •์ƒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. + """) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "ํ…Œ์ŠคํŠธ ์„ฑ๊ณต"), + @ApiResponse(responseCode = "COMMON401_1", description = "์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.") + }) + @GetMapping("/test-auth") + public CustomResponse testAuth( + @AuthenticationPrincipal CustomUserDetails user + ) { + String response = "Hello from Spring Boot ๐Ÿ‘‹"; + return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST,response); + } + + +} + diff --git a/src/main/java/com/example/RealMatch/global/presentation/ApiResponse.java b/src/main/java/com/example/RealMatch/global/presentation/CustomResponse.java similarity index 54% rename from src/main/java/com/example/RealMatch/global/presentation/ApiResponse.java rename to src/main/java/com/example/RealMatch/global/presentation/CustomResponse.java index b6449dc9..af6f39ae 100644 --- a/src/main/java/com/example/RealMatch/global/presentation/ApiResponse.java +++ b/src/main/java/com/example/RealMatch/global/presentation/CustomResponse.java @@ -2,6 +2,7 @@ import com.example.RealMatch.global.presentation.code.BaseErrorCode; import com.example.RealMatch.global.presentation.code.BaseSuccessCode; +import com.example.RealMatch.global.presentation.code.GeneralSuccessCode; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @@ -11,7 +12,7 @@ @Getter @AllArgsConstructor @JsonPropertyOrder({"isSuccess", "code", "message", "result"}) -public class ApiResponse { +public class CustomResponse { @JsonProperty("isSuccess") private final Boolean isSuccess; @@ -25,11 +26,16 @@ public class ApiResponse { @JsonProperty("result") private T result; - public static ApiResponse onSuccess(BaseSuccessCode code, T result) { - return new ApiResponse<>(true, code.getCode(), code.getMessage(), result); + // 200 OK + public static CustomResponse ok(T result) { + return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST, result); } - public static ApiResponse onFailure(BaseErrorCode code, T result) { - return new ApiResponse<>(false, code.getCode(), code.getMessage(), result); + public static CustomResponse onSuccess(BaseSuccessCode code, T result) { + return new CustomResponse<>(true, code.getCode(), code.getMessage(), result); + } + + public static CustomResponse onFailure(BaseErrorCode code, T result) { + return new CustomResponse<>(false, code.getCode(), code.getMessage(), result); } } diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java new file mode 100644 index 00000000..3db0712a --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java @@ -0,0 +1,38 @@ +package com.example.RealMatch.global.presentation.advice; + +import com.example.RealMatch.global.presentation.CustomResponse; +import com.example.RealMatch.global.presentation.code.GeneralErrorCode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.stereotype.Component; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +import java.io.IOException; + +@Component +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void handle( + HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException + ) throws IOException { + + response.setStatus(GeneralErrorCode.FORBIDDEN.getStatus().value()); + response.setContentType("application/json;charset=UTF-8"); + + CustomResponse body = + CustomResponse.onFailure( + GeneralErrorCode.FORBIDDEN, + null + ); + + response.getWriter() + .write(objectMapper.writeValueAsString(body)); + } +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java new file mode 100644 index 00000000..2c43b73a --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java @@ -0,0 +1,41 @@ +package com.example.RealMatch.global.presentation.advice; + +import com.example.RealMatch.global.presentation.CustomResponse; +import com.example.RealMatch.global.presentation.code.GeneralErrorCode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@Component +public class CustomAuthEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException + ) throws IOException { + + response.setStatus(GeneralErrorCode.UNAUTHORIZED.getStatus().value()); + response.setContentType("application/json;charset=UTF-8"); + + CustomResponse body = + CustomResponse.onFailure( + GeneralErrorCode.UNAUTHORIZED, + null + ); + + response.getWriter() + .write(objectMapper.writeValueAsString(body)); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/GlobalExceptionHandler.java b/src/main/java/com/example/RealMatch/global/presentation/advice/GlobalExceptionHandler.java index 1d4d3264..f40ff4fa 100644 --- a/src/main/java/com/example/RealMatch/global/presentation/advice/GlobalExceptionHandler.java +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/GlobalExceptionHandler.java @@ -5,7 +5,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.HandlerMethodValidationException; -import com.example.RealMatch.global.presentation.ApiResponse; +import com.example.RealMatch.global.presentation.CustomResponse; import com.example.RealMatch.global.presentation.code.GeneralErrorCode; import jakarta.validation.ConstraintViolationException; @@ -16,62 +16,62 @@ public class GlobalExceptionHandler { @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity> handleIllegalArgument(IllegalArgumentException e) { + public ResponseEntity> handleIllegalArgument(IllegalArgumentException e) { log.warn("[IllegalArgumentException] {}", e.getMessage()); return ResponseEntity .status(GeneralErrorCode.BAD_REQUEST.getStatus()) - .body(ApiResponse.onFailure(GeneralErrorCode.BAD_REQUEST, null)); + .body(CustomResponse.onFailure(GeneralErrorCode.BAD_REQUEST, null)); } @ExceptionHandler(ConstraintViolationException.class) - public ResponseEntity> handleConstraintViolation(ConstraintViolationException e) { + public ResponseEntity> handleConstraintViolation(ConstraintViolationException e) { log.warn("[ConstraintViolationException] {}", e.getMessage()); return ResponseEntity .status(GeneralErrorCode.INVALID_PAGE.getStatus()) - .body(ApiResponse.onFailure(GeneralErrorCode.INVALID_PAGE, null)); + .body(CustomResponse.onFailure(GeneralErrorCode.INVALID_PAGE, null)); } @ExceptionHandler(HandlerMethodValidationException.class) - public ResponseEntity> handleHandlerMethodValidation(HandlerMethodValidationException e) { + public ResponseEntity> handleHandlerMethodValidation(HandlerMethodValidationException e) { log.warn("[HandlerMethodValidationException] {}", e.getMessage()); return ResponseEntity .status(GeneralErrorCode.INVALID_PAGE.getStatus()) - .body(ApiResponse.onFailure(GeneralErrorCode.INVALID_PAGE, null)); + .body(CustomResponse.onFailure(GeneralErrorCode.INVALID_PAGE, null)); } @ExceptionHandler(SecurityException.class) - public ResponseEntity> handleSecurityException(SecurityException e) { + public ResponseEntity> handleSecurityException(SecurityException e) { log.warn("[SecurityException] {}", e.getMessage()); return ResponseEntity .status(GeneralErrorCode.UNAUTHORIZED.getStatus()) - .body(ApiResponse.onFailure(GeneralErrorCode.UNAUTHORIZED, null)); + .body(CustomResponse.onFailure(GeneralErrorCode.UNAUTHORIZED, null)); } @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity> handleResourceNotFound(ResourceNotFoundException e) { + public ResponseEntity> handleResourceNotFound(ResourceNotFoundException e) { log.warn("[ResourceNotFoundException] {}", e.getMessage()); return ResponseEntity .status(GeneralErrorCode.NOT_FOUND.getStatus()) - .body(ApiResponse.onFailure(GeneralErrorCode.NOT_FOUND, null)); + .body(CustomResponse.onFailure(GeneralErrorCode.NOT_FOUND, null)); } @ExceptionHandler(Exception.class) - public ResponseEntity> handleUnexpectedException(Exception e) { + public ResponseEntity> handleUnexpectedException(Exception e) { log.error("[UnexpectedException]", e); return ResponseEntity .status(GeneralErrorCode.INTERNAL_SERVER_ERROR.getStatus()) - .body(ApiResponse.onFailure(GeneralErrorCode.INTERNAL_SERVER_ERROR, null)); + .body(CustomResponse.onFailure(GeneralErrorCode.INTERNAL_SERVER_ERROR, null)); } } From 0ca21c8dd116f6f0deac77d65471f986488f0ccc Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Wed, 14 Jan 2026 19:44:16 +0900 Subject: [PATCH 04/12] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=EC=8A=A4=ED=83=80=EC=9D=BC=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.java | 27 ++++++------ .../global/config/SwaggerConfig.java | 21 +++++----- .../global/config/jwt/CustomUserDetails.java | 42 +++++++++++++++---- .../config/jwt/JwtAuthenticationFilter.java | 7 ++-- .../global/config/jwt/JwtProvider.java | 18 +++++--- .../global/controller/TestController.java | 28 +++++++------ .../advice/CustomAccessDeniedHandler.java | 12 +++--- .../advice/CustomAuthEntryPoint.java | 17 ++++---- .../presentation/code/BaseErrorCode.java | 4 +- .../presentation/code/BaseSuccessCode.java | 4 +- .../presentation/code/GeneralSuccessCode.java | 4 +- 11 files changed, 112 insertions(+), 72 deletions(-) diff --git a/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java b/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java index 88ef1dc5..ca77f6a0 100644 --- a/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java +++ b/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java @@ -1,9 +1,7 @@ package com.example.RealMatch.global.config; -import com.example.RealMatch.global.config.jwt.JwtAuthenticationFilter; -import com.example.RealMatch.global.presentation.advice.CustomAccessDeniedHandler; -import com.example.RealMatch.global.presentation.advice.CustomAuthEntryPoint; -import lombok.RequiredArgsConstructor; +import java.util.List; + import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -15,7 +13,11 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import java.util.List; +import com.example.RealMatch.global.config.jwt.JwtAuthenticationFilter; +import com.example.RealMatch.global.presentation.advice.CustomAccessDeniedHandler; +import com.example.RealMatch.global.presentation.advice.CustomAuthEntryPoint; + +import lombok.RequiredArgsConstructor; @Configuration @EnableWebSecurity @@ -24,7 +26,7 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private static final String[] PERMIT_ALL_URL_ARRAY = { - "/api/v1/*", + "/api/v1/test", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**", "/swagger-ui.html" }; @@ -34,10 +36,11 @@ public class SecurityConfig { @Value("${cors.allowed-origin}") private String allowedOrigin; - @Value("${swagger.server-url}") String prodSwaggerUrl; + @Value("${swagger.server-url}") + String prodSwaggerUrl; @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAuthEntryPoint customAuthEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) throws Exception{ + public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAuthEntryPoint customAuthEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) throws Exception { http .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(csrf -> csrf.disable()) @@ -57,16 +60,16 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAuthEntr } @Bean - public CorsConfigurationSource corsConfigurationSource(){ + public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(List.of(allowedOrigin,"http://localhost:8080",prodSwaggerUrl)); - configuration.setAllowedMethods(List.of("GET","POST","PUT","PATCH","DELETE","OPTIONS")); + configuration.setAllowedOrigins(List.of(allowedOrigin, "http://localhost:8080", prodSwaggerUrl)); + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); configuration.setAllowCredentials(true); // ์ฟ ํ‚ค/์ธ์ฆ์ •๋ณด ํฌํ•จ ์š”์ฒญ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**",configuration); + source.registerCorsConfiguration("/**", configuration); return source; } } diff --git a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java index 793a0fbe..e71c0f05 100644 --- a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java +++ b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java @@ -1,22 +1,24 @@ package com.example.RealMatch.global.config; -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; -import io.swagger.v3.oas.models.servers.Server; +import java.util.List; + import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import io.swagger.v3.oas.models.info.Info; import org.springframework.context.annotation.Profile; -import java.util.List; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; @Configuration public class SwaggerConfig { - @Value("${swagger.server-url}") String prodSwaggerUrl; + @Value("${swagger.server-url}") + String prodSwaggerUrl; @Bean @Profile("local") @@ -50,7 +52,6 @@ public OpenAPI localOpenAPI() { )); } - @Bean @Profile("prod") public OpenAPI prodOpenAPI() { @@ -85,4 +86,4 @@ public OpenAPI prodOpenAPI() { } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java b/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java index b106097a..4f6af748 100644 --- a/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java +++ b/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java @@ -1,11 +1,12 @@ package com.example.RealMatch.global.config.jwt; -import lombok.Getter; +import java.util.Collection; +import java.util.List; + import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import java.util.Collection; -import java.util.List; +import lombok.Getter; @Getter public class CustomUserDetails implements UserDetails { @@ -25,10 +26,33 @@ public Collection getAuthorities() { return List.of(() -> "ROLE_" + role); } - @Override public String getPassword() { return null; } - @Override public String getUsername() { return providerId; } // ์†Œ์…œ UUID ๊ธฐ์ค€ - @Override public boolean isAccountNonExpired() { return true; } - @Override public boolean isAccountNonLocked() { return true; } - @Override public boolean isCredentialsNonExpired() { return true; } - @Override public boolean isEnabled() { return true; } + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return providerId; + } // ์†Œ์…œ UUID ๊ธฐ์ค€ + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } } diff --git a/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java b/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java index 022866b8..37964ae8 100644 --- a/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java @@ -1,6 +1,7 @@ package com.example.RealMatch.global.config.jwt; -import lombok.RequiredArgsConstructor; +import java.io.IOException; + import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; @@ -11,8 +12,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; +import lombok.RequiredArgsConstructor; @Component @RequiredArgsConstructor @@ -60,4 +60,3 @@ protected void doFilterInternal(HttpServletRequest request, } } - diff --git a/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java b/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java index d3da0be7..9071df96 100644 --- a/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java +++ b/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java @@ -1,13 +1,20 @@ package com.example.RealMatch.global.config.jwt; -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; +import java.util.Base64; +import java.util.Date; + +import javax.crypto.SecretKey; + import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import javax.crypto.SecretKey; -import java.util.Base64; -import java.util.Date; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; @Component public class JwtProvider { @@ -23,7 +30,6 @@ public JwtProvider( ) { byte[] keyBytes = Base64.getDecoder().decode(secret); this.secretKey = Keys.hmacShaKeyFor(keyBytes); -// this.secretKey = Keys.hmacShaKeyFor(secret.getBytes()); this.accessTokenExpireMillis = accessTokenExpireMillis; this.refreshTokenExpireMillis = refreshTokenExpireMillis; } diff --git a/src/main/java/com/example/RealMatch/global/controller/TestController.java b/src/main/java/com/example/RealMatch/global/controller/TestController.java index d89c2f94..1524bf99 100644 --- a/src/main/java/com/example/RealMatch/global/controller/TestController.java +++ b/src/main/java/com/example/RealMatch/global/controller/TestController.java @@ -1,37 +1,39 @@ package com.example.RealMatch.global.controller; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + import com.example.RealMatch.global.config.jwt.CustomUserDetails; import com.example.RealMatch.global.presentation.CustomResponse; import com.example.RealMatch.global.presentation.code.GeneralSuccessCode; + import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -@Tag(name="test", description = "ํ…Œ์ŠคํŠธ์šฉ API") +@Tag(name = "test", description = "ํ…Œ์ŠคํŠธ์šฉ API") @RestController @RequestMapping("/api/v1") public class TestController { @Operation(summary = "api ํ…Œ์ŠคํŠธ ํ™•์ธ", description = """ - ํ…Œ์ŠคํŠธ์šฉ api์ž…๋‹ˆ๋‹ค. - ๋งŒ์•ฝ ์ด api๊ฐ€ ํ†ต๊ณผํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, SecurityConfig์— url์„ ์ถ”๊ฐ€ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. - - ์ธ์ฆ์ด ํ•„์š”์—†๋‹ค๋ฉด, PERMIT_ALL_URL_ARRAY์— ์ถ”๊ฐ€ํ•˜๊ณ , - ์ธ์ฆ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, REQUEST_AUTHENTICATED_ARRAY์— ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”. - """) + ํ…Œ์ŠคํŠธ์šฉ api์ž…๋‹ˆ๋‹ค. + ๋งŒ์•ฝ ์ด api๊ฐ€ ํ†ต๊ณผํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, SecurityConfig์— url์„ ์ถ”๊ฐ€ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. + + ์ธ์ฆ์ด ํ•„์š”์—†๋‹ค๋ฉด, PERMIT_ALL_URL_ARRAY์— ์ถ”๊ฐ€ํ•˜๊ณ , + ์ธ์ฆ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, REQUEST_AUTHENTICATED_ARRAY์— ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”. + """) @ApiResponses({ @ApiResponse(responseCode = "200", description = "ํ…Œ์ŠคํŠธ ์„ฑ๊ณต") }) @GetMapping("/test") public CustomResponse test() { String response = "Hello from Spring Boot ๐Ÿ‘‹"; - return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST,response); + return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST, response); } @Operation(summary = "api ๊ถŒํ•œ ํ…Œ์ŠคํŠธ ํ™•์ธ", @@ -48,7 +50,7 @@ public CustomResponse testAuth( @AuthenticationPrincipal CustomUserDetails user ) { String response = "Hello from Spring Boot ๐Ÿ‘‹"; - return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST,response); + return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST, response); } diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java index 3db0712a..edec902c 100644 --- a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java @@ -1,15 +1,17 @@ package com.example.RealMatch.global.presentation.advice; +import java.io.IOException; + +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + import com.example.RealMatch.global.presentation.CustomResponse; import com.example.RealMatch.global.presentation.code.GeneralErrorCode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.stereotype.Component; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.web.access.AccessDeniedHandler; - -import java.io.IOException; @Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java index 2c43b73a..a0de2da5 100644 --- a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java @@ -1,18 +1,17 @@ package com.example.RealMatch.global.presentation.advice; +import java.io.IOException; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + import com.example.RealMatch.global.presentation.CustomResponse; import com.example.RealMatch.global.presentation.code.GeneralErrorCode; import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.ServletException; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; @Component public class CustomAuthEntryPoint implements AuthenticationEntryPoint { @@ -38,4 +37,4 @@ public void commence( response.getWriter() .write(objectMapper.writeValueAsString(body)); } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/code/BaseErrorCode.java b/src/main/java/com/example/RealMatch/global/presentation/code/BaseErrorCode.java index 4c387271..43c2f3fe 100644 --- a/src/main/java/com/example/RealMatch/global/presentation/code/BaseErrorCode.java +++ b/src/main/java/com/example/RealMatch/global/presentation/code/BaseErrorCode.java @@ -3,8 +3,10 @@ import org.springframework.http.HttpStatus; public interface BaseErrorCode { - + HttpStatus getStatus(); + String getCode(); + String getMessage(); } diff --git a/src/main/java/com/example/RealMatch/global/presentation/code/BaseSuccessCode.java b/src/main/java/com/example/RealMatch/global/presentation/code/BaseSuccessCode.java index 962a30e3..6ae9b199 100644 --- a/src/main/java/com/example/RealMatch/global/presentation/code/BaseSuccessCode.java +++ b/src/main/java/com/example/RealMatch/global/presentation/code/BaseSuccessCode.java @@ -3,8 +3,10 @@ import org.springframework.http.HttpStatus; public interface BaseSuccessCode { - + HttpStatus getStatus(); + String getCode(); + String getMessage(); } diff --git a/src/main/java/com/example/RealMatch/global/presentation/code/GeneralSuccessCode.java b/src/main/java/com/example/RealMatch/global/presentation/code/GeneralSuccessCode.java index 2a347bd8..b326b6e5 100644 --- a/src/main/java/com/example/RealMatch/global/presentation/code/GeneralSuccessCode.java +++ b/src/main/java/com/example/RealMatch/global/presentation/code/GeneralSuccessCode.java @@ -16,8 +16,8 @@ public enum GeneralSuccessCode implements BaseSuccessCode { "AUTH201_1", "์ธ์ฆ์ด ํ™•์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."), CREATE(HttpStatus.CREATED, - "CREATE200_1", - "์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."), + "CREATE200_1", + "์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."), ALLOWED(HttpStatus.ACCEPTED, "AUTH203_1", "์š”์ฒญ์ด ํ—ˆ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."), From f4c8bbc672fc4ef1c8f017051dc8f8964431e62a Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Thu, 15 Jan 2026 02:42:35 +0900 Subject: [PATCH 05/12] =?UTF-8?q?fix(#7):=20jwt=20=EC=8B=9C=ED=81=AC?= =?UTF-8?q?=EB=A6=BF=EA=B0=92=20=EC=98=A4=EB=A5=98=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=B4=20CI=20workflow=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr-check.yml | 3 +++ .gitignore | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index ff2232e0..436b913b 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -15,6 +15,9 @@ jobs: POSTGRES_DB: myapp_db POSTGRES_USER: admin POSTGRES_PASSWORD: secret + JWT_SECRET: test-jwt-secret + JWT_ACCESS_EXPIRE_MS: 600000 + JWT_REFRESH_EXPIRE_MS: 1209600000 ports: - 5432:5432 options: >- diff --git a/.gitignore b/.gitignore index 9922c12a..7efdf480 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,12 @@ # ํŠน์ • ํŒŒ์ผ ์ œ์™ธ -src/main/resources/application-*.yml +#src/main/resources/application-*.yml src/main/resources/application.yml src/test/resources/application.yml src/test/resources/application-*.yml +# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ +.env + ### Gradle ### .gradle/ build/ From e029f785c32ea0d6bbd43381a439fac8296c7bfa Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Thu, 15 Jan 2026 03:22:31 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat(#7):=20CD=20=EC=9B=8C=ED=81=AC?= =?UTF-8?q?=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-prod.yml | 52 +++++++++++++++++++ .github/workflows/pr-check.yml | 9 ++-- docker-compose.yaml | 25 ++++----- .../global/config/SecurityConfig.java | 4 +- .../global/config/SwaggerConfig.java | 42 ++------------- src/main/resources/application-prod.yml | 48 +++++++++++++++++ 6 files changed, 123 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/cd-prod.yml create mode 100644 src/main/resources/application-prod.yml diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml new file mode 100644 index 00000000..cc9f1d4b --- /dev/null +++ b/.github/workflows/cd-prod.yml @@ -0,0 +1,52 @@ +name: CD - PROD + +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: "21" + distribution: "temurin" + + - uses: gradle/actions/setup-gradle@v3 + + - name: Grant execute permission + run: chmod +x gradlew + + - name: Build JAR + run: ./gradlew build -x test + + - name: Docker Login + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build & Push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/spot-backend:prod + ${{ secrets.DOCKERHUB_USERNAME }}/spot-backend:${{ github.sha }} + + - name: Deploy + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PROD_SERVER_IP }} + username: ubuntu + key: ${{ secrets.PROD_SERVER_SSH_KEY }} + script: | + cd /home/ubuntu/spot + git pull origin main + docker compose pull + docker compose up -d diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 436b913b..617d6bce 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -15,9 +15,6 @@ jobs: POSTGRES_DB: myapp_db POSTGRES_USER: admin POSTGRES_PASSWORD: secret - JWT_SECRET: test-jwt-secret - JWT_ACCESS_EXPIRE_MS: 600000 - JWT_REFRESH_EXPIRE_MS: 1209600000 ports: - 5432:5432 options: >- @@ -46,7 +43,7 @@ jobs: - name: Make application.properties run: | mkdir -p ./src/main/resources - echo "${{ secrets.APPLICATION_YML }}" > ./src/main/resources/application.yml + echo "${{ secrets.APPLICATION_TEST_YML }}" > ./src/main/resources/application.yml shell: bash - name: Check code formatting @@ -55,6 +52,10 @@ jobs: - name: Build with Gradle run: ./gradlew build -x test + env: + JWT_SECRET: test-jwt-secret + JWT_ACCESS_EXPIRE_MS: 600000 + JWT_REFRESH_EXPIRE_MS: 1209600000 - name: Run tests run: ./gradlew test diff --git a/docker-compose.yaml b/docker-compose.yaml index d81aed88..a2d2f9df 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,10 +6,10 @@ services: container_name: mysql_db restart: always environment: - MYSQL_DATABASE: myapp_db - MYSQL_USER: admin - MYSQL_PASSWORD: secret - MYSQL_ROOT_PASSWORD: rootsecret + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} ports: - "3306:3306" volumes: @@ -34,12 +34,13 @@ services: depends_on: - db - redis + env_file: + - .env environment: - - SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/myapp_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul - - SPRING_DATASOURCE_USERNAME=admin - - SPRING_DATASOURCE_PASSWORD=secret - - SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver - - SPRING_REDIS_HOST=redis - - SPRING_REDIS_PORT=6379 - - SPRING_PROFILES_ACTIVE=dev - + SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL} + SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME} + SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD} + SPRING_DATASOURCE_DRIVER_CLASS_NAME: ${SPRING_DATASOURCE_DRIVER_CLASS_NAME} + SPRING_REDIS_HOST: ${SPRING_REDIS_HOST} + SPRING_REDIS_PORT: ${SPRING_REDIS_PORT} + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} diff --git a/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java b/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java index ca77f6a0..1f558a40 100644 --- a/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java +++ b/src/main/java/com/example/RealMatch/global/config/SecurityConfig.java @@ -37,7 +37,7 @@ public class SecurityConfig { @Value("${cors.allowed-origin}") private String allowedOrigin; @Value("${swagger.server-url}") - String prodSwaggerUrl; + String swaggerUrl; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAuthEntryPoint customAuthEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) throws Exception { @@ -63,7 +63,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAuthEntr public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(List.of(allowedOrigin, "http://localhost:8080", prodSwaggerUrl)); + configuration.setAllowedOrigins(List.of(allowedOrigin, "http://localhost:8080", swaggerUrl)); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); configuration.setAllowCredentials(true); // ์ฟ ํ‚ค/์ธ์ฆ์ •๋ณด ํฌํ•จ ์š”์ฒญ diff --git a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java index e71c0f05..02accc0f 100644 --- a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java +++ b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java @@ -18,13 +18,12 @@ public class SwaggerConfig { @Value("${swagger.server-url}") - String prodSwaggerUrl; + String swaggerUrl; @Bean - @Profile("local") public OpenAPI localOpenAPI() { Info info = new Info() - .title("๐Ÿ”— RealMatch API (LOCAL)") + .title("๐Ÿ”— RealMatch API") .version("1.0.0") .description("ZzicGo API ๋ช…์„ธ์„œ์ž…๋‹ˆ๋‹ค."); @@ -41,49 +40,14 @@ public OpenAPI localOpenAPI() { Components components = new Components() .addSecuritySchemes(jwtSchemeName, securityScheme); - return new OpenAPI() - .info(info) - .addSecurityItem(securityRequirement) // โœ… ๋ชจ๋“  API์— ์ „์—ญ์ ์œผ๋กœ ์ธ์ฆ ์ ์šฉ - .components(components) - .servers(List.of( - new Server() - .url("http://localhost:8080") - .description("Local Development Server") - )); - } - - @Bean - @Profile("prod") - public OpenAPI prodOpenAPI() { - Info info = new Info() - .title("๐Ÿ”— RealMatch API (PROD)") - .version("1.0.0") - .description("RealMatch Production API ๋ช…์„ธ์„œ์ž…๋‹ˆ๋‹ค."); - - String jwtSchemeName = "JWT Authentication"; - - SecurityScheme securityScheme = new SecurityScheme() - .name("Authorization") - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT"); - - SecurityRequirement securityRequirement = - new SecurityRequirement().addList(jwtSchemeName); - - Components components = - new Components().addSecuritySchemes(jwtSchemeName, securityScheme); - return new OpenAPI() .info(info) .addSecurityItem(securityRequirement) .components(components) .servers(List.of( new Server() - .url(prodSwaggerUrl) - .description("Production Server") + .url(swaggerUrl) )); } - } diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 00000000..83a0b6ef --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,48 @@ +cors: + allowed-origin: http://${SERVER_IP} + +swagger: + server-url: ${SWAGGER_SERVER_URL} + +server: + timezone: Asia/Seoul + +spring: + application: + name: Spot + activate: + on-profile: prod + + datasource: + url: ${SPRING_DATASOURCE_URL} + username: ${SPRING_DATASOURCE_USERNAME} + password: ${SPRING_DATASOURCE_PASSWORD} + driver-class-name: ${SPRING_DATASOURCE_DRIVER_CLASS_NAME} + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + idle-timeout: 30000 + connection-timeout: 10000 + max-lifetime: 600000 + + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + format_sql: true + + security: + refresh-token: + expire-days: 14 + +logging: + level: + org.hibernate.SQL: warn + +jwt: + secret: ${JWT_SECRET} + access-expire-ms: ${JWT_ACCESS_EXPIRE_MS} + refresh-expire-ms: ${JWT_REFRESH_EXPIRE_MS} + From 5358d19b467cace003ee56b35460e45a79fe6369 Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Thu, 15 Jan 2026 03:25:55 +0900 Subject: [PATCH 07/12] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/RealMatch/global/config/SwaggerConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java index 02accc0f..eb7d38c9 100644 --- a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java +++ b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java @@ -5,7 +5,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; From bbe1e4ee48c0f2fdffbfdda473839183f367f9ea Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Thu, 15 Jan 2026 04:28:25 +0900 Subject: [PATCH 08/12] =?UTF-8?q?fix:=20application-test.yml=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20DB=20=EC=84=A4=EC=A0=95=EC=9D=84=20post?= =?UTF-8?q?gres=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr-check.yml | 18 +++++++---------- build.gradle | 3 +++ src/main/resources/application-test.yml | 27 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 src/main/resources/application-test.yml diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 617d6bce..869f393f 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -23,6 +23,12 @@ jobs: --health-timeout 5s --health-retries 5 + env: + SPRING_PROFILES_ACTIVE: test + JWT_SECRET: ZzM3YzN3M0E0c2N6VjZsZVdXa2h5V2h0d09uOFE= + JWT_ACCESS_EXPIRE_MS: 600000 + JWT_REFRESH_EXPIRE_MS: 1209600000 + steps: - name: Checkout code uses: actions/checkout@v4 @@ -33,29 +39,19 @@ jobs: java-version: '21' distribution: 'temurin' cache: gradle - + - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Make application.properties - run: | - mkdir -p ./src/main/resources - echo "${{ secrets.APPLICATION_TEST_YML }}" > ./src/main/resources/application.yml - shell: bash - - name: Check code formatting run: ./gradlew checkstyleMain checkstyleTest continue-on-error: true - name: Build with Gradle run: ./gradlew build -x test - env: - JWT_SECRET: test-jwt-secret - JWT_ACCESS_EXPIRE_MS: 600000 - JWT_REFRESH_EXPIRE_MS: 1209600000 - name: Run tests run: ./gradlew test diff --git a/build.gradle b/build.gradle index f490a7e2..8f359a16 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,9 @@ dependencies { // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9' + + // postgresql (CI test ์šฉ) + runtimeOnly 'org.postgresql:postgresql' } tasks.named('test') { diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 00000000..b1384a91 --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,27 @@ +spring: + application: + name: Spot + + datasource: + url: jdbc:postgresql://localhost:5432/myapp_db + username: admin + password: secret + driver-class-name: org.postgresql.Driver + + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + format_sql: true + show-sql: false + + security: + refresh-token: + expire-days: 14 + +jwt: + secret: ${JWT_SECRET} + access-expire-ms: ${JWT_ACCESS_EXPIRE_MS} + refresh-expire-ms: ${JWT_REFRESH_EXPIRE_MS} From d4c1826bbfd2201426a90d95ea280595d4f59784 Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Thu, 15 Jan 2026 04:38:45 +0900 Subject: [PATCH 09/12] =?UTF-8?q?fix:=20jwt=20=EC=8B=9C=ED=81=AC=EB=A6=BF?= =?UTF-8?q?=20=ED=82=A4=20=EA=B8=B8=EC=9D=B4=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 869f393f..bf727145 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -25,7 +25,7 @@ jobs: env: SPRING_PROFILES_ACTIVE: test - JWT_SECRET: ZzM3YzN3M0E0c2N6VjZsZVdXa2h5V2h0d09uOFE= + JWT_SECRET: pPmJ9ViYt8f6HAh2q5s36QmEUeyEFRcquPaNpnIGUK8er5DjfTKa4xbDsTFXQ7HRVfTLR2DIYs7s9iGdJ+Yb7Q== JWT_ACCESS_EXPIRE_MS: 600000 JWT_REFRESH_EXPIRE_MS: 1209600000 From c9432242e32355e85526fe9b636b6e12ad9b73fc Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Thu, 15 Jan 2026 04:48:01 +0900 Subject: [PATCH 10/12] =?UTF-8?q?fix:=20test=EC=97=90=EC=84=9C=20cors=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EB=B9=A0=EC=A7=84=20=EA=B2=83=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index b1384a91..41ce166c 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -1,3 +1,12 @@ +cors: + allowed-origin: http://localhost:3000 + +swagger: + server-url: http://localhost:8080 + +server: + timezone: Asia/Seoul + spring: application: name: Spot @@ -7,6 +16,12 @@ spring: username: admin password: secret driver-class-name: org.postgresql.Driver + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + idle-timeout: 30000 + connection-timeout: 10000 + max-lifetime: 600000 jpa: hibernate: From c898929682cf6bb15c940de471da0f9ecf9b80b7 Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Thu, 15 Jan 2026 04:57:33 +0900 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20CI=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=EC=97=90=EC=84=9C=20=EC=96=B4=ED=94=8C?= =?UTF-8?q?=EB=A6=AC=EC=BC=80=EC=9D=B4=EC=85=98=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index bf727145..ce2c3228 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -66,7 +66,7 @@ jobs: # ์ตœ๋Œ€ 60์ดˆ ๋Œ€๊ธฐํ•˜๋ฉด์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ํ™•์ธ for i in {1..60}; do # ๋กœ๊ทธ์—์„œ ์‹œ์ž‘ ์™„๋ฃŒ ๋ฉ”์‹œ์ง€ ํ™•์ธ - if grep -q "Started SpotApplication" bootrun.log 2>/dev/null; then + if grep -q "Started RealMatchApplication" bootrun.log; then echo "โœ… Application started successfully after ${i}s" kill $APP_PID 2>/dev/null || true exit 0 From b285988e275385989cd645d25b5b430852c76c41 Mon Sep 17 00:00:00 2001 From: ParkJiYeoung8297 Date: Thu, 15 Jan 2026 12:35:49 +0900 Subject: [PATCH 12/12] =?UTF-8?q?fix(#7):=20DI=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../com/example/RealMatch/global/config/SwaggerConfig.java | 4 ++-- .../example/RealMatch/global/controller/TestController.java | 2 +- .../global/presentation/advice/CustomAccessDeniedHandler.java | 4 +++- .../global/presentation/advice/CustomAuthEntryPoint.java | 4 +++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 7efdf480..45116f7b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ src/test/resources/application-*.yml # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ .env +.env.* ### Gradle ### .gradle/ diff --git a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java index eb7d38c9..258b3553 100644 --- a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java +++ b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java @@ -17,14 +17,14 @@ public class SwaggerConfig { @Value("${swagger.server-url}") - String swaggerUrl; + private String swaggerUrl; @Bean public OpenAPI localOpenAPI() { Info info = new Info() .title("๐Ÿ”— RealMatch API") .version("1.0.0") - .description("ZzicGo API ๋ช…์„ธ์„œ์ž…๋‹ˆ๋‹ค."); + .description("RealMatch API ๋ช…์„ธ์„œ์ž…๋‹ˆ๋‹ค."); String jwtSchemeName = "JWT Authentication"; diff --git a/src/main/java/com/example/RealMatch/global/controller/TestController.java b/src/main/java/com/example/RealMatch/global/controller/TestController.java index 1524bf99..3ee96566 100644 --- a/src/main/java/com/example/RealMatch/global/controller/TestController.java +++ b/src/main/java/com/example/RealMatch/global/controller/TestController.java @@ -16,7 +16,7 @@ @Tag(name = "test", description = "ํ…Œ์ŠคํŠธ์šฉ API") @RestController -@RequestMapping("/api/v1") +@RequestMapping("/api") public class TestController { @Operation(summary = "api ํ…Œ์ŠคํŠธ ํ™•์ธ", diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java index edec902c..012d123c 100644 --- a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java @@ -12,11 +12,13 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; @Component +@RequiredArgsConstructor public class CustomAccessDeniedHandler implements AccessDeniedHandler { - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper; @Override public void handle( diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java index a0de2da5..79fdc7ed 100644 --- a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java @@ -12,11 +12,13 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; @Component +@RequiredArgsConstructor public class CustomAuthEntryPoint implements AuthenticationEntryPoint { - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper; @Override public void commence(