How do you change the hostname in Spring Data REST HATEOAS links? - spring

I have a Spring Boot application with Spring Data Rest which I'm deploying via jar file with embedded Tomcat. The application runs on port 8080 but in front of Tomcat there's an httpd which passes the request through.
Now the application comes with HATEOAS links, which I'm using in my JS-Client.
The response looks something like this:
{
"property" : "value"
"_links" : {
"self" : {
"href" : "http://my.server:8080/resource/1"
}
}
}
My problem is that since I access the application via my.server, the application responds with an href to my.server:8080, which isn't accessible from the outside.
How can I change the href hostname to my.server (without the port) without letting tomcat run under port 80?
I tried subclassing RepositoryRestMvcConfiguration and setting the baseURI, but that does not work, since I get a 404 then.

If you can configure your httpd to not alter the HOST header you'll get what you're after as the default link builder uses the HOST header to construct URLs.
Alternatively you can have your httpd append an X-Forwarded-Host header, which overrides URL construction using the HOST header.

Related

Spring cloud gateway proxy to controller in same application

I want to achieve the following with a spring boot webflux application:
I have an endpoint api/test. I would like the same controller to be available on dynamically configured sub paths. E.g. if configured a sub-route "app" then a request to app/api/test should end up in the same controller.
To facilitate this I did the following using a RouteLocatorBuilder:
route(id = "proxy_api_test") {
host(location.host)
path("/${location.route}/api/test/**" )
filters{
filter(setPathGatewayFilter.apply(createSetPathConfig("/api/test")))
}
uri(location.uri)
}
In case of testing on localhost for example location.host would be "localhost:8080" and location.route could be "app" and location.uri would be "http://localhost:8080".
And createSetPathConfig is given as:
fun createSetPathConfig(template: String): SetPathGatewayFilterFactory.Config{
val config = SetPathGatewayFilterFactory.Config()
config.template = template
return config
}
When running the application that would work like a charm because requests to http://localhost:8080/app/api/test would be redirected to http://localhost:8080/api/test with the help of spring cloud gateway. I have chose this approach also because it could be various sub paths at the same time, so the same controller must be available from different entry paths.
Now what I see is that this does not work in unit-tests using an #Autowired val client: WebTestClient because when executing the unit-test in fact no web-server is running. Is there a way to indicate with the uri in with RouteLocatorBuilder that the request should be executed on the same host such that unit-test would also work with the same logic? Because in fact in this case I would like spring cloud gateway not to forward to another host but to just change the routes dynamically.

Handling of requests locally by Zuul Gateway server

How do I make Spring Boot Zuul Proxy Server handle a specific request locally, instead of proxying it to some other Server?
Let's say I want to do a custom health check of Zuul Proxy Server itself through an API which should return a local response, instead of returning a proxied response from some other remote server.
What kind of route configuration is required to forward the request?
Following is what I figured out and it worked for me:
1) Create a local request handler:
Add a regular Controller/RestController with a RequestMapping in a file in Zuul proxy Server code
#RestController
public class LocalRequestHandler {
#GetMapping(path = "/status", produces = MediaType.TEXT_PLAIN_VALUE);
private static ResponseEntity<String> getServiceStatus() {
String status = "I am alive";
return ResponseEntity.ok().body(status);
}
}
2) Create a local forwarding route in the configuration file (application.properties or as the case may be) where other routes are defined. In my case, Spring Boot Zuul Proxy server is running on Jetty container with GatewaySvc as the application context.
zuul.routes.gatewaysvc.path=/GatewaySvc/status
zuul.routes.gatewaysvc.url=forward:/GatewaySvc/status
For a standalone Spring Boot, which I have not tested, you may have to remove the context path from the above configuration.
zuul.routes.gatewaysvc.path=/status
zuul.routes.gatewaysvc.url=forward:/status
Reference:
Strangulation Patterns and Local Forwards

Spring Boot Cors behind reverse-proxy on non-default port

