From 40ef92b107911bde90aad48b732ef659f237607d Mon Sep 17 00:00:00 2001 From: SattarovAA Date: Tue, 6 May 2025 20:41:59 +0300 Subject: [PATCH] init --- docker-compose.yml | 18 ++--- pom.xml | 23 ++++++ .../example/keycloakdemo/DemoController.java | 21 ----- .../keycloakdemo/config/AppConfig.java | 11 +++ .../keycloakdemo/config/SecurityConfig.java | 64 +++++++++++++++ .../controller/AuthController.java | 77 +++++++++++++++++++ .../controller/DemoController.java | 41 ++++++++++ .../model/dto/UserRegistrationDto.java | 4 + src/main/resources/application.properties | 0 src/main/resources/config/application.yml | 15 ++++ 10 files changed, 244 insertions(+), 30 deletions(-) delete mode 100644 src/main/java/com/example/keycloakdemo/DemoController.java create mode 100644 src/main/java/com/example/keycloakdemo/config/AppConfig.java create mode 100644 src/main/java/com/example/keycloakdemo/config/SecurityConfig.java create mode 100644 src/main/java/com/example/keycloakdemo/controller/AuthController.java create mode 100644 src/main/java/com/example/keycloakdemo/controller/DemoController.java create mode 100644 src/main/java/com/example/keycloakdemo/model/dto/UserRegistrationDto.java delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/config/application.yml 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 +