How to use Spring Security with load balancer? - spring-boot

I am new at loadBalancing so please I need help and thats what i did :
i built 2 services as 2 apps (A,B) I used spring security on both of them
(both of them are restfull api , they have theymleaf and full frontEnd pages ),
then i had made another app as spring cloud loadbalancer .
when i send a request , it go from loadbalancer app to one of the 2 services but the problem is when iam not authenticated the response will be empty , it wont take me to the default login page as usual as when i use the normal A app directly , and when i go to pages that does not need to be authenticated to get to it , it is returned without my css/js styles
this is my A app controller ( it is returning view not json )
package com.hariri_stocks.controllers;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.hariri_stocks.models.Estates;
import com.hariri_stocks.models.SoldEstates;
import com.hariri_stocks.models.Users;
import com.hariri_stocks.services.estatesService;
#Controller
public class LoginController {
#Autowired
estatesService ES;
#GetMapping(value = "/")
public String login() {
return "/signIn-up.html";
}
#GetMapping(value = "/dashboard")
public String dashboard(Model model ,#RequestParam(required = false) String add_result
,#RequestParam(required = false) String alert_err) {
List<Estates> estates = ES.findAll();
model.addAttribute("estates",estates);
return "/dashboard";
}
#GetMapping(value = "/dashboard/unSold")
public String unselled_stocks(Model model) {
List<Estates> estates = ES.findUnsold();
if(estates.size() > 0)
model.addAttribute("estates",estates);
else
model.addAttribute("error","there is no sold estates yet !!");
return "/dashboard";
}
#Value(value = "${server.port}")
String port_num;
#GetMapping("/port")
public String hello() {
return port_num;
}
}
and this is my loadbalancer controller iam using #restcontroller
package com.hariri_loadbalancer;
import reactor.core.publisher.Mono;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
#SpringBootApplication
#RestController
public class UserApplication {
private final WebClient.Builder loadBalancedWebClientBuilder;
private final ReactorLoadBalancerExchangeFilterFunction lbFunction;
public UserApplication(WebClient.Builder webClientBuilder,
ReactorLoadBalancerExchangeFilterFunction lbFunction) {
this.loadBalancedWebClientBuilder = webClientBuilder;
this.lbFunction = lbFunction;
}
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
#RequestMapping("/port")
public Mono<String> showMePort() {
return loadBalancedWebClientBuilder.build().get().uri("http://hariri/port")
.retrieve().bodyToMono(String.class);
}
#RequestMapping("/")
public Mono<String> showMainPage() {
return loadBalancedWebClientBuilder.build().get().uri("http://hariri/")
.retrieve().bodyToMono(String.class);
}
}
So what should I do? I feel that what I am doing is stupid,
should I move all my Thymleaf pages to the loadbalancer maybe , so that the a app return what it want to return with #restController then the loadbalancer use #controller to get to the styling front pages or there is a way , and for the security , should i implement the spring security with the loadbalancer instead of the A,B apps
.........................
8080 is loadBalancer port
9091 is A app port
so it seams that when A is returning the html page , the html is searching for the css at the loadbalancer machin at 8080 , while they are existing at A app on 9091

bodyToMono decodes the body but you are not handling headers.
On spring security there is very likely a redirection to the login page ... so it wont work if you only attend to the body. This might be also affecting styles somehow.
Check something like this:
How to extract response header & status code from Spring 5 WebClient ClientResponse

Related

Spring Boot OAuth2 with Github

I am implementing OAuth2 architecture with GitHub as authorization server to log into my dummy web application. Everything works perfectly fine. I got problem when I log in using my GitHub credentials I want the redirected page to show some message for user for example Welcome XYZ, but I could not get username who is logged in currently but a number is shown instead. I used principal object as well as Authentication object. Please would any body tell how do I achieve it?
Here is code for OAuth2 GitHub Configuration.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
#Configuration
public class WebSecurityConfigurerAdapterImp extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login((c)->
{
c.clientRegistrationRepository(this.clientRegistrationRepository());
}
);
http.authorizeRequests().anyRequest().authenticated();
}
private ClientRegistrationRepository clientRegistrationRepository()
{
ClientRegistration c=this.clientRegistration();
return new InMemoryClientRegistrationRepository(c);
}
private ClientRegistration clientRegistration()
{
return CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("72bc31d8b0304575442c").clientSecret("XYZSECRET").build();
}
}
Code for main_controller to which user will be redirected after logging in.
package com.controllers;
import java.security.Principal;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class Main_Controller {
#GetMapping("/")
#ResponseBody
public String HomePage(Authentication p)
{
System.out.println("Hellow "+p.getName());
return "Hello People.";
}
}
By default if you use "CommonOAuth2Provider.GITHUB" then principal name is map to "id" of your github user. So, you need to map principal name attribute to "login"

