|
3 | 3 | import lombok.extern.slf4j.Slf4j; |
4 | 4 | import org.springframework.context.annotation.Bean; |
5 | 5 | import org.springframework.context.annotation.Configuration; |
6 | | -import org.springframework.http.HttpStatus; |
| 6 | +import org.springframework.core.annotation.Order; |
7 | 7 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; |
8 | | -import org.springframework.security.config.web.server.SecurityWebFiltersOrder; |
9 | 8 | import org.springframework.security.config.web.server.ServerHttpSecurity; |
10 | 9 | import org.springframework.security.web.server.SecurityWebFilterChain; |
11 | | -import org.springframework.web.server.ServerWebExchange; |
| 10 | +import org.springframework.http.server.reactive.ServerHttpRequest; |
12 | 11 | import org.springframework.web.server.WebFilter; |
13 | | -import org.springframework.web.server.WebFilterChain; |
14 | 12 | import reactor.core.publisher.Mono; |
15 | | -import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy; |
16 | 13 |
|
17 | 14 | import java.util.Arrays; |
18 | 15 | import java.util.List; |
|
23 | 20 | public class LightSecurityConfig { |
24 | 21 |
|
25 | 22 | private static final List<String> PUBLIC_PATHS = Arrays.asList( |
26 | | - "/actuator/", |
27 | | - "/v3/api-docs", |
28 | | - "/swagger-ui", |
29 | | - "/webjars/" |
30 | | - ); |
31 | | - |
32 | | - private static final List<RequiredHeader> REQUIRED_HEADERS = Arrays.asList( |
33 | | - new RequiredHeader("X-User-Id", "User ID is required"), |
34 | | - new RequiredHeader("X-User-Role", "User role is required"), |
35 | | - new RequiredHeader("X-User-Email", "User email is required") |
| 23 | + "/actuator", |
| 24 | + "/actuator/health", |
| 25 | + "/actuator/info", |
| 26 | + "/health", |
| 27 | + "/info", |
| 28 | + "/v3/api-docs", |
| 29 | + "/swagger-ui", |
| 30 | + "/webjars/" |
36 | 31 | ); |
37 | 32 |
|
38 | 33 | @Bean |
| 34 | + @Order(1) |
39 | 35 | public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { |
40 | 36 | return http |
41 | | - .csrf(ServerHttpSecurity.CsrfSpec::disable) |
42 | | - .cors(cors -> cors.disable()) |
43 | | - .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) |
44 | | - .formLogin(ServerHttpSecurity.FormLoginSpec::disable) |
45 | | - .authorizeExchange(exchanges -> exchanges |
46 | | - .pathMatchers(getPublicPaths()).permitAll() |
47 | | - .pathMatchers("/admin/**").hasRole("ADMIN") |
48 | | - .anyExchange().authenticated() |
49 | | - ) |
50 | | - .addFilterAt(headerValidationFilter(), SecurityWebFiltersOrder.AUTHENTICATION) |
51 | | - .exceptionHandling(handling -> handling |
52 | | - .authenticationEntryPoint((exchange, ex) -> { |
53 | | - exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); |
54 | | - return exchange.getResponse().setComplete(); |
55 | | - }) |
56 | | - ) |
57 | | - .headers(headers -> headers |
58 | | - .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'")) |
59 | | - .frameOptions(frame -> frame.mode(org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter.Mode.DENY)) |
60 | | - .referrerPolicy(referrer -> referrer.policy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)) |
61 | | - .xssProtection(xss -> xss.disable()) |
62 | | - ) |
63 | | - .build(); |
| 37 | + .csrf(ServerHttpSecurity.CsrfSpec::disable) |
| 38 | + .formLogin(ServerHttpSecurity.FormLoginSpec::disable) |
| 39 | + .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) |
| 40 | + .anonymous(anonymous -> anonymous.authorities("ROLE_ANONYMOUS")) |
| 41 | + .authorizeExchange(exchanges -> exchanges |
| 42 | + .pathMatchers("/**").permitAll() // Tüm isteklere izin ver, header kontrolünü WebFilter ile yap |
| 43 | + ) |
| 44 | + .build(); |
64 | 45 | } |
65 | 46 |
|
66 | 47 | @Bean |
67 | | - public WebFilter headerValidationFilter() { |
| 48 | + @Order(0) // En önce çalışacak |
| 49 | + public WebFilter loggingHeadersFilter() { |
68 | 50 | return (exchange, chain) -> { |
69 | | - if (isPublicPath(exchange.getRequest().getPath().value())) { |
| 51 | + ServerHttpRequest request = exchange.getRequest(); |
| 52 | + String path = request.getPath().value(); |
| 53 | + |
| 54 | + log.debug("İstek geldi: {} {}", request.getMethod(), path); |
| 55 | + |
| 56 | + if (isPublicPath(path)) { |
| 57 | + log.debug("Public path erişimi: {} - kontrolsüz geçiyor", path); |
70 | 58 | return chain.filter(exchange); |
71 | 59 | } |
72 | | - |
73 | | - for (RequiredHeader header : REQUIRED_HEADERS) { |
74 | | - String headerValue = exchange.getRequest().getHeaders().getFirst(header.name); |
75 | | - if (headerValue == null || headerValue.trim().isEmpty()) { |
76 | | - return handleMissingHeader(exchange, header.message); |
77 | | - } |
| 60 | + |
| 61 | + // Debug için tüm headerları yazdıralım |
| 62 | + request.getHeaders().forEach((key, values) -> |
| 63 | + log.debug("Header: {} = {}", key, values)); |
| 64 | + |
| 65 | + // X-User-Id header'ı kontrolü |
| 66 | + String userId = request.getHeaders().getFirst("X-User-Id"); |
| 67 | + |
| 68 | + if (userId == null || userId.isEmpty()) { |
| 69 | + log.warn("Gerekli X-User-Id header eksik, ancak isteğe devam ediliyor"); |
| 70 | + // İsteği reddetmek yerine loga yazıp devam edelim |
| 71 | + // API Gateway zaten yetkilendirmeyi yapıyor |
| 72 | + } else { |
| 73 | + log.debug("İstek kimlik doğrulaması başarılı: {}", userId); |
78 | 74 | } |
79 | | - |
| 75 | + |
80 | 76 | return chain.filter(exchange); |
81 | 77 | }; |
82 | 78 | } |
83 | 79 |
|
84 | 80 | private boolean isPublicPath(String path) { |
85 | 81 | return PUBLIC_PATHS.stream().anyMatch(path::startsWith); |
86 | 82 | } |
87 | | - |
88 | | - private String[] getPublicPaths() { |
89 | | - return PUBLIC_PATHS.stream() |
90 | | - .map(path -> path + "**") |
91 | | - .toArray(String[]::new); |
92 | | - } |
93 | | - |
94 | | - private Mono<Void> handleMissingHeader(ServerWebExchange exchange, String message) { |
95 | | - exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST); |
96 | | - exchange.getResponse().getHeaders().add("X-Error-Message", message); |
97 | | - return exchange.getResponse().setComplete(); |
98 | | - } |
99 | | - |
100 | | - private static class RequiredHeader { |
101 | | - final String name; |
102 | | - final String message; |
103 | | - |
104 | | - RequiredHeader(String name, String message) { |
105 | | - this.name = name; |
106 | | - this.message = message; |
107 | | - } |
108 | | - } |
109 | 83 | } |
| 84 | + |
0 commit comments