简体   繁体   English

在Spring Security中使用自定义过滤器时,Spring单元测试MockMvc失败

[英]Spring unit test MockMvc fails when using custom filter in Spring Security

I have a web application that should only be callable from specific IP addresses. 我有一个Web应用程序,只能从特定的IP地址调用。 Other than that, there is no need for authentication or for authorization; 除此之外,不需要认证或授权; if you're coming from the right IP, you can see everything. 如果你来自正确的IP,你可以看到一切。

To that end, searching StackOverflow and other places, I found a number of suggestions for filtering requests by IP address in Spring Security. 为此,在搜索StackOverflow和其他地方时,我发现了许多建议,用于在Spring Security中按IP地址过滤请求。 They all took this form (extending WebSecurityConfigurerAdapter using java configuration): 他们都采用了这种形式(使用java配置扩展WebSecurityConfigurerAdapter):

http.authorizeRequests().anyRequest().access("hasIpAddress('127.0.0.1/0')");

However, that never worked for me; 但是,这从来没有对我有用; it never rejected any request, no matter what IP address I made the request from. 它从不拒绝任何请求,无论我发出请求的IP地址。 Instead, I implemented my IP filtering with a custom filter like this: 相反,我使用这样的自定义过滤器实现了我的IP过滤:

@Configuration
@EnableWebSecurity
@PropertySource("classpath:config.properties")
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    @Autowired
    private Environment env;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        String ipRange = env.getRequiredProperty("trusted_ips");
        logger.info("@@@@@ SETTING UP SECURITY CONFIGURATION @@@@@@@@@@@@@");
        logger.info("@@ trusted_ips: " + ipRange);
        logger.info("@@@@@ SETTING UP SECURITY CONFIGURATION - END @@@@@@@@@@@@@");
        http.addFilterBefore(new IPSecurityFilter(ipRange), J2eePreAuthenticatedProcessingFilter.class)
            .authorizeRequests().antMatchers("/**").permitAll();
    }
}

My IPSecurityFilter: 我的IPSecurityFilter:

public class IPSecurityFilter extends OncePerRequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(IPSecurityFilter.class);
    private String[] ipAddresses;

    public IPSecurityFilter(String strIPAddresses) {
        logger.info("@@@@ Our IP Address whitelist: " + strIPAddresses);
        this.ipAddresses = strIPAddresses.split(",");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        logger.info("Checking whether request should be allowed: " + request.getRequestURI());
        logger.info("@@@ Request is coming from IP address: " + request.getRemoteAddr());
        for (String ipAddress : ipAddresses) {
            if (ipAddress.equals(request.getRemoteAddr())) {
                logger.info("@@@ Allowing request from ip address: " + request.getRemoteAddr());
                return;         // We accept requests from this IP address
            }
        }
        // The remote IP address isn't on our white list; throw an exception
        throw new AccessDeniedException("Access has been denied for your IP address: " + request.getRemoteAddr());
    }
}

This seems to work in that the request is rejected if it originates from an IP Address that isn't on my white list. 这似乎有效,因为如果请求来自不在我的白名单上的IP地址,则拒绝该请求。

However, with this configuration, my unit (using MockMvc) test fails; 但是,使用此配置,我的单元(使用MockMvc)测试失败; and it fails in a way that I would never have expected. 它以一种我从未预料到的方式失败。 When the unit test runs, it appears to use the Spring Security configuration correctly and the request passes the security test (the IP white list includes 127.0.0.1 and according to the log that is generated while the test is being run, the request is coming from that IP). 当单元测试运行时,它似乎正确使用Spring Security配置,并且请求通过安全测试(IP白名单包括127.0.0.1,并且根据运行测试时生成的日志,请求即将到来来自那个IP)。 However, the request never seems to be routed to my controller. 但是,请求似乎永远不会路由到我的控制器。

Here is my test: 这是我的测试:

@RunWith(SpringRunner.class)
@WebMvcTest()
//@WebMvcTest(value = HandlerController.class)
@AutoConfigureMockMvc
@Import(SecurityConfig.class)
public class HandlerControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void getIndex() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
           .andExpect(status().isOk())
           .andExpect(content().json("{\"services\":[\"OutboundMessageService\"]}", true));
    }
}

And finally, here is my controller (please ignore the idiotic way that I'm generating the JSON return value, it's still very early in development): 最后,这是我的控制器(请忽略我生成JSON返回值的愚蠢方式,它仍处于开发阶段):

@RestController
public class HandlerController {
    private static final Logger logger = LoggerFactory.getLogger(HandlerController.class);

    @RequestMapping("/")
    public String index() {
        logger.info("### handling a request for / ###");
        return "{\"services\":[\"OutboundMessageService\"]}";
    }
}

And here are the test results: 以下是测试结果:

2017-11-14 08:29:12.151  INFO 25412 --- [           main] c.z.s.controllers.HandlerControllerTest  : Starting HandlerControllerTest on 597NLL1 with PID 25412 (started by User in C:\Development\KnowledgeBin\NewArchitecture\OutboundMessageHandler)
2017-11-14 08:29:12.152  INFO 25412 --- [           main] c.z.s.controllers.HandlerControllerTest  : No active profile set, falling back to default profiles: default
2017-11-14 08:29:12.178  INFO 25412 --- [           main] o.s.w.c.s.GenericWebApplicationContext   : Refreshing org.springframework.web.context.support.GenericWebApplicationContext@209da20d: startup date [Tue Nov 14 08:29:12 MST 2017]; root of context hierarchy
2017-11-14 08:29:13.883  INFO 25412 --- [           main] b.a.s.AuthenticationManagerConfiguration : 

Using default security password: 56e3fab8-f7fb-4fbd-b2d2-e37eae8cef5e

2017-11-14 08:29:13.962  INFO 25412 --- [           main] c.z.services.security.IPSecurityFilter   : @@@@ Our IP Address whitelist: 122.22.22.22,127.0.0.1
2017-11-14 08:29:14.086  INFO 25412 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@3f4f9acd, org.springframework.security.web.context.SecurityContextPersistenceFilter@470a9030, org.springframework.security.web.header.HeaderWriterFilter@60c16548, org.springframework.security.web.csrf.CsrfFilter@435ce306, org.springframework.security.web.authentication.logout.LogoutFilter@607b2792, com.zpaper.services.security.IPSecurityFilter@46baf579, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@27494e46, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@36453307, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@4bf324f9, org.springframework.security.web.session.SessionManagementFilter@452c8a40, org.springframework.security.web.access.ExceptionTranslationFilter@39ce27f2, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@5767b2af]
2017-11-14 08:29:14.183  INFO 25412 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String com.zpaper.services.controllers.HandlerController.index()
2017-11-14 08:29:14.184  INFO 25412 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/OutboundMessageService]}" onto public java.lang.String com.zpaper.services.controllers.HandlerController.outboundMessage()
2017-11-14 08:29:14.189  INFO 25412 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-11-14 08:29:14.190  INFO 25412 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-11-14 08:29:14.243  INFO 25412 --- [           main] c.z.s.config.HandlerWebConfiguration     : #### My Configuration handler was called ####
2017-11-14 08:29:14.253  INFO 25412 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler]
2017-11-14 08:29:14.313  INFO 25412 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext@209da20d: startup date [Tue Nov 14 08:29:12 MST 2017]; root of context hierarchy
2017-11-14 08:29:14.784  INFO 25412 --- [           main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring FrameworkServlet ''
2017-11-14 08:29:14.784  INFO 25412 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : FrameworkServlet '': initialization started
2017-11-14 08:29:14.805  INFO 25412 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : FrameworkServlet '': initialization completed in 21 ms

2017-11-14 08:29:14.897  INFO 25412 --- [           main] c.z.s.controllers.HandlerControllerTest  : Started HandlerControllerTest in 3.095 seconds (JVM running for 3.995)
2017-11-14 08:29:14.981  INFO 25412 --- [           main] c.z.services.security.IPSecurityFilter   : Checking whether request should be allowed: /
2017-11-14 08:29:14.981  INFO 25412 --- [           main] c.z.services.security.IPSecurityFilter   : @@@ Request is coming from IP address: 127.0.0.1
2017-11-14 08:29:14.981  INFO 25412 --- [           main] c.z.services.security.IPSecurityFilter   : @@@ Allowing request from ip address: 127.0.0.1

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /
       Parameters = {}
          Headers = {Accept=[application/json]}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {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 = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 3.363 sec <<< FAILURE! - in com.zpaper.services.controllers.HandlerControllerTest
getIndex(com.zpaper.services.controllers.HandlerControllerTest)  Time elapsed: 0.12 sec  <<< ERROR!
org.json.JSONException: Unparsable JSON string: 
    at org.skyscreamer.jsonassert.JSONParser.parseJSON(JSONParser.java:42)

As can be seen in the log messages, the IP filter is being invoked and is allowing the request to continue. 从日志消息中可以看出,正在调用IP过滤器并允许请求继续。 However, the debug string that is being emitted in my handler is nowhere to be seen and the return body is blank. 但是,我的处理程序中发出的调试字符串无处可见,返回正文为空。 Can anyone tell me why my security filter would prevent MockMvc from having its request successfully routed to my controller? 任何人都可以告诉我为什么我的安全过滤器会阻止MockMvc将其请求成功路由到我的控制器?

Final Note: if I use the http.authorizeRequests().anyRequest().access("hasIpAddress('127.0.0.1/0')"); 最后注意:如果我使用http.authorizeRequests()。anyRequest()。access(“hasIpAddress('127.0.0.1/0')”); configuration that I first listed or completely get rid of Spring Security by removing my SecurityConfig class, the request is routed successfully to my handler. 我首先列出的配置或通过删除我的SecurityConfig类完全摆脱Spring Security,请求被成功路由到我的处理程序。

I figured out how to make the test work. 我想出了如何使测试工作。 I was not able to find a single article that answered my question but by taking different suggestions from multiple blog posts, I came up with this which works for me: 我无法找到一篇回答我的问题的文章,但是通过从多篇博文中提出不同的建议,我想出了这个对我有用的文章:

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = HandlerController.class)
public class HandlerControllerTest {

    private MockMvc mvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public void setUp() {
//        mvc = MockMvcBuilders.standaloneSetup(new HandlerController()).build();
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void getIndex() throws Exception {
        mvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
           .andExpect(status().isOk())
           .andExpect(content().json("{\"services\":[\"OutboundMessageService\"]}", true));
    }

    @Test
    public void getMessageService() throws Exception {
        mvc.perform(get("/OutboundMessageService").accept(MediaType.APPLICATION_JSON))
           .andExpect(status().isOk())
           .andExpect(content().json("{\"status\": \"SUCCESS\"}", true));
    }
}

As you can see, I am no longer auto-wiring the MockMvc object and allowing it be automatically set up but am instead setting it up myself in the setUp() method. 正如您所看到的,我不再自动连接MockMvc对象并允许它自动设置,而是在setUp()方法中自己设置它。 The commented-out line in the setUp() method works to successfully test my controller also but it doesn't route the request through my Spring Security IP address filter. setUp()方法中的注释掉的行也可以成功测试我的控制器,但它不会通过我的Spring Security IP地址过滤器路由请求。 I'm leaving it in so that users that don't need to test Spring Security can see an alternate method to set up the MockMvc object. 我将其保留下来,以便不需要测试Spring Security的用户可以看到另一种设置MockMvc对象的方法。 The uncommented line sets up a MockMvc object such that it runs the request through both the security filters and my controller. 未注释的行设置MockMvc对象,以便它通过安全过滤器和我的控制器运行请求。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM