Minha empresa está avaliando o Spring MVC para determinar se devemos usá-lo em um de nossos próximos projetos. Até agora, adoro o que vi e agora estou analisando o módulo Spring Security para determinar se é algo que podemos / devemos usar.
Nossos requisitos de segurança são bem básicos; um usuário só precisa fornecer um nome de usuário e senha para acessar determinadas partes do site (como obter informações sobre sua conta); e há algumas páginas no site (FAQs, suporte etc.) às quais um usuário anônimo deve ter acesso.
No protótipo que estou criando, tenho armazenado um objeto "LoginCredentials" (que apenas contém nome de usuário e senha) na Sessão para um usuário autenticado; alguns dos controladores verificam se este objeto está em sessão para obter uma referência ao nome de usuário conectado, por exemplo. Estou procurando substituir essa lógica caseira pela Spring Security, o que traria o benefício de remover qualquer tipo de "como rastreamos os usuários conectados?" e "como autenticamos usuários?" do meu controlador / código comercial.
Parece que o Spring Security fornece um objeto de "contexto" (por thread) para poder acessar o nome de usuário / informações principais de qualquer lugar do seu aplicativo ...
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
... que parece muito pouco primaveril, pois esse objeto é um singleton (global), de certa forma.
Minha pergunta é a seguinte: se esta é a maneira padrão de acessar informações sobre o usuário autenticado no Spring Security, qual é a maneira aceita de injetar um objeto de autenticação no SecurityContext, para que fique disponível para meus testes de unidade quando os testes de unidade exigirem um usuário autenticado?
Preciso conectar isso no método de inicialização de cada caso de teste?
protected void setUp() throws Exception {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
...
}
Isso parece muito detalhado. Existe uma maneira mais fácil?
O SecurityContextHolder
objeto em si parece muito pouco parecido com a Primavera ...
Faça da maneira usual e insira-o usando
SecurityContextHolder.setContext()
na sua classe de teste, por exemplo:Controlador:
Teste:
fonte
Authentication a
ser adicionado no controlador? Como posso entender em cada invocação de método? Está tudo bem no "caminho da primavera" apenas para adicioná-lo, em vez de injetar?@BeforeEach
(JUnit5) ou@Before
(JUnit 4). Bom e simples.Sem responder à pergunta sobre como criar e injetar objetos de autenticação, o Spring Security 4.0 fornece algumas alternativas bem-vindas quando se trata de teste. A
@WithMockUser
anotação permite que o desenvolvedor especifique um usuário simulado (com autoridades, nome de usuário, senha e funções opcionais) de maneira organizada:Também existe a opção
@WithUserDetails
de emular umUserDetails
retorno deUserDetailsService
, por exemplo,Mais detalhes podem ser encontrados nos capítulos @WithMockUser e @WithUserDetails nos documentos de referência do Spring Security (dos quais os exemplos acima foram copiados)
fonte
Você tem razão em se preocupar - as chamadas de método estático são particularmente problemáticas para o teste de unidade, pois você não pode zombar facilmente de suas dependências. O que vou mostrar a você é como deixar o contêiner Spring IoC fazer o trabalho sujo para você, deixando um código limpo e testável. SecurityContextHolder é uma classe de estrutura e, embora possa estar ok para o seu código de segurança de baixo nível estar vinculado a ele, você provavelmente deseja expor uma interface mais limpa aos seus componentes de interface do usuário (ou seja, controladores).
cliff.meyers mencionou uma maneira de contornar isso - crie seu próprio tipo "principal" e injete uma instância nos consumidores. A tag Spring < aop: scoped-proxy /> introduzida na 2.x combinada com uma definição de bean de escopo de solicitação e o suporte ao método de fábrica podem ser o ticket para o código mais legível.
Pode funcionar da seguinte maneira:
Nada complicado até agora, certo? Na verdade, você provavelmente já teve que fazer a maior parte disso. Em seguida, no seu contexto de bean, defina um bean com escopo de solicitação para armazenar o principal:
Graças à mágica da tag aop: scoped-proxy, o método estático getUserDetails será chamado toda vez que uma nova solicitação HTTP for recebida e qualquer referência à propriedade currentUser será resolvida corretamente. Agora o teste de unidade se torna trivial:
Espero que isto ajude!
fonte
Pessoalmente, eu usaria o Powermock junto com o Mockito ou o Easymock para zombar do SecurityContextHolder.getSecurityContext () estático no seu teste de unidade / integração, por exemplo
É certo que há um pouco de código da placa da caldeira aqui, ou seja, simula um objeto de autenticação, simula um SecurityContext para retornar a autenticação e, finalmente, simula o SecurityContextHolder para obter o SecurityContext, no entanto, é muito flexível e permite que você teste de unidade para cenários como objetos de autenticação nulos etc. sem ter que alterar seu código (sem teste)
fonte
Usar uma estática nesse caso é a melhor maneira de escrever código seguro.
Sim, as estáticas geralmente são ruins - geralmente, mas neste caso, a estática é o que você deseja. Como o contexto de segurança associa um Principal ao encadeamento atualmente em execução, o código mais seguro acessa a estática do encadeamento o mais diretamente possível. Ocultar o acesso atrás de uma classe de wrapper que é injetada fornece ao invasor mais pontos para atacar. Eles não precisariam acessar o código (que dificilmente mudariam se o jar fosse assinado), eles apenas precisariam de uma maneira de substituir a configuração, o que pode ser feito em tempo de execução ou colocar algum XML no caminho de classe. Mesmo usando a injeção de anotação seria substituível pelo XML externo. Esse XML pode injetar no sistema em execução um principal não autorizado.
fonte
Fiz a mesma pergunta aqui e acabei de publicar uma resposta que encontrei recentemente. A resposta curta é: injete ae
SecurityContext
refira-seSecurityContextHolder
apenas à sua configuração do Spring para obter oSecurityContext
fonte
Geral
Enquanto isso (desde a versão 3.2, no ano de 2013, graças à SEC-2298 ), a autenticação pode ser injetada nos métodos MVC usando a anotação @AuthenticationPrincipal :
Testes
No seu teste de unidade, você pode obviamente chamar esse método diretamente. Nos testes de integração,
org.springframework.test.web.servlet.MockMvc
você pode usarorg.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user()
para injetar o usuário assim:No entanto, isso apenas preencherá diretamente o SecurityContext. Se você deseja garantir que o usuário seja carregado de uma sessão em seu teste, você pode usar o seguinte:
fonte
Eu daria uma olhada nas classes de teste abstratas do Spring e nos objetos simulados mencionados aqui . Eles fornecem uma maneira poderosa de conectar automaticamente seus objetos gerenciados Spring, facilitando o teste de unidade e integração.
fonte
A autenticação é uma propriedade de um encadeamento no ambiente do servidor da mesma maneira que é uma propriedade de um processo no SO. Ter uma instância de bean para acessar informações de autenticação seria configuração inconveniente e sobrecarga da fiação sem nenhum benefício.
Em relação à autenticação de teste, existem várias maneiras de facilitar sua vida. O meu favorito é criar uma anotação personalizada
@Authenticated
e um ouvinte de execução de teste, que a gerencia. ProcureDirtiesContextTestExecutionListener
inspiração.fonte
Depois de muito trabalho, consegui reproduzir o comportamento desejado. Eu havia emulado o login pelo MockMvc. É muito pesado para a maioria dos testes de unidade, mas útil para testes de integração.
É claro que estou disposto a ver os novos recursos do Spring Security 4.0 que facilitarão nossos testes.
fonte