I am trying to get Header info from Request Controller and read into IntegrationFlow

I wanted to understand where is best location to read headers and use them inside my IntegrationFlow layer.
ServiceController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api/v1/integration")
public class ServiceController {
#Autowired
private ServiceGateway gateway;
#GetMapping(value = "info")
public String info() {
return gateway.info();
}
}
ServiceGateway.java
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.MessagingGateway;
#MessagingGateway
public interface ServiceGateway {
#Gateway(requestChannel = "integration.info.gateway.channel")
public String info();
}
ServiceConfig.java
import java.net.URISyntaxException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.http.dsl.Http;
import org.springframework.messaging.MessageHeaders;
#Configuration
#EnableIntegration
#IntegrationComponentScan
public class ServiceConfig {
#Bean
public IntegrationFlow info() throws URISyntaxException {
String uri = "http://localhost:8081/hellos/simpler";
return IntegrationFlows.from("integration.info.gateway.channel")
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST).expectedResponseType(String.class)).get();
}
}
From Consumer I am receiving some Header meta data. I want to know in above flow whether it is good idea from following approaches:
Read headers in Controller and then pass through into my IntegrationFlow: For this I am not aware how to pass through.
Is there best or any way exist to read request headers into IntegrationFlow layer?
For this second approach I have tried below code but runtime I am getting error as channel is one way and hence stopping the flow.
return IntegrationFlows.from("integration.info.gateway.channel").handle((request) -> {
MessageHeaders headers = request.getHeaders();
System.out.println("-----------" + headers);
}).handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST).expectedResponseType(String.class)).get();
My problem is how to send request parameters from incoming call to carry those internally invoking another rest call. Here I wanted to transform the data from request headers and construct into new json body and then send this to http://localhost:8081/hellos/simpler URL.
The flow:
I am trying to construct this RequestBody before sending to internal REST POST call:
A gateway method with no paylaod is for receiving data, not requesting it.
https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#gateway-calling-no-argument-methods
Add a #Header annotated parameter to the gateway.
https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#gateway-configuration-annotations
#MessagingGateway
public interface ServiceGateway {
#Gateway(requestChannel = "integration.info.gateway.channel")
public String info("", #Header("x-api") String xApi);
}
This will send a message with an empty string as the payload with the header set.

How do i connect my webMethods REStful service to my spring boot application

All i wanted to know is that if i have a flow service in SoftwareAG webMethods and i have converted it into a REStful service by making a rest resource and i have exposed it, so how do i make a call to that service via a spring boot appplication.
This is my code for the my spring application, Can someone please suggest me that how do i make a rest call to a webMethods Rest Resource flow service which is already been exposed.
A quick help is appreciated.
package com.scb.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.scb.entity.ReadingEntity;
import java.util.concurrent.atomic.AtomicLong;
#RestController
public class PreProcessorController {
private List<ReadingEntity> myEntity=new ArrayList();
private final AtomicLong counter = new AtomicLong();
#GetMapping("/GetData")
public void getData(#RequestBody ReadingSCBMLEntity entity) {
//myEntity.add(entity);
final String uri="http://uklvadapp881.uk.dev.net:5555/invoke/scb.wb.fm.support.flow.ResourceGet/_get?";
RestTemplate template=new RestTemplate();
String result=template.getForObject(uri, String.class);
System.out.println(result);
}
}

How to make Spring boot CSV message converter display CSV inline and not download when using a browser

I created a spring starter project in eclipse . Most of the code was from this link https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/csv-msg-converter.html.
I added content negotiation configuration to accept headers, path extension and parameters. It works great from postman.
But when I try in a browser http://localhost:8080/employeelist.csv. In all the cases CSV is getting downloaded in a file. I want it displayed inline on the browser. I tried to set content disposition as inline in Request mapping, http output message header but still CSV is always getting downloaded.
What should I be doing to get csv displayed inline? I had previously successfully displayed CSV inline in a browser by having separate request mapping method for CSV and make the method return void and accept httpservletresponse as parameter. But I want to use content negotiation and a single method for all formats - XML, CSV, json. Whatever format selected should be displayed inline in the browser.
Is that possible ?
Thanks a lot for your time.
Update : added portions of code which were edited
package ti.projects;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
#SuppressWarnings("deprecation")
#EnableWebMvc
#Configuration
#ComponentScan("ti.projects")
public class AppConfig extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new CsvHttpMessageConverter<>());
}
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).favorParameter(true).parameterName("mediaType").ignoreAcceptHeader(false)
.useJaf(false).mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("csv", new MediaType("text", "csv"));
}
}
package ti.projects;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.Arrays;
import java.util.List;
#Controller
public class ExampleController {
#RequestMapping(
value = "/newEmployee",
consumes = "text/csv",
produces = MediaType.TEXT_PLAIN_VALUE,
method = RequestMethod.POST)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
public String handleRequest (#RequestBody EmployeeList employeeList) {
System.out.printf("In handleRequest method, employeeList: %s%n", employeeList.getList());
String s = String.format("size: " + employeeList.getList().size());
System.out.println(s);
return s;
}
#RequestMapping(
value = "/employeeList",
produces = {"text/csv", "application/json"},
method = RequestMethod.GET
)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
public EmployeeList handleRequest2 () {
List<Employee> list = Arrays.asList(
new Employee("1", "Tina", "111-111-1111"),
new Employee("2", "John", "222-222-2222")
);
EmployeeList employeeList = new EmployeeList();
employeeList.setList(list);
return employeeList;
}
}
package ti.projects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class ContentNegotiationApplication {
public static void main(String[] args) {
SpringApplication.run(ContentNegotiationApplication.class, args);
}
}
The browser (should) use the provided mime type to decide how to display or process the response. What should work is using a MIME of text/plain to let the browser render the received content as text.
You can set the MIME type of your response in your spring Controller like this:
#GetMapping(produces = MediaType.TEXT_PLAIN_VALUE)
public String renderCsv() {...}
If you want to offer different MIME types with one method you have three options:
Use query parameter (e.g. ...?contentType=json)
Use path parameter (e.g..../{contentType})
Use accept header of client (preferably?)
You can register different MessageConverter for each contentType and configure a ContentNegotiationConfigurer to automatically choose the correct converter depending on given MIME type and your preferences.
I'll try to attach an example tonight.

