Spring Test & Security: Wie verspottet man die Authentifizierung?


123

Ich habe versucht herauszufinden, wie ein Unit-Test durchgeführt werden kann, wenn die URLs meiner Controller ordnungsgemäß gesichert sind. Nur für den Fall, dass jemand etwas ändert und versehentlich die Sicherheitseinstellungen entfernt.

Meine Controller-Methode sieht folgendermaßen aus:

@RequestMapping("/api/v1/resource/test") 
@Secured("ROLE_USER")
public @ResonseBody String test() {
    return "test";
}

Ich habe eine WebTestEnvironment wie folgt eingerichtet:

import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({ 
        "file:src/main/webapp/WEB-INF/spring/security.xml",
        "file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
        "file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {

    @Resource
    private FilterChainProxy springSecurityFilterChain;

    @Autowired
    @Qualifier("databaseUserService")
    protected UserDetailsService userDetailsService;

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    protected DataSource dataSource;

    protected MockMvc mockMvc;

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    protected UsernamePasswordAuthenticationToken getPrincipal(String username) {

        UserDetails user = this.userDetailsService.loadUserByUsername(username);

        UsernamePasswordAuthenticationToken authentication = 
                new UsernamePasswordAuthenticationToken(
                        user, 
                        user.getPassword(), 
                        user.getAuthorities());

        return authentication;
    }

    @Before
    public void setupMockMvc() throws NamingException {

        // setup mock MVC
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(this.wac)
                .addFilters(this.springSecurityFilterChain)
                .build();
    }
}

In meinem eigentlichen Test habe ich versucht, so etwas zu tun:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;

import eu.ubicon.webapp.test.WebappTestEnvironment;

public class CopyOfClaimTest extends WebappTestEnvironment {

    @Test
    public void signedIn() throws Exception {

        UsernamePasswordAuthenticationToken principal = 
                this.getPrincipal("test1");

        SecurityContextHolder.getContext().setAuthentication(principal);        

        super.mockMvc
            .perform(
                    get("/api/v1/resource/test")
//                    .principal(principal)
                    .session(session))
            .andExpect(status().isOk());
    }

}

Ich habe das hier aufgegriffen:

Wenn man genau hinschaut, hilft dies jedoch nur, wenn keine tatsächlichen Anforderungen an URLs gesendet werden, sondern nur, wenn Dienste auf Funktionsebene getestet werden. In meinem Fall wurde eine Ausnahme "Zugriff verweigert" ausgelöst:

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
        ...

Die folgenden zwei Protokollmeldungen besagen im Wesentlichen, dass kein Benutzer authentifiziert wurde, was darauf hinweist, dass die Einstellung Principalnicht funktioniert hat oder dass sie überschrieben wurde.

14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS

Ihr Firmenname eu.ubicon wird bei Ihrem Import angezeigt. Ist das nicht ein Sicherheitsrisiko?
Kyle Bridenstine

2
Hallo, danke für den Kommentar! Ich kann aber nicht verstehen warum. Es ist sowieso Open Source Software. Wenn Sie interessiert sind, besuchen Sie bitbucket.org/ubicon/ubicon (oder bitbucket.org/dmir_wue/everyaware für die neueste Gabelung). Lass es mich wissen, wenn ich etwas vermisse.
Martin Becker

Überprüfen Sie diese Lösung (die Antwort ist für Frühling 4): stackoverflow.com/questions/14308341/…
Nagy Attila

Antworten:


100

Als ich nach einer Antwort suchte, konnte ich nicht feststellen, dass sie gleichzeitig einfach und flexibel war. Dann fand ich die Spring Security Reference und stellte fest, dass es nahezu perfekte Lösungen gibt. AOP - Lösungen oft sind die Größten zum Testen, und Spring bietet es mit @WithMockUser, @WithUserDetailsund @WithSecurityContextin diesem Artefakt:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>4.2.2.RELEASE</version>
    <scope>test</scope>
</dependency>

In den meisten Fällen, @WithUserDetails sammelt sich die Flexibilität und Kraft, die ich brauche.

Wie funktioniert @WithUserDetails?

Grundsätzlich müssen Sie nur eine benutzerdefinierte Version UserDetailsServicemit allen möglichen Benutzerprofilen erstellen, die Sie testen möchten. Z.B

@TestConfiguration
public class SpringSecurityWebAuxTestConfig {

    @Bean
    @Primary
    public UserDetailsService userDetailsService() {
        User basicUser = new UserImpl("Basic User", "user@company.com", "password");
        UserActive basicActiveUser = new UserActive(basicUser, Arrays.asList(
                new SimpleGrantedAuthority("ROLE_USER"),
                new SimpleGrantedAuthority("PERM_FOO_READ")
        ));

        User managerUser = new UserImpl("Manager User", "manager@company.com", "password");
        UserActive managerActiveUser = new UserActive(managerUser, Arrays.asList(
                new SimpleGrantedAuthority("ROLE_MANAGER"),
                new SimpleGrantedAuthority("PERM_FOO_READ"),
                new SimpleGrantedAuthority("PERM_FOO_WRITE"),
                new SimpleGrantedAuthority("PERM_FOO_MANAGE")
        ));

        return new InMemoryUserDetailsManager(Arrays.asList(
                basicActiveUser, managerActiveUser
        ));
    }
}

Jetzt haben wir unsere Benutzer bereit. Stellen Sie sich vor, wir möchten die Zugriffssteuerung für diese Controller-Funktion testen:

@RestController
@RequestMapping("/foo")
public class FooController {

    @Secured("ROLE_MANAGER")
    @GetMapping("/salute")
    public String saluteYourManager(@AuthenticationPrincipal User activeUser)
    {
        return String.format("Hi %s. Foo salutes you!", activeUser.getUsername());
    }
}

Hier haben wir eine get kartiert Funktion auf die Route / foo / Gruß und testen wir eine rollenbasierte Sicherheit mit der @SecuredAnmerkung, obwohl Sie testen können @PreAuthorizeund @PostAuthorizewie gut. Erstellen wir zwei Tests, einen, um zu überprüfen, ob ein gültiger Benutzer diese Grußantwort sehen kann, und einen, um zu überprüfen, ob sie tatsächlich verboten ist.

@RunWith(SpringRunner.class)
@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = SpringSecurityWebAuxTestConfig.class
)
@AutoConfigureMockMvc
public class WebApplicationSecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithUserDetails("manager@company.com")
    public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
    {
        mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
                .accept(MediaType.ALL))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("manager@company.com")));
    }

    @Test
    @WithUserDetails("user@company.com")
    public void givenBasicUser_whenGetFooSalute_thenForbidden() throws Exception
    {
        mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
                .accept(MediaType.ALL))
                .andExpect(status().isForbidden());
    }
}

