Trying to test a spring controller that we have for multiple file upload. Here is the controller:
#RequestMapping("/vocabularys")
#Controller
public class VocabularyController {
...
The action I want to test:
#RequestMapping(value = "/import", method = {RequestMethod.PUT, RequestMethod.POST})
#ResponseBody
#CacheEvict(value="vocabulary", allEntries=true)
public Object importVocabulary(MultipartHttpServletRequest request, HttpServletResponse response) {
...
The resolver I have in the webmvc-config.xml:
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
The code works just fine and all. I'm running into problems when I am trying to unit/integration test this.
Here is my attempt at the test:
public class VocabularyControllerTest extends BaseControllerTest {
static final private String AdminUsername = "administrator";
#Test
public void shouldBeAbleToUploadAFile() throws Exception {
createTestWorkspace();
login(AdminUsername, "*");
MockMultipartFile file = new MockMultipartFile("test_vocab.xml", new FileInputStream("src/test/files/acme_vocabulary.xml"));
MockMultipartHttpServletRequestBuilder mockMultipartHttpServletRequestBuilder = (MockMultipartHttpServletRequestBuilder) fileUpload("/vocabularys/import").accept(MediaType.ALL).session(httpSession);
mockMultipartHttpServletRequestBuilder.file(file);
mockMultipartHttpServletRequestBuilder.content("whatever");
ResultActions resultActions = mockMvc.perform(mockMultipartHttpServletRequestBuilder);
resultActions.andExpect(status().isFound());
}
}
Ignore the createWorkspace() and login() and stuff - those are for passing through some security filters.
The relevant part of the BaseControllerTest:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy({
#ContextConfiguration(locations = {
"file:src/test/resources/META-INF/spring/applicationContext.xml",
"file:src/test/resources/META-INF/spring/applicationContext-security.xml",
"file:src/main/resources/META-INF/spring/applicationContext-database.xml",
"file:src/main/resources/META-INF/spring/applicationContext-activiti.xml",
"file:src/main/resources/META-INF/spring/applicationContext-cache.xml",
"file:src/main/resources/META-INF/spring/applicationContext-jms.xml",
"file:src/main/resources/META-INF/spring/applicationContext-mail.xml",
"file:src/main/resources/META-INF/spring/applicationContext-mongo.xml"}),
#ContextConfiguration(locations = {
"file:src/main/webapp/WEB-INF/spring/webmvc-config.xml",
"file:src/test/webapp/WEB-INF/spring/applicationContext-filters.xml"})
})
#Transactional
public class BaseControllerTest extends BaseTest {
#Autowired
WebApplicationContext wac;
#Autowired
MockHttpSession httpSession;
#Autowired
MockServletContext servletContext;
#Autowired
OpenEntityManagerInViewFilter openEntityManagerInViewFilter;
#Autowired
HiddenHttpMethodFilter hiddenHttpMethodFilter;
#Autowired
CharacterEncodingFilter characterEncodingFilter;
#Autowired
SessionFilter sessionFilter;
#Autowired
WorkflowAsSessionFilter workflowAsSessionFilter;
#Autowired
FilterChainProxy springSecurityFilterChain;
#Autowired
RequestFilter requestFilter;
MockMvc mockMvc;
protected static final String TestFileDir = "src/test/files/";
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(openEntityManagerInViewFilter, "/*")
.addFilter(hiddenHttpMethodFilter, "/*")
.addFilter(characterEncodingFilter, "/*")
.addFilter(sessionFilter, "/*")
.addFilter(workflowAsSessionFilter, "/*")
.addFilter(springSecurityFilterChain, "/*")
.addFilter(requestFilter, "/*")
.build();
servletContext.setContextPath("/");
Session session = Session.findBySessionId(httpSession.getId());
if (session == null) {
session = new Session();
session.setJsessionid(httpSession.getId());
session.persist();
}
}
...
The issue is that when I try debugging this, the perform action on the mockMvc object never hits my controller method. I thought it was an issue getting past our security filters (which is why I have all the login and stuff) but I tested other actions in the vocabulary controller and I am able to hit them just fine.
Thoughts? Ideas? Suggestions?
Alright, found the issue.
Spring's MockMultipartHttpServletRequestBuilder returns a MockHttpMultipartServletRequest object eventually.
What the browser does however is post a multipart-encoded request which then gets picked up and parsed by the CommonsMultipartResolver bean defined in the XML.
In the test however, since we are already posting a MockHttpMultipartServletRequest, we don't want the resolver parsing this, so all we got to do is have a profile where the resolver doesn't kick in.
What we have chosen to do however is end up constructing a MockHttpServletRequest that has multipart encoding and put it through the Spring filters so that we can also integration test the resolver kicking in.
Unfortunately I don't see any support/helper in the Spring testing lib which allows you to take a MockHttpServletRequest and addPart() to it, or something to that effect => handcoded browser emulation function :(
The simple way how to test multipart upload is use StandardServletMultipartResolver.
and for test use this code:
final MockPart profilePicture = new MockPart("profilePicture", "stview.jpg", "image/gif", "dsdsdsd".getBytes());
final MockPart userData = new MockPart("userData", "userData", "application/json", "{\"name\":\"test aida\"}".getBytes());
this.mockMvc.perform(
fileUpload("/endUsers/" + usr.getId().toString()).with(new RequestPostProcessor() {
#Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.addPart(profilePicture);
request.addPart(userData);
return request;
}
})
MockPart class
public class MockPart extends MockMultipartFile implements Part {
private Map<String, String> headers;
public MockPart(String name, byte[] content) {
super(name, content);
init();
}
public MockPart(String name, InputStream contentStream) throws IOException {
super(name, contentStream);
init();
}
public MockPart(String name, String originalFilename, String contentType, byte[] content) {
super(name, originalFilename, contentType, content);
init();
}
public MockPart(String name, String originalFilename, String contentType, InputStream contentStream) throws IOException {
super(name, originalFilename, contentType, contentStream);
init();
}
public void init() {
this.headers = new HashMap<String, String>();
if (getOriginalFilename() != null) {
this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"; filename=\"" + getOriginalFilename() + "\"");
} else {
this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"");
}
if (getContentType() != null) {
this.headers.put("Content-Type".toLowerCase(), getContentType());
}
}
#Override
public void write(String fileName) throws IOException {
}
#Override
public void delete() throws IOException {
}
#Override
public String getHeader(String name) {
return this.headers.get(name.toLowerCase());
}
#Override
public Collection<String> getHeaders(String name) {
List<String> res = new ArrayList<String>();
if (getHeader(name) != null) {
res.add(getHeader(name));
}
return res;
}
#Override
public Collection<String> getHeaderNames() {
return this.headers.keySet();
}
}
Related
This is how my microservices classes are. I have two questions.
Firstly, when I run the microservice locally, the swagger document does not open automatically. When I enter the link in the form of host/v2/api-docs with my hand, it opens as json, but the ui part does not come. I can edit and view it with the swagger editor. I added dependency to pom.xml for the UI part, but it doesn't work, how to open the UI screen?
Secondly,
Except for host/v2/api-docs, when I type a link to a controller specifically, I get a 403 authorization error. This is the most important problem that I want to overcome, how can I do it? can you help me?
link/swagger-ui.html#!/signin
localhost:8000/swagger-ui.html#!/signin
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Jun 23 16:04:09 TRT 2022
There was an unexpected error (type=Forbidden, status=403).
Access Denied
My pom.xml is :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
My SwaggerConfig class is:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any()).build().apiInfo(metaData());
}
#Bean
public UiConfiguration uiConfiguration() {
return UiConfigurationBuilder.builder().deepLinking(true).validatorUrl(null).build();
}
private static final Contact DEFAULT_CONTACT = new Contact("Rosaline Fox,Anna Hurt", "http://www.google.com",
"rosaline.fox#gmail.com,anna.hurt#gmail.com");
private ApiInfo metaData() {
return new ApiInfoBuilder().title("Auth Service Controller API Title")
.description("Auth Service Controller API Description").version("1.0")
.license("Apache License Version 2.0").licenseUrl("https://www.apache.org/licenses/LICENSE-2.0")
.contact(DEFAULT_CONTACT).build();
}
}
My WebSecurityConfig class is:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String XI_PARTNER = "XIPartner";
private static final String XI_CONSULTANT = "XIConsultant";
private static final String SALES = "Sales";
private static final String STANDART = "Standart";
public static final String ADMIN = "Admin";
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Autowired
private FilterChainExceptionHandler filterChainExceptionHandler;
#Autowired
private HandlerExceptionResolver handlerExceptionResolver;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Disable CSRF (cross site request forgery)
http.csrf().disable();
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
// No session will be created or used by spring security
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class);
http.exceptionHandling().accessDeniedHandler((req, res, e) -> handlerExceptionResolver.resolveException(req, res, null, e));
// Entry points
http.authorizeRequests()
.antMatchers("/**/signin/otp", "/**/signin/**", "/**/v2/api-docs/**", "/**/swagger-ui.html#/**").permitAll()
.antMatchers("/**/customers/create").hasAnyAuthority(SALES)
.antMatchers("/**/customers/update").hasAnyAuthority(SALES)
.antMatchers("/**/customers/all").hasAnyAuthority(SALES)
.antMatchers("/**/customers/deactivate").hasAnyAuthority(SALES)
.antMatchers("/**/customers/reactivate").hasAnyAuthority(SALES)
.antMatchers("/**/products/create").hasAnyAuthority(SALES)
.antMatchers("/**/products/update").hasAnyAuthority(SALES)
.antMatchers("/**/users/create").hasAnyAuthority(SALES)
.antMatchers("/**/users/update").hasAnyAuthority(SALES)
.antMatchers("/**/users/deactivate").hasAnyAuthority(SALES)
.antMatchers("/**/users/reactivate").hasAnyAuthority(SALES)
.antMatchers("/**/admin/user/all").hasAnyAuthority(ADMIN)
.antMatchers("/**/xicustomers/create").hasAnyAuthority(SALES)
.antMatchers("/**/xicustomers/update").hasAnyAuthority(SALES)
.antMatchers("/**/xicustomers/all").hasAnyAuthority(SALES)
.antMatchers("/**/partner/create").hasAnyAuthority(SALES)
.antMatchers("/**/xicustomers/list").hasAnyAuthority(XI_PARTNER,XI_CONSULTANT)
.antMatchers("/**/report/list/**").hasAnyAuthority(XI_CONSULTANT)
.antMatchers("/**/originator").hasAnyAuthority(STANDART)
.antMatchers("/**/blackhour/add").hasAnyAuthority(STANDART)
.antMatchers("/**/blackhour").hasAnyAuthority(STANDART)
.antMatchers("/**/access/**").anonymous()
.antMatchers("/**/pwd/forgot").anonymous()
.antMatchers("/**/maximo").anonymous()
.anyRequest().authenticated();
// Apply JWT
http.apply(new JwtTokenFilterConfigurer(jwtTokenProvider));
// Optional, if you want to test the API from a browser
// http.httpBasic();
}
#Override
public void configure(WebSecurity web) throws Exception {
// Allow eureka client to be accessed without authentication
web.ignoring().antMatchers("/*/")//
.antMatchers("/eureka/**")//
.antMatchers(HttpMethod.OPTIONS, "/**"); // Request type options
// should be
// allowed.
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
}
My Controller Class :
#ApiOperation(value = "Login Service", notes = "Login service with captcha verification.")
#PostMapping("/signin")
#ResponseBody
public ResponseEntity<LoginResponse> login(#RequestBody LoginRequest loginRequest) {
LoginResponse loginResponse = this.loginService.login(loginRequest);
return ResponseEntity.accepted().body(loginResponse);
}
My JwtTokenFilterConfigurer class :
public class JwtTokenFilterConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilterConfigurer(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(this.jwtTokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
My JwtTokenFilter class :
#Slf4j
#Component
public class JwtTokenFilter extends GenericFilterBean {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
//HttpServletResponse response = (HttpServletResponse) res;
String requestURI = request.getRequestURI();
String token = getBearerToken((HttpServletRequest) req);
if (token != null && !requestURI.contains("/signin/otp")) {
TokenParams params = null;
try {
params = this.jwtTokenProvider.validateToken(token);
} catch (JwtException | IllegalArgumentException e) {
log.warn("Invalid Token: {}, Error: {}", params, e.getMessage());
//response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "INVALID JWT token");
//return;
throw new UnauthorizedException();
}
if (!params.getRoles().contains(WebSecurityConfig.ADMIN) && params.isForOtp() == true) {
log.warn("Invalid Token: {}, it is for OTP!", params);
//response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "INVALID JWT token");
//return;
throw new UnauthorizedException();
}
Authentication auth = this.jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
HeaderMapRequestWrapper wrappedRequest = new HeaderMapRequestWrapper(request);
wrappedRequest.addHeader("companyId", params.getCompanyId());
wrappedRequest.addHeader("user", params.getEmail());
filterChain.doFilter(wrappedRequest, res);
} else {
filterChain.doFilter(req, res);
}
}
private static final String AUTHORIZATION = "Authorization";
private String getBearerToken(HttpServletRequest req) {
String bearerToken = req.getHeader(AUTHORIZATION);
/*
* if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
* return bearerToken.substring(7, bearerToken.length()); }
*/
if (bearerToken != null) {
return bearerToken;
}
return null;
}
}
I am creating an aspect to register my application using org.springframework.web.bind.annotation.RestController like #Pointcut, this works perfectly when my class responds normally, but when an exception occurs for some reason, the returned httpStatus is always 200, even If my http response returns 500 when an error occurs, I think this is because RestController does not set the http status, but delegates it to the exception handler, how do I fix this and still have traceability on top of the restcontroller?
Follow my rest controller
#Slf4j
#RestController
#RequestMapping("/api/conta")
public class ContaResourceHTTP {
#JetpackMethod("Pagamento de conta")
#PostMapping("/pagamento")
public void realizarPagamento(#RequestBody DTOPagamento dtoPagamento) throws InterruptedException
{
}
#JetpackMethod("TransferĂȘncia entre bancos")
#PostMapping("/ted")
public void realizarTED(#RequestBody DTOPagamento dtoPagamento) throws java.lang.Exception
{
if(true)
throw new Exception("XXX");
//log.info(dtoPagamento.toString());
}
}
my AOP implementation:
#Aspect
#Component
#EnableAspectJAutoProxy(proxyTargetClass = true)
#Slf4j
public class MetricsAspect {
//#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
#Pointcut("execution(* javax.servlet.http.HttpServlet.*(..)) *)")
public void springBeanPointcut() {
}
#Autowired
Tracer tracer;
#Around("springBeanPointcut()")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
long inicioProcesso = System.currentTimeMillis();
joinPoint.proceed();
long finalProcesso = System.currentTimeMillis();
long duracaoProcesso = finalProcesso - inicioProcesso;
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getResponse();
Metrics metricas = new Metrics();
metricas.setDuracaoMs(duracaoProcesso);
metricas.setDataHoraRequisicao(milissegundosToStringDate(inicioProcesso));
metricas.setDataHoraResposta(milissegundosToStringDate(finalProcesso));
metricas.setServidorOrigem(request.getRemoteAddr());
metricas.setPortaOrigem(request.getRemotePort());
metricas.setDominioAcesso(request.getLocalName());
metricas.setPortaAcesso(request.getLocalPort());
metricas.setUrlPath(request.getRequestURI());
metricas.setMetodoHttp(request.getMethod());
metricas.setIdTransacao(tracer.currentSpan().context().traceIdString());
metricas.setIdSpan(tracer.currentSpan().context().spanIdString());
metricas.setStatusHttp(response.getStatus());
log.info(JSONConversor.toJSON(metricas));
}
public String milissegundosToStringDate(long ms) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Date dataInicial = new Date(ms);
return dateFormat.format(dataInicial);
}
}
My exception handler:
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ExceptionControllerAdvice {
#ExceptionHandler({ Throwable.class })
public ResponseEntity<ApiError> handlerValidationException2(Throwable e) {
return new ResponseEntity<>(new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, e, traceRespostaAPI),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
After a while I was able to solve the problem with a solution that may not be the most elegant for the problem, basically I used two pointcuts, one in the restcontroller to intercept the #JetpackMethod annotation value and add it to the http response header with advice before and another around HttpServlet that really is the one who really gets back with the modified http status.
Here's the code below that solved my problem.
This class intercepts annotation and adds its value to the header.
#Aspect
#Component
public class InterceptRestAnnotationAspect {
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllerExecution() {}
#Before("restControllerExecution()")
public void setMetodoHttpHeader(JoinPoint joinPoint) throws Throwable {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getResponse();
String origem = VerificadorOrigem.processarOrigem(joinPoint);
response.setHeader("nomeMetodo", origem);
}
}
This other class logs the servlet metrics I needed and can retrieve the value entered in the header earlier.
#Aspect
#Component
#Slf4j
public class MetricsAspect {
#Pointcut("execution(* javax.servlet.http.HttpServlet.*(..)) *)")
public void servletService() {
}
#Autowired
Tracer tracer;
#Around("servletService()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
long inicioProcesso = System.currentTimeMillis();
Object result = joinPoint.proceed();
long finalProcesso = System.currentTimeMillis();
long duracaoProcesso = finalProcesso - inicioProcesso;
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getResponse();
Metrics metricas = new Metrics();
String funcionalidade = response.getHeader("nomeMetodo") == null ? "Indeterminada"
: response.getHeader("nomeMetodo");
metricas.setNivelLog("INFO");
metricas.setFuncionalidade(funcionalidade);
metricas.setDuracaoMs(duracaoProcesso);
metricas.setDataHoraRequisicao(ManipulaData.milissegundosToStringDate(inicioProcesso));
metricas.setDataHoraResposta(ManipulaData.milissegundosToStringDate(finalProcesso));
metricas.setServidorOrigem(request.getRemoteAddr());
metricas.setPortaOrigem(request.getRemotePort());
metricas.setDominioAcesso(request.getLocalName());
metricas.setPortaAcesso(request.getLocalPort());
metricas.setUrlPath(request.getRequestURI());
metricas.setMetodoHttp(request.getMethod());
metricas.setIdTransacao(tracer.currentSpan().context().traceIdString());
metricas.setIdSpan(tracer.currentSpan().context().spanIdString());
metricas.setStatusHttp(response.getStatus());
log.info(JSONConversor.toJSON(metricas));
return result;
}
}
I don't think the code after joinPoint.proceed(); gets executed in case of Exceptions.
You can have a different advice for execution in case of Exceptions:
#AfterThrowing(pointcut = "springBeanPointcut()", throwing = "e")
public void afterThrowingAdvice(JoinPoint jp, Exception e) {
....
}
I am trying to find a solution which will build a new link using X-Forwarded-* headers.
public class ApiUriBuilderTest {
private MockHttpServletRequest request = new MockHttpServletRequest();
private HttpRequest httpRequest = new ServletServerHttpRequest(request);
#Before
public void setUp() throws Exception {
request.setScheme("http");
request.setServerName("localhost");
request.setServerPort(80);
request.setRequestURI("/mvc-showcase");
request.addHeader("X-Forwarded-Proto", "https");
request.addHeader("X-Forwarded-Host", "84.198.58.199");
request.addHeader("X-Forwarded-Port", "443");
request.setContextPath("/mvc-showcase");
request.setServletPath("/app");
request.setRequestURI("/mvc-showcase/app/uri/of/request?hello=world&raw#my-frag");
httpRequest = new ServletServerHttpRequest(request);
}
#Test
public void test() {
String uri = ForwardedContextPathServletUriComponentsBuilder.fromRequest(request).build().toUriString();
assertThat(uri, is("https://84.198.58.199:443"));
}
#Test
public void test_uri_components_builder() throws URISyntaxException {
UriComponents result = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
assertEquals("https://84.198.58.199:443", result.toString());
}
But the returning value is "https://84.198.58.199/mvc-showcase/app/uri/of/request?hello=world&raw#my-frag". How can I possible get rid of context-path, setvlet-path and request uri?
#Test
public void test() {
String uri = ServletUriComponentsBuilder.fromRequest(request).replacePath("relativePath").replaceQuery(null).build().toUriString();
assertThat(uri, is("https://84.198.58.199:8080/relativePath"));
}
helped.
I want to write a simple test but my SecurityConfig gives me always Access Denied. Here is my config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new TokenAuthenticationFilter(userService), AnonymousAuthenticationFilter.class);
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
//Options preflight
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll();
//ACL
http.authorizeRequests().antMatchers(HttpMethod.POST, "/auth/password").hasAnyRole("ADMIN", "CUSTOMER", "REFUGEE");
http.authorizeRequests().antMatchers("/auth/**").anonymous();
//Tags
http.authorizeRequests().antMatchers(HttpMethod.GET, "/tags").hasAnyRole("ADMIN", "CUSTOMER", "REFUGEE");
http.authorizeRequests().antMatchers(HttpMethod.GET, "/tags/recommendation").hasAnyRole("ADMIN", "CUSTOMER", "REFUGEE");
http.authorizeRequests().antMatchers(HttpMethod.POST, "/tags").hasAnyRole("ADMIN", "REFUGEE");
http.authorizeRequests().antMatchers(HttpMethod.GET, "/tags/recommendation/random").hasAnyRole("ADMIN", "CUSTOMER", "REFUGEE");
http.authorizeRequests().antMatchers(HttpMethod.PUT, "/tags").hasAnyRole("ADMIN");
http.authorizeRequests().antMatchers(HttpMethod.GET, "/tags/notapproved").hasAnyRole("ADMIN");
http.authorizeRequests().antMatchers(HttpMethod.PUT, "/tags/approve/{id}").hasAnyRole("ADMIN");
}
And the AuthenticationFilter:
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private UserService userService;
public TokenAuthenticationFilter(UserService userService) {
super("/");
this.userService = userService;
}
private final String HEADER_SECURITY_TOKEN = "Authorization";
private final String PARAMETER_SECURITY_TOKEN = "access_token";
private String token = "";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
this.token = request.getHeader(HEADER_SECURITY_TOKEN);
if ("".equals(this.token) || this.token == null) {
this.token = request.getParameter(PARAMETER_SECURITY_TOKEN);
}
//Attempt to authenticate
Authentication authResult;
authResult = attemptAuthentication(request, response);
if (authResult == null) {
chain.doFilter(request, response);
} else {
successfulAuthentication(request, response, chain, authResult);
}
}
/**
* Attempt to authenticate request - basically just pass over to another
* method to authenticate request headers
*/
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Authentication userAuthenticationToken = authUserByToken();
if (userAuthenticationToken == null) {
//throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
}
return userAuthenticationToken;
}
/**
* authenticate the user based on token, mobile app secret & user agent
*
* #return
*/
private Authentication authUserByToken() {
Authentication securityToken = null;
try {
User user = userService.findUserByAccessToken(this.token);
securityToken = new PreAuthenticatedAuthenticationToken(
user, null, user.getAuthorities());
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return securityToken;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
And the simple test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath*:spring/test/test-servlet.xml"})
#WebAppConfiguration
public class TagControllerTest {
private MockMvc mockMvc;
private TagService tagServiceMock;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Resource
private WebApplicationContext webApplicationContext;
#Before
public void setUp() {
tagServiceMock = mock(TagService.class);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.addFilters(this.springSecurityFilterChain)
.build();
}
public TagControllerTest() {
}
/**
* Test of getAllTags method, of class TagController.
*/
#Test
public void testGetAllTags() throws Exception {
List<Tags> tagsList = new ArrayList<>();
tagsList.add(new Tags((long) 1, "test", 2, 1));
tagsList.add(new Tags((long) 2, "test2", 4, 0));
User user = TestUtil.EMPTY_USER;
String query = "";
String notIncluded = "";
Integer limit = 8;
Integer start = 0;
Language language = new Language(0);
Boolean approved = false;
when(tagServiceMock.findTagsByQuery(user, query, limit, start, language, approved)).thenReturn(tagsList);
when(tagServiceMock.findTags(user, limit, start, language)).thenReturn(tagsList);
SecurityContextHolder.getContext().setAuthentication(TestUtil.getPrincipal("ROLE_ADMIN"));
mockMvc.perform(get("/tags").with(securityContext(SecurityContextHolder.getContext())).header("host", "localhost:80"))
.andExpect(status().isOk())
.andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].id", is(1)))
.andExpect(jsonPath("$[1].id", is(2)))
.andExpect(jsonPath("$[0].label", is("test")))
.andExpect(jsonPath("$[1].label", is("test2")))
.andExpect(jsonPath("$[0].count", is(2)))
.andExpect(jsonPath("$[1].count", is(4)))
.andExpect(jsonPath("$[0].approved", is(1)))
.andExpect(jsonPath("$[1].approved", is(0)));
verify(tagServiceMock, times(1)).findTagsByQuery(user, query, limit, start, language, approved);
verifyNoMoreInteractions(tagServiceMock);
}
The problem is, that I always get a 403 -> Access Denied. It is because of the TokenAuthenticationFilter. If i set a header named "Authorization" with an accesstoken that is correct (correct means that is is used by an actual user) then it works. But i guess thats not how it should be for unit tests. So what to do? How to set a Role and pass the SecurityFilter?
You should turn off Spring security for the purpose of the tests if you are not doing integration tests which should include it.
I guess this is simple problem, but I am unable to get my head around it. this is the class for which I need to write test case
#Controller
#SessionAttributes
public class LoginController {
#RequestMapping(value = "/Login", method = RequestMethod.GET)
public ModelAndView displayLogin(#RequestParam(value = "error", required = false) String error,
#RequestParam(value = "logout", required = false) String logout,
HttpServletRequest request,
HttpServletResponse response) {
ModelAndView modelForLogin = new ModelAndView();
if (error != null) {
// Include login failure message
modelForLogin.addObject("loginFailure", "Invalid username and password!");
}
if ("user".equals(logout)) {
// Include logout message
modelForLogin.addObject("msg", "You've been logged out successfully.");
}
else {
modelForLogin.addObject("msg","");
}
modelForLogin.setViewName("Login");
return modelForLogin;
}
}
This is what I have got till now...
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration({ " servlet-xml "})
public class LoginControllerTest {
#Mock HttpServletRequest request;
#Mock HttpServletResponse response;
#Mock HttpSession session;
private MockMvc mockMvc;
#Before
protected void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ModelAndView modelForLogin = mockito.mock(ModelAndView.class);
mockito.when(modelForLogin.error()).thenReturn("error");
mockito.when(modelForLogin.logout()).thenReturn("logout");
}
#Test
public void TestLoginError() throws Exception {
mockMvc.perform(get("/Login").param()).andExpect(status().isOk()).andExpect(model().attributeExists("msg"));
}
#Test
public void testLogin() throws Exception {
mockMvc.perform(get("/Login")).andExpect(status().isOk());
mockMvc.perform(get("/Login").param("logout", "log")).andExpect(status().isOk()).andExpect(model().attributeExists("msg"));
mockMvc.perform(get("/Login").param("error", "log")).andExpect(status().isOk()).andExpect(model().attributeExists("error"));
mockMvc.perform(get("/Login").param("logout", "log").param("error", "log")).andExpect(status().isOk()).andExpect(model().attributeExists("msg")).andExpect(model().attributeExists("error"));
mockMvc.perform(get("/Login")).andExpect(status().isOk()).andExpect(view().name("login"));
}
}
Can anyone please let me know proper way to write test case for this?
Given the code you have, there is no need to mock. A sample test case would look like below:
ModelAndView mvw = displayLogin("error", null, null, null);
assertEquals("Invalid username and password!", mvw.getModelMap().get("loginFailure"));