diff --git a/pom.xml b/pom.xml
index f2ab9d3049519f6a30c5911e54b1053bc205bdad..5b674b88f1f5904d2f838f128d22d424f0581f31 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
 	</parent>
 	<groupId>de.ozgcloud</groupId>
 	<artifactId>administration</artifactId>
-	<version>0.4.0-SNAPSHOT</version>
+	<version>0.5.0-SNAPSHOT</version>
 	<name>Administration</name>
 	<description>Administration Backend Project</description>
 
diff --git a/src/main/java/de/ozgcloud/admin/Root.java b/src/main/java/de/ozgcloud/admin/Root.java
index 8fc3d5966abd999afa09d17b2c555cb4cc0b409d..13af4d82bbccf0c18deac076b9358babd4e9a904 100644
--- a/src/main/java/de/ozgcloud/admin/Root.java
+++ b/src/main/java/de/ozgcloud/admin/Root.java
@@ -30,7 +30,7 @@ import lombok.Getter;
 @Getter
 public class Root {
 	private String javaVersion;
-	private String buildVersion;
+	private String version;
 	private Instant buildTime;
 	private String buildNumber;
 }
diff --git a/src/main/java/de/ozgcloud/admin/RootController.java b/src/main/java/de/ozgcloud/admin/RootController.java
index 887862e364924be5ce3de9b80bb4c4d2afd5a0a7..54cc0572d94c47f3f141071066fd9bf02e776322 100644
--- a/src/main/java/de/ozgcloud/admin/RootController.java
+++ b/src/main/java/de/ozgcloud/admin/RootController.java
@@ -48,7 +48,7 @@ public class RootController {
 		return Root.builder()
 				.javaVersion(System.getProperty("java.version"))
 				.buildTime(buildProperties.getTime())
-				.buildVersion(buildProperties.getVersion())
+				.version(buildProperties.getVersion())
 				.buildNumber(buildProperties.get("number"))
 				.build();
 	}
diff --git a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java
index 2cf01448f6fa1ab4f52d3a72fc06b15d57c5cda7..319cb4d3525e1eede720494444555aa7c2f81a8d 100644
--- a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java
+++ b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java
@@ -21,6 +21,8 @@
  */
 package de.ozgcloud.admin;
 
+import de.ozgcloud.admin.common.user.CurrentUserService;
+import de.ozgcloud.admin.common.user.UserRole;
 import org.springframework.boot.autoconfigure.data.rest.RepositoryRestProperties;
 import org.springframework.hateoas.EntityModel;
 import org.springframework.hateoas.Link;
@@ -30,6 +32,9 @@ import org.springframework.stereotype.Component;
 
 import lombok.RequiredArgsConstructor;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @Component
 @RequiredArgsConstructor
 public class RootModelAssembler implements RepresentationModelAssembler<Root, EntityModel<Root>> {
@@ -37,13 +42,31 @@ public class RootModelAssembler implements RepresentationModelAssembler<Root, En
 
 	private final RepositoryRestProperties restProperties;
 
+	private final CurrentUserService currentUserService;
+
 	@Override
 	public EntityModel<Root> toModel(Root root) {
-		var rootLink = WebMvcLinkBuilder.linkTo(RootController.class);
-		var configLink = rootLink.toUriComponentsBuilder().replacePath(restProperties.getBasePath());
+		List<Link> links = buildRootModelLinks();
 		return EntityModel.of(
 				root,
-				Link.of(configLink.toUriString(), REL_CONFIGURATION),
-				rootLink.withSelfRel());
+				links);
+	}
+
+	List<Link> buildRootModelLinks() {
+		List<Link> links = new ArrayList<>();
+		var rootLinkBuilder = WebMvcLinkBuilder.linkTo(RootController.class);
+		links.add(rootLinkBuilder.withSelfRel());
+		if (currentUserService.hasRole(UserRole.ADMIN_ADMIN)) {
+			links.add(buildConfigLink());
+		}
+		return links;
+	}
+
+	private Link buildConfigLink() {
+		var rootLinkBuilder = WebMvcLinkBuilder.linkTo(RootController.class);
+		return Link.of(
+				rootLinkBuilder.toUriComponentsBuilder().replacePath(restProperties.getBasePath()).toUriString(),
+				REL_CONFIGURATION
+		);
 	}
 }
diff --git a/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java b/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java
index d5e5803b9ec8364e3298402bda28c84291a957e4..51fc68105ef225ee93d66f36a448ca4b54c5a9b0 100644
--- a/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java
+++ b/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java
@@ -29,62 +29,70 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 
-import jakarta.validation.ConstraintViolation;
-import jakarta.validation.ConstraintViolationException;
-
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ProblemDetail;
 import org.springframework.security.access.AccessDeniedException;
-import org.springframework.web.ErrorResponse;
 import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
 
 import de.ozgcloud.common.errorhandling.TechnicalException;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
 
 @RestControllerAdvice
 public class ExceptionController extends ResponseEntityExceptionHandler {
 
 	@ExceptionHandler(RuntimeException.class)
-	public ErrorResponse handleRuntimeException(RuntimeException ex) {
-		return ErrorResponse.builder(ex, HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage()).build();
+	@ResponseBody
+	public ProblemDetail handleRuntimeException(RuntimeException ex) {
+		return buildProblemDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex);
 	}
 
 	@ExceptionHandler(AccessDeniedException.class)
-	public ErrorResponse handleAccessDeniedException(AccessDeniedException ex) {
-		return ErrorResponse.builder(ex, HttpStatus.FORBIDDEN, ex.getLocalizedMessage()).build();
+	@ResponseBody
+	public ProblemDetail handleAccessDeniedException(AccessDeniedException ex) {
+		return buildProblemDetail(HttpStatus.FORBIDDEN, ex);
 	}
 
 	@ExceptionHandler(ResourceNotFoundException.class)
-	public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException ex) {
-		return ErrorResponse.builder(ex, HttpStatus.NOT_FOUND, ex.getLocalizedMessage()).build();
+	@ResponseBody
+	public ProblemDetail handleResourceNotFoundException(ResourceNotFoundException ex) {
+		return buildProblemDetail(HttpStatus.NOT_FOUND, ex);
 	}
 
 	@ExceptionHandler(FunctionalException.class)
-	public ErrorResponse handleFunctionalException(FunctionalException ex) {
-		return ErrorResponse.builder(ex, HttpStatus.BAD_REQUEST, ex.getLocalizedMessage()).build();
+	@ResponseBody
+	public ProblemDetail handleFunctionalException(FunctionalException ex) {
+		return buildProblemDetail(HttpStatus.BAD_REQUEST, ex);
 	}
 
 	@ExceptionHandler(TechnicalException.class)
-	public ErrorResponse handleTechnicalException(TechnicalException ex) {
-		return ErrorResponse.builder(ex, HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage()).build();
+	@ResponseBody
+	public ProblemDetail handleTechnicalException(TechnicalException ex) {
+		return buildProblemDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex);
+	}
+
+	private ProblemDetail buildProblemDetail(HttpStatus status, Exception ex) {
+		return ProblemDetail.forStatusAndDetail(status, ex.getLocalizedMessage());
 	}
 
 	@ExceptionHandler(ConstraintViolationException.class)
-	public ErrorResponse handleConstraintViolationException(ConstraintViolationException ex) {
-		var problemDetail = buildProblemDetail(HttpStatus.UNPROCESSABLE_ENTITY, ex);
-		return ErrorResponse.builder(ex, problemDetail).build();
+	@ResponseBody
+	public ProblemDetail handleConstraintViolationException(ConstraintViolationException ex) {
+		return buildConstraintViolationProblemDetail(HttpStatus.UNPROCESSABLE_ENTITY, ex);
 	}
 
-	private ProblemDetail buildProblemDetail(HttpStatus status, ConstraintViolationException ex) {
+	private ProblemDetail buildConstraintViolationProblemDetail(HttpStatus status, ConstraintViolationException ex) {
 		var problemDetail = ProblemDetail.forStatusAndDetail(status, ex.getLocalizedMessage());
 		problemDetail.setProperty("invalid-params", getDetailedviolationList(ex.getConstraintViolations()));
 		return problemDetail;
 	}
 
 	private List<Map<String, String>> getDetailedviolationList(Set<ConstraintViolation<?>> violations) {
-		List<Map<String, String>> detailedViolations = new ArrayList<>();
+		var detailedViolations = new ArrayList<Map<String, String>>();
 		Optional.ofNullable(violations).orElse(Collections.emptySet()).forEach(v -> detailedViolations.add(buildDetailedViolation(v)));
 		return detailedViolations;
 
diff --git a/src/main/java/de/ozgcloud/admin/common/user/CurrentUserHelper.java b/src/main/java/de/ozgcloud/admin/common/user/CurrentUserHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..16f4bf18d38516bbdf07cadfd58b9ce5fe793426
--- /dev/null
+++ b/src/main/java/de/ozgcloud/admin/common/user/CurrentUserHelper.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.admin.common.user;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+class CurrentUserHelper {
+	static final String ROLE_PREFIX = "ROLE_";
+	private static final Predicate<String> IS_ROLE_PREFIX_MISSING = role -> !role.startsWith(ROLE_PREFIX);
+	private static final AuthenticationTrustResolver TRUST_RESOLVER = new AuthenticationTrustResolverImpl();
+	private static final Predicate<Authentication> IS_TRUSTED = auth -> !TRUST_RESOLVER.isAnonymous(auth);
+
+	public static boolean hasRole(String role) {
+		var auth = getAuthentication();
+
+		if ((Objects.isNull(auth)) || (Objects.isNull(auth.getPrincipal()))) {
+			return false;
+		}
+
+		return containsRole(auth.getAuthorities(), role);
+	}
+
+	static boolean containsRole(Collection<? extends GrantedAuthority> authorities, String role) {
+		if (Objects.isNull(authorities)) {
+			return false;
+		}
+		return authorities.stream().anyMatch(a -> StringUtils.equalsIgnoreCase(addRolePrefixIfMissing(role), a.getAuthority()));
+	}
+
+	static String addRolePrefixIfMissing(String roleToCheck) {
+		return Optional.ofNullable(roleToCheck)
+				.filter(IS_ROLE_PREFIX_MISSING)
+				.map(role -> ROLE_PREFIX + role)
+				.orElse(roleToCheck);
+	}
+
+	static Authentication getAuthentication() {
+		return findAuthentication().orElseThrow(() -> new IllegalStateException("No authenticated User found"));
+	}
+
+	private static Optional<Authentication> findAuthentication() {
+		return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()).filter(IS_TRUSTED);
+	}
+}
diff --git a/src/main/java/de/ozgcloud/admin/common/user/CurrentUserService.java b/src/main/java/de/ozgcloud/admin/common/user/CurrentUserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..94912e9e4749fe871f4120477fe840fe617bb52b
--- /dev/null
+++ b/src/main/java/de/ozgcloud/admin/common/user/CurrentUserService.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.admin.common.user;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class CurrentUserService {
+	public boolean hasRole(String role) {
+		return CurrentUserHelper.hasRole(role);
+	}
+}
diff --git a/src/main/java/de/ozgcloud/admin/common/user/UserRole.java b/src/main/java/de/ozgcloud/admin/common/user/UserRole.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b0db41b19d75eb43875afd31da0ea2b0beb69b7
--- /dev/null
+++ b/src/main/java/de/ozgcloud/admin/common/user/UserRole.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.admin.common.user;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class UserRole {
+	public static final String ADMIN_ADMIN = "ADMIN_ADMIN";
+}
diff --git a/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java b/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java
index 568d79a76fc6268d93b7ecc2e172ae9625d40ed1..b1c6280e10acdf642638dea39854bd5eec7b4446 100644
--- a/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java
+++ b/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java
@@ -19,7 +19,13 @@
  */
 package de.ozgcloud.admin.security;
 
+import static java.util.stream.Collectors.*;
+
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -29,10 +35,15 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
+import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
 import org.springframework.security.web.SecurityFilterChain;
 
+import de.ozgcloud.admin.common.user.UserRole;
+import de.ozgcloud.admin.environment.OAuth2Properties;
 import lombok.RequiredArgsConstructor;
 
 @Configuration
@@ -43,6 +54,14 @@ public class SecurityConfiguration {
 
 	private final AdminAuthenticationEntryPoint authenticationEntryPoint;
 
+	private final OAuth2Properties oAuth2Properties;
+
+	static final String RESOURCE_ACCESS_KEY = "resource_access";
+
+	static final String SIMPLE_GRANT_AUTHORITY_PREFIX = "ROLE_";
+
+	static final String ROLES_KEY = "roles";
+
 	@Bean
 	SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
 
@@ -54,6 +73,8 @@ public class SecurityConfiguration {
 
 		http.authorizeHttpRequests(requests -> requests
 				.requestMatchers(HttpMethod.GET, "/api/environment").permitAll()
+				.requestMatchers("/api/configuration").hasRole(UserRole.ADMIN_ADMIN)
+				.requestMatchers("/api/configuration/**").hasRole(UserRole.ADMIN_ADMIN)
 				.requestMatchers("/api").authenticated()
 				.requestMatchers("/api/**").authenticated()
 				.requestMatchers("/actuator").permitAll()
@@ -67,9 +88,42 @@ public class SecurityConfiguration {
 	@Bean
 	JwtAuthenticationConverter jwtAuthenticationConverter() {
 		var jwtConverter = new JwtAuthenticationConverter();
-		jwtConverter.setJwtGrantedAuthoritiesConverter(jwt -> List.of(() -> "ROLE_USER"));
+		jwtConverter.setJwtGrantedAuthoritiesConverter(
+				this::convertJwtToGrantedAuthorities);
 		jwtConverter.setPrincipalClaimName(StandardClaimNames.PREFERRED_USERNAME);
 		return jwtConverter;
 	}
 
-}
\ No newline at end of file
+	Set<GrantedAuthority> convertJwtToGrantedAuthorities(Jwt jwt) {
+		return getRolesFromJwt(jwt)
+				.stream()
+				.map(this::mapRoleStringToGrantedAuthority)
+				.collect(toSet());
+	}
+
+	private GrantedAuthority mapRoleStringToGrantedAuthority(String role) {
+		return new SimpleGrantedAuthority(SIMPLE_GRANT_AUTHORITY_PREFIX + role);
+	}
+
+	List<String> getRolesFromJwt(Jwt jwt) {
+		return Optional.ofNullable(jwt.getClaimAsMap(RESOURCE_ACCESS_KEY))
+				.flatMap(resourceAccessMap -> getMap(resourceAccessMap, oAuth2Properties.getResource()))
+				.flatMap(adminClientMap -> getList(adminClientMap, ROLES_KEY))
+				.orElse(Collections.emptyList());
+	}
+
+	@SuppressWarnings("unchecked")
+	private Optional<Map<String, Object>> getMap(Map<String, Object> map, String mapKey) {
+		return Optional.ofNullable(map.get(mapKey))
+				.filter(Map.class::isInstance)
+				.map(obj -> (Map<String, Object>) obj);
+	}
+
+	@SuppressWarnings("unchecked")
+	private Optional<List<String>> getList(Map<String, Object> map, String mapKey) {
+		return Optional.ofNullable(map.get(mapKey))
+				.filter(List.class::isInstance)
+				.map(obj -> (List<String>) obj);
+	}
+
+}
diff --git a/src/test/java/de/ozgcloud/admin/ApiRootITCase.java b/src/test/java/de/ozgcloud/admin/ConfigurationITCase.java
similarity index 94%
rename from src/test/java/de/ozgcloud/admin/ApiRootITCase.java
rename to src/test/java/de/ozgcloud/admin/ConfigurationITCase.java
index 327eea8dfbe2120d59233c3891dc807f8882fbae..f3c0c415f3e73aa152eec948f20cf5a47bf9d249 100644
--- a/src/test/java/de/ozgcloud/admin/ApiRootITCase.java
+++ b/src/test/java/de/ozgcloud/admin/ConfigurationITCase.java
@@ -34,13 +34,14 @@ import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.ResultActions;
 
+import de.ozgcloud.admin.common.user.UserRole;
 import de.ozgcloud.common.test.ITCase;
 import lombok.SneakyThrows;
 
 @ITCase
 @AutoConfigureMockMvc
-@WithMockUser
-class ApiRootITCase {
+@WithMockUser(roles = UserRole.ADMIN_ADMIN)
+class ConfigurationITCase {
 
 	@Autowired
 	private RepositoryRestProperties restProperties;
@@ -49,7 +50,7 @@ class ApiRootITCase {
 	private MockMvc mockMvc;
 
 	@Nested
-	class TestRootEndpoint {
+	class TestConfigurationRestEndpoint {
 
 		@Test
 		void shouldBetSetToApi() {
diff --git a/src/test/java/de/ozgcloud/admin/RootControllerTest.java b/src/test/java/de/ozgcloud/admin/RootControllerTest.java
index 88e2bb4aada0d7fd5b28034dd77b3254f5e127d3..9401253fa33c9dd7a7b61a0efc32816dee8ee5c9 100644
--- a/src/test/java/de/ozgcloud/admin/RootControllerTest.java
+++ b/src/test/java/de/ozgcloud/admin/RootControllerTest.java
@@ -90,7 +90,7 @@ class RootControllerTest {
 
 			ResultActions result = doRequest();
 
-			result.andExpect(jsonPath("$.buildVersion").value(RootTestFactory.BUILD_VERSION));
+			result.andExpect(jsonPath("$.version").value(RootTestFactory.BUILD_VERSION));
 		}
 
 		@Test
diff --git a/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java b/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java
index b237432d1fc03ba9b2ea3b5afa924bc706ed54c6..9e797f60d83dadc15a280d94e692dc9d191b92d3 100644
--- a/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java
+++ b/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java
@@ -22,10 +22,9 @@
 package de.ozgcloud.admin;
 
 import static de.ozgcloud.admin.RootModelAssembler.*;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
+import static org.assertj.core.api.Assertions.*;
 
-import java.util.Optional;
+import java.util.List;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -33,12 +32,14 @@ import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.Spy;
 import org.springframework.boot.autoconfigure.data.rest.RepositoryRestProperties;
-import org.springframework.hateoas.EntityModel;
-import org.springframework.hateoas.IanaLinkRelations;
 import org.springframework.hateoas.Link;
 
+import de.ozgcloud.admin.common.user.CurrentUserService;
+import de.ozgcloud.admin.common.user.UserRole;
+
 class RootModelAssemblerTest {
 
 	private static final String BASE_PATH = "/api/base";
@@ -48,34 +49,65 @@ class RootModelAssemblerTest {
 
 	@Mock
 	private RepositoryRestProperties restProperties;
-
-	@BeforeEach
-	void mockBasePath() {
-		when(restProperties.getBasePath()).thenReturn(BASE_PATH);
-	}
+	@Mock
+	private CurrentUserService currentUserService;
 
 	@DisplayName("Entity Model")
 	@Nested
 	class TestEntityModel {
+		@BeforeEach
+		void beforeEach() {
+			Mockito.when(currentUserService.hasRole(UserRole.ADMIN_ADMIN)).thenReturn(true);
+			Mockito.when(restProperties.getBasePath()).thenReturn(BASE_PATH);
+		}
 
 		@Test
-		void shouldHaveHrefToBasePath() {
-			var configurationLink = toModel().getLink(REL_CONFIGURATION);
+		void shouldHaveRoot() {
+			var givenRoot = RootTestFactory.create();
+			List<Link> links = List.of();
+			Mockito.when(modelAssembler.buildRootModelLinks()).thenReturn(links);
 
-			assertEquals(Optional.of(Link.of(BASE_PATH, REL_CONFIGURATION)), configurationLink);
+			var resultRoot = modelAssembler.toModel(givenRoot).getContent();
+
+			assertThat(resultRoot).isEqualTo(givenRoot);
 		}
 
 		@Test
-		void shouldHaveHrefToSelf() {
-			var selfLink = toModel().getLink(IanaLinkRelations.SELF);
+		void shouldHaveLinks() {
+			List<Link> links = List.of(Link.of(RootController.PATH));
+			Mockito.when(modelAssembler.buildRootModelLinks()).thenReturn(links);
+
+			var modelLinks = modelAssembler.toModel(RootTestFactory.create()).getLinks();
 
-			assertEquals(Optional.of(Link.of(RootController.PATH)), selfLink);
+			assertThat(modelLinks).containsAll(links);
 		}
+	}
 
-		private EntityModel<Root> toModel() {
-			return modelAssembler.toModel(RootTestFactory.create());
+	@DisplayName("Root Model Links")
+	@Nested
+	class TestBuildRootModelLinks {
+
+		@Test
+		void shouldHaveHrefToBasePathIfAuthorized() {
+			Mockito.when(restProperties.getBasePath()).thenReturn(BASE_PATH);
+			Mockito.when(currentUserService.hasRole(UserRole.ADMIN_ADMIN)).thenReturn(true);
+
+			List<Link> links = modelAssembler.buildRootModelLinks();
+
+			assertThat(links).containsExactly(
+					Link.of(RootController.PATH),
+					Link.of(BASE_PATH, REL_CONFIGURATION));
 		}
 
+		@Test
+		void shouldNotHaveHrefToBasePathIfUnauthorized() {
+			Mockito.when(currentUserService.hasRole(UserRole.ADMIN_ADMIN)).thenReturn(false);
+
+			List<Link> links = modelAssembler.buildRootModelLinks();
+
+			assertThat(links).containsExactly(
+					Link.of(RootController.PATH));
+		}
 	}
 
 }
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/admin/RootTestFactory.java b/src/test/java/de/ozgcloud/admin/RootTestFactory.java
index e4a77201b527061485a7dcf7d567dfa161f73f7b..4c0312d9a9014b860c7ef9739245d5c012b1c61e 100644
--- a/src/test/java/de/ozgcloud/admin/RootTestFactory.java
+++ b/src/test/java/de/ozgcloud/admin/RootTestFactory.java
@@ -40,7 +40,7 @@ public class RootTestFactory {
 		return Root.builder()
 				.buildTime(BUILD_TIME)
 				.javaVersion(JAVA_VERSION)
-				.buildVersion(BUILD_VERSION)
+				.version(BUILD_VERSION)
 				.buildNumber(BUILD_NUMBER);
 	}
 }
diff --git a/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java
index 18880e1718ab14fcbddfe5663d8a6968740fce20..e965505fc19fda822757ed5b189c88fb9a1d7484 100644
--- a/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java
+++ b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java
@@ -6,7 +6,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
 
 import java.util.Set;
-import java.util.stream.Stream;
 
 import jakarta.validation.ConstraintViolation;
 import jakarta.validation.ConstraintViolationException;
@@ -18,13 +17,9 @@ import org.apache.commons.lang3.StringUtils;
 import org.assertj.core.util.Arrays;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.http.HttpStatus;
 import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.ResultActions;
@@ -47,38 +42,9 @@ class ExceptionControllerITCase {
 	@MockBean
 	private RootModelAssembler modelAssembler;
 
-	@Nested
-	class TestExceptions {
-		@ParameterizedTest
-		@MethodSource("exceptionAndExpectedStatus")
-		@SneakyThrows
-		void shouldHandleExceptionWithStatus(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) {
-			when(modelAssembler.toModel(any())).thenThrow(TestErrorController.EXCEPTION_PRODUCER.get(exceptionClass).produceException());
-
-			var result = performGet();
-
-			result.andExpect(status().is(expectedStatus.value()));
-		}
-
-		@ParameterizedTest
-		@MethodSource("exceptionAndExpectedStatus")
-		@SneakyThrows
-		void shouldRespondWithStatusInBody(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) {
-			when(modelAssembler.toModel(any())).thenThrow(exceptionClass);
-
-			var result = performGet();
-
-			result.andExpect(jsonPath("$.status").value(expectedStatus.value()));
-		}
-
-		private static Stream<Arguments> exceptionAndExpectedStatus() {
-			return TestErrorController.STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue()));
-		}
-
-	}
-
 	@Nested
 	class TestConstraintViolationException {
+
 		@Test
 		@SneakyThrows
 		void shouldHaveInvalidFieldNameInResponse() {
@@ -129,11 +95,9 @@ class ExceptionControllerITCase {
 			private String string2;
 		}
 
+		@SneakyThrows
+		private ResultActions performGet() {
+			return mockMvc.perform(get(RootController.PATH));
+		}
 	}
-
-	@SneakyThrows
-	private ResultActions performGet() {
-		return mockMvc.perform(get(RootController.PATH));
-	}
-
-}
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java
index 41f95cf47993da9c9a390d4e7d62702b2f0c79c7..367ed8afd534aa2c443453447e060011b6c1bd22 100644
--- a/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java
+++ b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java
@@ -21,23 +21,27 @@
  */
 package de.ozgcloud.admin.common.errorhandling;
 
+import static org.junit.Assert.*;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
 
-import java.util.stream.Stream;
-
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.data.rest.webmvc.ResourceNotFoundException;
 import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.ResultActions;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.jayway.jsonpath.JsonPath;
 
+import de.ozgcloud.common.errorhandling.TechnicalException;
 import lombok.SneakyThrows;
 
 class ExceptionControllerTest {
@@ -51,97 +55,392 @@ class ExceptionControllerTest {
 				.setControllerAdvice(new ExceptionController()).build();
 	}
 
-	@DisplayName("Error handler")
+	@DisplayName("Runtime Exception")
 	@Nested
-	class TestErrorHandler {
+	class TestRuntimeException {
+
+		private final static String TEST_ERROR_ENDPOINT = TestErrorController.BASE_PATH + "/runtime";
+		private final static int STATUS_VALUE = HttpStatus.INTERNAL_SERVER_ERROR.value();
 
-		@ParameterizedTest
-		@MethodSource("exceptionAndExpectedStatus")
 		@SneakyThrows
-		void shouldHandleExceptionWithStatus(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) {
-			var result = doPerformWithError(exceptionClass);
+		@Test
+		void shouldReturnStatus() {
+			var result = doPerformGet();
 
-			result.andExpect(status().is(expectedStatus.value()));
+			result.andExpect(status().is(STATUS_VALUE));
 		}
 
-		@ParameterizedTest
-		@MethodSource("exceptionAndExpectedStatus")
-		@SneakyThrows
-		void shouldRespondWithStatusInBody(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) {
-			var result = doPerformWithError(exceptionClass);
+		@DisplayName("response body")
+		@Nested
+		class TestRuntimeExceptionBody {
+
+			@SneakyThrows
+			@Test
+			void shouldHaveStatus() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.status").value(STATUS_VALUE));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveTitle() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.title").value("Internal Server Error"));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveDetail() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.detail").value("error message"));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveInstance() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.instance").value(TEST_ERROR_ENDPOINT));
+			}
 
-			result.andExpect(jsonPath("$.status").value(expectedStatus.value()));
+			@SneakyThrows
+			void shouldHaveType() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.type").value("about:blank"));
+			}
 		}
 
-		@ParameterizedTest
-		@MethodSource("exceptionAndExpectedStatus")
 		@SneakyThrows
-		void shouldRespondWithTitler(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) {
-			var result = doPerformWithError(exceptionClass);
-
-			result.andExpect(jsonPath("$.title").exists());
+		private ResultActions doPerformGet() {
+			return mockMvc.perform(get(TEST_ERROR_ENDPOINT));
 		}
+	}
+
+	@DisplayName("Access Denied Exception")
+	@Nested
+	class TestAccessDeniedException {
+
+		private final static String TEST_ERROR_ENDPOINT = TestErrorController.BASE_PATH + "/access-denied";
+		private final static int STATUS_VALUE = HttpStatus.FORBIDDEN.value();
 
-		@ParameterizedTest
-		@MethodSource("exceptionAndExpectedStatus")
 		@SneakyThrows
-		void shouldRespondWithDetail(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) {
-			var result = doPerformWithError(exceptionClass);
+		@Test
+		void shouldReturnStatus() {
+			var result = doPerformGet();
 
-			result.andExpect(jsonPath("$.detail").exists());
+			result.andExpect(status().is(STATUS_VALUE));
 		}
 
-		@ParameterizedTest
-		@MethodSource("exceptionAndExpectedStatus")
-		@SneakyThrows
-		void shouldRespondWithInstance(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) {
-			var result = doPerformWithError(exceptionClass);
+		@DisplayName("response body")
+		@Nested
+		class TestAccessDeniedExceptionBody {
+
+			@SneakyThrows
+			@Test
+			void shouldHaveStatus() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.status").value(STATUS_VALUE));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveTitle() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.title").value("Forbidden"));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveDetail() {
+				var result = doPerformGet();
 
-			result.andExpect(jsonPath("$.instance").exists());
+				result.andExpect(jsonPath("$.detail").value("error message"));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveInstance() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.instance").value(TEST_ERROR_ENDPOINT));
+			}
+
+			@SneakyThrows
+			void shouldHaveType() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.type").value("about:blank"));
+			}
 		}
 
-		@ParameterizedTest
-		@MethodSource("exceptionAndExpectedStatus")
 		@SneakyThrows
-		void shouldRespondWithType(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) {
-			var result = doPerformWithError(exceptionClass);
+		private ResultActions doPerformGet() {
+			return mockMvc.perform(get(TEST_ERROR_ENDPOINT));
+		}
+	}
 
-			result.andExpect(jsonPath("$.type").exists());
+	@DisplayName("ResourceNotFound Exception")
+	@Nested
+	class TestResourceNotFoundException {
+
+		private final static String TEST_ERROR_ENDPOINT = TestErrorController.BASE_PATH + "/resource-not-found";
+		private final static int STATUS_VALUE = HttpStatus.NOT_FOUND.value();
+
+		@SneakyThrows
+		@Test
+		void shouldReturnStatus() {
+			var result = doPerformGet();
+
+			result.andExpect(status().is(STATUS_VALUE));
 		}
 
-		private static Stream<Arguments> exceptionAndExpectedStatus() {
-			return TestErrorController.STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue()));
+		@DisplayName("response body")
+		@Nested
+		class TestResourceNotFoundExceptionBody {
+
+			@SneakyThrows
+			@Test
+			void shouldHaveStatus() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.status").value(STATUS_VALUE));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveTitle() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.title").value("Not Found"));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveDetail() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.detail").value("error message"));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveInstance() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.instance").value(TEST_ERROR_ENDPOINT));
+			}
+
+			@SneakyThrows
+			void shouldHaveType() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.type").value("about:blank"));
+			}
 		}
 
 		@SneakyThrows
-		private ResultActions doPerformWithError(Class<? extends Exception> exceptionClass) {
-			return mockMvc.perform(get("/api/test-error").param("errorClassName", exceptionClass.getName()));
+		private ResultActions doPerformGet() {
+			return mockMvc.perform(get(TEST_ERROR_ENDPOINT));
 		}
 	}
 
-	@DisplayName("ResourceNotFound error")
+	@DisplayName("Functional Exception")
 	@Nested
-	class TestResourceNotFoundError {
+	class TestFunctionalException {
+
+		private final static String TEST_ERROR_ENDPOINT = TestErrorController.BASE_PATH + "/functional";
+		private final static int STATUS_VALUE = HttpStatus.BAD_REQUEST.value();
 
-		@Test
 		@SneakyThrows
-		void shouldHaveStatus() {
-			var result = doRequestUnknown();
+		@Test
+		void shouldReturnStatus() {
+			var result = doPerformGet();
 
-			result.andExpect(status().is(HttpStatus.NOT_FOUND.value()));
+			result.andExpect(status().is(STATUS_VALUE));
+		}
+
+		@DisplayName("response body")
+		@Nested
+		class TestFunctionalExceptionBody {
+
+			@SneakyThrows
+			@Test
+			void shouldHaveStatus() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.status").value(STATUS_VALUE));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveTitle() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.title").value("Bad Request"));
+			}
+
+			@DisplayName("detail")
+			@Nested
+			class TestBodyDetail {
+
+				@SneakyThrows
+				@Test
+				void shouldContainErrorMessage() {
+					var result = doPerformGet();
+
+					assertTrue(getDetailFromResponseContent(result).contains("Functional error: error message"));
+				}
+
+				@SneakyThrows
+				@Test
+				void shouldContainExceptionId() {
+					var result = doPerformGet();
+
+					assertTrue(getDetailFromResponseContent(result).contains("(ExceptionId: "));
+				}
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveInstance() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.instance").value(TEST_ERROR_ENDPOINT));
+			}
+
+			@SneakyThrows
+			void shouldHaveType() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.type").value("about:blank"));
+			}
 		}
 
-		@Test
 		@SneakyThrows
-		void shouldRespondWithStatusInBody() {
-			var result = doRequestUnknown();
+		private ResultActions doPerformGet() {
+			return mockMvc.perform(get(TEST_ERROR_ENDPOINT));
+		}
+	}
+
+	@DisplayName("TechnicalException")
+	@Nested
+	class TestTechnicalException {
+
+		private final static String TEST_ERROR_ENDPOINT = TestErrorController.BASE_PATH + "/technical";
+		private final static int STATUS_VALUE = HttpStatus.INTERNAL_SERVER_ERROR.value();
+
+		@SneakyThrows
+		@Test
+		void shouldReturnStatus() {
+			var result = doPerformGet();
+
+			result.andExpect(status().is(STATUS_VALUE));
+		}
+
+		@DisplayName("response body")
+		@Nested
+		class TestTechnicalExceptionBody {
 
-			result.andExpect(jsonPath("$.status").value(HttpStatus.NOT_FOUND.value()));
+			@SneakyThrows
+			@Test
+			void shouldHaveStatus() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.status").value(STATUS_VALUE));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveTitle() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.title").value("Internal Server Error"));
+			}
+
+			@DisplayName("detail")
+			@Nested
+			class TestBodyDetail {
+
+				@SneakyThrows
+				@Test
+				void shouldContainErrorMessage() {
+					var result = doPerformGet();
+
+					assertTrue(getDetailFromResponseContent(result).contains("error message"));
+				}
+
+				@SneakyThrows
+				@Test
+				void shouldContainExceptionId() {
+					var result = doPerformGet();
+
+					assertTrue(getDetailFromResponseContent(result).contains("(ExceptionId: "));
+				}
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldHaveInstance() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.instance").value(TEST_ERROR_ENDPOINT));
+			}
+
+			@SneakyThrows
+			void shouldHaveType() {
+				var result = doPerformGet();
+
+				result.andExpect(jsonPath("$.type").value("about:blank"));
+			}
 		}
 
 		@SneakyThrows
-		private ResultActions doRequestUnknown() {
-			return mockMvc.perform(get("/api/unknown"));
+		private ResultActions doPerformGet() {
+			return mockMvc.perform(get(TEST_ERROR_ENDPOINT));
 		}
 	}
+
+	@SneakyThrows
+	private String getDetailFromResponseContent(ResultActions resultActions) {
+		return JsonPath.read(resultActions.andReturn().getResponse().getContentAsString(), "$.detail");
+	}
 }
+
+@RestController
+@RequestMapping(TestErrorController.BASE_PATH)
+class TestErrorController {
+
+	public final static String BASE_PATH = "/api/test-error";
+	static final String ERROR_MESSAGE = "error message";
+
+	@GetMapping("/runtime")
+	String throwRuntimeException() throws Exception {
+		throw new RuntimeException(ERROR_MESSAGE);
+	}
+
+	@GetMapping("/access-denied")
+	String throwAccessException() throws Exception {
+		throw new AccessDeniedException(ERROR_MESSAGE);
+	}
+
+	@GetMapping("/resource-not-found")
+	String throwResourceNotFoundException() throws Exception {
+		throw new ResourceNotFoundException(ERROR_MESSAGE);
+	}
+
+	@GetMapping("/functional")
+	String throwFunctionalExceptionException() throws Exception {
+		throw new FunctionalException(() -> ERROR_MESSAGE);
+	}
+
+	@GetMapping("/technical")
+	String throwTechnicalExceptionException() throws Exception {
+		throw new TechnicalException(ERROR_MESSAGE);
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java
deleted file mode 100644
index c49345edcbebd221c8b19ab7fd3e08d11177f4ba..0000000000000000000000000000000000000000
--- a/src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2024. Das Land Schleswig-Holstein vertreten durch das Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
- * Zentrales IT-Management
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.admin.common.errorhandling;
-
-import java.util.Collections;
-import java.util.Map;
-
-import jakarta.validation.ConstraintViolationException;
-
-import org.springframework.data.rest.webmvc.ResourceNotFoundException;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.access.AccessDeniedException;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import de.ozgcloud.common.errorhandling.TechnicalException;
-
-@RestController
-@RequestMapping("/api/test-error")
-class TestErrorController {
-
-	@FunctionalInterface
-	interface ExceptionProducer {
-		Exception produceException();
-	}
-
-	static final Map<Class<? extends Exception>, HttpStatus> STATUS_BY_EXCEPTION = Map.of(
-			RuntimeException.class, HttpStatus.INTERNAL_SERVER_ERROR,
-			AccessDeniedException.class, HttpStatus.FORBIDDEN,
-			ConstraintViolationException.class, HttpStatus.UNPROCESSABLE_ENTITY,
-			ResourceNotFoundException.class, HttpStatus.NOT_FOUND,
-			FunctionalException.class, HttpStatus.BAD_REQUEST,
-			TechnicalException.class, HttpStatus.INTERNAL_SERVER_ERROR);
-
-	static final String ERROR_MESSAGE = "error message";
-
-	static Map<Class<? extends Exception>, ExceptionProducer> EXCEPTION_PRODUCER = Map.of(
-			RuntimeException.class, () -> new RuntimeException(ERROR_MESSAGE),
-			AccessDeniedException.class, () -> new AccessDeniedException(ERROR_MESSAGE),
-			ConstraintViolationException.class, () -> new ConstraintViolationException(ERROR_MESSAGE, Collections.emptySet()),
-			ResourceNotFoundException.class, () -> new ResourceNotFoundException(ERROR_MESSAGE),
-			FunctionalException.class, () -> new FunctionalException(() -> ERROR_MESSAGE),
-			TechnicalException.class, () -> new TechnicalException(ERROR_MESSAGE));
-
-	@GetMapping
-	String throwException(@RequestParam String errorClassName) throws Exception {
-		throw EXCEPTION_PRODUCER.get(
-				Class.forName(errorClassName)).produceException();
-	}
-
-}
diff --git a/src/test/java/de/ozgcloud/admin/common/user/CurrentUserHelperTest.java b/src/test/java/de/ozgcloud/admin/common/user/CurrentUserHelperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..428668577ed0add18a3c5a9fc041e350ea7d97f8
--- /dev/null
+++ b/src/test/java/de/ozgcloud/admin/common/user/CurrentUserHelperTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.admin.common.user;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+
+class CurrentUserHelperTest {
+	@DisplayName("Has role")
+	@Nested
+	class TestHasRole {
+		@Mock
+		private final Authentication mockAuthentication = Mockito.mock(Authentication.class);
+		@Mock
+		private final User mockPrincipal = Mockito.mock(User.class);
+
+		@Test
+		void shouldReturnFalseOnMissingAuthentication() {
+			try (MockedStatic<CurrentUserHelper> mockUserHelper = Mockito.mockStatic(
+					CurrentUserHelper.class,
+					Mockito.CALLS_REAL_METHODS)) {
+				mockUserHelper.when(CurrentUserHelper::getAuthentication).thenReturn(null);
+
+				boolean hasRole = CurrentUserHelper.hasRole(UserRole.ADMIN_ADMIN);
+
+				assertThat(hasRole).isFalse();
+			}
+		}
+
+		@Test
+		void shouldReturnFalseOnMissingPrincipal() {
+			Mockito.when(mockAuthentication.getPrincipal()).thenReturn(null);
+			try (MockedStatic<CurrentUserHelper> mockUserHelper = Mockito.mockStatic(
+					CurrentUserHelper.class,
+					Mockito.CALLS_REAL_METHODS)) {
+				mockUserHelper.when(CurrentUserHelper::getAuthentication).thenReturn(mockAuthentication);
+
+				boolean hasRole = CurrentUserHelper.hasRole(UserRole.ADMIN_ADMIN);
+
+				assertThat(hasRole).isFalse();
+			}
+		}
+
+		@ParameterizedTest
+		@ValueSource(booleans = {false, true})
+		void shouldReturnValue(boolean containsRoleValue) {
+			Mockito.when(mockAuthentication.getPrincipal()).thenReturn(mockPrincipal);
+			List<GrantedAuthority> authorities = List.of();
+			Mockito.<Collection<? extends GrantedAuthority>>when(mockAuthentication.getAuthorities()).thenReturn(authorities);
+
+			try (MockedStatic<CurrentUserHelper> mockUserHelper = Mockito.mockStatic(
+					CurrentUserHelper.class,
+					Mockito.CALLS_REAL_METHODS)) {
+				mockUserHelper.when(CurrentUserHelper::getAuthentication).thenReturn(mockAuthentication);
+				mockUserHelper.when(() -> CurrentUserHelper.containsRole(Mockito.anyList(), Mockito.anyString()))
+						.thenReturn(containsRoleValue);
+
+				boolean hasRole = CurrentUserHelper.hasRole(UserRole.ADMIN_ADMIN);
+
+				mockUserHelper.verify(() -> CurrentUserHelper.containsRole(mockAuthentication.getAuthorities(), UserRole.ADMIN_ADMIN));
+				assertThat(hasRole).isEqualTo(containsRoleValue);
+			}
+		}
+	}
+
+	@DisplayName("Contains role")
+	@Nested
+	class TestContainsRole {
+		@Test
+		void shouldNotContainRoleIfAuthoritiesIsNull() {
+			boolean containsRole = CurrentUserHelper.containsRole(null, UserRole.ADMIN_ADMIN);
+
+			assertThat(containsRole).isFalse();
+		}
+
+		@Test
+		void shouldNotContainRole() {
+			List<GrantedAuthority> authorities = List.of(
+					new SimpleGrantedAuthority(CurrentUserHelper.ROLE_PREFIX + "OTHER"));
+
+			boolean containsRole = CurrentUserHelper.containsRole(authorities, UserRole.ADMIN_ADMIN);
+
+			assertThat(containsRole).isFalse();
+		}
+
+		@Test
+		void shouldContainRole() {
+			Collection<? extends GrantedAuthority> authorities = List.of(
+					new SimpleGrantedAuthority(CurrentUserHelper.ROLE_PREFIX + UserRole.ADMIN_ADMIN));
+
+			boolean containsRole = CurrentUserHelper.containsRole(authorities, UserRole.ADMIN_ADMIN);
+
+			assertThat(containsRole).isTrue();
+		}
+	}
+
+	@DisplayName("Add Role Prefix If Missing")
+	@Nested
+	class TestAddRolePrefixIfMissing {
+
+		@Test
+		void shouldAddPrefixIfMissing() {
+			var roleWithoutPrefix = UserRole.ADMIN_ADMIN;
+
+			var role = CurrentUserHelper.addRolePrefixIfMissing(roleWithoutPrefix);
+
+			assertThat(role).isEqualTo(CurrentUserHelper.ROLE_PREFIX + UserRole.ADMIN_ADMIN);
+		}
+
+		@Test
+		void shouldReturnRoleIfPrefixAlreadyExists() {
+			var roleWithPrefix = CurrentUserHelper.ROLE_PREFIX + UserRole.ADMIN_ADMIN;
+
+			var role = CurrentUserHelper.addRolePrefixIfMissing(roleWithPrefix);
+
+			assertThat(role).isEqualTo(roleWithPrefix);
+		}
+
+		@Test
+		void shouldReturnNullIfPassingNull() {
+			var role = CurrentUserHelper.addRolePrefixIfMissing(null);
+
+			assertThat(role).isNull();
+		}
+	}
+
+	@DisplayName("Get authentication")
+	@Nested
+	class TestGetAuthentication {
+		@Mock
+		private final SecurityContext mockSecurityContext = Mockito.mock(SecurityContext.class);
+
+		@Test
+		void shouldThrowIfNoAuthenticatedUser() {
+			Mockito.when(mockSecurityContext.getAuthentication()).thenReturn(null);
+
+			try (MockedStatic<SecurityContextHolder> contextHolder = Mockito.mockStatic(SecurityContextHolder.class)) {
+				contextHolder.when(SecurityContextHolder::getContext).thenReturn(mockSecurityContext);
+
+				assertThatIllegalStateException()
+						.isThrownBy(CurrentUserHelper::getAuthentication)
+						.withMessage("No authenticated User found");
+			}
+		}
+
+		@Test
+		void shouldPassAuthentication() {
+			Authentication mockAuthentication = Mockito.mock(Authentication.class);
+			Mockito.when(mockSecurityContext.getAuthentication()).thenReturn(mockAuthentication);
+
+			try (MockedStatic<SecurityContextHolder> contextHolder = Mockito.mockStatic(SecurityContextHolder.class)) {
+				contextHolder.when(SecurityContextHolder::getContext).thenReturn(mockSecurityContext);
+
+				Authentication authentication = CurrentUserHelper.getAuthentication();
+
+				assertThat(authentication).isSameAs(mockAuthentication);
+			}
+		}
+	}
+}
diff --git a/src/test/java/de/ozgcloud/admin/common/user/CurrentUserServiceTest.java b/src/test/java/de/ozgcloud/admin/common/user/CurrentUserServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..878292bc14ffc812739213d84ce34b0fd97a3b4a
--- /dev/null
+++ b/src/test/java/de/ozgcloud/admin/common/user/CurrentUserServiceTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.admin.common.user;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CurrentUserServiceTest {
+	private final CurrentUserService currentUserService = new CurrentUserService();
+
+	@DisplayName("Has role")
+	@Nested
+	class TestHasRole {
+		@ParameterizedTest
+		@ValueSource(booleans = {false, true})
+		void shouldReturnValue(boolean hasRoleValue) {
+			try (MockedStatic<CurrentUserHelper> mockUserHelper = Mockito.mockStatic(
+					CurrentUserHelper.class)
+			){
+				mockUserHelper.when(() -> CurrentUserHelper.hasRole(Mockito.anyString()))
+						.thenReturn(hasRoleValue);
+
+				boolean hasRole = currentUserService.hasRole(UserRole.ADMIN_ADMIN);
+
+				mockUserHelper.verify(() -> CurrentUserHelper.hasRole(UserRole.ADMIN_ADMIN));
+				assertThat(hasRole).isEqualTo(hasRoleValue);
+			}
+		}
+	}
+}
diff --git a/src/test/java/de/ozgcloud/admin/security/AuthenticationExceptionTestFactory.java b/src/test/java/de/ozgcloud/admin/security/AuthenticationExceptionTestFactory.java
index 3727bfc0c968540bafa8abe9dffdb6df9add2972..daf3d634ab66c34ea6f6e393d92681b69ed69b59 100644
--- a/src/test/java/de/ozgcloud/admin/security/AuthenticationExceptionTestFactory.java
+++ b/src/test/java/de/ozgcloud/admin/security/AuthenticationExceptionTestFactory.java
@@ -31,6 +31,7 @@ public class AuthenticationExceptionTestFactory {
 
 	@Builder
 	public static class DummyAuthenticationException extends AuthenticationException {
+		@SuppressWarnings("unused")
 		private String msg;
 
 		DummyAuthenticationException(String msg) {
diff --git a/src/test/java/de/ozgcloud/admin/security/JwtTestFactory.java b/src/test/java/de/ozgcloud/admin/security/JwtTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..719c94afe86b01a9f87d4b0bbf1f51a40fcdaa75
--- /dev/null
+++ b/src/test/java/de/ozgcloud/admin/security/JwtTestFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2024. Das Land Schleswig-Holstein vertreten durch das Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.admin.security;
+
+import static de.ozgcloud.admin.security.SecurityConfiguration.*;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.security.oauth2.jwt.Jwt;
+
+public class JwtTestFactory {
+
+	public static final String ROLE_1 = "ADMIN_ADMIN";
+	public static final String ROLE_2 = "Lower_case";
+	public static final String ROLE_3 = "UPPER";
+
+	public static final String AUTH_RESOURCE = "admin";
+
+	public static Jwt create() {
+		return createBuilder().build();
+	}
+
+	public static Jwt.Builder createWithRoles(List<String> roles) {
+		return createBuilder().claim(RESOURCE_ACCESS_KEY, Map.of(AUTH_RESOURCE, Map.of(ROLES_KEY, roles)));
+	}
+
+	public static Jwt.Builder createBuilder() {
+		return Jwt.withTokenValue("token-value").header("header-key", "header-value").claim("claim-key", "claim-value");
+	}
+
+}
diff --git a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java
index d8bf3dcae4e6b88a078fa58c860d8365bb77ae60..711a2d8806c5c79b96082073c7bfb09bc7294d76 100644
--- a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java
+++ b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java
@@ -46,9 +46,9 @@ class SecurityConfigurationITCase {
 	@Autowired
 	private MockMvc mockMvc;
 
-	@DisplayName("without authorization")
+	@DisplayName("without authentication")
 	@Nested
-	class TestWithoutAuthorization {
+	class TestWithoutAuthentication {
 
 		@DisplayName("allow for not found")
 		@SneakyThrows
@@ -132,33 +132,76 @@ class SecurityConfigurationITCase {
 		}
 	}
 
-	@DisplayName("with authorization")
+	@DisplayName("with authentication")
 	@Nested
-	class TestWithAuthorization {
-
+	class TestWithAuthentication {
 		static final String CLAIMS = """
 				{
 				  "preferredUsername": "testUser",
 				  "scope": "openid testscope"
 				}""";
 
+		@Test
 		@SneakyThrows
-		@ParameterizedTest
-		@ValueSource(strings = {
-				"/api/environment",
-				"/configserver/name/profile",
-				"/api", "/api/configuration", "/api/configuration/settings",
-		})
 		@WithJwt(CLAIMS)
-		void shouldAllow(String path) {
-			var result = doPerformAuthenticated(path);
+		void shouldAllowApiEndpoint() {
+			var result = doPerformAuthenticated("/api");
 
 			result.andExpect(status().isOk());
 		}
 
+		@Test
+		@SneakyThrows
+		@WithJwt(CLAIMS)
+		void shouldForbidSettingsEndpoint() {
+			var result = doPerformAuthenticated("/api/configuration/settings");
+
+			result.andExpect(status().isForbidden());
+		}
+
+		@Test
+		@SneakyThrows
+		@WithJwt(CLAIMS)
+		void shouldForbidConfigurationsEndpoint() {
+			var result = doPerformAuthenticated("/api/configuration");
+
+			result.andExpect(status().isForbidden());
+		}
+
 		@SneakyThrows
 		private ResultActions doPerformAuthenticated(String path) {
 			return mockMvc.perform(get(path));
 		}
 	}
+
+	@DisplayName("with admin role")
+	@Nested
+	class TestWithAdminRole {
+
+		static final String CLAIMS = """
+				{
+				  "preferredUsername": "testUser",
+				  "scope": "openid testscope",
+				  "resource_access": { "admin": { "roles": ["ADMIN_ADMIN"] } }
+				}""";
+
+
+		@Test
+		@SneakyThrows
+		@WithJwt(CLAIMS)
+		void shouldAllowSettings() {
+			var result = mockMvc.perform(get("/api/configuration/settings"));
+
+			result.andExpect(status().isOk());
+		}
+
+		@Test
+		@SneakyThrows
+		@WithJwt(CLAIMS)
+		void shouldAllowConfiguration() {
+			var result = mockMvc.perform(get("/api/configuration"));
+
+			result.andExpect(status().isOk());
+		}
+	}
 }
diff --git a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationTest.java b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9acdae93189e5d6e25d65361c78929584da935f7
--- /dev/null
+++ b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2024. Das Land Schleswig-Holstein vertreten durch das Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.admin.security;
+
+import static de.ozgcloud.admin.security.JwtTestFactory.*;
+import static de.ozgcloud.admin.security.SecurityConfiguration.*;
+import static java.util.Collections.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.admin.environment.OAuth2Properties;
+
+class SecurityConfigurationTest {
+
+	@Spy
+	@InjectMocks
+	private SecurityConfiguration securityConfiguration;
+
+	@Mock
+	private OAuth2Properties oAuth2Properties;
+
+	@DisplayName("jwt authentication converter")
+	@Nested
+	class TestJwtAuthenticationConverter {
+
+		private final String roleString = "ROLE_Test";
+
+		@BeforeEach
+		void mock() {
+			doReturn(Set.of(new SimpleGrantedAuthority(roleString))).when(securityConfiguration).convertJwtToGrantedAuthorities(any());
+		}
+
+		@DisplayName("should use preferred_username")
+		@Test
+		void shouldUsePreferredUsername() {
+			var preferredName = LoremIpsum.getInstance().getName();
+			var jwtWithPreferredName = JwtTestFactory.createBuilder()
+					.claim(StandardClaimNames.PREFERRED_USERNAME, preferredName)
+					.build();
+
+			var jwtAuthenticationConverter = securityConfiguration.jwtAuthenticationConverter();
+
+			var abstractAuthenticationToken = jwtAuthenticationConverter.convert(jwtWithPreferredName);
+			assertThat(abstractAuthenticationToken.getName()).isEqualTo(preferredName);
+		}
+
+		@DisplayName("should use granted authorities converter")
+		@Test
+		void shouldUseGrantedAuthoritiesConverter() {
+			var jwtWithRoles = JwtTestFactory.create();
+
+			var jwtAuthenticationConverter = securityConfiguration.jwtAuthenticationConverter();
+
+			var abstractAuthenticationToken = jwtAuthenticationConverter.convert(jwtWithRoles);
+			var securityRoleStrings = abstractAuthenticationToken.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();
+
+			assertThat(securityRoleStrings).isEqualTo(List.of(roleString));
+		}
+	}
+
+	@DisplayName("convert jwt to granted authorities")
+	@Nested
+	class TestConvertJwtToGrantedAuthorities {
+
+		private List<String> expectedSecurityRoleStrings;
+
+		@BeforeEach
+		void mock() {
+			var keycloakRoles = List.of(ROLE_1, JwtTestFactory.ROLE_2, JwtTestFactory.ROLE_3);
+			expectedSecurityRoleStrings = keycloakRoles.stream().map(role -> SIMPLE_GRANT_AUTHORITY_PREFIX + role).toList();
+			doReturn(keycloakRoles).when(securityConfiguration).getRolesFromJwt(any());
+		}
+
+		@DisplayName("should call get keycloak roles from jwt")
+		@Test
+		void shouldCallGetKeycloakRolesFromJwt() {
+			var jwt = JwtTestFactory.create();
+
+			securityConfiguration.convertJwtToGrantedAuthorities(jwt);
+
+			verify(securityConfiguration).getRolesFromJwt(jwt);
+		}
+
+		@DisplayName("should return granted authorities with ROLE_ prefix")
+		@Test
+		void shouldReturnGrantedAuthoritiesWithRolePrefix() {
+			var jwt = JwtTestFactory.create();
+
+			var grantedAuthorities = securityConfiguration.convertJwtToGrantedAuthorities(jwt);
+
+			var securityRoles = grantedAuthorities
+					.stream()
+					.map(GrantedAuthority::getAuthority).toList();
+			assertThat(securityRoles).containsAll(expectedSecurityRoleStrings);
+		}
+	}
+
+	@DisplayName("get roles from jwt")
+	@Nested
+	class TestGetRolesFromJwt {
+
+		@BeforeEach
+		void mock() {
+			lenient().when(oAuth2Properties.getResource()).thenReturn(JwtTestFactory.AUTH_RESOURCE);
+		}
+
+		@DisplayName("should return empty list if resource_access.admin.roles path is missing")
+		@ParameterizedTest
+		@MethodSource("getIncompleteJwt")
+		void shouldReturnEmptyListIfResourceAccessAdminRolesPathIsMissing(Jwt incompleteJwt) {
+			var roleStrings = securityConfiguration.getRolesFromJwt(incompleteJwt);
+
+			assertThat(roleStrings).isEmpty();
+		}
+
+		private static Stream<Arguments> getIncompleteJwt() {
+			return Stream.of(JwtTestFactory.create(),
+							JwtTestFactory.createBuilder().claim(RESOURCE_ACCESS_KEY, Map.of()).build(),
+							JwtTestFactory.createBuilder().claim(RESOURCE_ACCESS_KEY, Map.of("admin", Map.of())).build(),
+							JwtTestFactory.createWithRoles(emptyList()).build())
+					.map(Arguments::of);
+		}
+
+		@DisplayName("should return resource_access.admin.roles list")
+		@Test
+		void shouldReturnResourceAccessAdminRolesList() {
+			var expectedRoles = List.of(ROLE_1, JwtTestFactory.ROLE_2, JwtTestFactory.ROLE_3);
+			var jwtWithRoles = JwtTestFactory.createWithRoles(expectedRoles).build();
+
+			var roleStrings = securityConfiguration.getRolesFromJwt(jwtWithRoles);
+
+			assertThat(roleStrings).isEqualTo(expectedRoles);
+		}
+
+	}
+}
diff --git a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationWithKeycloakITCase.java b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationWithKeycloakITCase.java
deleted file mode 100644
index e336b21c6bde4f0904228557051a411c687276a9..0000000000000000000000000000000000000000
--- a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationWithKeycloakITCase.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright
- * (C) 2024 Das Land Schleswig-Holstein vertreten durch das
- *  Minis
- * erium für Energiewende, Klimaschutz, Umwelt und Natur Zentrales
- *  IT-Management 
- * 
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald diese von der
- * Europäischen Kommission genehmigt wurden - Folgeversionen der EUPL
- * 
- * 
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- * 
- * Sofern nicht durch anwendbare Rechtsvorschriften gefordert oder in
- * schriftlicher Form vereinbart, wird die unter der Lizenz verbreitete Software
- * "so wie sie ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet. Die sprachspezifischen
- * Genehmigungen und Beschränkungen unter der Lizenz sind dem Lizenztext zu
- * entnehmen.
- */
-package de.ozgcloud.admin.security;
-
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
-
-import java.net.URI;
-import java.util.Collections;
-import java.util.Map;
-
-import org.apache.http.client.utils.URIBuilder;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.http.MediaType;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.RestClient;
-
-import dasniko.testcontainers.keycloak.KeycloakContainer;
-import de.ozgcloud.admin.RootController;
-import de.ozgcloud.common.test.DataITCase;
-import lombok.SneakyThrows;
-
-@DataITCase
-@AutoConfigureMockMvc
-class SecurityConfigurationWithKeycloakITCase {
-	@Autowired
-	private MockMvc mockMvc;
-
-	static KeycloakContainer keycloak;
-
-	@BeforeAll
-	static void setupKeycloakContainer() {
-		keycloak = new KeycloakContainer().withRealmImportFile("keycloak/realm-export.json");
-		keycloak.start();
-	}
-
-	@AfterAll
-	static void closeKeycloakContainer() {
-		keycloak.close();
-	}
-
-	@DynamicPropertySource
-	static void registerResourceServerIssuerProperty(DynamicPropertyRegistry registry) {
-		registry.add("spring.security.oauth2.resourceserver.jwt.issuer-uri", () -> keycloak.getAuthServerUrl() + "/realms/by-kiel-dev");
-	}
-
-	@Nested
-	class TestSecuredEndpointWithKeycloakToken {
-		@SneakyThrows
-		@ParameterizedTest
-		@ValueSource(strings = {
-				"/api/environment",
-				"/configserver/name/profile",
-				"/api", "/api/configuration", "/api/configuration/param",
-		})
-		void shouldGetAccessWithToken() {
-			String token = getToken();
-
-			var result = mockMvc.perform(get(RootController.PATH).header("Authorization", token));
-
-			result.andExpect(status().isOk());
-		}
-
-		@SneakyThrows
-		String getToken() {
-			MultiValueMap<String, String> formData = setPostBodyForToken();
-
-			Map<String, String> resultBody = performPostRequestToKeycloak(formData);
-
-			return "Bearer " + resultBody.get("access_token").toString();
-		}
-
-		MultiValueMap<String, String> setPostBodyForToken() {
-			MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
-			formData.put("grant_type", Collections.singletonList("password"));
-			formData.put("client_id", Collections.singletonList("admin"));
-			formData.put("username", Collections.singletonList("admin-test"));
-			formData.put("password", Collections.singletonList("Password"));
-			return formData;
-		}
-
-		@SuppressWarnings("unchecked")
-		@SneakyThrows
-		Map<String, String> performPostRequestToKeycloak(MultiValueMap<String, String> formData) {
-			RestClient restClient = RestClient.create();
-			URI authorizationURI = new URIBuilder(keycloak.getAuthServerUrl() + "/realms/by-kiel-dev/protocol/openid-connect/token").build();
-			var response = restClient.post().uri(authorizationURI)
-					.contentType(MediaType.APPLICATION_FORM_URLENCODED)
-					.body(formData);
-			return response.retrieve().body(Map.class);
-
-		}
-
-	}
-}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/admin/setting/SettingITCase.java b/src/test/java/de/ozgcloud/admin/setting/SettingITCase.java
index 7bdbbf2ffc2120affc2cb715f47da788ced89619..04aaf44fb4105118d90343fc2271e58479959173 100644
--- a/src/test/java/de/ozgcloud/admin/setting/SettingITCase.java
+++ b/src/test/java/de/ozgcloud/admin/setting/SettingITCase.java
@@ -28,6 +28,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 
 import java.util.List;
 
+import de.ozgcloud.admin.common.user.UserRole;
 import org.apache.commons.lang3.StringUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
@@ -55,7 +56,7 @@ import lombok.SneakyThrows;
 
 @DataITCase
 @AutoConfigureMockMvc
-@WithMockUser
+@WithMockUser(roles = UserRole.ADMIN_ADMIN)
 class SettingITCase {
 
 	@Autowired
@@ -108,7 +109,7 @@ class SettingITCase {
 	class TestForSettingWithPostfach {
 		private static final String POSTFACH_NAME = "Postfach";
 
-		private Setting settingWithPostfach = SettingTestFactory.createBuilder()
+		private final Setting settingWithPostfach = SettingTestFactory.createBuilder()
 				.name(POSTFACH_NAME)
 				.settingBody(PostfachSettingBodyTestFactory.create())
 				.build();
@@ -284,13 +285,13 @@ class SettingITCase {
 		@Nested
 		class TestPut {
 			private String id;
-			private PostfachSettingBody updatedPostfach = PostfachSettingBodyTestFactory.createBuilder()
+			private final PostfachSettingBody updatedPostfach = PostfachSettingBodyTestFactory.createBuilder()
 					.absender(AbsenderTestFactory.createBuilder()
 							.name("Neuer Name")
 							.anschrift("Neue Anschrift")
 							.build())
 					.build();
-			private Setting updatedSetting = SettingTestFactory.createBuilder()
+			private final Setting updatedSetting = SettingTestFactory.createBuilder()
 					.name(POSTFACH_NAME)
 					.settingBody(updatedPostfach)
 					.build();
diff --git a/src/test/resources/application-itcase.yaml b/src/test/resources/application-itcase.yaml
index fc717c37b9cfb97360022930cb07c950b16d1983..3082babc0c50e52484ef75f5a650980975eea15d 100644
--- a/src/test/resources/application-itcase.yaml
+++ b/src/test/resources/application-itcase.yaml
@@ -1,2 +1,8 @@
 mongock:
-  enabled: false
\ No newline at end of file
+  enabled: false
+
+ozgcloud:
+  oauth2:
+    auth-server-url: https://sso.it-case.de
+    realm: by-kiel-dev
+    resource: admin
diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml
deleted file mode 100644
index bfdb349dcc960848ab0c7815b86b4c923f718393..0000000000000000000000000000000000000000
--- a/src/test/resources/application.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-management:
-  server:
-    port: 8081
-spring:
-  application:
-    name: OzgCloud_Administration
-  data:
-    mongodb:
-      authentication-database: admin
-    rest:
-      basePath: /api/configuration
-  cloud:
-    config:
-      server:
-        prefix: /configserver
-  security:
-    oauth2:
-      resourceserver:
-        jwt:
-          issuer-uri: ${ozgcloud.oauth2.auth-server-url}/realms/${ozgcloud.oauth2.realm}
-ozgcloud:
-  oauth2:
-    auth-server-url: https://sso.dev.by.ozg-cloud.de
-    realm: by-kiel-dev
\ No newline at end of file
diff --git a/src/test/resources/jsonTemplates/security/resource_access.template.json b/src/test/resources/jsonTemplates/security/resource_access.template.json
new file mode 100644
index 0000000000000000000000000000000000000000..44f43d8cb40f1ee887fe158bb9668c1074251f5d
--- /dev/null
+++ b/src/test/resources/jsonTemplates/security/resource_access.template.json
@@ -0,0 +1,7 @@
+{
+  "admin": {
+    "roles": [
+      "ADMIN_ADMIN"
+    ]
+  }
+}