java - test - Autenticación de arranque de primavera para pruebas de integración
test en java (7)
Por qué se aplica la configuración de seguridad
De acuerdo con la documentation Spring Boot, cuando realiza anotaciones en su clase con @SpringBootTest
, y no tiene una alternativa de configuración específica, entonces Spring con la búsqueda de una clase @SpringBootApplication
para que sirva como su configuración principal. Spring inicia la búsqueda en el paquete de su clase de prueba, luego busca el paquete de búsqueda. Presumiblemente, está encontrando su configuración principal, y todo lo que esto trae consigo, incluida su configuración de seguridad no deseada.
Solución
La solución más sencilla, verificada en Spring Boot 2.0.3, es cambiar @EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
por @SpringBootApplication(exclude = SecurityAutoConfiguration.class)
. Cuando realice este cambio, Spring registrará su clase de prueba como la clase de configuración principal y, por lo tanto, reconocerá su exclusión. Alternativamente, cree una clase de configuración separada para compartir a través de todas sus pruebas de integración que se encuentra en un paquete base y todas sus pruebas de integración encontrarán.
Estoy intentando ejecutar una prueba de integración para mi controlador, pero tengo problemas si no me autentico. Aquí está mi controlador:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {"security.basic.enabled=false", "management.security.enabled=false"})
@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class})
public class HelloControllerIT {
private final ObjectMapper mapper = new ObjectMapper();
@Autowired private TestRestTemplate template;
@Test
public void test1() throws Exception {
ObjectNode loginRequest = mapper.createObjectNode();
loginRequest.put("username","name");
loginRequest.put("password","password");
JsonNode loginResponse = template.postForObject("/authenticate", loginRequest.toString(), JsonNode.class);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue());
headers.add("Content-Type", "application/json");
return new HttpEntity<>(null, headers);
HttpEntity request = getRequestEntity();
ResponseEntity response = template.exchange("/get",
HttpMethod.GET,
request,
new ParameterizedTypeReference<List<Foo>>() {});
//assert stuff
}
}
Cuando corro esto, todo funciona. Pero si comento fuera de la línea:
headers.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue());
Me sale el error
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON document: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: java.io.PushbackInputStream@272a5bc6; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: java.io.PushbackInputStream@272a5bc6; line: 1, column: 1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:234)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:219)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:917)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:901)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:559)
at org.springframework.boot.test.web.client.TestRestTemplate.exchange(TestRestTemplate.java:812)
at com.test.HelloControllerIT.test1(HelloControllerIT.java:75)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: java.io.PushbackInputStream@272a5bc6; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1122)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1075)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:338)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:269)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:259)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2922)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:231)
... 38 more
Obviamente las anotaciones de seguridad en la parte superior no funcionan. Entonces, ¿cuál es exactamente el problema y cómo lo soluciono?
Edición 1 : Intenté hacer:
Object response = template.exchange("/get", HttpMethod.GET, request, Object.class);
Y consiguió:
<401 Unauthorized,{status=401, message=Authentication failed, errorCode=10, timestamp=1497654855545},{X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Content-Type=[application/json;charset=ISO-8859-1], Content-Length=[89], Date=[Fri, 16 Jun 2017 23:14:15 GMT]}>
Para nuestra seguridad, estamos usando org.springframework.security.authentication.AuthenticationProvider
y org.springframework.security.authentication.AuthenticationManager
Edit 2: Según la sugerencia de skadya, creé una nueva clase así:
@Configuration
public class AnonymousConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity web) throws Exception {
web.antMatcher("**/*").anonymous();
}
}
Pero ahora, cuando ejecuto mi prueba de integración, obtengo el siguiente error:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:47)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ''org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration'': Injection of autowired dependencies failed; nested exception is java.lang.IllegalStateException: @Order on WebSecurityConfigurers must be unique. Order of 100 was already used on config.AnonymousConfig$$EnhancerBySpringCGLIB$$ba18b8d7@6291f725, so it cannot be used on security.WebSecurityConfig$$EnhancerBySpringCGLIB$$9d88e7e@1bfaaae1 too.
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
... 23 more
Caused by: java.lang.IllegalStateException: @Order on WebSecurityConfigurers must be unique. Order of 100 was already used on config.AnonymousConfig$$EnhancerBySpringCGLIB$$ba18b8d7@6291f725, so it cannot be used on security.WebSecurityConfig$$EnhancerBySpringCGLIB$$9d88e7e@1bfaaae1 too.
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.setFilterChainProxySecurityConfigurer(WebSecurityConfiguration.java:148)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:701)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
... 40 more
Parece que está chocando con la configuración de seguridad web que tenemos en el proyecto normal. Aquí está ese archivo:
@EnableWebSecurity
@EnableWebMvc
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//configuration
}
Intenté agregar @Order(1000)
que solucionó el problema anterior, pero aún así terminó en un 401 Unauthorized
Descubrí cómo hacer esto sin autenticación y / o burla en la memoria.
public class TestConf extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.permitAll();
}
}
Y use los perfiles activos de primavera para ejecutar la configuración anterior solo cuando se ejecuten casos de prueba.
Parece que la autenticación funciona, pero usted maneja la respuesta de manera incorrecta.
Aquí está el código a continuación, donde está intentando analizar la respuesta como List<Foo>
ResponseEntity response = template.exchange("/get",
HttpMethod.GET,
request,
new ParameterizedTypeReference<List<Foo>>() {}
);
Pero como no ha proporcionado el encabezado de autenticación, el servidor responde con un error personalizado (obviamente envuelto en el objeto Json) y obtiene esta excepción en la prueba diciendo que no puede analizar ArrayList
desde el objeto Json (que comienza con el token START_OBJECT
, como {
).
Could not read JSON document: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
Trate de manejar la respuesta como Objeto para que pueda ver lo que realmente viene allí.
template.exchange("/get", HttpMethod.GET, request, Object.class);
Pero esto no funcionará como una solución final. Creo que debería manejar el cuerpo de respuesta basado en el código de respuesta Http , si es 200 - analizar como List<>
, de lo contrario analizar como Map<>
o cualquier estructura que devuelva el servidor.
Parece que la configuración de seguridad predeterminada está siendo activada. A menos que vea su configuración completa, es difícil confirmar esto. Si es posible, ¿podría publicar su proyecto mínimo (en github?).
Como no desea aplicar la autenticación durante las ejecuciones de las pruebas de integración, puede habilitar el acceso anónimo de los recursos de su aplicación.
Para habilitar el acceso anónimo, puede agregar la clase debajo de su directorio de origen de prueba . Configurará el acceso anónimo durante el arranque de la aplicación web. (no debería ver el código de respuesta 401)
@Configuration
public class AllowAnonymousWebAccess extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity web) throws Exception {
web.antMatcher("**/*").anonymous();
}
}
Puedes intentar excluir algunas configuraciones automáticas más:
@EnableAutoConfiguration(exclude = {
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class,
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration.class,
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration.class,
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration.class
})
Por cierto, una forma más elegante de excluir cosas es definiendo application-test.properties
en sus fuentes de prueba y marcando su prueba con @Profile("test")
. Luego simplemente agregue esto a su configuración:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration
Puede encontrar todas las configuraciones posibles que pueden excluirse aquí : spring.factories
Se enfrentó a este problema durante mucho tiempo. Finalmente lo resolví. Debe burlarse del servidor de Autorización para crear un perfil de prueba y también deberá simular el servicio de detalles de usuario de Spring Security. Aquí está el código que encontré en un blog .
Servidor de Autorización de Pruebas
@Configuration
@EnableAuthorizationServer
@ActiveProfiles("test")
public class AuthorizationTestServer extends AuthorizationServerConfigurerAdapter {
private AuthenticationManager authenticationManager;
@Autowired
public AuthorizationTestServer(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("permitAll()");
oauthServer.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
super.configure(clients);
clients.inMemory()
.withClient("user")
.secret("password")
.authorizedGrantTypes("password")
.scopes("openid");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints.authenticationManager(this.authenticationManager);
}
}
Servicio de detalles de usuario de prueba
@Service
@ActiveProfiles("test")
public class UserDetailTestService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return new User("dummyUser","dummyPassword",true,true,
true,true, AuthorityUtils.createAuthorityList("USER"));
}
}
Clase de prueba principal
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@AutoConfigureMockMvc
public class JmStudentServiceApplicationTests {
@Autowired
private WebApplicationContext wac;
@Autowired
private MockMvc mockMvc;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private StudentRepository studentRepository;
@Test
public void test() throws Exception{
String accessToken = obtainAccessToken("dummyUser", "dummyPassword");
Student student = new Student();
student.setId("2222");
student.setName("test student");
studentRepository.createStudent(student);
assertTrue(studentRepository.getStudentById("2222").getName().equals("test student"));
MvcResult result = mockMvc.perform(get("/students/by-id/2222")
.header("Authorization", "Bearer " + accessToken)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
String str = result.getResponse().getContentAsString();
assertTrue(str.contains("/"id/":/"2222/""));
}
private String obtainAccessToken(String username, String password) throws Exception {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "password");
params.add("username", username);
params.add("password", password);
params.add("scope", "openid");
String base64ClientCredentials = new String(Base64.encodeBase64("user:password".getBytes()));
ResultActions result
= mockMvc.perform(post("/oauth/token")
.params(params)
.header("Authorization","Basic " + base64ClientCredentials)
.accept("application/json;charset=UTF-8"))
.andExpect(status().isOk());
String resultString = result.andReturn().getResponse().getContentAsString();
JacksonJsonParser jsonParser = new JacksonJsonParser();
return jsonParser.parseMap(resultString).get("access_token").toString();
}
}
Tiene un par de opciones para proporcionar autenticación en la prueba de integración de arranque de primavera. Es posible que deba ajustar algunas cosas para que todo funcione al final.
Enfoque basado en simulacros
Esto utiliza la prueba WebApplicationContext
inyectada en MockMvc con la anotación @WithMockUser para proporcionar al usuario de autenticación y WithMockUserSecurityContextFactory
creando el contexto de seguridad para el usuario simulado.
SecurityMockMvcConfigurers
registra el filtro de seguridad springSecurityFilterChain
con MockMvc
.
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class HelloControllerIT {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // enable security for the mock set up
.build();
}
@WithMockUser(value = "test", password = "pass")
@Test
public void test() throws Exception {
String contentType = MediaType.APPLICATION_JSON + ";charset=UTF-8";
String authzToken = mvc
.perform(
post("/authenticate")
.contentType(
MediaType.APPLICATION_JSON).
content("")).
andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.token", is(notNullValue())))
.andReturn().getResponse().getContentAsString();
System.out.print(authzToken);//{"token":"1a3434a"}
}
}
Enfoque basado en el proveedor de autenticación en la memoria
Esto utiliza el proveedor de autenticación en memoria con el usuario de autenticación básica.
Registre el proveedor de autenticación en memoria y habilite la autenticación básica, deshabilite el acceso anónimo en HttpSecurity
en WebSecurityConfigurerAdapter
.
Cuando el proveedor en memoria está registrado, DefaultInMemoryUserDetailsManagerConfigurer
crea el usuario de autenticación básico en la memoria.
Cuando la autenticación básica está habilitada, HttpBasicConfigurer
configura BasicAuthenticationFilter
. Autentica al usuario de prueba y crea el contexto de seguridad.
Configuración de seguridad
@EnableWebSecurity
@EnableWebMvc
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// register test user with in memory authentication provider
auth.inMemoryAuthentication().withUser("test").password("pass").roles("ROLES");
}
@Override
public void configure(HttpSecurity http) throws Exception {
// enable basic authentication & disable anoymous access
http.authorizeRequests().anyRequest().authenticated().and().httpBasic().and().anonymous().disable();
}
}
Punto final de autenticación
@Controller
@RequestMapping("/authenticate")
public class AuthenticationController {
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public TokenClass getToken() {
TokenClass tokenClass = new TokenClass();
tokenClass.setToken("1a3434a");
return tokenClass;
}
}
Pojo
public class TokenClass {
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
Controlador de prueba
import com.fasterxml.jackson.databind.JsonNode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.Base64;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerIT {
@Autowired
private TestRestTemplate template;
@Test
public void test() throws Exception {
HttpHeaders authHeaders = new HttpHeaders();
String token = new String(Base64.getEncoder().encode(
("test" + ":" + "pass").getBytes()));
authHeaders.set("Authorization", "Basic " + token);
JsonNode loginResponse = template.postForObject("/authenticate", new HttpEntity<>(null, authHeaders), JsonNode.class);
HttpHeaders authzHeaders = new HttpHeaders();
authzHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
authzHeaders.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue());
authzHeaders.add("Content-Type", "application/json");
ResponseEntity response = template.exchange("/secure",
HttpMethod.GET,
new HttpEntity<>(null, authzHeaders),
String.class
);
}
}