4

認証と承認にSpring Social Securityを使用するアプリケーションがあります。残念ながら、Spring Security のモックに問題があります。まったく機能しないようです。

返す必要があるエンティティの識別子が利用できない場合に 404 Not Found を返す REST コントローラーがあります。ユーザーがログインしていない場合、どのページもアプリのソーシャル ログイン ページにリダイレクトされます。

ここで、@WithUserDetails注釈が私に最も適していることを読みました。

だから私のテスト方法はこのようになります

@Test
@SqlGroup({
    @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD, statements = "INSERT INTO UserAccount(id, creationtime, modificationtime, version, email, firstname, lastname, role, signinprovider) VALUES (1, '2008-08-08 20:08:08', '2008-08-08 20:08:08', 1, 'user', 'John', 'Doe', 'ROLE_USER', 'FACEBOOK')"), })
@Rollback
@WithUserDetails
public void ifNoTeamsInTheDatabaseThenTheRestControllerShouldReturnNotFoundHttpStatus() {
    ResponseEntity<String> response = restTemplate.getForEntity("/getTeamHistory/{team}", String.class, "Team");
    Assert.assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}

しかし、これはまったく機能していないようです。取得したステータスが 200 OK であるため、匿名ユーザーでテスト メソッドが実行されているようです。

私のテストクラスはこのように注釈が付けられています

@RunWith(SpringRunner.class)
@ActiveProfiles("dev")
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Transactional
public class TeamRestControllerTest {
    //...
}

Spring Social によって提供される Spring Security をモックすることで、このような問題を経験した人はいますか?

4

2 に答える 2

3

現時点ではテストできませんが、可能な解決策は次のとおりです。

@WithUserDetails の実装を見る:

@WithSecurityContext(factory = WithUserDetailsSecurityContextFactory.class)
public @interface WithUserDetails {
    ...
}

final class WithUserDetailsSecurityContextFactory implements
        WithSecurityContextFactory<WithUserDetails> {

    private BeanFactory beans;

    @Autowired
    public WithUserDetailsSecurityContextFactory(BeanFactory beans) {
        this.beans = beans;
    }

    public SecurityContext createSecurityContext(WithUserDetails withUser) {
        String beanName = withUser.userDetailsServiceBeanName();
        UserDetailsService userDetailsService = StringUtils.hasLength(beanName)
                ? this.beans.getBean(beanName, UserDetailsService.class)
                : this.beans.getBean(UserDetailsService.class);
        String username = withUser.value();
        Assert.hasLength(username, "value() must be non empty String");
        UserDetails principal = userDetailsService.loadUserByUsername(username);
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                principal, principal.getPassword(), principal.getAuthorities());
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        return context;
    }
}

同じパターンに従って、選択したセキュリティ コンテキストを作成できます。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(factory = WithoutUserFactory.class)
public @interface WithoutUser {
}

public class WithoutUserFactory implements WithSecurityContextFactory<WithoutUser> {
    public SecurityContext createSecurityContext(WithoutUser withoutUser) {
        return SecurityContextHolder.createEmptyContext();
    }
}

その他の利用可能な注釈: WithAnonymousUserWithMockUserWithSecurityContext(およびWithUserDetails)

于 2016-12-13T16:20:08.407 に答える
0

私の回避策を追加すると、おそらく他の人に役立つ可能性があります。

私は同じ問題に遭遇したと思います:

  • A @Testcontainers(PostgreSQL DB エミュレーション用) +@SpringBootTestテスト。
  • with mockingでSecurityContextvia アノテーションをモックしました。@WithSecurityContextfactory
  • RevisionListenerKeycloak によって通常作成されたものから userName と userId を取得する Envers のこのモックが必要SecurityContextです。
  • テストでSpring Beanを呼び出すと、モックが正常に機能します。
  • しかし、 経由で API を呼び出すとTestRestTemplateSecurityContextはモックされずnull、すべてのフィールド (プリンシパルなど) に対して を返します。

元のクラスは次のようになります。

@SpringBootTest(
    classes = SpringBootInitializer.class,
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
    properties = {"keycloak.enabled=false"}
)
@ContextConfiguration(
    classes = PersistenceConfiguration.class,
    initializers = MyTest.Initializer.class
)
// !!! the SecurityContext mocking will NOT work when calling the controller via REST
@MockKeycloakUser() // do not fail on getting Keycloak data in UserDataRevisionListener
@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class }) // turn off Spring Security to avoid 401 and 302 responses
@Testcontainers // required to fill @Container fields with containers
@Log4j2
@ActiveProfiles("integration-test")
class MyTest {

    @Autowired
    private TestRestTemplate restTemplate;

    // ...

    // call via restTemplate looks like this
    private List<MyDTO> executeSearchQuery(String query) {
        String searchUrl = getSearchUrl(port, query, filter);
        MyDTO[] results = this.restTemplate.getForObject(searchUrl, MyDTO[].class);
        return List.of(results);
    }

    // ...
}

私がSecurityContext作品を作るために使用したものは次のとおりです。

  • MockMvcフィールドをテスト クラスに追加します。
  • @AutoConfigureMockMvcテストクラスを追加します。
  • !!! MockMvcの代わりにAPI を実行します。TestRestTemplate

次のようになります。

// all other annotations on the test class stay the same

@AutoConfigureMockMvc // make MockMvc work
// ...
class MyTest {

    @Autowired
    private MockMvc mockMvc; // trick to make the mock SecurityContext work, which does not work when calling via TestRestTemplate

    // Execute the API via mockMvc looks like this:
    private String getApiResponse(MyRequest request, int expectedHttpStatus) {
        final String url = getRequestUrl();

        final String requestBody = JacksonUtils.serializeToString(request);

        try {
            final MockHttpServletRequestBuilder builder = MockMvcRequestBuilders
                .post(url)
                .contentType(MediaType.APPLICATION_JSON)
                .content(requestBody)
            ;

            // use MockMvc instead of TestRestTemplate to successfully use the mock user emulation
            return mockMvc
                .perform(builder)
                .andExpect(status().is(expectedHttpStatus))
                .andReturn()
                .getResponse()
                .getContentAsString(StandardCharsets.UTF_8);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // ...
}

于 2022-02-02T14:28:00.807 に答える