/* * 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"); } } } }