Extending SpringBootWebSecurityConfiguration with custom HttpSecurity configuration - spring

I am trying to gain a better understanding of the auto-configuration of spring boot. In particular I need to add some custom spring security configuration to disable authentication for HTTP OPTIONS verbs in order to get my CORS requests working.
Without any custom configuration by default the SpringBootWebSecurityConfiguration is loaded by Spring Boot's auto-configuration.
What I would like to do is to keep using this auto-configuration but add some additional http configuration. I tried this:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
Logger logger = LogManager.getLogger (SecurityConfiguration.class);
#Override
protected void configure (HttpSecurity http) throws Exception {
logger.info ("--- ALLOW all HTTP OPTIONS Requests");
http.authorizeRequests ().antMatchers (HttpMethod.OPTIONS, "*//**").permitAll ();
}
}
But this does not work as excepted. When I debug through SpringBootWebSecurityConfiguration and also the above code, I can see that both my configure-method and springs auto-configuration are executed but it looks like my http-configuration takes precedence.
So does that mean the auto-configuration is available only in an all-or-nothing kind of way? Or can I use the the auto-configuration but still extend it with some custom antMatcher?
What is the best-practice for this scenario?

You could create a new servlet filter and place it before the Spring security filter. For example:
#Component("CORSFilter")
public class CORSFilter implements Filter {
public void doFilter(
ServletRequest req,
ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Origin", "THE_HOST_YOU_WANT_TO_ALLOW_HERE");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT, PATCH, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"origin, content-type, accept, x-requested-with, authorization");
if (request.getMethod().equals("OPTIONS")) {
try {
response.getWriter().print("OK");
response.getWriter().flush();
} catch (IOException e) {
//...
}
} else {
chain.doFilter(req, res);
}
}
public void init(FilterConfig filterConfig) {
//...
}
public void destroy() {
//...
}
}
and then add this to your web.xml (or configure in your WebInitializer if you are using Java config only):
<filter>
<filter-name>CORSFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.[YOUR_SERVLET_NAME]</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

The reason you cannot do it is you are missing the annotation #EnableWebSecurity, have a look at the javadoc:
* Add this annotation to an {#code #Configuration} class to have the Spring Security
* configuration defined in any {#link WebSecurityConfigurer} or more likely by extending
* the {#link WebSecurityConfigurerAdapter} base class and overriding individual methods:

Related

Angular4 CORS header contains multiple values

please could somebody help to resolve issue:
backend spring application
web.xml
<filter>
<filter-name>corsFilter</filter-name>
<filter-class>package.controllers.auth.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>corsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
filter
public class CorsFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Origin", "*,*");
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,Access-Control-Allow-Origin");
httpResponse.setHeader("Access-Control-Expose-Headers", "Access-Control-Allow-Credentials");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Max-Age", "10");
System.out.println("---CORS Configuration Completed---");
chain.doFilter(request, response);
}
Angular frontend:
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(AppSettings.API_ENDPOINT + '/url', JSON.stringify(user), options)
.map(response => response.json())
}
Browser return issue:
Response to preflight request doesn't pass access control check: The
'Access-Control-Allow-Origin' header contains multiple values
I was also facing same issue. Root cause was that cors header was going multiple times. I commented out
httpResponse.setHeader("Access-Control-Allow-Origin", "*");"
line.
I had only one security config class which looks like below. This class, solved my issue of cors.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// your security config here
.authorizeRequests()
.antMatchers(HttpMethod.TRACE, "/**").denyAll()
.antMatchers("/admin/**").authenticated()
.anyRequest().permitAll()
.and().httpBasic()
.and().headers().frameOptions().disable()
.and().csrf().disable()
.headers()
// the headers you want here. This solved all my CORS problems!
/* .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Origin", "*"))*/
.addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Methods", "POST, GET"))
.addHeaderWriter(new StaticHeadersWriter("Access-Control-Max-Age", "3600"))
.addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Credentials", "true"))
.addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Headers", "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization"));
}
}
In my case, i am just doing POC, and calling Rest resource from angular application. I was facing only issue in CORS .
To solve only cors issue, bare minimum below code is also sufficient.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
}
}

How to handle CORS URLs on Prod/Dev environments?

