Skip to content

Commit b506031

Browse files
authored
Merge pull request #5 from Profect-4th-IRUM/feature/#3-security-filter
Feature/#3 security filter
2 parents ce9fc33 + 31c405c commit b506031

9 files changed

Lines changed: 218 additions & 1 deletion

File tree

build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,19 @@ ext {
2424
}
2525

2626
dependencies {
27+
//webflux
2728
implementation 'org.springframework.boot:spring-boot-starter-webflux'
29+
//gateway
2830
implementation 'org.springframework.cloud:spring-cloud-starter-gateway-server-webflux'
31+
//config
2932
implementation 'org.springframework.cloud:spring-cloud-starter-config'
3033

34+
//Spring Security & JWT
35+
implementation 'org.springframework.boot:spring-boot-starter-security'
36+
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
37+
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
38+
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
39+
3140
compileOnly 'org.projectlombok:lombok'
3241
annotationProcessor 'org.projectlombok:lombok'
3342

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.irum.apigateway.config;
2+
3+
import com.irum.apigateway.properties.GatewayPathProperties;
4+
import com.irum.apigateway.properties.JwtProperties;
5+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
6+
import org.springframework.context.annotation.Configuration;
7+
8+
@EnableConfigurationProperties({JwtProperties.class, GatewayPathProperties.class})
9+
@Configuration
10+
public class PropertiesConfig {}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.irum.apigateway.config;
2+
3+
import com.irum.apigateway.filter.JwtAuthenticationFilter;
4+
import com.irum.apigateway.properties.GatewayPathProperties;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
9+
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
10+
import org.springframework.security.config.web.server.ServerHttpSecurity;
11+
import org.springframework.security.web.server.SecurityWebFilterChain;
12+
import org.springframework.web.cors.CorsConfiguration;
13+
import org.springframework.web.cors.reactive.CorsConfigurationSource;
14+
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
15+
16+
@Configuration
17+
@EnableWebFluxSecurity
18+
@RequiredArgsConstructor
19+
public class SecurityConfig {
20+
21+
private final GatewayPathProperties paths;
22+
private final JwtAuthenticationFilter jwtAuthenticationFilter;
23+
24+
@Bean
25+
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
26+
http.csrf(ServerHttpSecurity.CsrfSpec::disable)
27+
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
28+
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
29+
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
30+
.authorizeExchange(
31+
exchange -> {
32+
paths.getPublicEndpoints()
33+
.forEach(
34+
route -> {
35+
route.getMethods()
36+
.forEach(
37+
httpMethod -> {
38+
exchange.pathMatchers(
39+
httpMethod,
40+
route.getPath())
41+
.permitAll();
42+
});
43+
});
44+
exchange.anyExchange().denyAll();
45+
});
46+
return http.addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
47+
.build();
48+
}
49+
50+
@Bean
51+
public CorsConfigurationSource corsConfigurationSource() {
52+
CorsConfiguration config = new CorsConfiguration();
53+
config.setAllowCredentials(true);
54+
config.addAllowedHeader("*");
55+
config.addAllowedMethod("*");
56+
config.addExposedHeader("Authorization");
57+
config.addAllowedOrigin("http://localhost:8080");
58+
config.setMaxAge(3600L);
59+
60+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
61+
source.registerCorsConfiguration("/**", config);
62+
return source;
63+
}
64+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.irum.apigateway.filter;
2+
3+
import com.irum.apigateway.properties.JwtProperties;
4+
import io.jsonwebtoken.Claims;
5+
import io.jsonwebtoken.JwtException;
6+
import io.jsonwebtoken.Jwts;
7+
import io.jsonwebtoken.security.Keys;
8+
import java.nio.charset.StandardCharsets;
9+
import java.util.Collection;
10+
import java.util.Collections;
11+
import javax.crypto.SecretKey;
12+
import lombok.RequiredArgsConstructor;
13+
import lombok.extern.slf4j.Slf4j;
14+
import org.springframework.http.HttpHeaders;
15+
import org.springframework.http.server.reactive.ServerHttpRequest;
16+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
17+
import org.springframework.security.core.GrantedAuthority;
18+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
19+
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
20+
import org.springframework.security.core.context.SecurityContextImpl;
21+
import org.springframework.stereotype.Component;
22+
import org.springframework.web.server.ServerWebExchange;
23+
import org.springframework.web.server.WebFilter;
24+
import org.springframework.web.server.WebFilterChain;
25+
import reactor.core.publisher.Mono;
26+
27+
@Component
28+
@RequiredArgsConstructor
29+
@Slf4j
30+
public class JwtAuthenticationFilter implements WebFilter {
31+
32+
private final JwtProperties jwtProperties;
33+
34+
@Override
35+
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
36+
String authHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
37+
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
38+
return chain.filter(exchange);
39+
}
40+
41+
String token = authHeader.substring(7);
42+
try {
43+
SecretKey key =
44+
Keys.hmacShaKeyFor(
45+
jwtProperties.accessTokenSecret().getBytes(StandardCharsets.UTF_8));
46+
Claims claims =
47+
Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload();
48+
String memberId = claims.getSubject();
49+
String role = claims.get("TOKEN_ROLE_NAME", String.class);
50+
GrantedAuthority authority = new SimpleGrantedAuthority(role);
51+
Collection<GrantedAuthority> authorities = Collections.singletonList(authority);
52+
53+
UsernamePasswordAuthenticationToken auth =
54+
new UsernamePasswordAuthenticationToken(memberId, null, authorities);
55+
ServerHttpRequest mutatedRequest =
56+
exchange.getRequest().mutate().header("X-Member-Id", memberId).build();
57+
58+
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
59+
60+
return chain.filter(mutatedExchange)
61+
.contextWrite(
62+
ReactiveSecurityContextHolder.withSecurityContext(
63+
Mono.just(new SecurityContextImpl(auth))));
64+
65+
} catch (JwtException e) {
66+
log.warn("JWT Validation failed: {}", e.getMessage());
67+
return chain.filter(exchange);
68+
}
69+
}
70+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.irum.apigateway.properties;
2+
3+
import java.util.List;
4+
import lombok.Data;
5+
import org.springframework.boot.context.properties.ConfigurationProperties;
6+
import org.springframework.cloud.context.config.annotation.RefreshScope;
7+
import org.springframework.http.HttpMethod;
8+
9+
@Data
10+
@RefreshScope
11+
@ConfigurationProperties(prefix = "security.endpoints")
12+
public class GatewayPathProperties {
13+
14+
private List<Route> publicEndpoints;
15+
private List<Route> customerEndpoints;
16+
private List<Route> ownerEndpoints;
17+
private List<Route> managerEndpoints;
18+
private List<Route> masterEndpoints;
19+
20+
@Data
21+
public static class Route {
22+
private String path;
23+
private List<HttpMethod> methods;
24+
}
25+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.irum.apigateway.properties;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
5+
@ConfigurationProperties(prefix = "security.jwt")
6+
// @RefreshScope
7+
public record JwtProperties(String accessTokenSecret) {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
spring:
2+
config:
3+
activate:
4+
on-profile: "local"
5+
6+
server:
7+
port: ${PORT:8080}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
spring:
2+
config:
3+
activate:
4+
on-profile: "security"
5+
security:
6+
endpoints:
7+
public-endpoints:
8+
- path: /member-service/**
9+
methods: [POST, GET, PUT, PATCH, DELETE]
10+
customer-endpoints:
11+
- path: /**
12+
methods: [POST, GET, PUT, PATCH, DELETE]
13+
manager-endpoints:
14+
- path: /**
15+
methods: [POST, GET, PUT, PATCH, DELETE]
16+
master-endpoints:
17+
- path: /**
18+
methods: [POST, GET, PUT, PATCH, DELETE]
19+
jwt:
20+
access-token-secret: ${JWT_ACCESS_TOKEN_SECRET}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
spring:
22
application:
33
name: api-gateway
4+
profiles:
5+
active: local
6+
include:
7+
- routes
8+
- security
49

510
config:
6-
import: "optional:configserver:${CONFIG_SERVER_URL:localhost:8888}"
11+
import: "optional:configserver:${CONFIG_SERVER_URL:http://localhost:8888}"

0 commit comments

Comments
 (0)