Wie Sie sehen, haben wir importiert SpringSecurityWebAuxTestConfig , um unsere Benutzer zum Testen bereitzustellen. Jeder wird für seinen entsprechenden Testfall verwendet, indem nur eine einfache Anmerkung verwendet wird, wodurch Code und Komplexität reduziert werden.

Verwenden Sie @WithMockUser besser, um die rollenbasierte Sicherheit zu vereinfachen

Wie Sie sehen, @WithUserDetailsverfügt es über die Flexibilität, die Sie für die meisten Ihrer Anwendungen benötigen. Sie können benutzerdefinierte Benutzer mit einer beliebigen GrantedAuthority verwenden, z. B. Rollen oder Berechtigungen. Wenn Sie jedoch nur mit Rollen arbeiten, kann das Testen noch einfacher sein und Sie können das Erstellen einer benutzerdefinierten Rolle vermeiden UserDetailsService. Geben Sie in solchen Fällen eine einfache Kombination aus Benutzer, Kennwort und Rollen mit @WithMockUser an .

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(
    factory = WithMockUserSecurityContextFactory.class
)
public @interface WithMockUser {
    String value() default "user";

    String username() default "";

    String[] roles() default {"USER"};

    String password() default "password";
}

Die Anmerkung definiert Standardwerte für einen sehr einfachen Benutzer. Da in unserem Fall die Route, die wir testen, nur erfordert, dass der authentifizierte Benutzer ein Manager ist, können wir die Verwendung beenden SpringSecurityWebAuxTestConfigund dies tun.

