Spring Boot Starter GraphQL OPTIONS Pre-Flight request rejected with HTTP 403 - spring

I've recently converted a GraphQL API from SpringBoot Web to WebFlux. In the previous version, the #RequestMapping was annotated with #CrossOrigin, which seemed to cover the OPTIONS HTTP verb.
In the new version, I am using the #MutationMapping / #QueryMapping annotations to map the schema to my methods, and responding to them reactively.
The problem is that for some of our frontend's, an OPTIONS preflight is sent and rejected with a 403 by this new implementation.
Is there an annotation or configuration I can enable where this preflight will not be rejected?
I've attempted to use the GraphQlWebFluxAutoConfiguration.GraphQlEndpointCorsConfiguration object however I can't seem to get it configured correctly. Any advice would be much appreciated, as I'm relatively new to the WebFlux stack.

Here's how I solved it - the issue was caused by my local dev environment having tomcat9, and the staging/qa environment I was deploying to having tomcat7 (not supporting WebFlux properly).
Please note that the app.enable-cors is a property defined in my application.properties file - you will need to add this if you are just copying+pasting this code.
I created two configuration classes, overriding both the Mvc and WebFlux Configurer.addCorsMapping(CorsRegistry registry) method. Depending on which auto configuration is loaded, the correct overriden method will be run. Code as follows:
First, the WebFlux version
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
#Configuration
#ConditionalOnProperty(name = "app.enable-cors", havingValue = "true")
public class GraphQLWebFluxConfiguration implements WebFluxConfigurer {
#Override
public void addCorsMappings(CorsRegistry corsRegistry){
corsRegistry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*");
}
}
Secondly the MVC version
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
#ConditionalOnProperty(name = "app.enable-cors", havingValue = "true")
public class GraphQLWebMvcConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry corsRegistry){
corsRegistry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*");
}
}
I obviously have this disabled for the prod environment, but as a workaround for your QA/local environment it works well.

Related

SpringBoot (Webflux) 2.7.3 disable WebSecurity

How to disable the Springboot Websecurity in Springboot 2.7.3 when Webflux is in use.
This does not disable the webflux security but acts as a very bad workaround for now.
#EnableWebFluxSecurity
class WebfluxSecurityConfig {
#Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
http.authorizeExchange()
.pathMatchers("/**").permitAll()
return http.build()
}
}
Remove dependency spring-boot-starter-security in your pom.xml or build.gradle .
Excluding security dependencies from your project doesn't seem like the best idea, since you probably will need than at some point in time.
The best way will be to introduce the feature flag (simple configuration property) for enabling/disabling the security configurations in your project.
Please check my example security configuration below:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
#Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${api.security.enabled:false}")
private Boolean enabled;
#Override
protected void configure(HttpSecurity http) throws Exception {
if (this.enabled) {
// your configuration for enabled security
} else {
((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)http.authorizeRequests().anyRequest()).permitAll();
log.warn("Security is disabled");
}
}
}
We such configuration you will need to have such property in your application.yml or application.property file:
api:
security:
enabled: false
By default, security will be disabled. If you will need to enable it at some point in time or on some specific environment or profile, all you need to do is simply assign the true value to this property in your configs.

How to override default Spring Boot Login page?

I'm totally new with Spring Boot 2 and I have this simple application that I'm trying to run. Unfortunately, each time I run my service, I end up with this login form from Spring Boot. Is there any way I can override this?
I've already tried the solution from this question but it didn't work for me:
How to override Spring Security default configuration in Spring Boot
Here is what I have so far for the code:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* Defines the application, supports service registry, circuit breaker
* for Spring Boot application.
*
* #EnableJpaRepositories - add this to enable
*
* #author himanshu sharma
* #since June 2017
*/
#EnableDiscoveryClient
#EnableCircuitBreaker
#EnableHystrixDashboard
#EnableSwagger2
#EnableCaching
#SpringBootApplication
#ImportResource("classpath:service-config.xml")
#ComponentScan(basePackages = "com.manulife.ap.*, io.swagger")
public class Application{
#Autowired
DiscoveryClient discoveryClient;
/**
* Application start point.
* #param args
*/
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
#RequestMapping(value="/greeting", method=RequestMethod.GET)
public String sayHello () {
return "Hello from Spring Boot!";
}
}
--EDIT:#devshawn--
I tried the solution of excluding the SecurityAutoConfiguration.class but I somehow got this error message when I tried to access my endpoint:
I'm assuming you're using Spring Boot 2, as you did not mention which version of Spring you are using. In spring boot 2, you cannot disable the default login via properties files as you could in spring boot 1. There's a few ways of doing this depending on what you're trying to achieve.
Permit All Access, Secure Endpoints Later
Add a class titled SecurityConfig that looks like this:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll();
}
}
This removes authentication on all endpoints in your service. Later on, you can configure security further via this class.
Remove Security Auto Configuration
When Spring Security is found on the classpath, Spring Boot automatically configures security. You can remove it all together if you don't want it auto configured by changing your #SpringBootApplication annotation.
#SpringBootApplication(exclude = {SecurityAutoConfiguration.class})

