Intercept calls to RestController in Spring Boot - spring

I've looked at similar problems:
how to intercept all requests in spring REST controllers?
spring boot adding http request interceptors
And still can't figure out the solution.
I have a Spring Boot app that runs a bunch of Rest Controllers and want to intercept calls and perform some business logic before the request is passed to the controllers. I have this:
My Application class for Sprint Boot:
package com.amazonaws.lambda.keefinty.application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
#SpringBootApplication(scanBasePackages="com.amazonaws.lambda.keefinty.controllers")
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
My Configuration class in same package a Application:
package com.amazonaws.lambda.keefinty.application;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.amazonaws.lambda.keefinty.interceptors.MyInterceptor;
#Configuration
public class MyConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MyInterceptor getInterceptor() {
return new MyInterceptor();
}
#Override
public void addInterceptors (InterceptorRegistry registry) {
System.out.println("\n\nAdding interceptors\n\n");
registry.addInterceptor(getInterceptor()).addPathPatterns("/**");
}
}
And finally the interceptor class:
package com.amazonaws.lambda.keefinty.interceptors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
#Component
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.print("\\n\\nIn MyInterceptor.preHandle\\n\\n");
String token = request.getParameter("token");
if (StringUtils.isBlank(token)) {
throw new Exception("Invalid User Id or Password. Please try again.");
}
return true;
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception exception) throws Exception {
// TODO Auto-generated method stub
System.out.print("\n\nIn MyInterceptor.afterCompletion\\n\\n");
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
System.out.print("\\n\\nIn MyInterceptor.postHandle\\n\\n");
}
}
The Configuration class never seems to get called to register the interceptors nor does MyInterceptor.

By declaring #SpringBootApplication(scanBasePackages="com.amazonaws.lambda.keefinty.controllers") only annotated components from com.amazonaws.lambda.keefinty.controllers package and it's sub packages will be discovered.
You MyConfiguration class is in com.amazonaws.lambda.keefinty.application package which is not part of the declared component scan package.
One way to resolve this is to remove scanBasePackages argument from your #SpringBootApplication declaration. This will allow MyConfiguration to be component scanned as by default the package in which #SpringBootApplication is declared is component scanned.

Related

Spring Security with multiple security models configured with SecurityFilterChain ignores authentication configuration

I am trying to build a Spring Boot application, that has different security models for different URLs. So I try to avoid creating a global AuthenticationManager bean, but instead configure it in the Spring Security DSL as described here: https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
However MyAuthProvider does not get called in either a/ or b/.
I would expect it to be called in both examples.
Surprisingly a/ returns 403 Access Denied and b/ returns 200 OK
What am I doing wrong?
Complete example provided here as a single class Spring Boot application (2.7.3)
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Set;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public SecurityFilterChain secureA(HttpSecurity http) throws Exception {
http.authorizeRequests(auth ->
auth.antMatchers("/a").hasRole("a")
);
http.authenticationProvider(new MyAuthProvider("a"));
return http.build();
}
#Bean
public SecurityFilterChain secureB(HttpSecurity http) throws Exception {
http.authorizeRequests(auth ->
auth.antMatchers("/b").hasRole("b")
);
http.authenticationManager(new ProviderManager(new MyAuthProvider("b")));
return http.build();
}
#RestController
public class RestService {
#GetMapping("/a")
public String a() {
return "hello a";
}
#GetMapping("/b")
public String b() {
return "hello b";
}
#GetMapping("/c")
public String c() {
return "hello c";
}
}
class MyAuthProvider implements AuthenticationProvider {
private String cfg;
public MyAuthProvider(String cfg) {
this.cfg = cfg;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("authenticate " + cfg);
var roles = Set.of(new SimpleGrantedAuthority("ROLE_" + cfg));
return new UsernamePasswordAuthenticationToken("user", "pass", roles);
}
#Override
public boolean supports(Class<?> authentication) {
System.out.println("supports " + cfg);
return true;
}
}
}

Spring Boot: "relaying" basic auth from REST controller to RestTemplate