@Test
@WithMockUser(roles = "MANAGER")
public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
{
    mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
            .accept(MediaType.ALL))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("user")));
}

Beachten Sie, dass wir jetzt anstelle des Benutzers manager@company.com den Standard erhalten, der bereitgestellt wird von @WithMockUser: user ; Dennoch spielt es keine Rolle, denn was uns wirklich wichtig ist, ist seine Rolle : ROLE_MANAGER.

Schlussfolgerungen

Wie Sie sehen mit Anmerkungen wie @WithUserDetailsund @WithMockUserwir können zwischen verschiedenen authentifizierten Benutzern Szenarien wechseln zur Herstellung von einfachen Tests ohne Gebäudeklassen entfremdet von unserer Architektur einfach. Es wird auch empfohlen, zu sehen, wie @WithSecurityContext für noch mehr Flexibilität funktioniert.


Wie verspotte ich mehrere Benutzer ? Zum Beispiel wird die erste Anfrage von gesendet tom, während die zweite von jerry?
ch271828n

Sie können eine Funktion erstellen, bei der sich Ihr Test mit Tom befindet, und einen weiteren Test mit derselben Logik erstellen und mit Jerry testen. Für jeden Test gibt es ein bestimmtes Ergebnis, daher gibt es unterschiedliche Aussagen. Wenn ein Test fehlschlägt, werden Sie anhand seines Namens darüber informiert, welcher Benutzer / welche Rolle nicht funktioniert hat. Denken Sie daran, dass der Benutzer in einer Anfrage nur einer sein kann. Daher ist es nicht sinnvoll, mehrere Benutzer in einer Anfrage anzugeben.
EliuX

Entschuldigung, ich meine ein solches Beispielszenario: Wir testen das, Tom erstellt einen geheimen Artikel, dann versucht Jerry, das zu lesen, und Jerry sollte es nicht sehen (da es geheim ist). In diesem Fall handelt es sich also um einen Einheitentest ...
ch271828n

Es sieht ziemlich ähnlich wie das BasicUserund Manager UserSzenario in der Antwort. Das Schlüsselkonzept ist, dass wir uns nicht um die Benutzer kümmern, sondern um ihre Rollen, aber jeder dieser Tests, die sich im selben Komponententest befinden, repräsentiert tatsächlich unterschiedliche Abfragen. von verschiedenen Benutzern (mit unterschiedlichen Rollen) auf demselben Endpunkt durchgeführt.
EliuX

61

Seit Spring 4.0+ besteht die beste Lösung darin, die Testmethode mit @WithMockUser zu kommentieren

@Test
@WithMockUser(username = "user1", password = "pwd", roles = "USER")
public void mytest1() throws Exception {
    mockMvc.perform(get("/someApi"))
        .andExpect(status().isOk());
}

Denken Sie daran, Ihrem Projekt die folgende Abhängigkeit hinzuzufügen

'org.springframework.security:spring-security-test:4.2.3.RELEASE'

1
Der Frühling ist unglaublich. Danke
TuGordoBello

Gute Antwort. Darüber hinaus - Sie müssen mockMvc nicht verwenden, aber wenn Sie z. B. PagingAndSortingRepository aus springframework.data verwenden - können Sie Methoden direkt aus dem Repository aufrufen (die mit EL @PreAuthorize (......) kommentiert sind).
Supertramp

49