My application has a backend built with Spring Boot, and a frontend web application built with Angular and served by nginx.
Nginx is configured to reverse-proxy requests to /api/ to the backend :
location /api/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://back:8080;
}
If nginx is running on the default port (80), I can access my application at http://myserver/ without issues. XHR calls to http://myserver/api/some/REST/resource work perfectly.
If I change nginx to listen to some other port (randomly : 9043), then all XHR calls fail with an error "Invalid CORS request". Which is unexpected, because only my frontend application is making calls to the API, and thanks to the reverse-proxy this API is served on the same host as the javascript files.
For it to function, I need to add this to my Spring application :
#Configuration
public class CorsConfig {
#Value("${url.base:''}")
private String urlBase;
#Value("${cors.allowed.origins:[]}")
private String[] allowedOrigins;
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(urlBase + "/**")
.allowedOrigins(allowedOrigins)
.allowedMethods("*");
}
};
}
}
and then set cors.allowed.origins to http://myserver:9043/ in my configuration.
This works, but it is not practical if I'm going to :
make the hostname and/or port dynamic (port is derived from the branch name during CI build, then the containers are deployed to a rancher cluster)
hide the nginx behind a load-balancer (= another level of reverse-proxying)
Would there be a solution to fix this by doing any of the following :
Have spring boot to ignore the port number when validating CORS requests ?
Have nginx tweak the contents of the proxied request so that CORS validation by Spring succeeds ?
EDIT : some more details and example :
The services are dockerized :
Frontend : nginx listens on port 80 inside the container, docker exposes it on the host as port 9043
Backend : spring boot listens on 8080. The port is not exported by Docker, so it is only accessible from the frontend container, which has a link to the backend container.
It only works if the frontend is exposed to the outside world on port 80...
Add two http header in reverse request:
X-Forwarded-Proto
X-Forwarded-Port
to indecate the origin proto and ports (match the origin request).
see more: https://moi.vonos.net/java/spring-forward/

Spring Boot: specify port at the mapping level

Spring Boot: I want to have achieved the following: some URL paths are mapped to a port, some to another.
In other words I'd like something like:
public class Controller1 {
#RequestMapping(value="/path1", port="8080") public...
#RequestMapping(value="/path2", port="8081") public...
}
So that my app responds to both localhost:8080/path1 and localhost:8081/path2
It's acceptable to have 2 separate controllers within the app.
I have managed to partially succeed by implementing an EmbeddedServletContainerCustomizer for tomcat, but it would be nice to be able to achieve this inside the controller if possible.
Is it possible?
What you are trying to do would imply that the application is listening on multiple ports. This would in turn mean that you start multiple tomcat, since spring-boot packages one container started on a single port.
What you can do
You can launch the same application twice, using different spring profiles. Each profile would configure a different port.
2 properties:
application-one.properties: server.port=8080
application-two.properties: server.port=8081
2 controllers
#Profile("one")
public class Controller1 {
#RequestMapping(value="/path1") public...
}
#Profile("two")
public class Controller2 {
#RequestMapping(value="/path2") public...
}
Each controller is activated when the specified spring profile is provided.
Launch twice
$ java -jar -Dspring.profiles.active=one YourApp.jar
$ java -jar -Dspring.profiles.active=two YourApp.jar
While you cannot prevent making call on the undesired port, you can specify HttpServletRequest among other parameters of the method of the controller, and then use HttpServletRequest.getLocalPort() to obtain the port the call is made on.
Then you can manually return the HTTP error code if the request is made on the wrong port, or forward to another controller if the design is such that same path on different ports must be differently processed.

Spring security oauth2 wrongly using internal URL as current URI for redirection

In the Spring definition of a remote resource that is protected via OAuth2 to which the client application wants access, I set use-current-uri to true, in other words, the current URI should be used as a redirect (if available). It looks like:
<oauth:resource id="myResourceId" type="authorization_code"
client-id="${clientId}" client-secret="${clientSecret}"
access-token-uri="${accessTokenUri}"
user-authorization-uri="${userAuthorizationUri}"
use-current-uri="true"
scope="myScope"
pre-established-redirect-uri="${preEstablishedRedirectUri}"/>
Now the problem is, the Spring Security OAuth2 client will pick up the current internal Tomcat URL instead of the public web application's URL. The scenario is Tomcat server sitting behind Apache server, which results in two sets of URLs:
The public web application's URL is http://example.com/users/login
The internal Tomcat URL is http://localhost:8080/myapplication/users/login
Because the redirection URL is for the authorization server (e.g., Twitter, ORCID) to use to send back the authorization code, the public web application's URL should be used, not the internal one.
By the way, I'm using the following version of spring-security-oauth2:
spring-security-oauth2-1.0.5.RELEASE
spring-core-3.1.2.RELEASE
spring-security-core-3.1.3.RELEASE
Wonder if there is a way to tell Spring to use the public URL. Thanks.
Inside your tomcat conf/server.xml's connector element , try setting your public URLs that front tomcat like this:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
proxyName="example.com"
proxyPort="443" (or whatever port you are using, same goes for scheme )
scheme="https" />
This way tomcat's internal getServerName and getServerPort methods will start giving the correct values which hopefully should create the correct URL.
You might also want to configure your webserver to route requests falling at
http://example.com/users/login to http://localhost:8080/myapplication/users/login if not already done.

Resources