Я пытался выяснить, как выполнить модульное тестирование, правильно ли защищены мои URL-адреса моих контроллеров. На всякий случай, если кто-то изменит ситуацию и случайно уберет настройки безопасности.
Мой метод контроллера выглядит так:
@RequestMapping("/api/v1/resource/test")
@Secured("ROLE_USER")
public @ResonseBody String test() {
return "test";
}
Я настроил WebTestEnvironment так:
import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/security.xml",
"file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
"file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {
@Resource
private FilterChainProxy springSecurityFilterChain;
@Autowired
@Qualifier("databaseUserService")
protected UserDetailsService userDetailsService;
@Autowired
private WebApplicationContext wac;
@Autowired
protected DataSource dataSource;
protected MockMvc mockMvc;
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected UsernamePasswordAuthenticationToken getPrincipal(String username) {
UserDetails user = this.userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
user,
user.getPassword(),
user.getAuthorities());
return authentication;
}
@Before
public void setupMockMvc() throws NamingException {
// setup mock MVC
this.mockMvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain)
.build();
}
}
В моем собственном тесте я пытался сделать что-то вроде этого:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import eu.ubicon.webapp.test.WebappTestEnvironment;
public class CopyOfClaimTest extends WebappTestEnvironment {
@Test
public void signedIn() throws Exception {
UsernamePasswordAuthenticationToken principal =
this.getPrincipal("test1");
SecurityContextHolder.getContext().setAuthentication(principal);
super.mockMvc
.perform(
get("/api/v1/resource/test")
// .principal(principal)
.session(session))
.andExpect(status().isOk());
}
}
Я взял вот это:
- http://java.dzone.com/articles/spring-test-mvc-junit-testing здесь:
- http://techdive.in/solutions/how-mock-securitycontextholder-perfrom-junit-tests-spring-controller или здесь:
- Как JUnit тестирует аннотацию @PreAuthorize и ее Spring EL, заданную Spring контроллером MVC?
Тем не менее, если присмотреться, это помогает только тогда, когда фактические запросы к URL-адресам не отправляются, а только при тестировании сервисов на функциональном уровне. В моем случае возникло исключение «доступ запрещен»:
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
...
Следующие два сообщения журнала заслуживают внимания, в основном они говорят о том, что ни один пользователь не прошел аутентификацию, что указывает на то, что настройка Principal
не сработала или что она была перезаписана.
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
Ответы:
В поисках ответа я не мог найти ни одного простого и гибкого одновременно, затем я нашел Spring Security Reference и понял, что есть почти идеальные решения. Решения АОП часто являются лучшими для тестирования, и Spring предоставляет их
@WithMockUser
,@WithUserDetails
и@WithSecurityContext
в этом артефакте:В большинстве случаев он
@WithUserDetails
обладает необходимой мне гибкостью и мощностью.Как работает @WithUserDetails?
По сути, вам просто нужно создать кастом
UserDetailsService
со всеми возможными профилями пользователей, которые вы хотите протестировать. НапримерТеперь у нас есть пользователи, поэтому представьте, что мы хотим протестировать контроль доступа к этой функции контроллера:
Здесь у нас есть функция получения сопоставления с маршрутом / foo / salute, и мы тестируем безопасность на основе ролей с
@Secured
аннотацией, хотя вы также можете протестировать@PreAuthorize
и@PostAuthorize
. Давайте создадим два теста: один для проверки, видит ли действительный пользователь этот ответ приветствия, а другой - для проверки, действительно ли он запрещен.Как видите, мы импортировали,
SpringSecurityWebAuxTestConfig
чтобы предоставить наших пользователей для тестирования. Каждый из них использовался в соответствующем тестовом примере, просто используя простую аннотацию, уменьшая код и сложность.Лучше использовать @WithMockUser для более простой безопасности на основе ролей
Как видите,
@WithUserDetails
обладает всей гибкостью, необходимой для большинства ваших приложений. Он позволяет использовать настраиваемых пользователей с любым GrantedAuthority, например с ролями или разрешениями. Но если вы просто работаете с ролями, тестирование может быть еще проще, и вы можете избежать создания пользовательскогоUserDetailsService
. В таких случаях укажите простую комбинацию пользователя, пароля и ролей с помощью @WithMockUser .В аннотации определены значения по умолчанию для очень простого пользователя. Поскольку в нашем случае тестируемый маршрут просто требует, чтобы аутентифицированный пользователь был менеджером, мы можем прекратить использование
SpringSecurityWebAuxTestConfig
и сделать это.Обратите внимание, что теперь вместо user manager@company.com мы получаем значение по умолчанию
@WithMockUser
: user ; но это не будет иметь значения , потому что мы действительно заботимся о его роли:ROLE_MANAGER
.Выводы
Как вы видите, с помощью таких аннотаций, как
@WithUserDetails
и,@WithMockUser
мы можем переключаться между различными сценариями аутентифицированных пользователей без создания классов, отчужденных от нашей архитектуры, только для выполнения простых тестов. Также рекомендуется посмотреть, как работает @WithSecurityContext, для еще большей гибкости.источник
tom
, а второй -jerry
?BasicUser
иManager User
сценарий , указанный в ответе. Ключевая концепция заключается в том, что вместо того, чтобы заботиться о пользователях, мы на самом деле заботимся об их ролях, но каждый из этих тестов, расположенных в одном модульном тесте, фактически представляет разные запросы. выполняется разными пользователями (с разными ролями) для одной и той же конечной точки.Начиная с Spring 4.0+, лучшее решение - аннотировать метод тестирования с помощью @WithMockUser.
Не забудьте добавить в свой проект следующую зависимость
источник
Оказалось, что
SecurityContextPersistenceFilter
, который является частью цепочки фильтров Spring Security, всегда сбрасывает мойSecurityContext
, который я установил для вызоваSecurityContextHolder.getContext().setAuthentication(principal)
(или с помощью.principal(principal)
метода). Этот фильтр устанавливаетSecurityContext
вSecurityContextHolder
сSecurityContext
от АSecurityContextRepository
перезаписывающего один я установить ранее. РепозиторийHttpSessionSecurityContextRepository
по умолчанию. ВHttpSessionSecurityContextRepository
инспектирует данныеHttpRequest
и пытаюсь получить доступ к соответствующемуHttpSession
. Если он существует, он попытается прочитатьSecurityContext
из файлаHttpSession
. Если это не удается, репозиторий генерирует пустойSecurityContext
.Таким образом, мое решение - передать
HttpSession
вместе с запросом, который содержитSecurityContext
:источник
getPrincipal()
которая, на мой взгляд, немного вводит в заблуждение. в идеале он должен был быть названgetAuthentication()
. аналогично, в вашемsignedIn()
тесте локальная переменная должна быть названаauth
илиauthentication
вместоprincipal
Добавьте в pom.xml:
и использовать
org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
для запроса авторизации. См. Пример использования на https://github.com/rwinch/spring-security-test-blog ( https://jira.spring.io/browse/SEC-2592 ).Обновить:
4.0.0.RC2 работает для spring -security 3.x. Для spring -security 4 spring-security-test становится частью spring-security ( http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test , версия такая же ).
Настройка изменена: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc
Пример для базовой аутентификации: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#testing-http-basic-authentication .
источник
Вот пример для тех, кто хочет протестировать Spring MockMvc Security Config с использованием базовой аутентификации Base64.
Зависимость от Maven
источник
Короткий ответ:
После выполнения
formLogin
весеннего теста безопасности каждый из ваших запросов будет автоматически вызываться как зарегистрированный пользователь.Длинный ответ:
Проверьте это решение (ответ для Spring 4): Как войти в систему с помощью Spring 3.2 New mvc testing
источник
Варианты, позволяющие избежать использования SecurityContextHolder в тестах:
SecurityContextHolder
с использованием какой-то библиотеки макетов - например, EasyMockSecurityContextHolder.get...
в свой код в какой-либо сервис - например, вSecurityServiceImpl
методе,getCurrentPrincipal
который реализуетSecurityService
интерфейс, а затем в своих тестах вы можете просто создать фиктивную реализацию этого интерфейса, которая возвращает желаемый принципал без доступа кSecurityContextHolder
.источник