Skip to content
Snippets Groups Projects
Commit 4421e319 authored by OZGCloud's avatar OZGCloud
Browse files

Merge remote-tracking branch 'origin/master' into OZG-4321-ErweiterungEnvironment

parents 4b4d396f 7e554b7e
No related branches found
No related tags found
No related merge requests found
Showing
with 2878 additions and 8 deletions
......@@ -19,6 +19,8 @@
<imageName>docker.ozg-sh.de/administration</imageName>
<build.number>SET_BY_JENKINS</build.number>
<spring-cloud-config-server.version>4.1.0</spring-cloud-config-server.version>
<testcontainers-keycloak.version>3.2.0</testcontainers-keycloak.version>
<keycloak-admin-client.version>23.0.6</keycloak-admin-client.version>
</properties>
<dependencies>
......@@ -56,6 +58,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- Dev -->
<dependency>
......@@ -90,11 +96,29 @@
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency><dependency>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.dasniko</groupId>
<artifactId>testcontainers-keycloak</artifactId>
<version>${testcontainers-keycloak.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>${keycloak-admin-client.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
......
......@@ -36,5 +36,5 @@ public class ProductionProperties {
/**
* True if the angular frontend (admin client) should build in production mode.
*/
private boolean production;
private boolean production = true;
}
......@@ -59,4 +59,5 @@ public class AdminExceptionHandler extends ResponseEntityExceptionHandler {
return ErrorResponse.builder(ex, status, ex.getLocalizedMessage()).build();
}
}
/*
* 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 java.io.IOException;
import java.net.URI;
import java.util.List;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.ErrorResponse;
import org.springframework.web.servlet.function.ServerResponse;
import lombok.RequiredArgsConstructor;
@Component
@RequiredArgsConstructor
class AdminAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final List<HttpMessageConverter<?>> converters;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
ServerResponse
.from(buildErrorResponse(authException, request.getRequestURI()))
.writeTo(request, response, () -> converters);
}
ErrorResponse buildErrorResponse(AuthenticationException ex, String requestUri) {
return ErrorResponse
.builder(ex, HttpStatus.UNAUTHORIZED, ex.getLocalizedMessage())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE)
.instance(URI.create(requestUri))
.header(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\"")
.build();
}
}
/*
* 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 java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import lombok.RequiredArgsConstructor;
@Configuration
@EnableMethodSecurity(securedEnabled = true)
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {
private final AdminAuthenticationEntryPoint authenticationEntryPoint;
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.exceptionHandling(eh -> eh.authenticationEntryPoint(authenticationEntryPoint));
http.authorizeHttpRequests(requests -> requests
.requestMatchers(HttpMethod.GET, "/api/environment").permitAll()
.requestMatchers("/api").authenticated()
.requestMatchers("/api/**").authenticated()
.requestMatchers("/actuator").permitAll()
.requestMatchers("/actuator/**").permitAll()
.requestMatchers("/configserver/**").permitAll()
.anyRequest().denyAll());
return http.build();
}
@Bean
JwtAuthenticationConverter jwtAuthenticationConverter() {
var jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(jwt -> List.of(() -> "ROLE_USER"));
jwtConverter.setPrincipalClaimName(StandardClaimNames.PREFERRED_USERNAME);
return jwtConverter;
}
}
\ No newline at end of file
ozgcloud:
production: false
oauth2:
auth-server-url: https://sso.dev.by.ozg-cloud.de
realm: by-kiel-dev
\ No newline at end of file
ozgcloud:
oauth2:
auth-server-url: https://sso.dev.by.ozg-cloud.de
realm: by-kiel-dev
\ No newline at end of file
......@@ -49,12 +49,17 @@ management:
spring:
application:
name: OzgCloud_Administration
cloud:
config:
server:
prefix: /configserver
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}
\ No newline at end of file
......@@ -9,10 +9,12 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.security.test.context.support.WithMockUser;
import de.ozgcloud.common.test.DataITCase;
@DataITCase
@WithMockUser
class AdminEnvironmentRepositoryITCase {
@Autowired
......
/*
* 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 org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.net.URI;
import java.util.List;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.ErrorResponse;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.servlet.function.ServerResponse.Context;
import lombok.SneakyThrows;
@ExtendWith(MockitoExtension.class)
public class AdminAuthenticationEntryPointTest {
private static final String REQUEST_URI = "/request-uri";
@Spy
@InjectMocks
private AdminAuthenticationEntryPoint authenticationEntryPoint;
@Mock
private List<HttpMessageConverter<?>> converters;
@Mock
private HttpServletRequest request;
@DisplayName("commence")
@Nested
class TestCommence {
@Mock
private HttpServletResponse response;
@Mock
private ServerResponse serverResponse;
@BeforeEach
void initMock() {
when(request.getRequestURI()).thenReturn(REQUEST_URI);
}
@Captor
ArgumentCaptor<Context> contextCaptor;
@DisplayName("call writeTo")
@SneakyThrows
@Test
void shouldCallWriteTo() {
var authException = AuthenticationExceptionTestFactory.create();
try (var mockServerResponse = mockStatic(ServerResponse.class)) {
mockServerResponse.when(() -> ServerResponse.from(any(ErrorResponse.class))).thenReturn(serverResponse);
authenticationEntryPoint.commence(request, response, authException);
verify(serverResponse).writeTo(eq(request), eq(response), contextCaptor.capture());
assertThat(contextCaptor.getValue().messageConverters()).isEqualTo(converters);
}
}
}
@DisplayName("error response")
@Nested
class TestBuildErrorResponse {
@Nested
class TestBody {
@Test
void shouldHaveInstance() {
var problemDetail = getErrorResponseBody();
assertThat(problemDetail.getInstance()).isEqualTo(URI.create(REQUEST_URI));
}
@Test
void shouldHaveDetail() {
var problemDetail = getErrorResponseBody();
assertThat(problemDetail.getDetail()).isEqualTo(AuthenticationExceptionTestFactory.AUTH_MESSAGE);
}
private ProblemDetail getErrorResponseBody() {
return getServerErrorResponse().getBody();
}
}
@Nested
class TestHeaders {
@Test
void shouldHaveContentType() {
var headers = getHttpHeaders();
assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
}
@Test
void shouldHaveWwwAuthentication() {
var headers = getHttpHeaders();
assertThat(headers.getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer realm=\"Restricted Content\"");
}
private HttpHeaders getHttpHeaders() {
return getServerErrorResponse().getHeaders();
}
}
@Test
void shouldHaveStatus() {
var response = getServerErrorResponse();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
private ErrorResponse getServerErrorResponse() {
return authenticationEntryPoint.buildErrorResponse(
AuthenticationExceptionTestFactory.create(),
REQUEST_URI);
}
}
}
/*
* 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 org.springframework.security.core.AuthenticationException;
import lombok.Builder;
public class AuthenticationExceptionTestFactory {
public static final String AUTH_MESSAGE = "auth error message";
@Builder
public static class DummyAuthenticationException extends AuthenticationException {
private String msg;
DummyAuthenticationException(String msg) {
super(msg);
this.msg = msg;
}
}
public static DummyAuthenticationException create() {
return builder().build();
}
public static DummyAuthenticationException.DummyAuthenticationExceptionBuilder builder() {
return DummyAuthenticationException.builder().msg(AUTH_MESSAGE);
}
}
/*
* 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 org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.apache.http.HttpHeaders;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import de.ozgcloud.common.test.DataITCase;
import lombok.SneakyThrows;
@DataITCase
@AutoConfigureMockMvc
class SecurityConfigurationITCase {
@Autowired
private MockMvc mockMvc;
@DisplayName("without authorization")
@Nested
class TestWithoutAuthorization {
@DisplayName("allow for not found")
@SneakyThrows
@ParameterizedTest
@ValueSource(strings = {
"/actuator", "/actuator/x", "/actuator/x/y",
"/configserver", "/configserver/x",
})
void shouldAllowForNotFound(String path) {
var result = doPerform(path);
result.andExpect(status().isNotFound());
}
@DisplayName("allow")
@SneakyThrows
@ParameterizedTest
@ValueSource(strings = {
"/api/environment",
"/configserver/name/profile"
})
void shouldAllow(String path) {
var result = doPerform(path);
result.andExpect(status().isOk());
}
@SneakyThrows
@ParameterizedTest
@ValueSource(strings = {
"/api", "/api/configuration", "/api/configuration/param",
"/unknown",
})
void shouldDeny(String path) {
var result = doPerform(path);
result.andExpect(status().isUnauthorized());
}
@DisplayName("deny with problem details")
@Nested
class TestDenyWithProblemDetails {
@SneakyThrows
@Test
void shouldHaveStatus() {
var result = doPerform("/api");
result.andExpect(jsonPath("$.status").value(HttpStatus.UNAUTHORIZED.value()));
}
@SneakyThrows
@Test
void shouldHaveInstanceURI() {
var result = doPerform("/api");
result.andExpect(jsonPath("$.instance").value("/api"));
}
@Test
@SneakyThrows
void shouldHaveErrorDetailInBody() {
var result = doPerform("/api");
result.andExpect(jsonPath("$.detail").value("Full authentication is required to access this resource"));
}
@Test
@SneakyThrows
void shouldHaveHeader() {
var result = doPerform("/api");
result.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\""));
}
}
@SneakyThrows
private ResultActions doPerform(String path) {
return mockMvc.perform(get(path).header("Authorization", "invalid"));
}
}
@DisplayName("with authorization")
@Nested
class TestWithAuthorization {
static final String CLAIMS = """
{
"preferredUsername": "testUser",
"scope": "openid testscope"
}""";
@SneakyThrows
@ParameterizedTest
@ValueSource(strings = {
"/api/environment",
"/configserver/name/profile",
"/api", "/api/configuration", "/api/configuration/param",
})
@WithJwt(CLAIMS)
void shouldAllow(String path) {
var result = doPerformAuthenticated(path);
result.andExpect(status().isOk());
}
@SneakyThrows
private ResultActions doPerformAuthenticated(String path) {
return mockMvc.perform(get(path));
}
}
}
/*
* 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
/*
* 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 java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.test.context.support.WithSecurityContext;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
import org.springframework.util.StringUtils;
import com.nimbusds.jwt.JWTClaimNames;
import lombok.RequiredArgsConstructor;
import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
/**
* Annotation to setup test {@link SecurityContext} with an {@link Authentication}. Adjusted from source:
* com.c4_soft.springaddons.security.oauth2.test.annotations.WithJwt Author: Jérôme Wacongne &lt;ch4mp&#64;c4-soft.com&gt;
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(factory = WithJwt.AuthenticationFactory.class)
public @interface WithJwt {
String value() default "";
String bearerString() default AuthenticationFactory.DEFAULT_BEARER;
String headers() default AuthenticationFactory.DEFAULT_HEADERS;
@RequiredArgsConstructor
final class AuthenticationFactory implements WithSecurityContextFactory<WithJwt> {
static final String DEFAULT_BEARER = "test.jwt.bearer";
static final String DEFAULT_HEADERS = "{\"alg\": \"none\"}";
private final Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter;
@Override
public SecurityContext createSecurityContext(WithJwt annotation) {
var auth = authentication(annotation);
var securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(auth);
return securityContext;
}
private AbstractAuthenticationToken authentication(WithJwt annotation) {
var claims = parseJson(annotation.value());
var headers = parseJson(annotation.headers());
var bearerString = annotation.bearerString();
var now = Instant.now();
var iat = Optional.ofNullable((Integer) claims.get(JWTClaimNames.ISSUED_AT)).map(Instant::ofEpochSecond).orElse(now);
var exp = Optional.ofNullable((Integer) claims.get(JWTClaimNames.EXPIRATION_TIME)).map(Instant::ofEpochSecond)
.orElse(now.plusSeconds(42));
var jwt = new Jwt(bearerString, iat, exp, headers, claims);
return jwtAuthenticationConverter.convert(jwt);
}
private static Map<String, Object> parseJson(String json) {
if (!StringUtils.hasText(json)) {
return Map.of();
}
try {
return new JSONParser(JSONParser.MODE_PERMISSIVE).parse(json, JSONObject.class);
} catch (final ParseException e) {
throw new RuntimeException("Invalid JSON payload in @WithJwt");
}
}
}
}
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
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment