Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
23 changes: 23 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,34 @@
<relativePath/>
</parent>

<properties>
<java.version>17</java.version>
<org.keycloak.version>21.1.0</org.keycloak.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>${org.keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>${org.keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
21 changes: 0 additions & 21 deletions src/main/java/com/example/keycloakdemo/DemoController.java

This file was deleted.

11 changes: 11 additions & 0 deletions src/main/java/com/example/keycloakdemo/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -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 {
}
64 changes: 64 additions & 0 deletions src/main/java/com/example/keycloakdemo/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.example.keycloakdemo.model.dto;

public record UserRegistrationDto(String username, String password, String role) {
}
Empty file.
15 changes: 15 additions & 0 deletions src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
@@ -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