How to redirect automatically to https with Spring Boot - spring

How I can easily configure the embedded tomcat server to redirect all http traffic to https? I have Spring Boot running on an ec2 instance that is behind an elastic load balancer. I have configured the ELB to handle ssl for me (which is awesome) and it sets the X-FORWARDED-PROTO header to "https". I want to detect when that isn't set, and redirect the user to force them to use https if they aren't already.
So far, I have tried adding the following to my application.properties file with no luck:
server.tomcat.protocol-header=x-forwarded-proto
security.require-ssl=true

My answer is a little late but I just recently had this problem and want to post a solution which worked for me.
Originally, I thought that setting tomcat up to use the X-Forwarded headers would suffice but the RemoteIPValve from Tomcat, which should normally handle this case, didnt work for me.
My solution was to add an EmbeddedServletContainerCustomizer and add a ConnectorCustomizer:
(note that I am using Tomcat 8 here)
#Component
public class TomcatContainerCustomizer implements EmbeddedServletContainerCustomizer {
private static final Logger LOGGER = LoggerFactory.getLogger(TomcatContainerCustomizer.class);
#Override
public void customize(final ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
final TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.addConnectorCustomizers(connector -> {
connector.setScheme("https");
connector.setProxyPort(443);
});
LOGGER.info("Enabled secure scheme (https).");
} else {
LOGGER.warn("Could not change protocol scheme because Tomcat is not used as servlet container.");
}
}
}
The important thing is that you not only set the Scheme to https but also the ProxyPort without which all internal redirects from Spring Boot were routed to port 80.

The configuration property security.require-ssl doesn't work when basic authentication is disabled (at least on old versions of Spring Boot). So you probably need to secure all the requests manually with code similar to this one:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Inject private SecurityProperties securityProperties;
#Override
protected void configure(HttpSecurity http) throws Exception {
if (securityProperties.isRequireSsl()) http.requiresChannel().anyRequest().requiresSecure();
}
}
You can check my full answer here: Spring Boot redirect HTTP to HTTPS

You will need a keystore file and few config classes.
The below link explains it in detail.
Https on embedded tomcat

Spring Boot 2.0 redirection of http to https:
Add the following to the #Configuration
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(redirectConnector());
return tomcat;
}
private Connector redirectConnector() {
Connector connector = new Connector(
TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}

Related

Is it possible to run SpringFox' swagger and swagger-ui on a different port than the main application?

We are using SpringBoot and SpringFox using #EnableSwagger2 to expose the swagger-ui.html API documentation (we don't need it to automate client code, just as documentation and test ui).
Is it possible to run all swagger related endpoints under a different port (for example the spring boot management/monitoring port) than the main application?
I researched a bit, but did not find a way in swagger's/springfox' configuration to do it. Is there a spring way to do this?
Yes, there is a Spring way of doing this:
Step 1. Adding an additional Tomcat connector
To add a port to the embedded server an additional connector needs to be configured.
We will do it by providing custom WebServerFactoryCustomizer:
#Component
public class TomcatContainerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Value("${swagger.port}")
private int swaggerPort;
#Override
public void customize(TomcatServletWebServerFactory factory) {
Connector swaggerConnector = new Connector();
swaggerConnector.setPort(swaggerPort);
factory.addAdditionalTomcatConnectors(swaggerConnector);
}
}
Now Tomcat listens on two ports but it serves the same content on both of them. We need to filter it.
Step 2. Adding a filter
Adding a servlet filter is pretty straightforward with a FilterRegistrationBean.
It can be created anywhere, I added it directly to the TomcatContainerCustomizer.
#Component
public class TomcatContainerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Value("${swagger.port}")
private int swaggerPort;
#Value("${swagger.paths}")
private List<String> swaggerPaths;
#Override
public void customize(TomcatServletWebServerFactory factory) {
Connector swaggerConnector = new Connector();
swaggerConnector.setPort(swaggerPort);
factory.addAdditionalTomcatConnectors(swaggerConnector);
}
#Bean
public FilterRegistrationBean<SwaggerFilter> swaggerFilterRegistrationBean() {
FilterRegistrationBean<SwaggerFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new SwaggerFilter());
filterRegistrationBean.setOrder(-100);
filterRegistrationBean.setName("SwaggerFilter");
return filterRegistrationBean;
}
private class SwaggerFilter extends OncePerRequestFilter {
private AntPathMatcher pathMatcher = new AntPathMatcher();
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
boolean isSwaggerPath = swaggerPaths.stream()
.anyMatch(path -> pathMatcher.match(path, httpServletRequest.getServletPath()));
boolean isSwaggerPort = httpServletRequest.getLocalPort() == swaggerPort;
if(isSwaggerPath == isSwaggerPort) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
httpServletResponse.sendError(404);
}
}
}
}
The properties swagger.port and swagger.paths are configured in the application.yaml:
server.port: 8080
swagger:
port: 8088
paths: |
/swagger-ui.html,
/webjars/springfox-swagger-ui/**/*,
/swagger-resources,
/swagger-resources/**/*,
/v2/api-docs
So far so good: the swagger-ui is served on the port 8088, our api on the 8080.
But there is a problem: when we try to connect to the api from the swagger-ui,
the requests are sent to the 8088 instead of 8080.
Step 3. Adjusting SpringFox config.
Swagger assumes that the api runs on the same port as the swagger-ui.
We need to explicitly specify the port:
#Value("${server.port}")
private int serverPort;
#Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.host("localhost:" + serverPort);
}
And the last problem: as the ui runs on a different port than the api,
the requests are considered cross-origin. We need to unblock them.
It can be done globally:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**/*").allowedOrigins("http://localhost:" + swaggerPort);
}
};
}
or by adding annotations to the controllers:
#CrossOrigin(origins = "http://localhost:${swagger.port}")
Versions used: SpringBoot 2.2.2.RELEASE, springfox-swagger2 2.9.2
For a working example see https://github.com/mafor/swagger-ui-port
I don't think so. When you're setting the Spring Boot management port (management.server.port), a second application server gets started to serve the actuator stuff. As far as I know there is no possibility (apart from custom actuator endpoints) to publish something on that server.
What is your use case exactly? Do you want to prevent access to Swagger in production or for non-authenticated users?

Make websocket (sockjs) work with spring4 running at GlassFish 3.1.2.2

I try to create websocket connection using sockjs on client and spring4 (with java config) on backend under GlassFish 3.1.2.2 but whatever I do it always fail with the following problem:
java.lang.IllegalArgumentException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "<async-supported>true</async-supported>" to servlet and filter declarations in web.xml. Also you must use a Servlet 3.0+ container
However it works correctly under Tomcat without any error.
Is there a way to make this work under GlassFish 3.1.2.2? Or should I rewrite Spring java config back to web.xml style? If yes, can somebody show me an example?
Thank you.
I already enabled websocket in http protocol configuration in GlassFish manager and set
dynamic.setAsyncSupported(true);
in the WebApplicationInitializer.
#Configuration
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(final ServletContext servletContext) throws ServletException {
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(IdCardReaderWebsocketApp.class);
ctx.setServletContext(servletContext);
final Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.setLoadOnStartup(2);
dynamic.addMapping("/sockjs/*");
dynamic.setAsyncSupported(true);
}
}
#Configuration
#ComponentScan("com.something.websocket.idcardreader.mock")
#EnableWebMvc
#Import({ IdCardReaderWebsocketConfig.class })
public class IdCardReaderWebsocketApp extends WebMvcConfigurerAdapter {
}
#Configuration
#EnableWebSocketMessageBroker
public class IdCardReaderWebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/idCardReaderWebsocketMockEndpoint").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(final MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/mockApp");
registry.enableSimpleBroker("/idcard");
}
}
Ok, finally I fixed the issue. I had also web.xml file with an other filter. So I added
<async-supported>true</async-supported>
to that filter and problem solved. It still failes with ws:// protocol but at least it works with http when sockjs trying.

spring boot actuator connect jmx programmatically

I'd like to use the shutdown endpoint of my Spring Boot 2.0.1 application from the command line. For that I have only added the spring-boot-starter-actuator to my Gradle file and enabled the shutdown endpoint in the configuration.
I also created a very simple tool that tries to connect via JMX to the running application.
Snippet:
String url = "service:jmx:rmi:///jndi/rmi://127.0.01:<which port?>/jmxrmi";
JMXServiceURL serviceUrl = new JMXServiceURL(url);
JMXConnectorFactory.connect(serviceUrl, null); <-- KAPOW!
JMX is working because I can use jconsole to connect locally. I just have no clue how to do it programmatically.
Any other attempts to explicitly set a port as mentioned here didn't work. Any hints?
It's probably easier to enable jolokia rather than using RMI; then you can simply
curl http://localhost:8080/actuator/jolokia/exec/org.springframework.boot:type=Admin,name=SpringApplication/shutdown
EDIT
If you prefer to use RMI, refer to the Spring Framework JMX Documentation.
Server app:
#SpringBootApplication
public class So50392589Application {
public static void main(String[] args) {
SpringApplication.run(So50392589Application.class, args);
}
#Bean
public RmiRegistryFactoryBean rmi() {
RmiRegistryFactoryBean rmi = new RmiRegistryFactoryBean();
rmi.setPort(1099);
return rmi;
}
#Bean
public ConnectorServerFactoryBean server() throws Exception {
ConnectorServerFactoryBean fb = new ConnectorServerFactoryBean();
fb.setObjectName("connector:name=rmi");
fb.setServiceUrl("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector");
return fb;
}
}
Client app:
#SpringBootApplication
public class JmxClient {
public static void main(String[] args) {
new SpringApplicationBuilder(JmxClient.class)
.web(WebApplicationType.NONE)
.run(args);
}
#Bean
public ApplicationRunner runner(MBeanServerConnection jmxConnector) {
return args -> {
jmxConnector.invoke(new ObjectName("org.springframework.boot:type=Admin,name=SpringApplication"),
"shutdown", new Object[0], new String[0]);
};
}
#Bean
public MBeanServerConnectionFactoryBean jmxConnector() throws Exception {
MBeanServerConnectionFactoryBean jmx = new MBeanServerConnectionFactoryBean();
jmx.setServiceUrl("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector");
return jmx;
}
}
There is a much simpler approach if you do not need to connect to the app remotely using the jcmd tool introduced in Java SE 7, and the Attach API introduced in Java SE 6.
I've written a blog post that explains this in detail. It's too big to simply copy-paste here, following is the link to the relevant section.
https://blog.asarkar.com/technical/grpc-kubernetes-spring/#jmx
This is not a duplicate answer because when the question was asked, this answer didn't exist. This answer has already been tailored to this question; let's not get trigger happy mods.

SpringBoot HandlerInterceptor not intercepting library endpoint

I have a SpringBoot app where I have implemented a HandlerInterceptor to log general information about API usage. I want it to also log requests to Spring Security's OAuth2 endpoint but it does not intercept the request.
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
// register the interceptor that will write API usage info to a file
registry.addInterceptor(new ServiceUsageInterceptor());
}
How can I configure the HandlerInterceptor to intercept all requests?
Thanks
This turned out to be unrelated to the interceptor. The usage was being written to a log file using a custom AccessLogValve in the embedded Tomcat. Updating the pattern seemed to resolve the issue.
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory factory = (TomcatEmbeddedServletContainerFactory) container;
CustomAccessLogValve accessLogValve = new CustomAccessLogValve();
accessLogValve.setEnabled(true);
// set pattern
accessLogValve.setPattern("timestamp=\"%t\" local_host=\"%v\" status=\"%s\" remote_host=\"%h\" client_id=\"%q\" uri=\"%r\" execution_time=\"%D\"");
factory.addContextValves(accessLogValve);
}
}

Spring Boot - replace default embedded Tomcat connector

I need to add an AJP connector to embedded Tomcat and disable (or replace) the default connector that listens on 8080.
I've tried customizing this with EmbeddedServletContainerCustomizer, but I can't get a handle on the Tomcat object to replace the default connector created there. As a result I end up with the http port on 8080 in addition to my AJP ports.
Next, I've tried extending TomcatEmbeddedServletContainerFactory and overriding its getTomcatEmbeddedServletContainer method. Per the JavaDoc, this appears to be the perfect place to replace the default connector, but it still ends up being enabled (and doesn't create my AJP connector either). Any ideas what I might be missing? I've verified with the debugger that my configuration is being run.
Per answer below, here's the cleanest solution:
#Bean
public EmbeddedServletContainerFactory tomcat() {
TomcatEmbeddedServletContainerFactory myFactory = new TomcatEmbeddedServletContainerFactory();
myFactory.setProtocol("AJP/1.3");
myFactory.setPort(9000);
return myFactory;
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer2() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setRedirectPort(9001);
}
});
}
};
}
You can use a TomcatConnectorCustomizer to configure the existing connector to use AJP by adding it to the TomcatEmbeddedServletContainerFactory.
Just create a EmbeddedServletContainerCustomizer bean and reconfigure it to AJP:
#Configuration
public class ServletConfig {
// AJP port defined in properties (default 666)
#Value("${tomcat.ajp.port:666}")
private Integer ajpPort;
#Bean
public EmbeddedServletContainerCustomizer ajpContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.setProtocol("AJP/1.3");
tomcat.setPort(ajpPort);
}
};
}
}

Resources