Используйте @WithMockUser (с @SpringBootTest) внутри приложения сервера ресурсов oAuth2

Среда. У меня есть приложение с микросервисной архитектурой на основе весенней загрузки, состоящее из нескольких инфраструктурных сервисов и ресурсных сервисов (содержащих бизнес-логику). Авторизацией и аутентификацией занимается служба oAuth2, управляющая объектами пользователей и создающая токены JWT для клиентов.

Чтобы полностью протестировать одно микросервисное приложение, я попытался создать тесты с помощью testNG, spring.boot.test, org.springframework.security.test ...

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, properties = {"spring.cloud.discovery.enabled=false", "spring.cloud.config.enabled=false", "spring.profiles.active=test"})
@AutoConfigureMockMvc
@Test
public class ArtistControllerTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private MockMvc mvc;

  @BeforeClass
  @Transactional
  public void setUp() {
    // nothing to do
  }

  @AfterClass
  @Transactional
  public void tearDown() {
    // nothing to do here
  }

  @Test
  @WithMockUser(authorities = {"READ", "WRITE"})
  public void getAllTest() throws Exception {

    // EXPECT HTTP STATUS 200
    // BUT GET 401
    this.mvc.perform(get("/")
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
  }
}

где конфигурация безопасности (сервера ресурсов) следующая

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

  // get the configured token store
  @Autowired
  TokenStore tokenStore;

  // get the configured token converter
  @Autowired
  JwtAccessTokenConverter tokenConverter;

  /**
   * !!! configuration of springs http security !!!
   */
  @Override
  public void configure(HttpSecurity http) throws Exception {
      http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/**").authenticated();
  }

  /**
   * configuration of springs resource server security
   */
  @Override
  public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    // set the configured tokenStore to this resourceServer
    resources.resourceId("artist").tokenStore(tokenStore);
  }

}

и следующая проверка безопасности на основе метода, аннотированная внутри класса контроллера

@PreAuthorize("hasAuthority('READ')")
@RequestMapping(value = "/", method = RequestMethod.GET)
public List<Foo> getAll(Principal user) {
    List<Foo> foos = fooRepository.findAll();
    return foos;
}

Я думал, что это сработает, но при запуске теста я получаю только ошибку утверждения

java.lang.AssertionError: Status 
Expected :200
Actual   :401


Вопрос. Есть ли что-то совершенно очевидное, что я делаю неправильно? Или @WithMockUser не будет работать с @SpringBootTest и @AutoConfigureMockMvc в среде oAuth2? Если это так... каков наилучший подход для тестирования конфигураций безопасности на основе маршрутов и методов в рамках такого (интеграционного) теста, как этот?


