web-dev-qa-db-ja.com

Spring Boot Unit TestでJWT認証をモックする方法は?

Spring BootにJWT Authentication using Auth0を追加しましたREST APIの後に この例 を追加しました。

予想どおり、以前に機能していたController単体テストでは、テストでJWTを渡していないため、401 Unauthorizedではなく200 OKの応答コードが返されます。

RESTコントローラテストのJWT/Authentication部分をモックするにはどうすればよいですか?

単体テストクラス:

    @AutoConfigureMockMvc
    public class UserRoundsControllerTest extends AbstractUnitTests {

        private static String STUB_USER_ID = "user3";
        private static String STUB_ROUND_ID = "7e3b270222252b2dadd547fb";

        @Autowired
        private MockMvc mockMvc;

        private Round round;

        private ObjectId objectId;

        @BeforeEach
        public void setUp() {
            initMocks(this);
            round = Mocks.roundOne();
            objectId = Mocks.objectId();
        }

        @Test
        public void shouldGetAllRoundsByUserId() throws Exception {

            // setup
            given(userRoundService.getAllRoundsByUserId(STUB_USER_ID)).willReturn(Collections.singletonList(round));

            // mock the rounds/userId request
            RequestBuilder requestBuilder = Requests.getAllRoundsByUserId(STUB_USER_ID);

            // perform the requests
            MockHttpServletResponse response = mockMvc.perform(requestBuilder)
                .andReturn()
                .getResponse();

            // asserts
            assertNotNull(response);
            assertEquals(HttpStatus.OK.value(), response.getStatus());
        }

        //other tests
}

リクエストクラス(上記で使用):

public class Requests {

    private Requests() {
    }

    public static RequestBuilder getAllRoundsByUserId(String userId) {

        return MockMvcRequestBuilders
            .get("/users/" + userId + "/rounds/")
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON);
    }

}

Spring Securty Config:

/**
 * Configures our application with Spring Security to restrict access to our API endpoints.
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${auth0.audience}")
    private String audience;

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuer;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        /*
        This is where we configure the security required for our endpoints and setup our app to serve as
        an OAuth2 Resource Server, using JWT validation.
        */

        http.cors().and().csrf().disable().sessionManagement().
            sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
            .mvcMatchers(HttpMethod.GET,"/users/**").authenticated()
            .mvcMatchers(HttpMethod.POST,"/users/**").authenticated()
            .mvcMatchers(HttpMethod.DELETE,"/users/**").authenticated()
            .mvcMatchers(HttpMethod.PUT,"/users/**").authenticated()
            .and()
            .oauth2ResourceServer().jwt();
    }

    @Bean
    JwtDecoder jwtDecoder() {
        /*
        By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
        indeed intended for our app. Adding our own validator is easy to do:
        */

        NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
            JwtDecoders.fromOidcIssuerLocation(issuer);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }


    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

抽象ユニットテストクラス:

@ExtendWith(SpringExtension.class)
@SpringBootTest(
    classes = PokerStatApplication.class,
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
public abstract class AbstractUnitTests {

   // mock objects etc


}
8
java12399900

私があなたのケースを正しく理解していれば、解決策の1つがあります。

ほとんどの場合、JwtDecoder Beanは、トークンが要求ヘッダーに存在する場合、トークンの解析と検証を実行します。

あなたの設定の例:

_    @Bean
    JwtDecoder jwtDecoder() {
        /*
        By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
        indeed intended for our app. Adding our own validator is easy to do:
        */

        NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
            JwtDecoders.fromOidcIssuerLocation(issuer);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }
_

したがって、テストでは、このBeanのスタブを追加する必要があります。また、SpringコンテキストでこのBeanを置き換えるには、テスト構成を使用する必要があります。

次のような場合があります。

_@TestConfiguration
public class TestSecurityConfig {

  static final String AUTH0_TOKEN = "token";
  static final String SUB = "sub";
  static final String AUTH0ID = "sms|12345678";

  public JwtDecoder jwtDecoder() {
    // This anonymous class needs for the possibility of using SpyBean in test methods
    // Lambda cannot be a spy with spring @SpyBean annotation
    return new JwtDecoder() {
      @Override
      public Jwt decode(String token) {
        return jwt();
      }
    };
  }

  public Jwt jwt() {

    // This is a place to add general and maybe custom claims which should be available after parsing token in the live system
    Map<String, Object> claims = Map.of(
        SUB, USER_AUTH0ID
    );

    //This is an object that represents contents of jwt token after parsing
    return new Jwt(
        AUTH0_TOKEN,
        Instant.now(),
        Instant.now().plusSeconds(30),
        Map.of("alg", "none"),
        claims
    );
  }

}
_

テストでこの構成を使用するには、このテストセキュリティ構成を取得するだけです。

@SpringBootTest(classes = TestSecurityConfig.class)

また、テストリクエストには、_Bearer .. something_のようなトークンを含む認証ヘッダーを含める必要があります。

設定に関する例は次のとおりです。

_    public static RequestBuilder getAllRoundsByUserId(String userId) {

        return MockMvcRequestBuilders
            .get("/users/" + userId + "/rounds/")
            .accept(MediaType.APPLICATION_JSON)
            .header(HttpHeaders.AUTHORIZATION, "Bearer token"))
            .contentType(MediaType.APPLICATION_JSON);
    }
_
2
Danil Kuznetsov

アプリケーションの一部をテストする場合は、必要な構成のみをロードする必要があります。あなたのケースでは、セキュリティ設定を含むコンテキスト全体をロードする@WebMvcTest(controllers = ...,...,)の代わりに@SpringBootTest(...)を使用してコントローラのみをロードする必要があります。交換するだけで期待どおりに機能するはずです

統合テストを作成してアプリが適切に保護されていることを確認する必要があることに注意してください。今回は@SpringBootTest(...)を使用してコンテキスト全体をロードし、auth0にテストユーザーを作成して、それを使用して実装をテストする

0
stacker

create application.properties in test/resources(メインをオーバーライドしますが、テストステージのみ)

指定によるセキュリティの無効化:

security.ignored=/**
security.basic.enable= false
spring.autoconfigure.exclude= org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
0
Oleg Maksymuk

@WithMockUser

    @Test
    @WithMockUser(username="ahmed",roles={"ADMIN"})
    public void shouldGetAllRoundsByUserId() throws Exception {
0
Mukhtiar Ahmed

Bearerトークンを取得して、HTTPヘッダーとして渡すことができます。以下は、参照用のテストメソッドのサンプルスニペットです。

@Test
public void existentUserCanGetTokenAndAuthentication() throws Exception {
   String username = "existentuser";
   String password = "password";

   String body = "{\"username\":\"" + username + "\", \"password\":\" 
              + password + "\"}";

   MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/token")
          .content(body))
          .andExpect(status().isOk()).andReturn();

   String response = result.getResponse().getContentAsString();
   response = response.replace("{\"access_token\": \"", "");
   String token = response.replace("\"}", "");

   mvc.perform(MockMvcRequestBuilders.get("/users/" + userId + "/rounds")
      .header("Authorization", "Bearer " + token))
      .andExpect(status().isOk());
}
0
Vignesh T I