How to serve static resources in Spring boot with #EnableWebMvc

Cracking head over this. The documentation says:
By default Spring Boot will serve static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext.
Minimal example:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
#SpringBootApplication
#EnableWebMvc
public class Main {
public static void main(final String[] args) {
SpringApplication.run(Main.class, args);
}
}
And create one directory /src/main/resources/public placing static resources there. When I run the application I only get 404. When I remove #EnableWebMvc resources are served as expected.
Attempted (and failed) remedies
/**
* #see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#addResourceHandlers(org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry)
*/
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/public/**").addResourceLocations("/public/");
}
In the application.properties added:
spring.mvc.static-path-pattern=/**
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
The question
So my question: What do I need to configure to serve static resource when I want to use the #EnableWebMvc annotation?
In the documentation you mentionned, it says :
If you want to take complete control of Spring MVC, you can add your
own #Configuration annotated with #EnableWebMvc.
You should try to use #EnableWebMvc with your configuration instead of your Spring boot application.
There's an example of this in this documentation.
Enabling the MVC Java Config or the MVC XML Namespace
To enable MVC Java config add the annotation #EnableWebMvc to one of
your #Configuration classes:
#Configuration
#EnableWebMvc
public class WebConfig {
}
Also in these examples :
Customizing the Provided Configuration
Conversion and Formatting
I hope this will help you.
You should configure a ViewResolver something like below along with your ResourceHandlers. Check this
#Bean
public InternalResourceViewResolver defaultViewResolver() {
return new InternalResourceViewResolver();
}

Spring Security LDAP Authentication does not work with Java 8

I am trying to use LDAP Authentication provided by Spring Security. Everything compiles fine. I get the following error on deploying the application.
Caused by: java.lang.RuntimeException: Could not postProcess org.springframework.security.ldap.server.ApacheDSContainer#54a76efa of type class org.springframework.security.ldap.server.ApacheDSContainer
at org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor.postProcess(AutowireBeanFactoryObjectPostProcessor.java:70)
at org.springframework.security.config.annotation.SecurityConfigurerAdapter$CompositeObjectPostProcessor.postProcess(SecurityConfigurerAdapter.java:123)
at org.springframework.security.config.annotation.SecurityConfigurerAdapter.postProcess(SecurityConfigurerAdapter.java:82)
at org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer.access$400(LdapAuthenticationProviderConfigurer.java:58)
at org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer$ContextSourceBuilder.build(LdapAuthenticationProviderConfigurer.java:555)
at org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer$ContextSourceBuilder.access$500(LdapAuthenticationProviderConfigurer.java:446)
at org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer.getContextSource(LdapAuthenticationProviderConfigurer.java:606)
at org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer.build(LdapAuthenticationProviderConfigurer.java:76)
Spring Core version is 4.3.2. Spring Security LDAP version is 4.1.1.
My Google research listed a 2013 post which says that the issue is because of incompatibility between Spring Security LDAP and Java 8. The same article said it has been fixed in some Spring Boot version. It does not talk about any fix for non-Spring boot libraries.
Has anybody tried Spring Security LDAP Authentication using Java 8? Please help.
Here's my working config using Java 8 and Spring Security LDAP. We're connecting our Spring Web app to an Active Directory instance to secure access by URL.
If I recall correctly, it took longer than I expected to get this working.
You'll need to change the "Base" for the LDAP context path, and note that the ldap.user is the full LDAP CN, not just a username. You can use and LDAP browser like JXplorer (http://jxplorer.org/) to get the LDAP settings correct.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);
#Value("ldap://${ldap.host}:${ldap.port:389}")
private String url;
#Value("${ldap.user}")
private String user;
#Value("${ldap.password}")
private String password;
#Override
protected void configure(HttpSecurity http) throws Exception {
LOGGER.info("Configuring security...");
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/index.html").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchFilter("(&(objectClass=user)(sAMAccountName={0}))")
.contextSource(ldapContextSource());
}
#Bean
public BaseLdapPathContextSource ldapContextSource() {
LOGGER.info("LDAP: {}", url);
LdapContextSource bean = new LdapContextSource();
bean.setUrl(url);
bean.setBase("DC=CORP,DC=MyCompany,DC=com");
bean.setUserDn(user);
bean.setPassword(password);
bean.setPooled(true);
bean.setReferral("follow");
return bean;
}
}
This assumes you have your LDAP settings in a configuration file that looks something like this
ldap.host=ldap.mycompany.com
ldap.user=CN=MyUser,OU=Service Accounts,OU=New-York,DC=CORP,DC=MyCompany,DC=com
# Encrypt using Jasypt or something
ldap.password=B1gS3cr3t

Validating JMS payload in Spring

I have a simple service sending emails. It can be invoked using REST and JMS APIs. I want the requests to be validated before processing.
When I invoke it using REST I can see that org.springframework.validation.DataBinder invokes void validate(Object target, Errors errors, Object... validationHints) and then validator from Hibernate is invoked. This works as expected.
The problem is I can't achieve the same effect with JMS Listener. The listener is implemented as follows:
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import our.domain.mailing.Mailing;
import our.domain.mailing.jms.api.SendEmailFromTemplateRequest;
import our.domain.mailing.jms.api.SendSimpleEmailRequest;
import javax.validation.Valid;
#ConditionalOnProperty("jms.configuration.destination")
#Component
#AllArgsConstructor(onConstructor = #__(#Autowired))
#Slf4j
public class SendMailMessageListener {
Mailing mailing;
#JmsListener(destination = "${jms.configuration.destination}")
public void sendEmailUsingTemplate(#Valid SendEmailFromTemplateRequest request) {
log.debug("Received jms message: {}", request);
mailing.sendEmailTemplate(
request.getEmailDetails().getRecipients(),
request.getEmailDetails().getAccountType(),
request.getTemplateDetails().getTemplateCode(),
request.getTemplateDetails().getLanguage(),
request.getTemplateDetails().getParameters());
}
#JmsListener(destination = "${jms.configuration.destination}")
public void sendEmail(#Valid SendSimpleEmailRequest request) {
log.debug("Received jms message: {}", request);
mailing.sendEmail(
request.getRecipients(),
request.getSubject(),
request.getMessage());
}
}
The methods receive payloads but they are not validated. It's Spring Boot application and I have #EnableJms added. Can you guide what part of Spring source code is responsible for discovering #Validate and handling it? If you have any hints on how to make it running I would appreciate it a lot.
The solution is simple and was clearly described in official documentation: 29.6.3 Annotated endpoint method signature. There are few things you have to do:
Provide configuration implementing JmsListenerConfigurer (add #Configuration class implementing this interface)
Add annotation #EnableJms on the top of this configuration
Create bean DefaultMessageHandlerMethodFactory. It can be done in this configuration
Implement method void configureJmsListeners(JmsListenerEndpointRegistrar registrar) of interface JmsListenerConfigurer implemented by your configuration and set MessageHandlerMethodFactory using the bean you've just created
Add #Validated instead of #Valid to payload parameters
You can use #Valid in your listeners. Your answer was very close to it. In the step when you create DefaultMessageHandlerMethodFactory call .setValidator(validator) where validator is from org.springframework.validation. You can configure validator like this:
#Bean
public LocalValidatorFactoryBean configureValidator ()
{
return new LocalValidatorFactoryBean();
}
And then inject validator instance into your jms config

Resources