Transfer a file using Apache Camel file component

I am trying a demo file transfer program using Spring Boot and Apache Camel file component. I have exposed a REST Controller using Spring Boot which is calling an Apache Camel route and it is doing the file transfer. I have three files in the directory C:\CamelDemo\inputFolder namely input1.txt, input2.txt and input3.txt. I want to only transfer the file input2.txt in the output folder. My Spring Boot controller is as below:
package com.example.demo.controller;
import java.util.HashMap;
import java.util.Map;
import org.apache.camel.ProducerTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/camel")
public class FileTransferController {
#Autowired private ProducerTemplate producerTemplate;
#RequestMapping(value="/file", method=RequestMethod.GET)
public String callCamelRoute() {
String fileName = "input2.txt";
Map<String, Object> headerMap = new HashMap<String, Object>();
headerMap.put("fileName", fileName);
producerTemplate.sendBodyAndHeaders("direct:transferFile", null, headerMap);
return "Route invoked";
}
}
My Route is as below:
package com.example.demo.route;
import org.apache.camel.LoggingLevel;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
#Component
public class FileTransferRoute extends RouteBuilder {
#SuppressWarnings("deprecation")
#Override
public void configure() {
errorHandler(defaultErrorHandler()
.maximumRedeliveries(3)
.redeliverDelay(1000)
.retryAttemptedLogLevel(LoggingLevel.WARN));
from("direct:transferFile")
.log("Route reached")
.log("file:C:\\CamelDemo\\inputFolder?fileName=${in.headers.fileName}&noop=true")
.pollEnrich("file://C:/CamelDemo/inputFolder?fileName=${in.headers.fileName}&noop=true")
.to("file://C:/CamelDemo/outputFolder?autoCreate=false")
.end();
}
}
But the first time I invoke this route, the file input1.txt is getting transferred even when I have specified the fileName parameter. Please help.
I think the issue is that your file name isn't being set, because you're not telling Camel that you're using a Simple expression, rather than a fixed URI.
Looking at the manual (https://camel.apache.org/manual/latest/pollEnrich-eip.html#_using_dynamic_uris), it implies that you will need
.pollEnrich().simple("file://C:/CamelDemo/inputFolder?fileName=${in.headers.fileName}&noop=true")
to be able to use the dynamic endpoint.

Resources