I am trying to add junit test case for my Spring Boot OncePerRequestFilter shouldNotFilter method logic. The logic works fine with real-time REST calls but junit case is failing. Any idea?.
Here is test code.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringFilterTest {
#Test
public void getHealthTest() throws Exception {
standaloneSetup(new PersonController()).addFilter(new SkipFilter()).build().perform(get("/health")).andExpect(status().isOk());
}
#Test
public void getPersonTest() throws Exception {
standaloneSetup(new PersonController()).addFilter(new SkipFilter()).build().perform(get("/person")).andExpect(status().isAccepted());
}
private class SkipFilter extends OncePerRequestFilter {
private Set<String> skipUrls = new HashSet<>(Arrays.asList("/health"));
private AntPathMatcher pathMatcher = new AntPathMatcher();
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(request, response);
response.setStatus(HttpStatus.ACCEPTED.value());
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return skipUrls.stream().anyMatch(p -> pathMatcher.match(p, request.getServletPath()));
}
}
#RestController
#RequestMapping(value = "/")
private static class PersonController {
#GetMapping("person")
public void getPerson() {
}
#GetMapping("health")
public void getHealth() {
}
}
}
I am expecting both of junit #Test cases to be successful but health one is always failing(its using Filter).
Incase, if you want to replicate below is complete repo code.
https://github.com/imran9m/spring-filter-test
Below Expression evaluates to false with request.getServletPath() when /health
skipUrls.stream().anyMatch(p -> pathMatcher.match(p, request.getServletPath()));
Change to request.getRequestURI() to get the uri and below condition matches the path
skipUrls.stream().anyMatch(p -> pathMatcher.match(p, request.getRequestURI()));
Related
I am trying to create a jar file to inject into any of my spring boot project for logging the request details.
I am able to do this in one of my project. You can see the code below.
How to create the jar out of it and how to inject into other projects?
#Component
public class Interceptor extends HandlerInterceptorAdapter {
private static Logger log = LoggerFactory.getLogger(Interceptor.class);
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// log.info("Inside prehandle");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// log.info("Inside postHandle");
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("Inside afterCompletion");
sendToLoggerApi(request, response);
}
}
#Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
#Autowired
Interceptor interceptor;
#Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(interceptor);
}
}
i am studying spring 5 and i can not use #RequestMapping annotation and don't know why
#RequestMapping includes #Component annotation so I just thought I can use that
initRequest includes URL parameter by string
i just expected initRequest(/hello) parameter binds URL
here is my code
public class SimpleControllerTest extends AbstractDispatcherServletTest {
#Test
public void helloSimpleController() throws ServletException, IOException {
setClasses(HelloController.class);
initRequest("/hello").addParameter("name", "spring");
runService();
assertModel("message", "Hello spring");
assertViewName("/WEB-INF/view/hello.jsp");
}
#Test(expected=Exception.class)
public void noParameterHelloSimpleController() throws ServletException, IOException {
setClasses(HelloController.class);
initRequest("/hello");
runService();
}
#Component("/hello")
//#RequestMapping("/hello")
static class HelloController extends SimpleController {
public HelloController() {
this.setRequiredParams(new String[] {"name"});
this.setViewName("/WEB-INF/view/hello.jsp");
}
public void control(Map<String, String> params, Map<String, Object> model) throws Exception {
model.put("message", "Hello " + params.get("name"));
}
}
static abstract class SimpleController implements Controller {
private String[] requiredParams;
private String viewName;
public void setRequiredParams(String[] requiredParams) {
this.requiredParams = requiredParams;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
final public ModelAndView handleRequest(HttpServletRequest req,
HttpServletResponse res) throws Exception {
...
}
public abstract void control(Map<String, String> params, Map<String, Object> model) throws Exception;
}
}
You need to work on your Spring basics. Your understanding of which annotations do what is incorrect and incomplete. The following links provide good knowledge on these. Go through these, revise your code, and you will solve this problem without needing help.
Spring Framework Annotations
Spring Annotations - JournalDev
I am trying to write a unit test for my "OncePerRequestFilter" filter. The problem is the Autowired bean appearing as null inside "public OncePerRequestFilter clientInterceptorFilter()" only in a unit test. The code snippets of my unit test and Filter class is copied below. Can somebody guide me how I can inject the dependent beans in Servlet filter.
My Unit test code goes here
#RunWith(SpringRunner.class)
#ImportAutoConfiguration({ RefreshAutoConfiguration.class })
#TestPropertySource(locations = "classpath:application.properties")
#Import({ FilterConfig.class, IAuthServiceClient.class, AppConfig.class })
// #TestExecutionListeners({
// DependencyInjectionTestExecutionListener.class,
// DirtiesContextTestExecutionListener.class})
public class FilterConfigTest implements AppConstants {
#MockBean
private IAuthServiceClient authService;
#Autowired
FilterConfig config;
#Autowired
ResourceLoader loader;
#Autowired
AppConfig appconfig;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MockFilterChain chain;
private OncePerRequestFilter filter;
#SuppressWarnings("serial")
#Before
public void setUp() throws Exception {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
chain = new MockFilterChain();
filter = new FilterConfig().clientInterceptorFilter();
}
#Test
public void validTokenTest() throws IOException, ServletException {
BDDMockito.given(authService.getPrincipal(anyString(), anyList())).willReturn(getStubbedPrincipal());
this.request.addHeader(HttpHeaders.AUTHORIZATION, "sometoken");
this.request.addHeader(XH_AUTH_HEADER, "someauthheader");
this.filter.doFilter(this.request, this.response, this.chain);
}
}
My Filter class is below. Both "authService" and "config" are null inside "public OncePerRequestFilter clientInterceptorFilter() " function
#Configuration
public class FilterConfig implements AppConstants {
#Autowired
private IAuthServiceClient authService;
#Autowired
AppConfig config;
private final static Logger logger = LoggerFactory.getLogger(FilterConfig.class);
#Bean
public OncePerRequestFilter clientInterceptorFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorization = Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION)).orElse(null);
String xhAuth = Optional.ofNullable(request.getHeader(XH_AUTH_HEADER)).orElse(null);
List<String> scopes = config.getscopesAsList();
try {
Principal principal = authService.getPrincipal(authorization, scopes);
if(principal != null) {
//do something
filterChain.doFilter(request, response);
}
} catch (Exception e) {
throw new NoAuthorizedPrincipalFound(HttpStatus.UNAUTHORIZED, INVALID_AUTOIRIZED_PRINCIPAL);
}
}
};
}
}
Background
Let's say we have a #RestController with the following method (Spring Boot 1.3.5.RELEASE):
#RequestMapping(value = "/helloworld", method = RequestMethod.POST)
public Map<String, String> helloWorld(#RequestBody Map<String, String> m) {
m.put("Hello", "2");
m.put("World", "1");
return m;
}
And a #Test using TestRestTemplate:
RestTemplate restTemplate = new TestRestTemplate();
#Test
public void testHelloWorld() {
Map<String, String> request = new HashMap<>();
request.put("Hello", "1");
request.put("World", "2");
Map<String, String> respons = this.restTemplate.postForObject("/helloworld", request, Map.class);
}
Question
How can one print/log the actual request/respons being sent/received?
I.e how to print/log the serialized versions of the request/respons?
This is availible on the level of request filters:
public class YourCustomFilter implements Filter {
#Override
public void destroy() {
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// your code here
}
#Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// your code here
}
}
But, the question is: Would you really like to do this and because of what? Remember, that this is really error prone.
You can use aspect to log request and response
example snippet of request:
#Aspect
#Component
public class InputLoggerAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(InputLoggerAspect.class);
#Autowired
private ObjectMapper objectMapper;
#Before(value = "valueToPointCut")
public void before(JoinPoint pointcut) throws Exception {
Object[] args = pointcut.getArgs();
for (Object object : args) {
LOGGER.info("{}:{}", object.getClass(), objectMapper.writeValueAsString(object));
}
}
}
I need to use autowired in a filter. So i annotate my filter class using #Component,
import org.springframework.web.filter.GenericFilterBean;
#Component
public class TokenAuthorizationFilter extends GenericFilterBean {
#Autowired
public EnrollCashRepository enrollCashRepository;
}
Then i add my filter as below in SecurityConfig,
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity webSecurity) throws Exception
{
webSecurity.ignoring().antMatchers(HttpMethod.GET, "/health");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new TokenAuthorizationFilter(), BasicAuthenticationFilter.class);
http.authorizeRequests().antMatchers("/api/**").authenticated();
}
My problem is my filter get invoked twice with the #Component annotation. If i remove the #Component annotation it only invoke once.
Then i add below as a fix in my Spring boot main class. Then i comment the line of addFilterBefore in SecurityConfig.
#Bean
public FilterRegistrationBean tokenAuthFilterRegistration() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new PITokenAuthorizationFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setEnabled(false);
return filterRegistrationBean;
}
But then my filter get invoked once. But even i make the setEnabled true or false, i get a 403 Forbiddon Error when i invoke my rest api, http://localhost:8080/api/myservice
How can i fix this situation where i can use #Autowired in my Spring Filter?
Edit: Add controller and Filter class,
#RestController
#RequestMapping(value = "/api")
public class SpringToolController {
#RequestMapping(value = "/myservice", method = RequestMethod.GET)
public HttpEntity<String> myService() {
System.out.println("-----------myService invoke-----------");
return new ResponseEntity<String>(HttpStatus.OK);
}
}
public class TokenAuthorizationFilter extends GenericFilterBean {
public TokenAuthorizationFilter(EnrollCashRepository enrollCashRepository) {
this.enrollCashRepository = enrollCashRepository;
}
public EnrollCashRepository enrollCashRepository;
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
System.out.println("before PITokenAuthorizationFilter");
chain.doFilter(servletRequest, servletResponse);
System.out.println("after PITokenAuthorizationFilter");
}
public EnrollCashRepository getEnrollCashRepository() {
return enrollCashRepository;
}
public void setEnrollCashRepository(EnrollCashRepository enrollCashRepository) {
this.enrollCashRepository = enrollCashRepository;
}
}
Remove your FilterRegistrationBean and initialize TokenAuthorizationFilter inside your SecurityConfig like this:
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public EnrollCashRepository enrollCashRepository;
#Override
public void configure(WebSecurity webSecurity) throws Exception
{
webSecurity.ignoring().antMatchers(HttpMethod.GET, "/health");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.addFilterBefore(tokenAuthorizationFilter(), BasicAuthenticationFilter.class);
http.authorizeRequests().antMatchers("/api/**").authenticated();
}
private TokenAuthorizationFilter tokenAuthorizationFilter()
{
return new TokenAuthorizationFilter(enrollCashRepository);
}
}
Remove #Autowired and #Component annotation and set your EnrollCashRepository with constructor injection:
import org.springframework.web.filter.GenericFilterBean;
public class TokenAuthorizationFilter extends GenericFilterBean {
private final EnrollCashRepository enrollCashRepository;
public TokenAuthorizationFilter(EnrollCashRepository enrollCashRepository)
{
this.enrollCashRepository = enrollCashRepository
}
}
I Added a Test Filter to my working class now and it worked fine. Here are the codes related to it.
Filter
#Component
public class TestFilter extends GenericFilterBean {
private static final Logger logger = LoggerFactory.getLogger(TestFilter.class);
#Autowired
UserService userService;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.error("=====================AWESOME=======================");
chain.doFilter(request, response);
userService.activate("123"); //this works
}
}
App Security Config
#Configuration
#EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private TestFilter testFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
//loginFailureHandler.setDefaultFailureUrl("/login?error=true");
http.addFilterBefore(testFilter, BasicAuthenticationFilter.class);
//Http other config here.
}
}
App Config
#Configuration
#ImportResource({
"classpath*:/context.xml"
})
#PropertySources(
#PropertySource({
"classpath:/application.yml"
})
)
#Import({AppSecurityConfig.class, WebConfig.class,TestFilter.class})
public class AppConfig {
}