Приложение: я также пробовал разные подходы, например, что-то вроде следующего... но это привело к тому же результату :(

this.mvc.perform(get("/")
        .with(user("admin").roles("READ","WRITE").authorities(() -> "READ", () -> "WRITE"))
        .accept(MediaType.APPLICATION_JSON))

см.:
весеннее тестирование безопасности
тестирование весенней загрузки 1.4


person David    schedule 24.01.2017    source источник


Ответы (3)


@WithMockUser создает аутентификацию в SecurityContext. То же самое относится и к with(user("username")).

По умолчанию OAuth2AuthenticationProcessingFilter не использует SecurityContext, а всегда строит аутентификацию на основе токена ("без сохранения состояния").

Вы можете легко изменить это поведение, установив для флага без сохранения состояния в конфигурации безопасности сервера ресурсов значение false:

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration implements ResourceServerConfigurer {

    @Override
    public void configure(ResourceServerSecurityConfigurer security) throws Exception {
        security.stateless(false);
    }

    @Override
    public void configure(HttpSecurity http) {}

}

Другой вариант — расширить ResourceServerConfigurerAdapter, но проблема в том, что он поставляется с конфигурацией, которая принудительно проверяет подлинность всех запросов. Реализация интерфейса оставляет вашу основную конфигурацию безопасности неизменной, за исключением безгражданства.

Конечно, установите флаг false только в ваших тестовых контекстах.

person Jochen Christ    schedule 12.06.2017
comment
Добавление этого класса в мой (проект maven) src/test/java работает для меня с Spring Boot 1.5.4 и 1.5.9. Теперь я могу использовать @WithMockUser, как и ожидал. - person DaShaun; 14.01.2018
comment
Добавление этого класса в мой проект позволяет мне обойти безопасность, однако это не похоже на то, что пользователь имеет значение. Он всегда проходит проверку безопасности, независимо от объема, который я даю своим тестовым пользователям. Это намерение? - person Rig; 02.03.2018
comment
Есть ли ссылки на руководства пользователя/документацию? - person Witold Kaczurba; 20.04.2018
comment
как я могу сделать это только для тестового контекста? - person aycanadal; 18.01.2019
comment
Я взял флаг из application.properties и включил его только для тестирования: @Value(${oauth.stateless:true}) private boolean oauthstateless; - person Fabian Mendez; 04.02.2019

У меня была такая же проблема, и единственный способ, который я нашел, - это создать токен и использовать его в выполнении mockMvc.

mockMvc.perform(get("/resource")
                    .with(oAuthHelper.bearerToken("test"))

И OAuthHelper:

@Component
@EnableAuthorizationServer
public class OAuthHelper extends AuthorizationServerConfigurerAdapter {

    @Autowired
    AuthorizationServerTokenServices tokenservice;

    @Autowired
    ClientDetailsService clientDetailsService;

    public RequestPostProcessor bearerToken(final String clientid) {
        return mockRequest -> {
            OAuth2AccessToken token = createAccessToken(clientid);
            mockRequest.addHeader("Authorization", "Bearer " + token.getValue());
            return mockRequest;
        };
    }

    OAuth2AccessToken createAccessToken(final String clientId) {
        ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        Collection<GrantedAuthority> authorities = client.getAuthorities();
        Set<String> resourceIds = client.getResourceIds();
        Set<String> scopes = client.getScope();

        Map<String, String> requestParameters = Collections.emptyMap();
        boolean approved = true;
        String redirectUrl = null;
        Set<String> responseTypes = Collections.emptySet();
        Map<String, Serializable> extensionProperties = Collections.emptyMap();

        OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities,
                approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties);

        User userPrincipal = new User("user", "", true, true, true, true, authorities);
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities);
        OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);

        return tokenservice.createAccessToken(auth);
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("test")
                .authorities("READ");
    }

}
person Laureano Gabriel Clausi    schedule 03.02.2017
comment
проблема в том, что моя служба безопасности, которая создает токены в производстве, является микрослужбой, отличной от той, которая использует токен для проверки авторизации и аутентификации... и, поскольку это токен JWT, он использует файл закрытого ключа для создания токена и я не хочу, чтобы частный токен распространялся на все мои микросервисы ресурсов. или я что-то не так понимаю? - person David; 04.02.2017
comment
Да, у меня такой же дизайн, поэтому для тестирования микросервиса, который выставляет ресурсы, я сделал это в тестах - person Laureano Gabriel Clausi; 06.02.2017
comment
У меня также есть закрытый ключ в службе аутентификации и ключ в другом микросервисе. - person Laureano Gabriel Clausi; 06.02.2017
comment
Это сработало для меня, спасибо... Мне пришлось удалить EnableAuthorizationServer, потому что я хотел только сгенерировать токен вручную и без загрузки какой-либо другой вещи. - person xbmono; 11.01.2018

Поскольку я специально пытался написать тесты для нашей ResourceServerConfiguration, я обошел эту проблему, создав для нее тестовую оболочку, которая установила для security.stateless значение false:

@Configuration
@EnableResourceServer
public class ResourceServerTestConfiguration extends ResourceServerConfigurerAdapter {
  private ResourceServerConfiguration configuration;

  public ResourceServerTestConfiguration(ResourceServerConfiguration configuration) {
    this.configuration = configuration;
  }

  @Override
  public void configure(ResourceServerSecurityConfigurer security) throws Exception {
    configuration.configure(security);
    security.stateless(false);
  }

  @Override
  public void configure(HttpSecurity http) throws Exception {
    configuration.configure(http);
  }
}
person piepera    schedule 01.09.2019