Тест интеграции Spring Boot и пользовательский AuthenticationProvider

#java #spring-boot #spring-security #integration-testing

#java #spring-boot #spring-безопасность #интеграция-тестирование

Вопрос:

У меня есть приложение SpringBoot (версия 1.5.21.RELEASE), которое работает нормально, но я не могу пройти интеграционный тест.

У меня есть обычай AuthenticationProvider , в котором метод аутентификации возвращает это:

 new UsernamePasswordAuthenticationToken(attempUser.getUsername(), authentication.getCredentials(), grantedAuths);
  

Как вы можете видеть, Principal определяется как имя пользователя зарегистрированного пользователя.

Итак, в моем приложении всякий раз, когда я запускаю это:

 Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String loggedUsername = auth.getPrincipal().toString();
  

loggedUsername — это атрибут имени пользователя зарегистрированного пользователя.

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

При тестировании auth.getPrincipal().toString() возвращает не имя пользователя, а реализацию toString() org.springframework.security.core.userdetails.User

Это мой тест:

 @ActiveProfiles("test")
@SpringBootTest
@WebAppConfiguration
public class MyTest {
    @Test
    @WithMockUser(username = "cool-username", password = "pwd", authorities = AUTHORITY_ADMIN)
    public void createBound_forAnyUser_ifAdmin() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String loggedUsername = auth.getPrincipal().toString();

        assertThat(loggedUsername).isEqualsTo("cool-username");
    }
}
  

Этот тест (фиктивный тест для иллюстрации моей проблемы) завершается неудачей, потому что loggedUsername на самом деле:

org.springframework.security.core.userdetails.Пользователь @35c43c96: Имя пользователя: cool-username; Пароль: [ЗАЩИЩЕН]; Включено: true; Срок действия учетной записи истек: true; Срок действия учетных данных истек: true; Учетная запись заблокирована: true; Предоставленные полномочия: ПОЛЬЗОВАТЕЛЬ

Я знаю, что потенциальным решением было бы использовать auth.getName() вместо этого, но это будет означать повторную запись во все рабочее приложение.

Я перепробовал много возможных аннотаций и конфигураций, но все с тем же результатом. Я хотел бы, чтобы ТЕСТ имел ТОТ ЖЕ SecurityContext, что и приложение реального мира.

Обновить

Возможно, я делаю что-то неправильно, и решение, которое я ищу, заключается в рефакторинге кода.

Когда код выполняет:

 String loggedUsername = auth.getPrincipal().toString();
  

тогда это может сделать две вещи:

1- Найдите это имя пользователя в базе данных, чтобы получить информацию о профиле

2- Сопоставьте пользователя с каким-либо другим связанным объектом. Например, a Car , у которого есть User owner , тогда он будет соответствовать owner.getUsername().equals(loggedUsername) , чтобы предоставить доступ к этому ресурсу запрашивающему пользователю.

Должен ли я сделать это каким-либо другим способом? Спасибо

ОБХОДНОЙ ПУТЬ

Поскольку я не нашел решения своей проблемы (как было сказано ранее, возможно, проблема в том, как я сталкиваюсь с проблемой). Я решил издеваться над собой SecurityContext вместо того, чтобы издеваться над ним с @WithMockUser помощью, как предложил @gtiwari333. Итак, у меня есть этот метод:

     private void mockSecurityContext(String username, String authority){
        Collection authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority(authority));
        Authentication authentication = Mockito.mock(Authentication.class);
        when(authentication.getPrincipal()).thenReturn(username);
        when(authentication.getAuthorities()).thenReturn(authorities);
        SecurityContext securityContext = Mockito.mock(SecurityContext.class);
        when(securityContext.getAuthentication()).thenReturn(authentication);
        SecurityContextHolder.setContext(securityContext);
    }
  

В будущем я могу создать пользовательскую аннотацию, подобную @WithMockUser . Если вы чувствуете, что я делаю что-то не так, и я могу улучшить то, как я выполняю свой тест (или даже мою реализацию кода), я буду рад услышать от вас

Комментарии:

1. @WithMockUser возвращает UsernamePasswordAuthenticationToken значение в org.springframework.security.core.userdetails.User качестве основного. Кроме того, при этом не учитывается, какой поставщик / менеджер аутентификации вы используете в основном источнике.

2. Итак, зачем вам нужно переписывать все приложение?

3. Как следует из названия @WithMockUser , это просто макет. Его можно использовать для обхода проверок безопасности на контроллерах. Если ваш серверный код зависит от дополнительного атрибута пользователя (например, пользовательского класса UserDetails), вам может потребоваться вручную создать свой пользовательский объект аутентификации. например: void setCurrentUserWithToken(String username, String token, String... roles) { var ctx = SecurityContextHolder.createEmptyContext(); ctx.setAuthentication(auth); //custom auth obj SecurityContextHolder.setContext(ctx); }

4. @gtiwari333 есть несколько мест с вызовом auth.getPrincipal().toString(); . Для того, чтобы быть последовательным и сделать тест для них, мне нужно будет переписать их. Что касается вашего другого комментария, вы говорите мне, что я могу перезаписать SecurityContextHolder в моем тесте?

5. пожалуйста, ознакомьтесь с моим обновлением в исходном вопросе