-
-
Save brunopk/37ef22a18c08ccda647e4cbecc972735 to your computer and use it in GitHub Desktop.
Para usar una base de datos H2 en memoria, y poder ver el contenido mientras se está corriendo un test, es decir hacer consultas mientras la ejecución de un test está bloqueada en un breakpoint, se debe tener la siguiente configuración en el archivo de properties:
spring:
h2:
console:
enabled: true
path: /h2-console
Una forma de acceder a una base de datos H2 es por cualquier browser a través de la URL : http://localhost:8080/h2-console (ver final de este archivo para obtener el puerto correcto).
Para poder visualizar bien el cliente web que dispone H2, también se debe editar las clases de configuración de Spring correcta. En este caso, se debe editar la clase (bean) de configuración que extienda de WebSecurityConfigurationLocal
y exista en el ambiente (profile) de test que esta siendo utilizado:
package x;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* WebSecurity Configuration Local.
*/
@Configuration
@Profile({"local | integration_test"})
public class WebSecurityConfigurationLocal extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.headers().frameOptions().disable();
httpSecurity.csrf().disable().authorizeRequests().anyRequest().permitAll();
}
}
Lo importante aquí es que se invoque frameOptions()
.
Finalmente, se debe agregar un loop infinito while(true) {}
enseguida después de la parte de código que se quiera analizar, por ejemplo, después de hacer un INSERT
en base, sobre el cual se quiera saber bien que y como es lo que se está insertando.
El puerto es el mismo en el que el servidor (ejemplo Jetty) dispone la aplicación. Se puede encontrar en la salida del servidor, buscando la palabra port
:
Endpoint que retorna un archivo (byte[]
) a partir de una ruta absoluta (/Users/brpiaggio/Downloads/latest.tar.gz
) :
@GetMapping(value = "/latest.tar.gz", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public @ResponseBody byte[] getFile() throws IOException {
InputStream inputStream = Files.newInputStream(Paths.get("/Users/brpiaggio/Downloads/latest.tar.gz"));
return inputStream.readAllBytes();
}
package com.your.package;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class TestTasks {
@Async
@Scheduled(cron = "*/10 * * * * *")
public void testTask() {
String currentThread = Thread.currentThread().getName();
System.out.println(String.format("Starting testTask on thread %s...", currentThread));
long timeElapsed = 0;
long currentTimeMillis = System.currentTimeMillis();
long seconds = 0;
long secondsAux = 0;
while (timeElapsed < 8 * 1000) {
if (secondsAux > seconds) {
seconds = secondsAux;
System.out.println(String.format("Seconds elapsed (thread %s): %s", currentThread, secondsAux));
}
timeElapsed = System.currentTimeMillis() - currentTimeMillis;
secondsAux = timeElapsed / 1000;
}
while (8 * 1000 <= timeElapsed && timeElapsed < (8 * 1000) + 1000 * 1000) {
if (secondsAux > seconds) {
seconds = secondsAux;
System.out.println(String.format("Seconds elapsed (thread %s): %s", currentThread, secondsAux));
}
timeElapsed = System.currentTimeMillis() - currentTimeMillis;
secondsAux = timeElapsed / 1000;
}
System.out.println("Finalizing testTask...");
}
}
- Set the
CommonsRequestLoggingFilter
filter with a Spring bean in a configuration file (@Configuration
annotated class):@Bean public FilterRegistrationBean<CommonsRequestLoggingFilter> loggingFilterRegistration() { CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); filter.setIncludeQueryString(true); filter.setIncludePayload(true); filter.setBeforeMessagePrefix("PAYLOAD: "); FilterRegistrationBean<CommonsRequestLoggingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setName("CommonsRequestLoggingFilter"); registrationBean.setFilter(filter); registrationBean.setOrder(2); registrationBean.addUrlPatterns("*/bigqueue/consumer"); return registrationBean; }
- Set
DEBUG
log level forCommonsRequestLoggingFilter
in your properties:spring: logging: level: org: springframework: web: filter: CommonsRequestLoggingFilter: 'INFO'
- Configure Spring to log all set with
DEBUG
, for example if you have alogback-spring.xml
, log level is set with something like this :<?xml version="1.0" encoding="UTF-8"?> <configuration scan="false"> <!-- use Spring default values --> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <springProfile name="local | integration_test"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- @formatter:off --> <pattern> %clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(--){faint} %clr([%1.15t]){faint} %clr(%-1.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}} </pattern> <!-- @formatter:on --> <charset>utf8</charset> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="STDOUT"/> </root> </springProfile> ...
-
Create the
CachedHttpServletRequest
class :package yourpackage; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.springframework.util.StreamUtils; public class CachedHttpServletRequest extends HttpServletRequestWrapper { private byte[] cachedPayload; public CachedHttpServletRequest(HttpServletRequest request) throws IOException { super(request); InputStream requestInputStream = request.getInputStream(); this.cachedPayload = StreamUtils.copyToByteArray(requestInputStream); } @Override public ServletInputStream getInputStream() { return new CachedServletInputStream(this.cachedPayload); } @Override public BufferedReader getReader() { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedPayload); return new BufferedReader(new InputStreamReader(byteArrayInputStream)); } }
-
Create the
CachedServletInputStream
class :package yourpackage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CachedServletInputStream extends ServletInputStream { private final static Logger LOGGER = LoggerFactory.getLogger(CachedServletInputStream.class); private InputStream cachedInputStream; public CachedServletInputStream(byte[] cachedBody) { this.cachedInputStream = new ByteArrayInputStream(cachedBody); } @Override public boolean isFinished() { try { return cachedInputStream.available() == 0; } catch (IOException exp) { LOGGER.error(exp.getMessage()); } return false; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { throw new UnsupportedOperationException(); } @Override public int read() throws IOException { return cachedInputStream.read(); } }
-
Create the
RequestCachingFilter
class:package com.mercadolibre.chronos_consistency_checker; import java.io.IOException; import java.nio.charset.StandardCharsets; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.filter.OncePerRequestFilter; public class RequestCachingFilter extends OncePerRequestFilter { private final static Logger LOGGER = LoggerFactory.getLogger(RequestCachingFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { CachedHttpServletRequest cachedHttpServletRequest = new CachedHttpServletRequest(request); LOGGER.info("REQUEST DATA: " + IOUtils.toString(cachedHttpServletRequest.getInputStream(), StandardCharsets.UTF_8)); filterChain.doFilter(cachedHttpServletRequest, response); } }
-
Set the custom filter with a Spring bean in a configuration file (
@Configuration
annotated class):@Bean public FilterRegistrationBean<RequestCachingFilter> loggingFilterRegistration() { RequestCachingFilter requestCachingFilter = new RequestCachingFilter(); FilterRegistrationBean<RequestCachingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setName("CommonsRequestLoggingFilter"); registrationBean.setFilter(requestCachingFilter); registrationBean.setOrder(2); registrationBean.addUrlPatterns("*/bigqueue/consumer"); return registrationBean; }
In order to authenticate endpoints in Spring 3, some configurations must be done which are different to deprecated security configurations in previous versions of Spring which indicates to extend the class WebSecurityConfigurerAdapter
which is deprecated. The provided configurations below are suitable for custom security configurations. In general, these configurations are commonly used for basic authorization with a username and password, as well as other standard or common methods of authorization.
-
Add required dependencies to pom.xml :
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
Create a class to implement the
Authentication
interface :package com.your.package; import jakarta.servlet.http.HttpServletRequest; import java.util.Collection; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @Slf4j public class CustomAuthentication implements Authentication { /** * Constructor. */ public CustomAuthentication() { } @Override public Collection<? extends GrantedAuthority> getAuthorities() { throw new UnsupportedOperationException(); } @Override public Object getCredentials() { throw new UnsupportedOperationException(); } @Override public Object getDetails() { throw new UnsupportedOperationException(); } @Override public Object getPrincipal() { return null; } @Override public boolean isAuthenticated() { // Implement custom authenticatin logic } @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { throw new UnsupportedOperationException(); } @Override public String getName() { throw new UnsupportedOperationException(); } }
For this simple case just implement
isAuthenticated
. Throwing exceptions ingetPrincipal
can generate issues on responses. -
Create a Spring filter using previously implemented authentication:
package com.your.package; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.validation.constraints.NotNull; import org.springframework.lang.NonNullApi; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; /** * For each request, creates an instance of {@code CustomAuthentication} which implements {@code Authentication} * and makes it available for Spring Security by setting it on the {@code SecurityContext}. */ public class AuthenticationFilter extends OncePerRequestFilter { public AuthenticationFilter() { } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // ... Authentication authentication = new CustomAuthentication(); SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(request, response); } }
Important: don't forget to set the Authentication instance on the security context and invoke
doFilter
at the end in order to allow Spring to follow the correct filter chain -
Set the filter through a Spring bean in
@Configuration
annotated class:... @Bean @Profile({"!integration_test & !local"}) @Autowired public SecurityFilterChain furySecurityFilterChain(HttpSecurity http, SecurityService securityService) throws Exception { return http .csrf(CsrfConfigurer::disable) .addFilterAfter(new AuthenticationFilter(), LogoutFilter.class) .authorizeHttpRequests(request -> request.requestMatchers("topics/**").authenticated() .anyRequest().permitAll()) .build(); } ...
Important:
- Without
csrf(CsrfConfigurer::disable)
Spring will block all POST endpoints (see this Stack Overflow thread for more information). - Requests patterns used in
requestMatchers
method may contain**
, not regular expresions. - The
LogoutFilter.class
argument tells Spring in which order to execute filters, which is mandatory.
- Without