Es stellte sich heraus, dass das SecurityContextPersistenceFilter, das Teil der Spring Security-Filterkette ist, immer mein zurücksetzt SecurityContext, was ich als Aufruf festgelegt habe SecurityContextHolder.getContext().setAuthentication(principal)(oder mithilfe der .principal(principal)Methode). Dieser Filter setzt den SecurityContextin SecurityContextHoldermit einem SecurityContextvon einem SecurityContextRepository OVERWRITING den zuvor eingestellten. Das Repository ist HttpSessionSecurityContextRepositorystandardmäßig ein. Der HttpSessionSecurityContextRepositoryprüft das Gegebene HttpRequestund versucht auf das entsprechende zuzugreifen HttpSession. Wenn es existiert, wird versucht, das SecurityContextaus dem zu lesen HttpSession. Wenn dies fehlschlägt, generiert das Repository ein leeres SecurityContext.

Daher besteht meine Lösung darin, eine HttpSessionmit der Anfrage weiterzuleiten , die Folgendes enthält SecurityContext:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;

import eu.ubicon.webapp.test.WebappTestEnvironment;

public class Test extends WebappTestEnvironment {

    public static class MockSecurityContext implements SecurityContext {

        private static final long serialVersionUID = -1386535243513362694L;

        private Authentication authentication;

        public MockSecurityContext(Authentication authentication) {
            this.authentication = authentication;
        }

        @Override
        public Authentication getAuthentication() {
            return this.authentication;
        }

        @Override
        public void setAuthentication(Authentication authentication) {
            this.authentication = authentication;
        }
    }

    @Test
    public void signedIn() throws Exception {

        UsernamePasswordAuthenticationToken principal = 
                this.getPrincipal("test1");

        MockHttpSession session = new MockHttpSession();
        session.setAttribute(
                HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, 
                new MockSecurityContext(principal));


        super.mockMvc
            .perform(
                    get("/api/v1/resource/test")
                    .session(session))
            .andExpect(status().isOk());
    }
}

2
Wir haben noch keine offizielle Unterstützung für Spring Security hinzugefügt. Siehe jira.springsource.org/browse/SEC-2015 Eine Übersicht darüber, wie es aussehen wird, finden Sie unter github.com/SpringSource/spring-test-mvc/blob/master/src/test/…
Rob Winch

Ich denke nicht, dass das Erstellen eines Authentifizierungsobjekts und das Hinzufügen einer Sitzung mit dem entsprechenden Attribut so schlecht ist. Denken Sie, dass dies eine gültige "Umgehung" ist? Direkte Unterstützung wäre natürlich großartig. Sieht ziemlich ordentlich aus. Danke für den Link!
Martin Becker

tolle Lösung. hat für mich gearbeitet! nur ein kleines Problem mit der Benennung der geschützten Methode, getPrincipal()das meiner Meinung nach etwas irreführend ist. idealerweise hätte es benannt werden sollen getAuthentication(). Ebenso sollte in Ihrem signedIn()Test die lokale Variable benannt werden authoder authenticationanstelle vonprincipal
Tanvir

Was ist "getPrincipal (" test1 ")? Könnten Sie erklären, wo es das ist? Vielen Dank im Voraus
user2992476

@ user2992476 Es wird wahrscheinlich ein Objekt vom Typ UsernamePasswordAuthenticationToken zurückgegeben. Alternativ erstellen Sie GrantedAuthority und erstellen dieses Objekt.
Bluelurker

31

In pom.xml hinzufügen:

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <version>4.0.0.RC2</version>
    </dependency>

und org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessorsfür Autorisierungsanfragen verwenden. Die Beispielverwendung finden Sie unter https://github.com/rwinch/spring-security-test-blog ( https://jira.spring.io/browse/SEC-2592) ).

Aktualisieren:

4.0.0.RC2 arbeitet für die Federsicherheit 3.x. Für Spring-Security 4 werden Spring-Security-Test Teil von Spring-Security ( http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test) , Version ist dieselbe ).

Die Einrichtung wurde geändert: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc

public void setup() {
    mvc = MockMvcBuilders
            .webAppContextSetup(context)
            .apply(springSecurity())  
            .build();
}

Beispiel für die Basisauthentifizierung: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#testing-http-basic-authentication .