I'm working with two Spring Boot applications, let's call them ServiceA and ServiceB, both exposing a REST API.
ServiceA is called by end users from the browser via a frontend app (we use #RestController classes). On some calls, ServiceA has to call ServiceB (using RestTemplate). We've got authentication and authorization sorted out for our target environment, but for testing locally we are relying on Basic Auth instead, and that's where we're hitting a snag: we would like ServiceA to re-use the Basic Auth credentials the user provided when calling Service B.
Is there an easy way to pass the Basic Auth credentials used on the call to our REST controller to the RestTemplate call?
Quick and dirty solution
The easiest way to do this would be:
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
#RestController
class SomeController {
private final RestTemplate restTemplate = new RestTemplate();
#PostMapping("/delegate/call")
public void callOtherService(#RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, authorization);
restTemplate.postForEntity("other-service.com/actual/call", new HttpEntity<Void>(null, headers), Void.class);
// handling the response etc...
}
}
Using interceptors and RestTemplateCustomizer
I didn't want to change to add an extra parameter on each controller method, and I wanted a way to enable or disable this behavior depending on the environment, so here is a slightly more complicated solution that can be enabled using Spring profiles, and doesn't touch the controllers:
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class BasicAuthPropagationInterceptor implements HandlerInterceptor, ClientHttpRequestInterceptor {
private final ThreadLocal<String> cachedHeader = new ThreadLocal<>();
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
cachedHeader.set(header);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
cachedHeader.remove();
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
String ch = cachedHeader.get();
if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION) && ch != null) {
request.getHeaders().add(HttpHeaders.AUTHORIZATION, ch);
}
return execution.execute(request, body);
}
}
This stores the received header in a ThreadLocal and adds it with an interceptor for RestTemplate.
This can then be configured as such:
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
#Profile("LOCAL")
class LocalConfiguration implements WebMvcConfigurer {
private final BasicAuthPropagationInterceptor basicAuthPropagationInterceptor
= new BasicAuthPropagationInterceptor();
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(basicAuthPropagationInterceptor);
}
#Bean
RestTemplateCustomizer restTemplateCustomizer() {
return restTemplate -> restTemplate.getInterceptors().add(basicAuthPropagationInterceptor);
}
}
RestTemplate obtained by using the default RestTemplateBuilder bean will then automatically set the Authorization HTTP header if it's available in the current thread.

How to make post request in apache camel rest

I am new apache rest dsl with spring boot, have made following changes
Main Class
package com.javaoutofbounds.pojo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication(scanBasePackages = {"com.ccs.batchfile"})
public class BatchFileApplication {
public static void main(String[] args) {
SpringApplication.run(BatchFileApplication.class, args);
}
}
Service class
package com.ccs.batchfile.service;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
import org.springframework.stereotype.Component;
#Component
public class BatchFileService extends RouteBuilder {
#Override
public void configure() throws Exception {
restConfiguration().component("servlet").bindingMode(RestBindingMode.json);
rest("/batchFile").consumes("application/json").produces("application/json").get("/routeStart").to("direct:startRoute");
}
}
Route class
package com.ccs.batchfile.routes;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ccs.batchfile.processor.StartRouteProcessor;
#Component
public class StartRoute extends RouteBuilder{
#Autowired
private StartRouteProcessor startRouteProcessor;
#Override
public void configure() throws Exception {
from("direct:startRoute").log("Inside StartRoute")
.process(startRouteProcessor);
}
}
Processor class
package com.ccs.batchfile.processor;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.springframework.stereotype.Component;
#Component("startRouteProcessor")
public class StartRouteProcessor implements Processor{
public void process(Exchange exchange) throws Exception {
String message = exchange.getIn().getBody(String.class);
System.out.println(message);
}
}
I am not getting control to StartRouteProcessor, when i make below post request in postman
http://localhost:8080/batchFile/routeStart/
I have used below test payload to check if works.
{
"title" : "test title",
"singer" : "some singer"
}
When i post the above request i am getting 404 error. Kindly help on this please
I tried your example and you need to add two changes.
In your "main" class, the 'component scan' annotation is right, but you have to add a 'ServletRegistrationBean' with name 'CamelServlet':
package org.funcode.app.main;
import org.apache.camel.component.servlet.CamelHttpTransportServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
#SpringBootApplication(scanBasePackages = {"org.funcode.app"})
public class BatchFileApplication {
private static final String CAMEL_URL_MAPPING = "/api/*";
private static final String CAMEL_SERVLET_NAME = "CamelServlet";
public static void main(String[] args) {
SpringApplication.run(BatchFileApplication.class, args);
}
#Bean
public ServletRegistrationBean servletRegistrationBean() {
ServletRegistrationBean registration =
new ServletRegistrationBean(new CamelHttpTransportServlet(), CAMEL_URL_MAPPING);
registration.setName(CAMEL_SERVLET_NAME);
return registration;
}
}
And if you want view on the log the content you posted on the request, you need to change the method of the request to "post":
package org.funcode.app.main;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
import org.springframework.stereotype.Component;
#Component
public class BatchFileService extends RouteBuilder {
#Override
public void configure() throws Exception {
restConfiguration().component("servlet").bindingMode(RestBindingMode.json);
rest("/batchFile")
.consumes("application/json")
.produces("application/json")
.post("/routeStart")
.to("direct:startRoute");
}
}
I hope it helps.

