Eu estava tentando descobrir como testar a unidade se as URLs dos meus controladores estão devidamente protegidas. Para o caso de alguém mudar as coisas e remover acidentalmente as configurações de segurança.
Meu método de controle é assim:
@RequestMapping("/api/v1/resource/test")
@Secured("ROLE_USER")
public @ResonseBody String test() {
return "test";
}
Eu configurei um WebTestEnvironment assim:
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();
}
}
Em meu teste real, tentei fazer algo assim:
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());
}
}
Eu peguei isso aqui:
- http://java.dzone.com/articles/spring-test-mvc-junit-testing aqui:
- http://techdive.in/solutions/how-mock-securitycontextholder-perfrom-junit-tests-spring-controller ou aqui:
- Como o JUnit testa uma anotação @PreAuthorize e sua mola EL especificada por um controlador MVC de mola?
No entanto, se olharmos de perto, isso só ajuda quando não enviamos solicitações reais para URLs, mas apenas quando testamos serviços em um nível de função. No meu caso, foi lançada uma exceção de "acesso negado":
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]
...
As duas mensagens de log a seguir são dignas de nota basicamente dizendo que nenhum usuário foi autenticado, indicando que a configuração de Principal
não funcionou ou que foi substituída.
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
Respostas:
Procurando uma resposta, não consegui encontrar nenhuma que fosse fácil e flexível ao mesmo tempo, então encontrei a Referência de Segurança Spring e percebi que existem soluções quase perfeitas. Soluções AOP muitas vezes são os maiores queridos para testes, e Spring fornece-lo com
@WithMockUser
,@WithUserDetails
e@WithSecurityContext
, neste artefato:Na maioria dos casos,
@WithUserDetails
reúne a flexibilidade e a potência de que preciso.Como funciona @WithUserDetails?
Basicamente, você só precisa criar um personalizado
UserDetailsService
com todos os perfis de usuários possíveis que deseja testar. Por exemploAgora que temos nossos usuários prontos, imagine que queremos testar o controle de acesso a esta função do controlador:
Aqui temos uma função mapeada para a rota / foo / salute e estamos testando uma segurança baseada em função com a
@Secured
anotação, embora você possa testar@PreAuthorize
e@PostAuthorize
também. Vamos criar dois testes, um para verificar se um usuário válido pode ver essa resposta de saudação e outro para verificar se é realmente proibido.Como você pode ver, importamos
SpringSecurityWebAuxTestConfig
para fornecer nossos usuários para teste. Cada um é usado em seu caso de teste correspondente apenas usando uma anotação direta, reduzindo o código e a complexidade.Use melhor @WithMockUser para uma segurança baseada em funções mais simples
Como você pode ver,
@WithUserDetails
tem toda a flexibilidade de que você precisa para a maioria de seus aplicativos. Ele permite que você use usuários personalizados com qualquer GrantedAuthority, como funções ou permissões. Mas se você está apenas trabalhando com funções, o teste pode ser ainda mais fácil e você pode evitar a construção de um personalizadoUserDetailsService
. Nesses casos, especifique uma combinação simples de usuário, senha e funções com @WithMockUser .A anotação define valores padrão para um usuário muito básico. Como em nosso caso a rota que estamos testando requer apenas que o usuário autenticado seja um gerente, podemos parar de usar
SpringSecurityWebAuxTestConfig
e fazer isso.Observe que agora, em vez do usuário [email protected] , estamos obtendo o padrão fornecido por
@WithMockUser
: usuário ; no entanto, não importará porque o que realmente importa é o seu papel:ROLE_MANAGER
.Conclusões
Como você pode ver com anotações como
@WithUserDetails
e@WithMockUser
podemos alternar entre diferentes cenários de usuários autenticados sem construir classes alienadas de nossa arquitetura apenas para fazer testes simples. Também é recomendado que você veja como @WithSecurityContext funciona para ter ainda mais flexibilidade.fonte
tom
, enquanto a segunda é enviada porjerry
?BasicUser
eManager User
fornecido na resposta. O conceito principal é que, em vez de nos preocuparmos com os usuários, realmente nos preocupamos com suas funções, mas cada um desses testes, localizados no mesmo teste de unidade, na verdade representam consultas diferentes. feito por usuários diferentes (com funções diferentes) para o mesmo terminal.Desde o Spring 4.0+, a melhor solução é anotar o método de teste com @WithMockUser
Lembre-se de adicionar a seguinte dependência ao seu projeto
fonte
Acontece que o
SecurityContextPersistenceFilter
, que faz parte da cadeia de filtros do Spring Security, sempre redefine meuSecurityContext
, que eu defini chamandoSecurityContextHolder.getContext().setAuthentication(principal)
(ou usando o.principal(principal)
método). Este filtro define oSecurityContext
emSecurityContextHolder
com umSecurityContext
de umSecurityContextRepository
SOBRESCREVER aquele que eu defini anteriormente. O repositório é umHttpSessionSecurityContextRepository
por padrão. OHttpSessionSecurityContextRepository
inspeciona o dadoHttpRequest
e tenta acessar o correspondenteHttpSession
. Se existir, ele tentará ler oSecurityContext
doHttpSession
. Se isso falhar, o repositório gerará um vazioSecurityContext
.Assim, minha solução é repassar um
HttpSession
junto com a solicitação, que contémSecurityContext
:fonte
getPrincipal()
que, na minha opinião, é um pouco enganador. idealmente, deveria ter sido nomeadogetAuthentication()
. da mesma forma, em seusignedIn()
teste, a variável local deve ser nomeadaauth
ou emauthentication
vez deprincipal
Adicionar em pom.xml:
e uso
org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
para solicitação de autorização. Veja o exemplo de uso em https://github.com/rwinch/spring-security-test-blog ( https://jira.spring.io/browse/SEC-2592 ).Atualizar:
4.0.0.RC2 funciona para spring-security 3.x. Para spring-security 4 spring-security-test torna-se parte de spring-security ( http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test , a versão é a mesma )
A configuração foi alterada: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc
Amostra para autenticação básica: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#testing-http-basic-authentication .
fonte
Aqui está um exemplo para aqueles que desejam testar a configuração de segurança do Spring MockMvc usando autenticação básica Base64.
Dependência Maven
fonte
Resposta curta:
Após realizar o
formLogin
teste de segurança da primavera, cada uma das suas solicitações será automaticamente chamada de usuário conectado.Resposta longa:
Verifique esta solução (a resposta é para a primavera 4): Como fazer o login de um usuário com o novo teste mvc da primavera 3.2
fonte
Opções para evitar o uso de SecurityContextHolder em testes:
SecurityContextHolder
usando alguma biblioteca simulada - EasyMock por exemploSecurityContextHolder.get...
em seu código em algum serviço - por exemplo,SecurityServiceImpl
com o métodogetCurrentPrincipal
que implementa aSecurityService
interface e, em seguida, em seus testes, você pode simplesmente criar uma implementação simulada dessa interface que retorna o principal desejado sem acesso aSecurityContextHolder
.fonte