Dies hat auch mein Problem mit dem Erhalt eines 404 behoben, wenn versucht wurde, sich über einen Anmeldesicherheitsfilter anzumelden. Vielen Dank!
Ian Newland

Hallo, beim Testen wie von GKislin erwähnt. Ich erhalte die folgende Fehlermeldung "Authentifizierung fehlgeschlagen UserDetailsService hat null zurückgegeben, was eine Verletzung des Schnittstellenvertrags darstellt". Irgendwelche Vorschläge bitte. final AuthenticationRequest auth = new AuthenticationRequest (); auth.setUsername (userId); auth.setPassword (Passwort); mockMvc.perform (post ("/ api / auth /"). content (json (auth)). contentType (MediaType.APPLICATION_JSON));
Sanjeev

7

Hier ist ein Beispiel für diejenigen, die Spring MockMvc Security Config mithilfe der Base64-Basisauthentifizierung testen möchten.

String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("<username>:<password>").getBytes()));
this.mockMvc.perform(get("</get/url>").header("Authorization", basicDigestHeaderValue).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk());

Maven-Abhängigkeit

    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.3</version>
    </dependency>

3

Kurze Antwort:

@Autowired
private WebApplicationContext webApplicationContext;

@Autowired
private Filter springSecurityFilterChain;

@Before
public void setUp() throws Exception {
    final MockHttpServletRequestBuilder defaultRequestBuilder = get("/dummy-path");
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
            .defaultRequest(defaultRequestBuilder)
            .alwaysDo(result -> setSessionBackOnRequestBuilder(defaultRequestBuilder, result.getRequest()))
            .apply(springSecurity(springSecurityFilterChain))
            .build();
}

private MockHttpServletRequest setSessionBackOnRequestBuilder(final MockHttpServletRequestBuilder requestBuilder,
                                                             final MockHttpServletRequest request) {
    requestBuilder.session((MockHttpSession) request.getSession());
    return request;
}

Nach der Durchführung formLogindes Sicherheitstests im Frühjahr wird jede Ihrer Anforderungen automatisch als angemeldeter Benutzer aufgerufen.

Lange Antwort:

Überprüfen Sie diese Lösung (die Antwort ist für Spring 4): So melden Sie einen Benutzer mit Spring 3.2 New MVC Testing an


2

Optionen zur Vermeidung der Verwendung von SecurityContextHolder in Tests:

  • Option 1 : Mocks verwenden - ich meine Mock SecurityContextHoldermit einer Mock-Bibliothek - EasyMock zum Beispiel
  • Option 2 : Wrap-Aufruf SecurityContextHolder.get...in Ihren Code in einem Dienst - zum Beispiel SecurityServiceImplmit einer Methode getCurrentPrincipal, die die SecurityServiceSchnittstelle implementiert , und dann in Ihren Tests können Sie einfach eine Scheinimplementierung dieser Schnittstelle erstellen, die den gewünschten Principal ohne Zugriff auf zurückgibt SecurityContextHolder.

Mh, vielleicht verstehe ich nicht das ganze Bild. Mein Problem war, dass der SecurityContextPersistenceFilter den SecurityContext durch einen SecurityContext aus einem HttpSessionSecurityContextRepository ersetzt, der wiederum den SecurityContext aus dem entsprechenden HttpSession liest. Also die Lösung mit der Sitzung. Zum Aufruf des SecurityContextHolder: Ich habe meine Antwort so bearbeitet, dass ich keinen Aufruf mehr zum SecurityContextHolder verwende. Aber auch ohne Einführung von Wrapping- oder zusätzlichen Spottbibliotheken. Denken Sie, das ist eine bessere Lösung?
Martin Becker

Es tut mir leid, dass ich nicht genau verstanden habe, wonach Sie gesucht haben, und ich kann keine bessere Antwort geben als die Lösung, die Sie gefunden haben, und - es scheint eine gute Option zu sein.
Pavla Nováková

OK, danke. Ich werde meinen Vorschlag vorerst als Lösung annehmen.
Martin Becker
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.