In our Spring Boot app, we made the first deployment on our Quality environment and now we want to make it simple defining URLs to accept petitions from our FrontEnd application.
We build our application with maven and then we execute it with the command
java -Dspring.profiles.active=prod -jar myapp-0.0.1-SNAPSHOT.jar
We thought we could set the URL on the application.properties/application-prod.properties file, but this does not work as in execution time it is null. Another workaround would be somehow to get the parameter -Dspring.profiles.active=prod we pass when running the application and then take one URL or another but this seems to be a little dirty...
So what do you guys would do? I was impressed not finding anything on google, apparently people have different workarounds or I am searching in the wrong way.
Edit
Cross Origin info:
This is how we implemented it at first.
#CrossOrigin(origins = BasicConfiguration.CLIENT_URL)
And this is how we want to do it now with a filter with Spring Security
public class CorsFilter implements Filter, ApplicationContextAware {
#Value("${urlServer}")
private String urlServer;
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", urlServer);
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
response.setHeader("Access-Control-Expose-Headers", "Location");
chain.doFilter(req, res);
}
#Override
public void init(FilterConfig filterConfig) {}
#Override
public void destroy() {}
}
Of course urlServer is defined in application.properties with its corresponding metadata.
EDIT 2
How I initialize the filter:
#Bean
public FilterRegistrationBean corsFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CorsFilter());
registration.addUrlPatterns("/sessionLogin");
return registration;
}
The problem is that you CorsFilter is not a spring bean. You can eather define it like a bean, or do something like this:
#Bean
public FilterRegistrationBean corsFilter(#Value("${app.cors.url.server}") String urlServer) {
FilterRegistrationBean registration = new FilterRegistrationBean();
CorsFilter corsFilter = new CorsFilter();
corsFilter.setUrlServer(urlServer);
registration.setFilter(corsFilter);
registration.addUrlPatterns("/sessionLogin");
return registration;
}
Of course, you will need to define setter in your CorsFilter for urlServer field

SpringMVC Session Timeout - Redirect to a Special JSP

