Spring Boot and Zuul routes - java

There is a simple proxy:
#EnableZuulProxy
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public SimpleFilter simpleFilter(){
return new SimpleFilter();
}
}
Pre filter:
public class SimpleFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(SimpleFilter.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
return null;
}
}
and properties:
zuul.ignored-patterns=/myserver/web/**
zuul.routes.myserver.path=/myserver/api/**
zuul.routes.myserver.url=http://localhost:80/myserver/api
zuul.routes.myserver.sensitiveHeaders = Cookie,Set-Cookie
server.port=3000
In general, everything works well.
But the web pages that the proxy sends have links like
href="http://localhost:80/myserver/api/item"
A must be of the form like:
href="http://server_ip:3000/myserver/api/item"
How to configure a server to send the correct links?
Cases:
1.When accessing the myserver directly from the Internet like:
http://server_ip:80/myserver/api/item
server sends the page with the links like:
href="http://server_ip:80/myserver/api/item"
2.When accessing the proxy from the Internet like:
http://server_ip:3000/myserver/api/item
proxy-server sends the page with the links like:
href="http://localhost:80/myserver/api/item"

Understood and tried different options.
All I needed to solve the problem was add to the settings:
.properties
......
zuul.add-host-header = true
......

Related

how to force hostname when behind a chain of 2 revese proxy

An spring boot application is hosted behind 2 reverse proxy (chained).
reverse-proxy 1 --> reverse-proxy 2 --> spring boot app
And the host and forward headers are not chain correctly. there is a way to force the host to a fixed value? like the hostname of the "reverse proxy 1"?
i have fixed my issue by changing the serverName in incoming request.
i have add a valve to tomcat:
public class HostForceValve extends ValveBase {
private final String proxyName;
public HostForceValve(String proxyName) {
this.proxyName = proxyName;
}
#Override public void invoke(Request request, Response response) throws IOException, ServletException {
org.apache.coyote.Request coyoteRequest = request.getCoyoteRequest();
MimeHeaders mimeHeaders = coyoteRequest.getMimeHeaders();
mimeHeaders.removeHeader("host");
final MessageBytes host = mimeHeaders.addValue("host");
host.setString(proxyName);
request.setRemoteHost(proxyName);
request.getCoyoteRequest().serverName().setString(proxyName);
try {
Valve next = getNext();
if (null == next) {
return;
}
next.invoke(request, response);
} finally {
request.setRemoteHost(proxyName);
}
}
}
And add this value to the tomcat embedded server:
#Component
public class MyTomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Value("${proxyName:}")
private String proxyName;
#Override
public void customize(TomcatServletWebServerFactory factory) {
final Collection<Valve> currents = factory.getEngineValves();
final ArrayList<Valve> addValves = new ArrayList<>(currents);
if (StringUtils.hasLength(proxyName)) {
addValves.add(0, new HostForceValve(proxyName));
}
factory.setEngineValves(addValves);
}
}

How to get the hostname of the server to which a request will be forwarded to in zuul/ribbon

I'm currently using Zuul and Ribbon as a reverse proxy and load balancer respectively. I'm also using Eureka as service discovery. I have more than one instance of a service in Eureka, and I want to know the hostname of the server chosen by Ribbon.
This is my current configuration:
GatewayApplication.java:
#EnableZuulProxy
#EnableDiscoveryClient
#SpringBootApplication
public class GatewayApplication {
static RequestQueue q = new RequestQueue();
public static void main(String[] args) {
q.start();
SpringApplication.run(GatewayApplication.class, args);
}
#Bean
public LogIncomingRequest logIncomingRequest() {
return new LogIncomingRequest(q);
}
#Bean
public LogLeavingRequest logLeavingRequest() {
return new LogLeavingRequest(q);
}
}
application.yml:
server:
port: 4000
spring:
application:
name: zuul-gateway
zuul:
sensitive-headers:
eureka:
client:
serviceUrl:
defaultZone: http://${EUREKA:10.0.2.15:8761}/eureka/
register-with-eureka: true
fetch-registry: true
instance:
prefer-ip-address: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000
I also have a pre and a post filter. How can I get the information about the server that was chosen by ribbon?
I found this code but I have no idea where to use it and how to access the information.
#Component
public class RibbonInterceptor extends ZoneAvoidanceRule {
#Override
public Server choose(Object key) {
Server choose = super.choose(key);
System.out.println(choose);
return choose;
}
Are there any other solutions?
Thanks in advance!
Implement a "pre" filter in your zuul api gateway, if you have a look at PreDecorationFilter of zuul, you will see that it determines where and how to route based on the supplied. Also sets various proxy related headers for downstream requests
in your filters run method
context = RequestContext.getCurrentContext();
request = context.getRequest();
call the getRouteHost method on context object it will give you all the route related information like protocol, host, port etc..
RequestContext.getCurrentContext().getRouteHost();
NOTE: the order of your filter should be > 5, since preDecorationFilter has order of 5
#Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER;
}
I just managed to find a solution for my problem. I created a POST filter and i got the information i needed using this code:
RequestContext ctx = RequestContext.getCurrentContext();
((IResponse) ctx.get("ribbonResponse")).getRequestedURI();
The solution that Fábio Pina posted just works for me. Here is what I'm using for logging:
#Component
public class ZuulLoggingFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
RequestContext ctx = RequestContext.getCurrentContext();
logger.info("********************************************************");
logger.info("RequestedURI -> {}", ((IResponse) ctx.get("ribbonResponse")).getRequestedURI());
logger.info("********************************************************");
return null;
}
#Override
public String filterType() {
return "post";
}
#Override
public int filterOrder() {
return 1;
}
}

How to pass NTLM credentials to zuul request header

I have a Windows service "A" being used for authentication purposes (NOT managed by us) and I have Spring-boot based REST Api service "B" (managed by us) which uses Zuul to route traffic. There is an external service "C" (NOT managed by us) that needs to talk to the Windows service through our REST Apis. Since "A" uses NTLM authentication we need to pass the request body from "C" and add the ntlm credentials in the headers at "B" and route the traffic using zuul.
My question is, how do I add NTLM credentials in Java to the routed traffic in zuul headers?
~ Jatin
You need to write your own ZuulFilter.
Something along the lines of
import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.ZuulFilter;
public class MyFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// now add your headers to the request
return null;
}
}
In your app just make sure the filter bean is created and it will be automatically registered:
#EnableZuulProxy
#SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
#Bean
public MyFilter myFilter() {
return new MyFilter();
}
}
Have a look at this guide for more info.
Zuul will work fine with Spring Session. There are many blogs about this.
http://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot.html

Spring Security: isAuthenticated using Ajax

I'm looking for better architecture solution. Currently we have following end-point:
/**
* Endpoint for frontend to be sure we are logged in
*/
#RequestMapping(value = "/is_auth")
public boolean getAuth() {
return true;
}
This end-point is covered by Spring Security and only authenticated users have access to it.
What is the best practice of making frontend aware of user authentication state?
It looks like you are using pooling to check the login status. Your controller method
#RequestMapping(value = "/is_auth")
public boolean getAuth() {
return true;
}
will never return false. So in general there is no need to have a return value in this case.
#ResponseStatus(value = HttpStatus.OK)
#RequestMapping(value = "/is_auth")
public void ping() {
// log ?
}
I believe the best solution would be a websocket connection between client and server. If you then implement a SessionListener, you can very easy send a login status to corresponding client if his session get expired:
//
// pseudo code
//
#Component
public class SessionListener implements HttpSessionListener {
private static final Logger logger = LoggerFactory.getLogger(SessionListener.class);
#Autowired
private IWebsocketService websocketService; // you own service here
#Override
public void sessionCreated(HttpSessionEvent se) {
logger.debug("sessionCreated: {}", se.getSession().getId());
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
String sessionId = se.getSession().getId();
logger.debug("sessionDestroyed: {}", sessionId);
websocketService.sendLoginStatus(sessionId, false);
}
}
EDIT: here is a very good example how to implement websockets with spring and javascript: Using WebSocket to build an interactive web application

