Skip to content
Snippets Groups Projects
WithJwt.java 4.15 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * 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");
    			}
    		}
    	}
    }