JWT token w testach integracyjnych

0

Chciałbym napisac test integracyjny do takiej metody :

   @PostMapping(value = "/create", produces = APPLICATION_JSON_VALUE)
    public ResponseEntity<ProductRS> createNewProducts(
            @RequestHeader("Authorization") String token,
            @RequestBody ProductRQ productRQ) {
        return foundryWrapperClientAdapter.invokeCreateProduct(productRQ, token);
    }

problem jest w tym, że jest tu przekazywany JWT token i jest zalozony filtr ktory sprawdza poprawnosc a zeby zdobyc JWT musialbym stuknac do innego serwisu.
W jaki sposób rozwiazuje sie takie problemy ? Skad wziac token w testach integracyjnych, ktory zawsze dziala i nigdy sie nie przeterminuje ?

2

Raptem 3 dni temu robiłem coś takiego ;) Po co chcesz tak kombinować? Rób ten token tak jak należy. Tzn postaw sobie fejkowy HttpServer który odpowiada na requesty o token i o klucz publiczny (np. to będzie niech WireMock jakiś albo https://mvnrepository.com/artifact/pl.codewise.canaveral/canaveral-downstream-http-mock) a sam token po prostu wygeneruj, bo to kilka linijek kodu. Generujesz klucz, tworzysz token i voila. Czemu chcesz robić jakieś oszustwa z hardkodowanym tokenem który się nie przeterminuje? Testuj aplikacje tak jak realnie będzie działała.

Swoją drogą czemu bierzesz ten token jako stringa a nie masz tam Spring Security jakiegoś które automatycznie go ogarnia?

0

Ja do tematu podszedłem zupełnie inaczej. Zamiast kombinować z tokenami nadaję użytkownikowi uprawnienia ręcznie grzebiąc w security context holderze - pomijam filtry i testowanie tego, czy framework springa działa. Nie polecam ogólnie takiego podejścia, ale ogólnie na szybko do własnego małego projektu działa znakomicie bez dużego kombinowania.


import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;

@SpringBootTest
@AutoConfigureMockMvc
@ExtendWith(SpringExtension.class)
class AnotherCoolIntegrationTest {

    private MockMvc mockMvc;

    @Autowired
    public CommentControllerITest(WebApplicationContext applicationContext) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext)
                  .apply(springSecurity())
                  .build();
    }

    @BeforeEach
    void setUp() {
        // MAGIA TUTAJ

        User user = ... // użytkownik, który ma zostać uwierzytelniony
        Authentication authentication = new TestAuthentication(user);
        SecurityContextHolder.getContext().setAuthentication(authentication);

    }

    // TESTY INTEGRACYJNE

}

// fake implementacja tylko do testów

public class TestAuthentication implements Authentication {

    private MyUserDetails user;
    private List<? extends GrantedAuthority> roles;

    public TestAuthentication(User user) {
        this.roles = user.getRoles()
                            .stream()
                            .map() // mapowanie POJO do GrantedAuthority
                            .collect(toList());
        this.user = new MyUserDetails(user, roles);
    }

    //NADPISANE METODY

}

// implementacja UserDetails ze Spring Security 

public class MyUserDetails implements UserDetails {

    private Collection<? extends GrantedAuthority> authorities;
    private User user;

    public MyUserDetails(User user, Collection<? extends GrantedAuthority> authorities) {
        this.user = user;
        this.authorities = Collections.unmodifiableCollection(authorities);
    }

    // NAPISANE METODY

}


1

@witold12 tylko ze w ten sposób nie sprawdziłeś wcale czy twoja konfiguracja do spring security nie jest zrypana, więc takie testy to trochę fejki mimo wszystko. Bo jedną sprawą jest że spring działa, a zupełnie inną kwestią jest, czy dobrze go skonfigurowałeś ;) Ja jednak zalecam testować realny kod aplikacji, a nie mocki i fejki. Szczególnie ze takie generowanie tokenu i ustawienie embedded http servera to jest dosłownie 10 linijek kodu.

Samo stukanie przez MockMvc też uważam za trochę oszukiwanie i zalecałbym stawiać aplikacje w teście i stukać do niej prawdziwym klientem http, bo inaczej znów nie testujesz pewnych aspektów aplikacji.

4

Przykład do tego o czym mówie.
Robimy security config, żeby nie bawić się w jakieś branie stringowego tokena i validowanie go ręcznie:

    @Configuration
    public class TokenSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .antMatcher("/costam/costam")
                    .authorizeRequests()
                    .antMatchers("/costam/costam").authenticated()
                    .and()
                    .oauth2ResourceServer()
                    .jwt();
        }
    }

W propertisach mamy spring.security.oauth2.resourceserver.jwt.jwk-set-uri=URL_DO_KLUCZA_PUBLICZNEGO_SERVERA_OD_TOKENÓW
Następnie robimy sobie kontroler:

@RestController
@RequestMapping("/costam")
public class TokenAuthController {
    @GetMapping(value="costam")
    public A method(Authentication principal){
        // costam
    }
}

I voila. Teraz chcemy to testować, to bierzemy sobie coś w stylu:

httpMock = HttpNoDepsMockProvider
		.newConfig()
		.build("httpMock");
httpMock .start(new DummyRunnerContext());

Przypisujemy do tego naszego property spring.security.oauth2.resourceserver.jwt.jwk-set-uri w teście adres tego postawionego http servera (localhost:jakiś_port) a w tekście robimy sobie jakieś:

RSAKey rsaJWK = new RSAKeyGenerator(2048)
		.keyID("someId")
		.keyUse(KeyUse.SIGNATURE)
		.generate();
JWSSigner signer = new RSASSASigner(rsaJWK);
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
		.subject(user.getName())
		.jwtID(UUID.randomUUID().toString())
		.audience("someAudience")
		.issuer("someIssuer")
		.expirationTime(new Date(new Date().getTime() + 60 * 1000))
		.build();
SignedJWT signedJWT = new SignedJWT(
		new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaJWK.getKeyID()).build(),
		claimsSet);
signedJWT.sign(signer);
RSAKey rsaPublicJWK = rsaJWK.toPublicJWK();
String pubkey = rsaPublicJWK.toJSONObject().toString();
httpMock.createRule()
		.whenCalledWith(Method.GET, "/")
		.thenRespondWith(MockRuleProvider.Body.asJsonFrom("{\"keys\":[" + pubkey + "]}"));
return signedJWT.serialize();

I voila, mamy wygenerowany token który poprawnie zwaliduje się, jak Spring security uderzy po klucz publiczny do naszego http servera.

A jak testujemy naszą aplikację? Tworzymy http clienta i stukamy sobie w localhost:server.port

1 użytkowników online, w tym zalogowanych: 0, gości: 1