How to disable ErrorPageFilter in Spring Boot?

I'm creating a SOAP service that should be running on Tomcat.
I'm using Spring Boot for my application, similar to:
#Configuration
#EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)
public class AppConfig {
}
My webservice (example):
#Component
#WebService
public class MyWebservice {
#WebMethod
#WebResult
public String test() {
throw new MyException();
}
}
#WebFault
public class MyException extends Exception {
}
Problem:
Whenever I throw an exception within the webservice class, the following message is logged on the server:
ErrorPageFilter: Cannot forward to error page for request
[/services/MyWebservice] as the response has already been committed.
As a result, the response may have the wrong status code. If your
application is running on WebSphere Application Server you may be able
to resolve this problem by setting
com.ibm.ws.webcontainer.invokeFlushAfterService to false
Question:
How can I prevent this?
To disable the ErrorPageFilter in Spring Boot (tested with 1.3.0.RELEASE), add the following beans to your Spring configuration:
#Bean
public ErrorPageFilter errorPageFilter() {
return new ErrorPageFilter();
}
#Bean
public FilterRegistrationBean disableSpringBootErrorFilter(ErrorPageFilter filter) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setEnabled(false);
return filterRegistrationBean;
}
The simpliest way to disable ErrorPageFilter is:
#SpringBootApplication
public class App extends SpringBootServletInitializer {
public App() {
super();
setRegisterErrorPageFilter(false); // <- this one
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(App.class);
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
//set register error pagefilter false
setRegisterErrorPageFilter(false);
builder.sources(MyApplication.class);
return builder;
}
}
The best way is to tell the WebSphere container to stop ErrorPageFiltering. To achieve this we have to define a property in the server.xml file.
<webContainer throwExceptionWhenUnableToCompleteOrDispatch="false" invokeFlushAfterService="false"></webContainer>
Alternatively, you also can disable it in the spring application.properties file
logging.level.org.springframework.boot.context.web.ErrorPageFilter=off
I prefer the first way.Hope this helps.
I found in the sources that the ErrorPageFilter.java has the following code:
private void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
ErrorWrapperResponse wrapped = new ErrorWrapperResponse(response);
try {
chain.doFilter(request, wrapped);
int status = wrapped.getStatus();
if (status >= 400) {
handleErrorStatus(request, response, status, wrapped.getMessage());
response.flushBuffer();
}
else if (!request.isAsyncStarted() && !response.isCommitted()) {
response.flushBuffer();
}
}
catch (Throwable ex) {
handleException(request, response, wrapped, ex);
response.flushBuffer();
}
}
As you can see when you throw an exception and return a response code >= 400 it will do some code. there should be some additional check if the response was already committed or not.
The way to remove the ErrorPageFilter is like this
protected WebApplicationContext run(SpringApplication application) {
application.getSources().remove(ErrorPageFilter.class);
return super.run(application);
}
Chris
public class Application extends SpringBootServletInitializer
{
private static final Logger logger = LogManager.getLogger(Application.class);
public Application()
{
super();
setRegisterErrorPageFilter(false);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}

Categories

Resources