Skip to content
Snippets Groups Projects
Commit 8d741f16 authored by Lukas Malte Monnerjahn's avatar Lukas Malte Monnerjahn
Browse files

OZG-5176 reintroduce WithJwt

parent 078acd12
Branches
Tags
No related merge requests found
......@@ -137,10 +137,15 @@ class SecurityConfigurationITCase {
@DisplayName("with authentication")
@Nested
class TestWithAuthentication {
static final String CLAIMS = """
{
"preferredUsername": "testUser",
"scope": "openid testscope"
}""";
@Test
@SneakyThrows
@WithMockUser
@WithJwt(CLAIMS)
void shouldAllowApiEndpoint() {
var result = doPerformAuthenticated("/api");
......@@ -149,7 +154,7 @@ class SecurityConfigurationITCase {
@Test
@SneakyThrows
@WithMockUser
@WithJwt(CLAIMS)
void shouldForbidSettingsEndpoint() {
var result = doPerformAuthenticated("/api/configuration/settings");
......@@ -158,7 +163,7 @@ class SecurityConfigurationITCase {
@Test
@SneakyThrows
@WithMockUser
@WithJwt(CLAIMS)
void shouldForbidConfigurationsEndpoint() {
var result = doPerformAuthenticated("/api/configuration");
......
/*
* 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 <ch4mp@c4-soft.com>
*/
@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");
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment