How can I get the Response body from HttpServletResponse - spring

I want to get the Response body during Post HandlerInterceptor but it comes up empty.
spring-boot.version: 2.7.4
CustomPostHandlerInterceptor.java
#Component
#Order(1)
public class CustomPostHandlerInterceptor implements HandlerInterceptor {
#Override
public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
#Override
public final void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
ContentCachingResponseWrapper resp = new ContentCachingResponseWrapper(response);
byte[] responseBody = resp.getContentAsByteArray();
String res = new String(responseBody, StandardCharsets.UTF_8);
}
#Override
public final void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
}
}
Controller class
#RestController
#RequestMapping("/exampleRest")
public class ExampleRest {
#RequestMapping("/getUsername")
#GetMapping
public String getUsername() {
return "michael";
}
}
I want to get response body from HttpServletResponse.
I tried different methods but it comes up empty.
How can I fix?

AFAIK the response can only be read once so you need to copy it.
You need a filter as follows:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
#Component
public class LogFilter extends HttpFilter {
private FilterConfig filterConfigObj;
public void init(FilterConfig config) throws ServletException {
this.filterConfigObj = config;
}
public void destroy() {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
HttpServletResponseCopier responseCopier = new HttpServletResponseCopier((HttpServletResponse) response);
try {
chain.doFilter(request, responseCopier);
responseCopier.flushBuffer();
} finally {
byte[] copy = responseCopier.getCopy();
System.out.println(new String(copy, response.getCharacterEncoding()));
}
}
}
And these dependent classes to read and preserve the response.
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class HttpServletResponseCopier extends HttpServletResponseWrapper {
private ServletOutputStream outputStream;
private PrintWriter writer;
private ServletOutputStreamCopier copier;
public HttpServletResponseCopier(HttpServletResponse response) throws IOException {
super(response);
}
#Override
public ServletOutputStream getOutputStream() throws IOException {
if (writer != null) {
throw new IllegalStateException("getWriter() has already been called on this response.");
}
if (outputStream == null) {
outputStream = getResponse().getOutputStream();
copier = new ServletOutputStreamCopier(outputStream);
}
return copier;
}
#Override
public PrintWriter getWriter() throws IOException {
if (outputStream != null) {
throw new IllegalStateException("getOutputStream() has already been called on this response.");
}
if (writer == null) {
copier = new ServletOutputStreamCopier(getResponse().getOutputStream());
writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true);
}
return writer;
}
#Override
public void flushBuffer() throws IOException {
if (writer != null) {
writer.flush();
} else if (outputStream != null) {
copier.flush();
}
}
public byte[] getCopy() {
if (copier != null) {
return copier.getCopy();
} else {
return new byte[0];
}
}
}
and
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
public class ServletOutputStreamCopier extends ServletOutputStream {
private OutputStream outputStream;
private ByteArrayOutputStream copy;
public ServletOutputStreamCopier(OutputStream outputStream) {
this.outputStream = outputStream;
this.copy = new ByteArrayOutputStream(1024);
}
#Override
public void write(int b) throws IOException {
outputStream.write(b);
copy.write(b);
}
public byte[] getCopy() {
return copy.toByteArray();
}
#Override
public boolean isReady() {
// TODO Auto-generated method stub
return true;
}
#Override
public void setWriteListener(WriteListener listener) {
// TODO Auto-generated method stub
}
}

You will have to cache the response in the first place, you can extend the OncePerRequestFilter from spring and then cache your response, the following code registers a filter that creates a ContentCachingWrapper for your request and response, using this you should be able to get the response using the code that you have mentioned.
#Component
public class CachingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(new ContentCachingRequestWrapper(request), ContentCachingResponseWrapper(response));
}

Related

My authentication exceptions are not handled

