diff --git a/docker-compose.yml b/docker-compose.yml
index bec2026..718ca3b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,12 +11,12 @@ services:
ports:
- "8080:8080"
- spring-app:
- build: .
- container_name: spring-keycloak-demo
- ports:
- - "8081:8081"
- environment:
- SPRING_PROFILES_ACTIVE: dev
- depends_on:
- - keycloak
+# spring-app:
+# build: .
+# container_name: spring-keycloak-demo
+# ports:
+# - "8088:8088"
+# environment:
+# SPRING_PROFILES_ACTIVE: dev
+# depends_on:
+# - keycloak
diff --git a/pom.xml b/pom.xml
index cd1fa84..9829762 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,11 +13,34 @@
+
+ 17
+ 21.1.0
+
+
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.keycloak
+ keycloak-spring-boot-starter
+ ${org.keycloak.version}
+
+
+ org.keycloak
+ keycloak-admin-client
+ ${org.keycloak.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-authorization-server
+
diff --git a/src/main/java/com/example/keycloakdemo/DemoController.java b/src/main/java/com/example/keycloakdemo/DemoController.java
deleted file mode 100644
index 76af2bb..0000000
--- a/src/main/java/com/example/keycloakdemo/DemoController.java
+++ /dev/null
@@ -1,21 +0,0 @@
-
-package com.example.keycloakdemo;
-
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/api")
-public class DemoController {
-
- @GetMapping("/public")
- public String publicEndpoint() {
- return "This is a public endpoint accessible by anyone.";
- }
-
- @GetMapping("/protected")
- public String protectedEndpoint() {
- return "This is a protected endpoint accessible only by authenticated users.";
- }
-}
diff --git a/src/main/java/com/example/keycloakdemo/config/AppConfig.java b/src/main/java/com/example/keycloakdemo/config/AppConfig.java
new file mode 100644
index 0000000..c0e8de3
--- /dev/null
+++ b/src/main/java/com/example/keycloakdemo/config/AppConfig.java
@@ -0,0 +1,11 @@
+package com.example.keycloakdemo.config;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+@Configuration
+@ComponentScan("com.example")
+@PropertySource("classpath:config/application.yml")
+public class AppConfig {
+}
diff --git a/src/main/java/com/example/keycloakdemo/config/SecurityConfig.java b/src/main/java/com/example/keycloakdemo/config/SecurityConfig.java
new file mode 100644
index 0000000..ca0fa3e
--- /dev/null
+++ b/src/main/java/com/example/keycloakdemo/config/SecurityConfig.java
@@ -0,0 +1,64 @@
+package com.example.keycloakdemo.config;
+
+import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
+import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
+import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
+import org.springframework.security.core.session.SessionRegistryImpl;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+
+@KeycloakConfiguration
+@EnableMethodSecurity
+public class SecurityConfig {
+
+ @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
+ private String certsUrl;
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) {
+ KeycloakAuthenticationProvider keycloakAuthenticationProvider = new KeycloakAuthenticationProvider();
+ keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
+ auth.authenticationProvider(keycloakAuthenticationProvider);
+ }
+
+ @Bean
+ protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
+ return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
+ }
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http.authorizeHttpRequests(auth -> auth
+ .requestMatchers("/api/auth/*").permitAll()
+ .requestMatchers("/api/public").permitAll()
+ .anyRequest().authenticated())
+ .csrf(AbstractHttpConfigurer::disable)
+ .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
+ return http.build();
+ }
+
+ @Bean
+ public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
+ return new KeycloakSpringBootConfigResolver();
+ }
+
+ @Bean
+ public JwtDecoder jwtDecoder() {
+ return NimbusJwtDecoder.withJwkSetUri(certsUrl)
+ .jwsAlgorithm(SignatureAlgorithm.RS256)
+ .build();
+ }
+}
diff --git a/src/main/java/com/example/keycloakdemo/controller/AuthController.java b/src/main/java/com/example/keycloakdemo/controller/AuthController.java
new file mode 100644
index 0000000..97a14a9
--- /dev/null
+++ b/src/main/java/com/example/keycloakdemo/controller/AuthController.java
@@ -0,0 +1,77 @@
+package com.example.keycloakdemo.controller;
+
+import com.example.keycloakdemo.model.dto.UserRegistrationDto;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.KeycloakBuilder;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.ws.rs.core.Response;
+import java.util.Collections;
+
+@RestController
+@RequestMapping("/api/auth")
+public class AuthController {
+
+ @Value("${keycloak.realm}")
+ private String realm;
+
+ @Value("${keycloak.auth-server-url}")
+ private String authServerUrl;
+
+ @PostMapping("/register")
+ public String registerUser(@RequestBody UserRegistrationDto request) {
+ try {
+ Keycloak keycloak = KeycloakBuilder.builder()
+ .serverUrl(authServerUrl)
+ .realm("master")
+ .username("admin")
+ .password("admin")
+ .clientId("admin-cli")
+ .build();
+
+ UserRepresentation user = new UserRepresentation();
+ user.setUsername(request.username());
+ user.setEnabled(true);
+ user.setEmailVerified(true);
+
+ CredentialRepresentation credential = new CredentialRepresentation();
+ credential.setType(CredentialRepresentation.PASSWORD);
+ credential.setValue(request.password());
+ credential.setTemporary(false);
+ user.setCredentials(Collections.singletonList(credential));
+
+ Response response = keycloak.realm(realm).users().create(user);
+
+ if (response.getStatus() == 201) {
+ String userId = response.getLocation()
+ .getPath()
+ .replaceAll(".*/([^/]+)$", "$1");
+
+ RoleRepresentation representation = keycloak.realm(realm)
+ .roles()
+ .get(request.role())
+ .toRepresentation();
+
+ keycloak.realm(realm)
+ .users()
+ .get(userId)
+ .roles()
+ .realmLevel()
+ .add(Collections.singletonList(representation));
+
+ return "User registered successfully with role: " + request.role();
+ } else {
+ return "Error registering user: " + response.getStatusInfo().getReasonPhrase();
+ }
+ } catch (Exception e) {
+ return "Registration failed: " + e.getMessage();
+ }
+ }
+}
diff --git a/src/main/java/com/example/keycloakdemo/controller/DemoController.java b/src/main/java/com/example/keycloakdemo/controller/DemoController.java
new file mode 100644
index 0000000..f22b98e
--- /dev/null
+++ b/src/main/java/com/example/keycloakdemo/controller/DemoController.java
@@ -0,0 +1,41 @@
+
+package com.example.keycloakdemo.controller;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api")
+public class DemoController {
+
+ @GetMapping("/public")
+ public String publicEndpoint() {
+ return "This is a public endpoint accessible by anyone.";
+ }
+
+ @GetMapping("/protected")
+ @PreAuthorize("isAuthenticated()")
+ public String protectedEndpoint() {
+ return "This is a protected endpoint accessible only by authenticated users.";
+ }
+
+ @GetMapping("/admin")
+ @PreAuthorize("hasRole('ADMIN')")
+ public String adminEndpoint() {
+ return "This is admin endpoint accessible only by admin";
+ }
+
+ @GetMapping("/user")
+ @PreAuthorize("hasRole('USER')")
+ public String userEndpoint() {
+ return "This is user endpoint accessible only by user";
+ }
+
+ @GetMapping("/moderator")
+ @PreAuthorize("hasRole('MODERATOR')")
+ public String moderatorEndpoint() {
+ return "This is moderator endpoint accessible only by moderator";
+ }
+}
diff --git a/src/main/java/com/example/keycloakdemo/model/dto/UserRegistrationDto.java b/src/main/java/com/example/keycloakdemo/model/dto/UserRegistrationDto.java
new file mode 100644
index 0000000..333794a
--- /dev/null
+++ b/src/main/java/com/example/keycloakdemo/model/dto/UserRegistrationDto.java
@@ -0,0 +1,4 @@
+package com.example.keycloakdemo.model.dto;
+
+public record UserRegistrationDto(String username, String password, String role) {
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
deleted file mode 100644
index e69de29..0000000
diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml
new file mode 100644
index 0000000..5f65958
--- /dev/null
+++ b/src/main/resources/config/application.yml
@@ -0,0 +1,15 @@
+server:
+ port: 8088
+
+keycloak:
+ realm: my_realm
+ auth-server-url: http://localhost:8080
+ resource: my_client
+ public-client: true
+spring:
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: ${keycloak.auth-server-url}/realms/${keycloak.realm}/protocol/openid-connect/certs
+