Java 用于集成测试的 Spring Boot 身份验证

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44467133/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 01:33:32  来源:igfitidea点击:

Spring Boot Authentication for Integration Tests

javaspringauthenticationspring-bootintegration-testing

提问by Richard

I'm trying to run an integration test for my controller but I am running into issues if I don't authenticate. Here's my controller:

我正在尝试为我的控制器运行集成测试,但如果我不进行身份验证,就会遇到问题。这是我的控制器:

@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
    }
}

When I run this, everything works. But if I comment out the line:

当我运行它时,一切正常。但如果我注释掉这一行:

headers.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue());

I get the 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.Hymanson.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.AbstractHymanson2HttpMessageConverter.readJavaType(AbstractHymanson2HttpMessageConverter.java:234)
    at org.springframework.http.converter.json.AbstractHymanson2HttpMessageConverter.read(AbstractHymanson2HttpMessageConverter.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.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.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access
Object response = template.exchange("/get", HttpMethod.GET, request, Object.class);
0(ParentRunner.java:58) at org.junit.runners.ParentRunner.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.Hymanson.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.Hymanson.databind.JsonMappingException.from(JsonMappingException.java:270) at com.fasterxml.Hymanson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234) at com.fasterxml.Hymanson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1122) at com.fasterxml.Hymanson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1075) at com.fasterxml.Hymanson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:338) at com.fasterxml.Hymanson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:269) at com.fasterxml.Hymanson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:259) at com.fasterxml.Hymanson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26) at com.fasterxml.Hymanson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798) at com.fasterxml.Hymanson.databind.ObjectMapper.readValue(ObjectMapper.java:2922) at org.springframework.http.converter.json.AbstractHymanson2HttpMessageConverter.readJavaType(AbstractHymanson2HttpMessageConverter.java:231) ... 38 more

Obviously the security annotations at the top are not working. So what exactly is the issue and how do I fix it?

显然,顶部的安全注释不起作用。那么究竟是什么问题,我该如何解决呢?

Edit 1: I tried doing:

编辑1:我试过这样做:

<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]}>

And got:

并得到:

@Configuration
public class AnonymousConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity web) throws Exception {
        web.antMatcher("**/*").anonymous();
    }
}

For our security we're using org.springframework.security.authentication.AuthenticationProviderand org.springframework.security.authentication.AuthenticationManager

为了我们的安全,我们正在使用org.springframework.security.authentication.AuthenticationProviderorg.springframework.security.authentication.AuthenticationManager

Edit 2:Per skadya's suggestion I created a new class like so:

编辑 2:根据 skadya 的建议,我创建了一个新类,如下所示:

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.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.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access
@EnableWebSecurity
@EnableWebMvc
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//configuration
}
0(ParentRunner.java:58) at org.junit.runners.ParentRunner.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$d88e7e@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.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$d88e7e@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

But now when I run my integration test I get the following error:

但是现在当我运行我的集成测试时,我收到以下错误:

@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
})

Looks like it's clashing with the websecurity config we have in the normal project. Here's that file:

看起来它与我们在普通项目中的 websecurity 配置冲突。这是那个文件:

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

I tried adding @Order(1000)which fixed the above issue but still ended up in a 401 Unauthorized

我尝试添加@Order(1000)哪个解决了上述问题,但仍然以401 Unauthorized

采纳答案by Danylo Zatorsky

You can try excluding few more auto configurations:

您可以尝试排除更多自动配置:

ResponseEntity response = template.exchange("/get",
    HttpMethod.GET,
    request,
    new ParameterizedTypeReference<List<Foo>>() {}
);

Btw, more elegant way of excluding stuff is by defining application-test.propertiesin your test sources and marking your test with @Profile("test"). Then just add this to your config:

顺便说一句,排除内容的更优雅的方法是application-test.properties在您的测试源中定义并使用@Profile("test"). 然后只需将其添加到您的配置中:

Could not read JSON document: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token

All the possible configurations that can be excluded you can find here: spring.factories

您可以在此处找到可以排除的所有可能配置spring.factories

回答by ikryvorotenko

It looks like authentication does work, but you handle the response in wrong way.

看起来身份验证确实有效,但您以错误的方式处理响应。

Here's the code below, where you're trying to parse response as List<Foo>

这是下面的代码,您尝试将响应解析为 List<Foo>

template.exchange("/get", HttpMethod.GET, request, Object.class);

But since you haven't provided authentication header, the server responses with some custom error (obviously wrapped into Json Object) and you get this exception in the test saying it can't parse ArrayListfrom Json Object (which starts with START_OBJECTtoken, like {).

但是由于您没有提供身份验证标头,服务器响应一些自定义错误(显然包含在 Json 对象中)并且您在测试中收到此异常,说明它无法ArrayList从 Json 对象(以START_OBJECT令牌开头,例如{)进行解析。

@Configuration
public class AllowAnonymousWebAccess extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity web) throws Exception {
        web.antMatcher("**/*").anonymous();
    }
}

Try to handle the response as Object so you can see what's actually comes there.

尝试将响应作为 Object 处理,以便您可以看到实际出现的内容。

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"}

    }

}

But this won't work as a final solution. I believe you should handle the response body based on Http response code, if that's 200 - parse as List<>, otherwise parse as Map<>or whatever structure the server returns.

但这不能作为最终解决方案。我相信您应该根据 Http 响应代码处理响应主体,如果是 200 - 解析为List<>,否则解析为Map<>或服务器返回的任何结构。

回答by skadya

It looks like default security configuration are getting kicked-in. Unless I see your your complete configuration it is hard to confirm this. If possible, could you post the your minimal project (on github?).

看起来默认安全配置正在被启动。除非我看到您的完整配置,否则很难确认这一点。如果可能,您能发布您的最小项目吗(在 github 上?)。

Since you do not want to enforce the authentication during executions of integration tests, you may enable the anonymous access of your application resources.

由于您不想在执行集成测试期间强制执行身份验证,因此您可以启用对应用程序资源的匿名访问。

To enable anonymous access, you may add below class under your test sourcedirectory. It will configure the anonymous access during bootstrapping the web application. (should not see 401 response code)

要启用匿名访问,您可以在测试源目录下添加以下类。它将在引导 Web 应用程序期间配置匿名访问。(不应该看到 401 响应代码)

@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();    
    }

}

回答by Sagar Veeram

You have couple of options to provide authentication in the spring boot integration test. You may need to adjust a few things to make it all work at your end.

您有几个选项可以在 Spring Boot 集成测试中提供身份验证。您可能需要调整一些内容以使其最终都能正常工作。

Mock based approach

基于模拟的方法

This uses test WebApplicationContextinjected into MockMvcwith @WithMockUserannotation to provide authentication user and WithMockUserSecurityContextFactorycreating the security context for the mock user.

这使用通过@WithMockUser注释WebApplicationContext注入MockMvc 的测试来提供身份验证用户并为模拟用户创建安全上下文。WithMockUserSecurityContextFactory

SecurityMockMvcConfigurersregisters the security filter springSecurityFilterChainwith MockMvc.

SecurityMockMvcConfigurers注册安全过滤springSecurityFilterChainMockMvc

@Controller
@RequestMapping("/authenticate")
public class AuthenticationController {

    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public TokenClass getToken() {
        TokenClass tokenClass = new TokenClass();
        tokenClass.setToken("1a3434a");
        return tokenClass;
    }

}

In-memory auth provider based approach

基于内存身份验证提供程序的方法

This uses in-memory auth provider with basic authentication user.

这使用具有基本身份验证用户的内存身份验证提供程序。

Register in-memory auth provider and enable basic auth, disable anonymous access in HttpSecurityin the WebSecurityConfigurerAdapter.

在内存中注册身份验证提供程序并启用基本身份验证,HttpSecurityWebSecurityConfigurerAdapter.

When in-memory provider is registered, DefaultInMemoryUserDetailsManagerConfigurercreates the basic auth user in the memory.

注册内存中提供程序时,在内存中DefaultInMemoryUserDetailsManagerConfigurer创建基本身份验证用户。

When basic authentication is enabled, HttpBasicConfigurerconfigures BasicAuthenticationFilter. Authenticates the test user and creates the security context.

启用基本身份验证后,HttpBasicConfigurer配置BasicAuthenticationFilter. 验证测试用户并创建安全上下文。

Security Configuration

安全配置

public class TokenClass {

    private String token;

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
}

Authentication Endpoint

身份验证端点

import com.fasterxml.Hymanson.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
        );
    }
}

Pojo

波乔

public class TestConf extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf()
                .disable()
                .authorizeRequests()
                .anyRequest()
                .permitAll();
    }
}

Test Controller

测试控制器

@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);
    }
}

回答by Richard

I figured out how to do this without in-memory authentication and/or mocking.

我想出了如何在没有内存中身份验证和/或模拟的情况下做到这一点。

@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"));
    }
}

And use spring active profiles to run the above config only when running test cases.

并且仅在运行测试用例时使用 spring 活动配置文件来运行上述配置。

回答by The Gilbert Arenas Dagger

Why Security Configuration is Enforced

为什么要强制执行安全配置

According to the Spring Boot documentation, when you annotate your class with @SpringBootTest, and you do not have specify a configuration alternative, then Spring with search for a @SpringBootApplicationclass to serve as your primary configuration. Spring starts the search in the package of your test class, then searches up the package hiearchy. Presumably, it is finding your primary configuration, and everything this brings with it including your unwanted security configuration.

根据 Spring Boot文档,当您使用 注释您的类@SpringBootTest并且您没有指定配置替代时,Spring 会搜索一个@SpringBootApplication类作为您的主要配置。Spring 在您的测试类的包中开始搜索,然后搜索包层次结构。据推测,它正在寻找您的主要配置,以及随之而来的一切,包括您不需要的安全配置。

Solution

解决方案

The simplest solution, verified in Spring Boot 2.0.3, is to change @EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)to @SpringBootApplication(exclude = SecurityAutoConfiguration.class). When you make this change Spring will register your test class as the primary configuration class and will therefore acknowledge your exclusion. Alternatively, create a separate configuration class to share across all your integration tests that resides in a base package and will be found by all your integration tests.

在 Spring Boot 2.0.3 中验证过的最简单的解决方案是更改@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)@SpringBootApplication(exclude = SecurityAutoConfiguration.class). 当您进行此更改时,Spring 会将您的测试类注册为主要配置类,因此会确认您的排除。或者,创建一个单独的配置类,以在驻留在基本包中的所有集成测试之间共享,并将被所有集成测试找到。

回答by Bablu

Was facing this issue for a long time. Finally got it resolved. You need to mock the Authorization server creating a test profile and will also need to Mock Spring Security user details service. Here is the code that I found in a blog.

长期面临这个问题。终于解决了。您需要模拟授权服务器创建测试配置文件,还需要模拟 Spring Security 用户详细信息服务。这是我在博客中找到的代码。

Test Authorization server

测试授权服务器

@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();

      HymansonJsonParser jsonParser = new HymansonJsonParser();
      return jsonParser.parseMap(resultString).get("access_token").toString();
   }

}

Test User Details Service

测试用户详情服务

##代码##

Main Test class

主要测试类

##代码##