I am currently trying to create a fullstack app, with Angular 14 and spring boot,
i am stack with authentication.
my problem is that i use my own form to get the password and the username from the user, then trying to authenticate in the backend, i created an Authentication Filter, in which i override the attemptAuthentication() method, which recives a JSON object containing the username and password,
Then i test if the username exists if not i throw UserNotFoundException , if the password is wrong i throw BadCredentialsException then if everything went well i return an authentication object, here is the method:
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// JSON body authentication
try {
System.err.println("attempting authentication");
LoginBody loginBody = new ObjectMapper().readValue(request.getInputStream(), LoginBody.class);
AppUser user = this.userService.loadUserByUsername(loginBody.getUsername());
if (user == null) {
throw new UserNotFoundException("No user with this username") {
};
}
if ( user.getPassword().equals(passwordEncoder.encode(loginBody.getPassword()))) {
throw new BadCredentialsException("Bad credentials") {
};
}
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginBody.getUsername(),loginBody.getPassword()));
} catch (Exception e) {
System.err.println(e.getMessage());
throw new AuthenticationException(e.getMessage()) {
} ;
}
i have created an exeption handler which works fine for my controller methods whith have the endpoint /api/... , but not for the authentication with the endpoint /auth/login, all it returns is the HTTP status 403 (forbidden) like in this image
here is my exception handler class
package com.webapps.Focus.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class UserExceptionController {
#ExceptionHandler(value = UserNotFoundException.class)
public ResponseEntity<Object> exception(UserNotFoundException exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
}
#ExceptionHandler(value = BadCredentialsException.class)
public ResponseEntity<Object> exception(BadCredentialsException exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
}
}
I appreciate your help.
According to this article, Exceptionhandler doesn't handle spring security exceptions, like AuthenticationException, hence nothing except UNAUTHORIZED status is shown as an answer,
one solution is to create a customized implementation for AuthenticationFailureHandler interface, then override onAuthenticationFailureonAuthenticationFailure() method, in which you use your own exception handling like in this example:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
#Component("userAuthFailureHandler")
public class UserAuthenticationFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
try {
Map<String, String> status = new HashMap<>();
status.put("status", HttpStatus.UNAUTHORIZED.toString());
status.put("value", HttpStatus.UNAUTHORIZED.value() + "");
status.put("reason", HttpStatus.UNAUTHORIZED.getReasonPhrase());
status.put("error", exception.getMessage());
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
new ObjectMapper().writeValue(response.getOutputStream(), status);
}catch (Exception e) {
throw e;
}
}
}
Then in SecurityConfig class, consider injecting a bean with Qualifier("userAuthFailureHandler") , then set the attribute AuthenticationFailureHandler of your AuthenticationFilter to that bean:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
private AuthenticationFailureHandler failureHandler;
private AuthenticationEntryPoint authEntryPoint;
public SecurityConfig(...
#Qualifier("delegatedAuthenticationEntryPoint") AuthenticationEntryPoint authEntryPoint,
#Qualifier("userAuthFailureHandler")AuthenticationFailureHandler failureHandler) {
...
this.authEntryPoint = authEntryPoint;
this.failureHandler = failureHandler;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure the stateless authentication
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
...
JWTAuthenticationFilter authenticationFilter = new JWTAuthenticationFilter(authenticationManagerBean(), userService, passwordEncoder);
authenticationFilter.setFilterProcessesUrl("/auth/login");
authenticationFilter.setAuthenticationFailureHandler(this.failureHandler);
http.addFilter(authenticationFilter);
http.addFilterBefore(new JWTAuthorisationFilter(), UsernamePasswordAuthenticationFilter.class);
// allow security exceptions handling to component with qualifier delegatedAuthenticationEntryPoint
http.exceptionHandling().authenticationEntryPoint(authEntryPoint);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Then delegate security exception handling to your ow implementation of AuthenticationEntryPoint like below
//This class will help handle security exceptions that couldn't be handled by ControllerAdvice
#Component("delegatedAuthenticationEntryPoint")
public class DelegatedAuthenticationEntryPoint implements AuthenticationEntryPoint {
private HandlerExceptionResolver resolver;
public DelegatedAuthenticationEntryPoint( #Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
resolver.resolveException(request, response, null, authException);
}
}
I had the same problem. It happened because of anyRequest().authenticated() in Security Configuration: "/error" page is blocked too. So u should write something like this: authorizeHttpRequests(auth -> auth.requestMatchers("/error").permitAll() or authorizeHttpRequests().requestMatchers("/error").permitAll() as you wish.

Custom Spring Security Filter Test with MockMvc always returns 404

I am trying to test a custom filter using MockMvc and it always returns status 404 instead of the actual status returned from the filter class.
The filter class looks as follows:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
// some logic
chain.doFilter(servletRequest, servletResponse);
}
}
This is registered in with the SpringBootApplication using FilterRegistrationBean
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
try {
ConfigurableApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
} catch (Throwable e) {
}
#Bean
public FilterRegistrationBean<CustomFilter> customFilter() {
FilterRegistrationBean<CustomFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setFilter(new CustomFilter());
filterRegistration.addUrlPatterns("/test/*");
return filterRegistration;
}
}
The test for this is written as follows:
#RunWith(SpringRunner.class)
#SpringBootTest(CustomFilter.class)
#EnableAutoConfiguration
#WebAppConfiguration
public class CustomFilterTest {
#Autowired
private CustomFilter filter;
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
this.mvc = MockMvcBuilders.webAppContextSetup(context)
.addFilters(filter)
.build();
}
#Test
public void testCustomFilter() throws Throwable {
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
.request(HttpMethod.POST, "/resource/abc")
.header("SomeHeader", "xxx")
.content("{}");
MockHttpServletResponse response = mvc.perform(request).andReturn().getResponse();
assertEquals(response.getStatus(), HttpServletResponse.SC_CONTINUE);
}
}
The assertion always returns the status value of 404.
Am I missing something here?
Add urlPatterns to the addFilter(filter, "/resource/abc") and add a test controller for the above code snippet as follows:
#RestController
public class TestController {
#PostMapping("/resource/abc")
public void testSecureEndpoint() {
}
}

How can I add newly signed up user in the Spring boot security config?

I am working Spring-Boot, Spring Security with basic Authentication. I will send login url from my client application written in AngularJS via RESTful API call.
Everything works as expected. All the users in the DB configured in the SecurityConfiguration.java as below.
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
List<User> users = userService.getUsers();
for (User user : users) {
auth.inMemoryAuthentication().withUser(user.getUserName()).password(user.getPassword())
.roles(user.getRole().getName());
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/server/rest/secure/**")
.hasRole("ADMIN").and()
.httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint());
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
return new CustomBasicAuthenticationEntryPoint();
}
CustomBasicAuthenticationEntryPoint.java
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName() + "");
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 : " + authException.getMessage());
response.setHeader("WWW-Authenticate", "FormBased");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("MY_TEST_REALM");
super.afterPropertiesSet();
}
}
So If I signup a new user which will inserted in the DB but not added in the above implementation. So authentication fails.
How can refresh the above implementation whenever i'm and doing signup of a new user
When doing authentication with db, you should do the following:
#Service("userDetailsService")
#Transactional
public class MUserDetailService implements UserDetailsService {
#Autowired
AppUserDao appUserDao;
#Override
public UserDetails loadUserByUsername(final String appUserName) throws UsernameNotFoundException {
AppUser appUser = appUserDao.findByName(appUserName);
if (appUser == null) throw new UsernameNotFoundException(appUserName);
else{
return new User(appUser.getUsername(),appUser.getPassword(),appUser.getActive(),true,true,true,getGrantedAuthorities(appUser));
}
}
private List<GrantedAuthority> getGrantedAuthorities(AppUser appUser){
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (Authority authority : appUser.getAuthorities()){
authorities.add(new SimpleGrantedAuthority(authority.getAuthorityName()));
}
return authorities;
}
}
and then define SecurityConfiguration as follows:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}

Logging interceptor is not working

The issue is that it seems like interceptor is not called.
#Component
public class LoggingInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LogManager.getLogger(MethodHandles.lookup().lookupClass());
#Overridegre
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3)
throws Exception {
LOGGER.info("Request Completed!");
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model)
throws Exception {
LOGGER.info("Method executed");
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
LOGGER.info("Before process request");
return true;
}
}
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Autowired
LoggingInterceptor loggingInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor);
}
}
I've found examples but they are not working !
I'm trying to add start and end log and also performance log. Any idea please ?
#SpringBootApplication(scanBasePackages = { "com.sofelite.proj.controllers" })
public class ProjApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(GrentyApplication.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(GrentyApplication.class, args);
}
}
This is the Application class
Please note that in com.sofelite.proj I have all application packages such as controllers and interceptors.
Mine is working:
#Configuration
public class LoggingConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor());
}
}
and the LoggingInterceptor class:
#Component
public class LoggingInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER =
LoggerFactory.getLogger(LoggingInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler)
throws Exception {
long startTime = System.currentTimeMillis();
LOGGER.info("Request URL: " + request.getRequestURL());
LOGGER.info("Start Time: " + System.currentTimeMillis());
request.setAttribute("startTime", startTime);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
StringBuilder sb = new StringBuilder();
sb.append("!Status: "+response.getStatus()+"\n");
sb.append("!URL: "+ request.getRequestURL());
sb.append("!Method: " + request.getMethod() + "\n");
LOGGER.info(sb.toString());
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
long endTime = System.currentTimeMillis();
System.out.println("URL Request Completed. End Time: "+ endTime);
}
}

Spring 4 autowire to Filter

I build app with Spring 4.1.6, I have all config in java class. My AppConfig:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "pl.wrweb.springrest")
public class AppConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean(name = "messageSource")
public ReloadableResourceBundleMessageSource getMessageSource() {
ReloadableResourceBundleMessageSource resource = new ReloadableResourceBundleMessageSource();
resource.setBasename("classpath:messages");
resource.setDefaultEncoding("UTF-8");
return resource;
}
}
AppInitializer:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
ctx.setServletContext(container);
ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
container.addFilter("customFilter", new DelegatingFilterProxy(new CustomFilter())).addMappingForUrlPatterns(null, true, "/*");
}
#Override
protected String[] getServletMappings() {
return new String[0];
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[0];
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[0];
}
}
and my CustomFilter:
#Configuration
#EnableWebSecurity
public class CustomFilter implements Filter {
#Autowired
UserService userService;
public void init(FilterConfig fc) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain fc) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (httpServletRequest.getRequestURI().contains("/employee/")) {
String token = httpServletRequest.getHeader("x-token");
System.out.println("token " + token);
Long userId = TokenUtils.getUserNameFromToken(token);
System.out.println("ObjectStore.token " + ObjectStore.token);
if (token!=null) {
if (token.equals(String.valueOf(ObjectStore.token))) {
int index = ObjectStore.token++;
ObjectStore.token = index+1;
System.out.println("index " +ObjectStore.token + "token " + token);
response.setHeader("x-token", String.valueOf(ObjectStore.token));
fc.doFilter(req, response);
} else {
response.sendError(HttpStatus.FORBIDDEN.value());
fc.doFilter(req, response);
return;
}
} else {
response.sendError(HttpStatus.FORBIDDEN.value());
fc.doFilter(req, response);
return;
}
} else {
fc.doFilter(req, res);
}
}
public void destroy() {
}
}
Everything works fine but I can't inject userService and it's null, I need this to check token in request.
You should use Code as this!
private UserService userService;
and then add this code
public void init(FilterConfig fc) throws ServletException {
ApplicationContext app = WebApplicationContextUtils.getRequiredWebApplicationContext(fc.getServletContext());
userService= app.getBean(UserService.class);
}

Resources