404 error in Spring Maven project

I am new to Spring and trying to develop one small application using spring annotation with Maven. But I am getting **"The requested resource is not available."**I can understand that server is not able to locate the requested resource. But I am not able to resolve it, So please help me on this.
Below are my project structure and code:-
SpringRootConfig.java
package com.capp.config;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
#Configurable
#ComponentScan( basePackages = {"com.capp"})
public class SpringRootConfig {
}
package com.capp.config;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
#SuppressWarnings("deprecation")
#Configurable
#ComponentScan( basePackages = {"com.capp"})
#EnableWebMvc
public class SpringWebConfig extends WebMvcConfigurerAdapter {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver vr =new InternalResourceViewResolver();
vr.setViewClass(JstlView.class);
vr.setPrefix("/WEB-INF/view/");
vr.setSuffix(".jsp");
return vr;
}
}
ContactAppDispatcherServletIntializer.java
package com.capp.config;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ContactAppDispatcherServletIntializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {SpringRootConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class[] {SpringRootConfig.class};
}
#Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[] {"/"};
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// TODO Auto-generated method stub
super.onStartup(servletContext);
}
}
TestController.java
package com.capp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
public class TestController {
#RequestMapping(value="/test/hello")
public String helloWorld() {
return "hello";
}
}
When i am runing the application with http://localhost:8080/SpringContactApp/test/hello not able to find the hello.jsp which is under WEB-INF folder.
I presume the JSP is directly under the WEB-INF folder. If this is the case, create a subdirectory 'view' and move the JSP there.
In the view resolver, you set the prefix to "/WEB-INF/view/", your view resources must be in that location.

How to redirect to other site from onApplicationEvent() in spring boot application

I have a class:
#Component
public class AuthenticationSuccessListener implements ApplicationListener<InteractiveAuthenticationSuccessEvent>
and method which is void:
#Override
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent arg0)
I would like to redirect to other site
if(daysBetweenLocalDates(LocalDate.now(), operator.getDateOfChangePassword()) >= 30)
{
System.out.println("###DEB forcing operator to change pass because date of change pass is: " + operator.getDateOfChangePassword());
ModelAndView mav = new ModelAndView();
mav.setViewName("redirect:/changePassword");
}
but I need return mav; and there is problem since onApplicationEvent is void. How can I redirect user to another website from that method?
As #Brian Clozel suggested Handler works great!
So I made a handler:
package local.vlex.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
#Component
public class VlexAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
response.sendRedirect("/changePassword");
}
}
and used it in WebSecurityConfig:
#Autowired
VlexAuthenticationSuccessHandler successHandler;
then:
.formLogin().loginPage("/login").permitAll().successHandler(successHandler)
Thank you #Brian Clozel!

Resources