Skip to content
Snippets Groups Projects
Commit 79992129 authored by Jan Zickermann's avatar Jan Zickermann
Browse files

OZG-4939 OZG-4992 Refactor method to separate AuthenticationEntryPoint implementation

parent fcd0417d
No related branches found
No related tags found
No related merge requests found
/*
* 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
public class AdminAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final List<HttpMessageConverter<?>> converters;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
ServerResponse
.from(errorResponse(authException, request.getRequestURI()))
.writeTo(request, response, () -> converters);
}
ErrorResponse errorResponse(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();
}
}
......@@ -19,28 +19,20 @@
*/
package de.ozgcloud.admin.security;
import java.net.URI;
import java.util.List;
import org.apache.http.HttpHeaders;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
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.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.ErrorResponse;
import org.springframework.web.servlet.function.ServerResponse;
import lombok.RequiredArgsConstructor;
......@@ -50,7 +42,7 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class SecurityConfiguration {
private final List<HttpMessageConverter<?>> converters;
private final AdminAuthenticationEntryPoint authenticationEntryPoint;
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
......@@ -63,9 +55,7 @@ public class SecurityConfiguration {
// Disable CSRF because of state-less session-management
http.csrf(AbstractHttpConfigurer::disable);
http.exceptionHandling(eh -> eh.authenticationEntryPoint((request, response, authException) ->
errorServerResponse(authException, request.getRequestURI())
.writeTo(request, response, () -> converters)));
http.exceptionHandling(eh -> eh.authenticationEntryPoint(authenticationEntryPoint));
http.authorizeHttpRequests(requests -> requests
.requestMatchers(HttpMethod.GET, "/api/environment").permitAll()
......@@ -79,16 +69,6 @@ public class SecurityConfiguration {
return http.build();
}
private ServerResponse errorServerResponse(AuthenticationException ex, String requestUri) {
return ServerResponse
.from(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());
}
@Bean
JwtAuthenticationConverter jwtAuthenticationConverter() {
var jwtConverter = new JwtAuthenticationConverter();
......
/*
* 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.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 lombok.SneakyThrows;
@ExtendWith(MockitoExtension.class)
public class AdminAuthenticationEntryPointTest {
private static final String REQUEST_URI = "/request-uri";
@Spy
@InjectMocks
private AdminAuthenticationEntryPoint authenticationEntryPoint;
@Mock
List<HttpMessageConverter<?>> converters;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Mock
private ServerResponse serverResponse;
@DisplayName("consume")
@Nested
class TestConsume {
@BeforeEach
void initMock() {
when(request.getRequestURI()).thenReturn(REQUEST_URI);
}
@Captor
ArgumentCaptor<ServerResponse.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 TestErrorResponse {
@DisplayName("body")
@Nested
class TestBody {
@DisplayName("have instance")
@Test
void shouldHaveInstance() {
var problemDetail = body();
assertThat(problemDetail.getInstance()).isEqualTo(URI.create(REQUEST_URI));
}
@DisplayName("have detail")
@Test
void shouldHaveDetail() {
var problemDetail = body();
assertThat(problemDetail.getDetail()).isEqualTo(AuthenticationExceptionTestFactory.AUTH_MESSAGE);
}
private ProblemDetail body() {
return serverErrorResponse().getBody();
}
}
@DisplayName("headers")
@Nested
class TestHeaders {
@DisplayName("have content type")
@Test
void shouldHaveContentType() {
var headers = httpHeaders();
assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
}
@DisplayName("have www authentication")
@Test
void shouldHaveWwwAuthentication() {
var headers = httpHeaders();
assertThat(headers.getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer realm=\"Restricted Content\"");
}
private HttpHeaders httpHeaders() {
return serverErrorResponse().getHeaders();
}
}
@DisplayName("have status")
@Test
void shouldHaveStatus() {
var response = serverErrorResponse();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
private ErrorResponse serverErrorResponse() {
return authenticationEntryPoint.errorResponse(
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().msg(AUTH_MESSAGE).build();
}
public static DummyAuthenticationException.DummyAuthenticationExceptionBuilder builder() {
return DummyAuthenticationException.builder();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment