I've created manually a new spring FilterChainProxy:
private FilterChainProxy getCustomFilterChainProxy()
{
List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>();
securityFilterChains.add(new DefaultSecurityFilterChain( new AntPathRequestMatcher("/**"), new MyFilter1()));
securityFilterChains.add(new DefaultSecurityFilterChain( new AntPathRequestMatcher("/admin/**"), new MyFilter2()));
return new FilterChainProxy(securityFilterChains);
}
And considering that I have a new HttpServletRequest, I would like to check the request against the custom FilterChainProxy. Something like this:
FilterChainProxy customFilterChainProxy = getCustomFilterChainProxy();
customFilterChainProxy.doFilter(request, null, (FilterChain) customFilterChainProxy.getFilterChains() );
But I'm struggling in correctly define the FilterChain used in the 3rd parameter of the doFilter() method.
How can I do this ?
Thanks.
Why don't you write an integration test, using MockMvc with .apply(SecurityMockMvcConfigurers.springSecurity()) Here is a link to an post about integration MockMvc and SpringSecurity.
It is extremely useful, and allows you to test your login, and protected services. I often find myself writing/modifying the test before I try to see if it works in a browser.
Related
I consider myself a novice at unit-testing, completely new to Mockito and junit. I have to write unit-tests for some simple api-calls. But my test seems somewhat pointless to me, I can't tell where I am going wrong. I have added a method to an existing web-service, ManagerWS.java , See Below.
ManagerWS.java Method:
public String healthCheck(String userId) {
String healthCheckUrlEndpoint = this.baseUrl()+"/health";
logger.debug("Calling health check: {}", healthCheckUrlEndpoint);
HttpHeaders healthCheckHeaders = new HttpHeaders();
healthCheckHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
healthCheckHeaders.add(USER_KEY, userId);
healthCheckHeaders.add(TOKEN_NAME, TOKEN_VALUE);
healthCheckHeaders.add(Constants.ACCEPT_LANGUAGE_HEADER, LocaleContextHolder.getLocale().toString());
healthCheckHeaders.add(CORRELATION_HEADER, myService.get(AppLoggingMDCService.LOG_KEY_REQUEST_ID));
HttpEntity<Object> request = new HttpEntity<Object>(healthCheckHeaders);
ResponseEntity<String> response;
try {
response = makeRequest(HttpMethod.GET, healthCheckUrlEndpoint, request, String.class);
} catch (Exception e) {
logger.error("Exception encountered during health check", e);
throw e;
}
logger.debug("RESPONSE : http status: {} - body: {}", response.getStatusCode(), response.getBody());
return response.getStatusCode().toString();
}
The logic is simple. Construct the url, create headers and add headers to the request. make the request and extract the status-code from the response. Here is my test. NOTE: the class is using #RunWith(SpringRunner.class) and I am using #Mock for dependencies and #InjectMocks for the local instance ManagerWS. ManagerWS.java is the service calss being tested.
TEST-CLASS TEST-Method:
#Test
public void testHealthCheck() throws Exception {
//Given
managerWS = new ManagerWS(templateFactory, configParamService, mdcService, env);
String url = "http://baseurl/health";
HttpHeaders headers = new HttpHeaders();
HttpEntity<Object> request = new HttpEntity<Object>(headers);
ResponseEntity<String> response = new ResponseEntity<>(HttpStatus.OK);
//when
when(managerWS.makeRequest(HttpMethod.GET, url, request, String.class)).thenReturn(response);
String actualStatus = response.getStatusCode().toString();
//then
Assert.assertEquals("200",actualStatus);
}
To me this test seems stupid (for want of a batter word). I basicall set the status to give a "200" and assert that what i set is "200". That is not really making much sense.To me it literally does nothing. I tried using spy(ManagerWS.class). But I am literally grasping at straws without the full understanding.
SonarQube still complains with "Not covered by unit tests". I cam completely stumped as to how else to write this test. I also have to do similar tests for three other calls.
I am a total novice to testing and I cannot see my mistake. Please advise.
SonarQube still complains with "Not covered by unit tests".
Your unit test doesn't test from the entry point of the method to test : healthCheck(String), so it is not covered by unit tests.
Besides, you also mock the part of the method that you want to test :
when(managerWS.makeRequest(HttpMethod.GET, url, request, String.class)).thenReturn(response);
So indeed your approach looks wrong.
In fact, writing an unit test for this code looks wrong too or at least looks like a white box test with few value.
Why ?
Your logic depends on :
response = makeRequest(HttpMethod.GET, healthCheckUrlEndpoint, request, String.class);
But you can know if it works only at runtime, with a running HTTP Server.
So the single thing that you can do is mocking everything, spying the object under test and verifying that each statement in the implementation is performed : no readable test, no robust and few/no value.
Your method that relies essentially on side effect would make more sense to be tested with as an integration test :
ManagerWS managerWS; // real ManagerWS implementation without mock
#Test
public void healthCheck() throws Exception {
//Given
String url = "http://baseurl/health";
// When
String actual managerWS.healthCheck(url);
// Then
String expected = "...";
Assertions.assertEquals(expected, actual);
}
As a side note, if you used Spring, I would advise you to look at test slicing #WebMvcTest that focuses on the web part of the component under test. It allows mainly to test the HTTP part/logic (headers, request, response).
I am playing around with Spring Security and now I am trying get some knowledge about testing my REST-controller with regards to security.
So I prepared my test-class with:
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
containing test-cases mostly of the following or similar form:
#Test
public void handleSecuredRequest_shouldReturn200_withAdminUser() throws Exception{
ResultActions action = mvc.perform(get("/secured").with(user("admin").roles("ADMIN")));
int status = action.andReturn().getResponse().getStatus();
assertTrue("expected status code = 200 ; current status code = " + status, status == 200);
}
What I was not able to achieve until now were things concerning sessions.
Most notably I would be interested to verify that session-invalidation is performed correctly.
How can I achieve that?
EDIT:
I was able to find something close to a solution doing the following based on
https://stackoverflow.com/a/26281932/6294605 :
#Test
public void logout_shouldInvalidateSession_withLoggedInUser() throws Exception{
ResultActions action = mvc.perform(get("/userAsJSON").with(user("user")));
MockHttpSession session = (MockHttpSession) action.andReturn().getRequest().getSession();
ResultActions action2 = mvc.perform(post("/logout").session(session));
ResultActions action3 = mvc.perform(get("/userAsJSON").session(session));
int status3 = action3.andReturn().getResponse().getStatus();
assertTrue("expected status code = 401 ; current status code = " + status3, status3 == 401);
}
But I am not entirely satisfied with this.
It requires to process several steps to let me test what I want.
Resulting from 1.: it is not detached from certain other things to function correctly (e.g. the "/userAsJSON" endpoint returning 401 for not authenticated users).
Resulting from 2.: it requires additional attention to make sure that test-cases exist that ensure that the formal requirements for my test to be valid are met.
So I would wish for an option to make this less error-prone.
Any ideas?
In my shiro application, I want to define a AuthenticationFilter for all paths except REST.
ie /rest/... doesnt go through it but everything else would.
I'm using Shiro-Guice so my filter setups are of the form
addFilterChain("/rest/**" ,restFilter)
addFilterChain("/**", filter) //I want this one to work on everything except my rest filter
I looked at this question about Ant path pattern style but there doesnt seem to be support for regexes.
You can't do it like that. The way shiro works is that it checks the filters in the order they are configured. It first checks the first filter, if it can't authenticate, it will move on to the next. There is no exclusion pattern for that.
You can write your own custom shiro filter that will deny authrorization on de rest url.
I don't know how it will work in guice, but in shiro.ini you can do something like:
[main]
myfilter = UrlBasedAuthzFilter
restFilter = YourRestFilterClass
[urls]
/rest/** = restFilter
/** = myfilter
And the filter class:
public class UrlBasedAuthzFilter extends AuthorizationFilter {
#Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
if (request.getServletContext().getContextPath().startsWith("/rest"){
return false;
}
return super.isAccessAllowed(request, response, mappedValue);
}
}
#Bean("shiroFilter")
public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("adminLoginFilter", new AdminLoginFilter());
filterMap.put("jwtAdminAuthcFilter", new JwtAdminAuthcFilter());
factoryBean.setFilters(filterMap);
factoryBean.setSecurityManager(securityManager);
factoryBean.setUnauthorizedUrl("/401");
Map<String, String> filterRuleMap = new LinkedHashMap<>();
filterRuleMap.put("/admin/auth/login","adminLoginFilter");
filterRuleMap.put("/admin/**", "jwtAdminAuthcFilter");
filterRuleMap.put("/401", "anon");
factoryBean.setFilterChainDefinitionMap(filterRuleMap);
return factoryBean;
}
I need to test a servlet, which is working fine now.
The servlet needs to use a Spring service, so it is modified for that this way:
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(
this, config.getServletContext()); // ImageServlet.java line 49
After migration to Spring 4, the test broke and currently it throws this exception:
java.lang.IllegalStateException:
No WebApplicationContext found: no ContextLoaderListener registered?
at org.springframework.web.context.support.WebApplicationContextUtils.
getRequiredWebApplicationContext(WebApplicationContextUtils.java:84)
at org.springframework.web.context.support.SpringBeanAutowiringSupport.
processInjectionBasedOnServletContext(SpringBeanAutowiringSupport.java:107)
at package.ImageServlet.init(ImageServlet.java:49)
at in.nasv.utils.ImageServletTest.accessingImageViaHttp(ImageServletTest.java:45)
Here is the portion of code of ImageServletTest:
// prepare servlet instance
MockServletConfig config = new MockServletConfig(
new MockServletContextPatched());
ImageServlet servlet = new ImageServlet();
servlet.init( config ); // ImageServletTest, line 45
And this patched class (is not actually patched now):
public class MockServletContextPatched extends MockServletContext{ }
What am I supposed to do to avoid this "IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?" ?
I found an solution. But clear enough, but an solution.
Now servlet initialization is:
MockServletContext servletContext = new MockServletContextPatched();
MockServletConfig config = new MockServletConfig( servletContext );
ImageServlet servlet = new ImageServlet();
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext( "spring-data-app-context.xml" );
DefaultListableBeanFactory dlbf = new DefaultListableBeanFactory(appContext.getBeanFactory());
GenericWebApplicationContext gwac = new GenericWebApplicationContext(dlbf);
servletContext.setAttribute(GenericWebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, gwac);
gwac.setServletContext(servletContext);
gwac.refresh();
servlet.init( config );
Preparing request and response in standard way:
MockHttpServletResponse response = new MockHttpServletResponse();
URL serverUrl = new URL( propertyExtendedService.getServerAddress(true) );
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI( "/what-you-want" );
request.setPathInfo( "/" + TEST_IMAGE );
request.setContentType("image/jpeg");
request.addHeader("Accept", "image/jpeg;image/jpg;" );
Final step is to call the filter and assert returned values:
servlet.doGet( request, response );
assertEquals( response.getStatus(), 200 );
// assert everything you want
Update: the updated documentation for getServletContext() is now online.
It is not necessary to implement a custom MockServletContextPatched class just to configure a custom MIME type in Spring's MockServletContext.
Since Spring's MockServletContext uses the Java Activation Framework (JAF) to implement the ServletContext.getMimeType(String) method, it is quite easy to configure a custom MIME type via JAF's MimetypesFileTypeMap.addMimeTypes(String) method as follows.
MockServletContext mockServletContext = new MockServletContext();
MimetypesFileTypeMap mimetypesFileTypeMap =
(MimetypesFileTypeMap) MimetypesFileTypeMap.getDefaultFileTypeMap();
mimetypesFileTypeMap.addMimeTypes("text/enigma enigma");
assertEquals("text/enigma", mockServletContext.getMimeType("filename.enigma"));
In the above JUnit based test code, I configured a custom MIME type "text/enigma" for files that have the extension .enigma.
Hope this helps!
Regards,
Sam (author of the Spring TestContext Framework)
p.s. I created JIRA issue SPR-12126 in order to improve the documentation of MockServletContext.
I've configured my servlets/filters using guice-servlet. With bindings like
serve("/foo").with(HelloServlet.class);
Now, I want to test that mapping. I've used jetty-testing
private ServletTester tester;
private HttpTester request;
private HttpTester response;
#BeforeMethod
public void setUp() {
this.tester = new ServletTester();
this.tester.setContextPath("/");
this.tester.addEventListener(new Config()); //my guice servlet config goes there.
this.tester.addFilter(GuiceFilter.class, "/*", 0);
this.tester.addServlet(FakeServlet.class, "/*"); <-----!!!!
this.tester.start();
this.request = new HttpTester();
this.response = new HttpTester();
this.request.setMethod("GET");
this.request.setHeader("Host", "tester");
this.request.setVersion("HTTP/1.0");
}
#Test
public void test() {
this.request.setURI("/foo");
this.response.parse(tester.getResponses(request.generate()));
assertEquals(this.response.getContent(), "Hello World");
}
It works. But it made me to add some fake servlet that should bewer be invoked. How I can test it without adding such servlet?
The servlet spec requires a servlet for the doFilter chain to make sense.
If you don't want to create your own Servlet in the test cases, just use the org.eclipse.jetty.servlet.DefaultServlet.class instead (found in the jetty-servlet.jar).