I've looked everywhere but haven't found a simple solution.
We have a special JSP, timeout.jsp, that needs to be shown whenever a SpringMVC module intercepts an invalid session action. The timeout is already configured in web.xml and works correctly.
Previously in Struts, it was a matter of defining a forward and intercepting dispatchMethod,
<forward name="sessionTimeout" path="/WEB-INF/timeout.jsp" redirect="false" />
#Override
protected ActionForward dispatchMethod(final ActionMapping mapping, final ActionForm form,
final HttpServletRequest request, final HttpServletResponse response, final String name)
throws Exception {
//...
if (!isSessionValid())
return mapping.findForward("sessionTimeout");
}
But how would you implement a catch-all solution in SpringMVC modules?
All my SpringMVC URLs come to this servlet mapping, *.mvc:
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>*.mvc</url-pattern>
</servlet-mapping>
Anything that sends a URL with this pattern should be cross-checked for session validity and if invalid, redirected to timeout.jsp.
NOTE
The solution given here (https://stackoverflow.com/a/5642344/1005607) did not work:
<web-app>
<error-page>
<exception-type>org.springframework.web.HttpSessionRequiredException</exception-type>
<location>/index.jsp</location>
</error-page>
</web-app>
There's a NullPointerException in my SpringMVC Form Code even before any kind of SessionRequiredException, as soon as I try to access the session. I need to globally protect against these NullPointerExceptions.
My final solution: an old-fashioned Filter. It works for me, no other simple solution available.
web.xml
<filter>
<filter-name>spring_mvc_controller_filter</filter-name>
<filter-class>myapp.mypackage.SpringMVCControllerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>spring_mvc_controller_filter</filter-name>
<url-pattern>*.mvc</url-pattern>
</filter-mapping>
SpringMVCControllerFilter
public class SpringMVCControllerFilter implements Filter
{
#Override
public void destroy() {
// TODO Auto-generated method stub
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpSession session = request.getSession(false);
if (session.isValid() && !session.isNew())
{
chain.doFilter(request, response);
}
else
{
request.getRequestDispatcher("/WEB-INF/jsp/sessionTimeout.jsp").forward(request, response);
}
}
#Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}

Spring - Resteasy - Cors Double Access-Control-Allow-Origin header in response

I setup a web application with Spring 3 and Resteasy; since my resources require authentication I am not allowed to use * as Access-Control-Allow-Origin.
So I configured
org.jboss.resteasy.plugins.interceptors.CorsFilter
with the right origin domain.
This works with a desktop client (Paw for Mac Os and others), but not with the browser (Chrome); the problem is that the response contains a double value for Access-Control-Allow-Origin, that is the one I configured and '*'.
CorsFilter is not to blame because, even if you configure more than one origin, it always puts just one value for the header, the one which the request asked for.
I simply have no idea on who's putting that extra (and wrong) header, any idea on where I could look for?
Please note that the double header occurs on GET requests but not on OPTIONS requests.
I'm not sure where your doubled header comes from, but did you try to use custom filter?
e.g. :
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {
public SimpleCorsFilter() {
}
#Override
public void doFilter(ServletRequest req,
ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Authorization, Content-Type");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
I finally found out there is a proprietary MessageBodyWriterInterceptor in the classpath which does a wrong add header; now it's on me to remove that.
One thing I learned is that if something happens only when there is a body to write, a good starting point is surely the rendering pipeline
I've tried the following actions and it worked as a charm:
First, register the CorsFilter provider class in your web.xml:
<context-param>
<param-name>resteasy.providers</param-name>
<param-value>org.jboss.resteasy.plugins.interceptors.CorsFilter</param-value>
</context-param>
By doing so, your server is already enabled to handle CORS requests, however, you need to add some allowed origins to get it working, therefore, you should get access to the CorsFilter's instance, which was created by RestEasy then add all the URLs you wish to grant access to or add a * if you wish to grant access to any.
In this regard, if you're using RestEasy Spring Integration, you'll need to grab an instance of the org.jboss.resteasy.spi.ResteasyProviderFactory class by autowiring it into your code:
#Autowired
private ResteasyProviderFactory processor;
then use a setup method annotated with #PostConstruct to get the instance of the CorsFilter from the ResteasyProviderFactory like the code snippet below:
#PostConstruct
public void setUp() {
ContainerRequestFilter[] requestFilters = processor.getContainerRequestFilterRegistry().preMatch();
CorsFilter filter = (CorsFilter) Iterables.getLast(Iterables.filter(Arrays.asList(requestFilters), Predicates.instanceOf(CorsFilter.class)));
filter.getAllowedOrigins().add("*");
}
P.S.: I'm using this frameworks:
Spring Framework 3.2.18.RELEASE
RestEasy 3.0.12.Final
I hope it helps!
For those struggling like me who don't use the Application starter but only Spring + Reasteasy.
Just add in web.xml:
<context-param>
<param-name>resteasy.providers</param-name>
<param-value>package.to.your.cors.CORSFilter</param-value>
</context-param>
And create the Java Class CORSFilter like
package package.to.your.cors;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
#Provider
public class CORSFilter implements ContainerResponseFilter {
#Override
public void filter(final ContainerRequestContext requestContext,
final ContainerResponseContext cres) throws IOException {
cres.getHeaders().add("Access-Control-Allow-Origin", "*");
cres.getHeaders().add("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
cres.getHeaders().add("Access-Control-Allow-Headers", "X-Auth-Token, Content-Type");
cres.getHeaders().add("Access-Control-Max-Age", "4800");
}
}
It works like a charm.
PS: inspired by s_bighead answer but I could not comment his answer to add my details.

SimpleCORSFilter not working

I am using Spring Rest as my backend, when I sent request by $.ajax{}, I got error message:
XMLHttpRequest cannot load http://121.40.249.129:8080/user/login. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8000' is therefore not allowed access.
So, I added SimpleCORSFilter in my Spring Project:
SimpleCORSFilter:
#Component
public class SimpleCORSFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version");
chain.doFilter(req, res);
}
#Override
public void init(FilterConfig filterConfig) {}
#Override
public void destroy() {}
}
The since I don't have web.xml, so I didn't add the code to web.xml:
<filter>
<filter-name>simpleCORSFilter</filter-name>
<filter-class>xxx.xxx.SimpleCORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>simpleCORSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
So, I still get the error, how can I fix it.
As I have experienced, * won't work for Access-Control-Allow-Origin when Access-Control-Allow-Credentials is set to true.
So, you should instead have
response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8000");
Actually, instead of hardcoding the url, you can have that as a property. If you want to allow multiple origins, this answer would help.
Also:
It's advised to annotate the class with #Order(Ordered.HIGHEST_PRECEDENCE), because this filter should come first.
If you are using CSRF, the corrosponding header should also be added to the list in Access-Control-Allow-Headers.
Update:
As #RTzhong did (see his comments below), replacing * with request.getHeader("Origin") seems like the ideal fix. However, a better security practice in general would be to specify the actual url or first checking with a whitelist, unless one must expose his API publicly to unknown websites.
Refer to Spring Lemon's source code for a concrete example.

Resources