Skip to content

Commit 533c443

Browse files
committed
Added security for local running applications
1 parent a44e611 commit 533c443

File tree

13 files changed

+554
-17
lines changed

13 files changed

+554
-17
lines changed

.idea/workspace.xml

Lines changed: 38 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker/docker-compose-infra.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ services:
3030
- ./postgresql/init.sql:/docker-entrypoint-initdb.d/init.sql
3131
networks:
3232
- shared-network
33+
34+
keycloak:
35+
image: quay.io/keycloak/keycloak:latest
36+
container_name: keycloak
37+
ports:
38+
- "8081:8080"
39+
environment:
40+
KEYCLOAK_ADMIN: admin
41+
KEYCLOAK_ADMIN_PASSWORD: admin
42+
command: [ "start-dev" ]
43+
networks:
44+
- shared-network
45+
3346
networks:
3447
shared-network:
3548
name: shared-network

microservices/course-composite-service/pom.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@
3838
<groupId>org.springframework.boot</groupId>
3939
<artifactId>spring-boot-starter-webflux</artifactId>
4040
</dependency>
41+
<!--Security related dependency-->
42+
<dependency>
43+
<groupId>org.springframework.boot</groupId>
44+
<artifactId>spring-boot-starter-security</artifactId>
45+
</dependency>
4146

47+
<dependency>
48+
<groupId>org.springframework.boot</groupId>
49+
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
50+
</dependency>
4251
<dependency>
4352
<groupId>io.javatab.util</groupId>
4453
<artifactId>util</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.javatab.microservices.composite.course.config;
2+
3+
import org.springframework.security.access.prepost.PreAuthorize;
4+
import org.springframework.security.core.Authentication;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
@RestController
10+
@RequestMapping("/protectedapi")
11+
public class ResourceController {
12+
13+
@GetMapping("/public")
14+
public String publicResource() {
15+
return "This is a public resource. No authentication required.";
16+
}
17+
18+
@GetMapping("/user")
19+
//@PreAuthorize("hasRole('USER')")
20+
public String userResource(Authentication authentication) {
21+
return "Hello, " + authentication.getName() + "! You have USER access.";
22+
}
23+
24+
@GetMapping("/admin")
25+
//@PreAuthorize("hasRole('ADMIN')")
26+
public String adminResource(Authentication authentication) {
27+
return "Hello, " + authentication.getName() + "! You have ADMIN access.";
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package io.javatab.microservices.composite.course.config;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.core.convert.converter.Converter;
8+
import org.springframework.security.config.Customizer;
9+
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
10+
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
11+
import org.springframework.security.config.web.server.ServerHttpSecurity;
12+
import org.springframework.security.core.GrantedAuthority;
13+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
14+
import org.springframework.security.oauth2.jwt.Jwt;
15+
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
16+
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
17+
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
18+
import org.springframework.security.web.server.SecurityWebFilterChain;
19+
import org.springframework.web.server.ServerWebExchange;
20+
import reactor.core.publisher.Mono;
21+
22+
import java.util.ArrayList;
23+
import java.util.Collection;
24+
import java.util.List;
25+
import java.util.Map;
26+
27+
@Configuration
28+
@EnableWebFluxSecurity
29+
public class SecurityConfig {
30+
31+
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
32+
33+
@Bean
34+
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
35+
http
36+
.authorizeExchange(exchanges -> exchanges
37+
.pathMatchers("/api/course-aggregate").hasRole("COURSE-AGGREGATE-READ")
38+
.anyExchange().authenticated()
39+
)
40+
.oauth2ResourceServer(oauth2 -> oauth2
41+
.jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor()))
42+
);
43+
44+
// Add filter to log roles
45+
http.addFilterAt((exchange, chain) -> logRoles(exchange).then(chain.filter(exchange)),
46+
SecurityWebFiltersOrder.AUTHORIZATION);
47+
48+
return http.build();
49+
}
50+
51+
@Bean
52+
public ReactiveJwtDecoder jwtDecoder() {
53+
return NimbusReactiveJwtDecoder.withJwkSetUri("http://localhost:8081/realms/course-management-realm/protocol/openid-connect/certs").build();
54+
}
55+
56+
@Bean
57+
public Converter<Jwt, Mono<JwtAuthenticationToken>> grantedAuthoritiesExtractor() {
58+
return new Converter<Jwt, Mono<JwtAuthenticationToken>>() {
59+
@Override
60+
public Mono<JwtAuthenticationToken> convert(Jwt jwt) {
61+
Collection<GrantedAuthority> authorities = new ArrayList<>();
62+
63+
// Extract realm roles
64+
Map<String, Object> realmAccess = jwt.getClaim("realm_access");
65+
if (realmAccess != null && realmAccess.containsKey("roles")) {
66+
List<String> roles = (List<String>) realmAccess.get("roles");
67+
authorities.addAll(roles.stream()
68+
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
69+
.toList());
70+
}
71+
72+
// Extract client roles (replace "my-resource-server" with your client ID)
73+
/*Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
74+
if (resourceAccess != null) {
75+
Map<String, Object> clientRoles = (Map<String, Object>) resourceAccess.get("my-resource-server");
76+
if (clientRoles != null && clientRoles.containsKey("roles")) {
77+
List<String> roles = (List<String>) clientRoles.get("roles");
78+
authorities.addAll(roles.stream()
79+
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
80+
.toList());
81+
}
82+
}*/
83+
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
84+
if (resourceAccess != null) {
85+
resourceAccess.forEach((resource, access) -> {
86+
if (access instanceof Map) {
87+
Map<String, Object> clientRoles = (Map<String, Object>) access;
88+
if (clientRoles.containsKey("roles")) {
89+
List<String> roles = (List<String>) clientRoles.get("roles");
90+
authorities.addAll(roles.stream()
91+
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
92+
.toList());
93+
}
94+
}
95+
});
96+
}
97+
98+
return Mono.just(new JwtAuthenticationToken(jwt, authorities));
99+
}
100+
};
101+
}
102+
103+
private Mono<Void> logRoles(ServerWebExchange exchange) {
104+
return exchange.getPrincipal()
105+
.cast(JwtAuthenticationToken.class)
106+
.doOnNext(jwtAuth -> {
107+
Collection<? extends GrantedAuthority> authorities = jwtAuth.getAuthorities();
108+
logger.info("Roles in Resource Server: {}", authorities);
109+
})
110+
.then();
111+
}
112+
}

microservices/course-composite-service/src/main/resources/application.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ logging:
1414
level:
1515
root: INFO
1616
io.javatab.microservices.composite.course: DEBUG
17+
18+
# Security related properties
19+
security:
20+
oauth2:
21+
resourceserver:
22+
jwt:
23+
issuer-uri: http://localhost:8081/realms/course-management-realm
24+
jwk-set-uri: http://localhost:8081/realms/course-management-realm/protocol/openid-connect/certs
25+
1726
---
1827
spring:
1928
config:

microservices/course-service/pom.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@
6161
<artifactId>spring-boot-starter-validation</artifactId>
6262
</dependency>
6363

64+
<dependency>
65+
<groupId>org.springframework.boot</groupId>
66+
<artifactId>spring-boot-starter-security</artifactId>
67+
</dependency>
68+
69+
<dependency>
70+
<groupId>org.springframework.boot</groupId>
71+
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
72+
</dependency>
6473

6574
<dependency>
6675
<groupId>io.javatab.util</groupId>

0 commit comments

Comments
 (0)