Apache Tomcat 8.5.2 + Resteasy CORS filter stops working suddenly - ajax

I have a JAX-RS application (RestEasy, 3.1.2.Final) running on an embedded Apache Tomcat (8.5.2) instance. This is a public REST service so I have added a CORS filter from RestEasy to it:
import org.jboss.resteasy.plugins.interceptors.CorsFilter;
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = new LinkedHashSet<>();
// = = = CORS = = =
CorsFilter cors = getCorsFilter();
singletons.add(cors);
//...
return singletons;
}
private CorsFilter getCorsFilter() {
CorsFilter cors = new CorsFilter();
cors.getAllowedOrigins().add("*");
cors.setAllowCredentials(true);
cors.setAllowedMethods("GET,POST,PUT,DELETE,HEAD");
cors.setCorsMaxAge(1209600);
cors.setAllowedHeaders("Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,Accept-Encoding,Accept-Language,Access-Control-Request-Method,Cache-Control,Connection,Host,Referer,User-Agent");
return cors;
}
There is a security constraint defined in web.xml as well:
<security-constraint>
<web-resource-collection>
<web-resource-name>Tango RESTful gateway</web-resource-name>
<url-pattern>/rest/*</url-pattern>
<http-method>GET</http-method>
<http-method>HEAD</http-method>
<http-method>POST</http-method>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>desy-user</role-name>
<role-name>mtango-rest</role-name>
</auth-constraint>
</security-constraint>
In this setup everything works fine BUT for a few weeks. After a few weeks CORS preflight fails with 401 instead of normal sequence:
Request:
Host: mstatus.esrf.fr
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Origin: https://ingvord.github.io
DNT: 1
Connection: keep-alive
Response:
Connection: Keep-Alive
Content-Length: 62
Content-Type: application/octet-stream
Date: Sun, 17 Sep 2017 07:28:19 GMT
Keep-Alive: timeout=5, max=85
Server: Apache-Coyote/1.1
Obviously CORS filter is not executed anymore - there ain't response headers that it sets.
What could be the reason of a such behavior? Once again it works for a few weeks after a restart.
Application link: https://ingvord.github.io/tango-controls.demo/
Thanks in advance,

Apparently it seems the following extra configuration in web.xml has solved the issue:
<security-constraint>
<web-resource-collection>
<web-resource-name>CORS preflight</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>OPTIONS</http-method>
</web-resource-collection>
</security-constraint>
Well, at least we do not observe any misbehavior for a quite long period of time.

Related

Is this bad practice in Spring

#Controller
public class View implements InitializingBean {
#GetMapping("log")
public String log() {
return "log";
}
#Override
public void afterPropertiesSet() throws Exception {
System.out.println("init");
}
}
server.xml
<Host name="localhost" appBase="webapps"
autoDeploy="false" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">>
<Context crossContext="true" debug="5" docBase="/data/project/chenshun-tag-test/code" path="" reloadable="false">
</Context>
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
When I deploy in tomcat 8.5.45 and afterPropertiesSet fails to execute, tomcat will start successfully, but the context start successfully. I can curl jsp and return 200. What I want is that tomcat fails to start, Is there a problem with my use or configuration?
ths in advance :)
➜ bin curl -I 127.0.0.1:18080/fix.jsp
HTTP/1.1 200
Set-Cookie: JSESSIONID=5223231CAB44739BEAEB0BDEA879649F; Path=/; HttpOnly
Content-Type: text/html;charset=ISO-8859-1
Transfer-Encoding: chunked
Date: Fri, 27 Dec 2019 14:08:59 GMT
➜ bin curl -I 127.0.0.1:18080/view/1.json
HTTP/1.1 500
Content-Type: text/html;charset=utf-8
Content-Language: zh-CN
Transfer-Encoding: chunked
Date: Fri, 27 Dec 2019 14:09:08 GMT
Connection: close
Currently when a servlet with load-on-startup >=0 fails its startup, the context startup is still considered as OK.
With some webapps (like spring-based ones with a DispatcherServlet), this makes no sense at all : if the servlet failed its startup, the webapp is unuseable and it would be more sensible to have tomcat mark the context as failed.
refer https://bz.apache.org/bugzilla/show_bug.cgi?id=56461

Is it possible to define an end point in a Controller with a RequestBody and a RequestPart?

I need to create a single end point that accepts a RequestBody OR a RequestPart.
If the request contains the RequestPart it will execute some logic to process the MultipartFile otherwise it will process the object passed in the RequestBody.
I checked How to send #Requestbody and #Requestpart together in spring but it differs from my question because I don't want to send both, RequestBody and RequestPart, at the same time.
I defined my entry point as:
#RequestMapping(value="/xyz/api/{endPoint}", method= RequestMethod.POST)
public void endPointPost(
#PathVariable String endPoint,
HttpServletRequest request,
HttpServletResponse response,
#RequestBody(required=false) Object body,
#RequestPart(required=false) MultipartFile uploadFile) throws Exception {
If the request contains only the RequestBody it works correctly, for instance:
{"body":{"companyCD":"myTest"}}
However, when sending the multipart request it fails with the follwing error:
2019-10-18 00:50:43,440 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.handler.AbstractHandlerMapping: Mapped to public void com.monoplus.mcd.rest.GenericController.endPointPost(java.lang.String,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.lang.Object,org.springframework.web.multipart.MultipartFile) throws java.lang.Exception
2019-10-18 00:50:43,440 INFO [http-nio-8080-exec-8] com.monoplus.mcd.rest.ServletControllerInterceptor: ServletControllerInterceptor - preHandle
2019-10-18 00:50:43,442 DEBUG [http-nio-8080-exec-8] org.springframework.web.method.support.InvocableHandlerMethod: Could not resolve parameter [3] in public void com.monoplus.mcd.rest.GenericController.endPointPost(java.lang.String,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.lang.Object,org.springframework.web.multipart.MultipartFile) throws java.lang.Exception: Content type 'multipart/form-data;boundary=----WebKitFormBoundaryG1Xr4xtC2rNYWuCd;charset=UTF-8' not supported
2019-10-18 00:50:43,446 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: Using #ExceptionHandler public final org.springframework.http.ResponseEntity<java.lang.Object> org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest) throws java.lang.Exception
2019-10-18 00:50:43,481 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor: No match for [text/html, application/xhtml+xml, image/webp, image/apng, application/signed-exchange;v=b3, application/xml;q=0.9, */*;q=0.8], supported: []
2019-10-18 00:50:43,482 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver: Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundaryG1Xr4xtC2rNYWuCd;charset=UTF-8' not supported]
2019-10-18 00:50:43,483 INFO [http-nio-8080-exec-8] com.monoplus.mcd.rest.ServletControllerInterceptor: ServletControllerInterceptor - afterCompletion - org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryResponseWrapper#6c9a1e05
2019-10-18 00:50:43,484 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.FrameworkServlet: Completed 415 UNSUPPORTED_MEDIA_TYPE
Please note that Could not resolve parameter [3]... refers to the RequestBody parameter.
This is my multipart request:
Header
Host: localhost:88
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------262541039624932
Content-Length: 1401
Connection: keep-alive
Referer: http://localhost:88/appl/html/master/FileImport.html
Cookie: JSESSIONID=3f052417-1702-48b6-b7c2-cac5609ef525; SESSION=M2YwNTI0MTctMTcwMi00OGI2LWI3YzItY2FjNTYwOWVmNTI1
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
Body
-----------------------------262541039624932
Content-Disposition: form-data; name="uploadFile"; filename="testFile.txt"
Content-Type: text/plain
1 - File content
-----------------------------262541039624932
Content-Disposition: form-data; name="_ns"
-----------------------------262541039624932
Content-Disposition: form-data; name="_qt"
false
-----------------------------262541039624932
Content-Disposition: form-data; name="_body"
{"USER_NAME":""}
-----------------------------262541039624932--
Any help is appreciated.
Thank you
I'm thinking about this question from a RESTful point of view and not necessarily spring. If you are 1) trying to create or edit (post or put) a resource or 2) trying to upload a file; shouldn't those be two different URI Paths?
Thanks to Chris suggestion I was able to solve my question, I defined a different entry point for the Multipart content.
#RequestMapping(value="/xyz/api/{endPoint}", method= RequestMethod.POST, consumes = {"multipart/form-data"})
public void multiPartEndPointPost(
#PathVariable String endPoint,
HttpServletRequest request,
HttpServletResponse response
) throws Exception {
this.doSomeStuff(endPoint, request, response);
}
The important part is the consumes = {"multipart/form-data"} then I can use Apache Commons FileUpload to upload the files.
The answer for [Cannot use Apache Commons FileUpload with Spring Boot multipart.resolve-lazily also helped me to solve my question.

How to disable HttpOnly flag on Set-Cookie header on login in Spring Boot 2.1.0

I am having issues disabling the httpOnly flag on the set-cookie header. This is mainly an issue on login when the JSESSIONID is being sent back in the response. Note that this is on a tomcat server deployed on AWS EBS.
Any of the configs below work fine locally but no on deployment.
I have tried the following solutions, none seem to work
application.yml config
server:
servlet:
session:
cookie:
http-only: false
Servlet Context Initializer
#Bean
open fun servletContextInitializer(): ServletContextInitializer {
return ServletContextInitializer { servletContext ->
servletContext.setSessionTrackingModes(setOf(SessionTrackingMode.COOKIE))
val sessionCookieConfig = servletContext.sessionCookieConfig
sessionCookieConfig.isHttpOnly = false
}
WebServerFactoryCustomizer
#Bean
open fun tomcatCustomizer(): WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
return WebServerFactoryCustomizer { tomcat ->
tomcat
.addContextCustomizers(TomcatContextCustomizer { context -> context.useHttpOnly = false })
}
web.xml
<session-config>
<cookie-config>
<http-only>false</http-only>
</cookie-config>
</session-config>
Sample Request Header
Host:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer:
Authorization: Bearer null
Content-Type: application/json
Content-Length: 58
Origin:
Connection: keep-alive
TE: Trailers
Sample Response Header
HTTP/2.0 200 OK
date: Sat, 16 Mar 2019 14:11:58 GMT
set-cookie: AWSALB=qBpX9uFjtkP4H7gyJ3EXL8na0a7aARiEN/twi0cc2sPywvbysKXXaNfQbe8HaS5hcC6VRnkp09VYj0pGcXiHbWRod9OithDlQ0ZIvHSbY7B5xiJT1r8N+lcRdCcp; Expires=Sat, 23 Mar 2019 14:11:57 GMT; Path=/
server: Apache/2.4.37 (Amazon) OpenSSL/1.0.2k-fips
vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers
access-control-allow-origin:
access-control-allow-credentials: true
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: 0
strict-transport-security: max-age=31536000 ; includeSubDomains
x-frame-options: DENY
set-cookie: JSESSIONID=70F12355ABFDD0F42292D9F6CEAA22BF; Path=/; Secure; HttpOnly
X-Firefox-Spdy: h2
I was finally able to resolve it by creating a Filter that runs as part of Spring Security. The filter executes before the SecurityContextPersistenceFilter, thus waiting until the set-cookie header is added then updates the headers (before in the chain, gets last call after doFilter() executes).
Filter Implementation
package com.zambezii.app.security.filter
import org.springframework.web.filter.GenericFilterBean
import java.io.IOException
import javax.servlet.FilterChain
import javax.servlet.ServletException
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
class SessionFilter : GenericFilterBean() {
#Throws(IOException::class, ServletException::class)
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
val req = request as HttpServletRequest
val res = response as HttpServletResponse
chain.doFilter(req, res)
removeHttpOnlyFlag(res)
}
private fun removeHttpOnlyFlag(res: HttpServletResponse) {
val setCookieHeaderName = "set-cookie"
var setCookieHeader = res.getHeader(setCookieHeaderName)
if (setCookieHeader != null) {
setCookieHeader = setCookieHeader.replace("; HttpOnly", "")
res.setHeader(setCookieHeaderName, setCookieHeader)
}
}
}
Security Config
open class WebSecurityConfig() : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
...
.authenticated()
.and()
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter::class.java)
.addFilterBefore(SessionFilter(), SecurityContextPersistenceFilter::class.java)

Spring Boot logs out after custom error page

I have a Spring Boot application with HttpSecurity, which looks like the following.
Code
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.authorizeRequests()
.antMatchers("/*").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.antMatchers("/**").denyAll()
.and()
.formLogin()
.loginPage("/login").permitAll()
.usernameParameter("email")
.defaultSuccessUrl("/user/uploads")
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
}
I have added a custom error page (403) as displayed on the docs.spring.io website (the file structure).
Last, I created a small 403.html file:
<!DOCTYPE html>
<html lang="en" layout:decorator="layout/main">
<body>
<div layout:fragment="content">
<h1>403 - Permission Denied</h1>
<p>You do not have permission to retrieve the URL or link you requested.</p>
<p>Please contact the administrator of the referring page, if you think this was a mistake.</p>
<p>If you did this on purpose: behave and go back to the Homepage.</p>
</div>
</body>
</html>
This works: If a user is logged in and tries to access a /admin/ page, he will see the custom 403 page.
Problem
But the user is also logged out, for some reason! If I change the URL from the Access Denied page to /user/uploads (which I'm allowed to see as a logged in user), it will redirect me back to the login page.
Question
How can I make sure that the user isn't logged out when s/he sees the custom 403 page?
EDIT
Added the Spring Security debug log. It consists of 3 actions:
Log in to the website
Going to a forbidden /admin/users.html page
Clicking the 'homepage' link in the custom 403 page
Log:
2017-06-13 14:55:41.874 INFO 7144 --- [nio-8080-exec-1] Spring Security Debugger :
************************************************************
Request received for GET '/static/js/passwordChanging.js':
org.apache.catalina.connector.RequestFacade#146c683
servletPath:/static/js/passwordChanging.js
pathInfo:null
headers:
host: localhost:8080
user-agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0
accept: */*
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
referer: http://localhost:8080/adminconsole/
cookie: JSESSIONID=F690AA15EEAAF2DC9BD35E7CCFA5E94F
connection: keep-alive
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************
2017-06-13 14:55:41.879 INFO 7144 --- [nio-8080-exec-5] Spring Security Debugger :
************************************************************
Request received for GET '/static/js/login.js':
org.apache.catalina.connector.RequestFacade#108c693
servletPath:/static/js/login.js
pathInfo:null
headers:
host: localhost:8080
user-agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0
accept: */*
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
referer: http://localhost:8080/adminconsole/
cookie: JSESSIONID=F690AA15EEAAF2DC9BD35E7CCFA5E94F
connection: keep-alive
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************
2017-06-13 14:55:41.964 INFO 7144 --- [nio-8080-exec-3] Spring Security Debugger :
************************************************************
Request received for GET '/static/js/utils.js':
org.apache.catalina.connector.RequestFacade#108c693
servletPath:/static/js/utils.js
pathInfo:null
headers:
host: localhost:8080
user-agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0
accept: */*
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
referer: http://localhost:8080/adminconsole/
cookie: JSESSIONID=F690AA15EEAAF2DC9BD35E7CCFA5E94F
connection: keep-alive
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************

How can I logon and POST to RESTful resource protected by Spring Security?

I am creating a RESTful web appliaction and am using Spring for the backend. When I wasn't implementing Spring Security, I could successfully add a new record to the JPA entity "Timestamp" by using a curl command from the command line. I am using Spring Security now so that when a user is not authenticated they are redirected to the login page. However now when I try curl to add a record with the following:
curl -i -X POST -v -u myusername:mypass -H "Content-Type:application/json" -d '{ "timestamp" : "2016-06-16T08:17:20.000Z", "peopleIn" : "1", "peopleOut":"0", "venue": "localhost://8181/venues/12" }' http://localhost:8181/timestamps
This is displayed in the terminal:
* Trying ::1...
* Connected to localhost (::1) port 8181 (#0)
* Server auth using Basic with user 'harry.quigley2#mail.dcu.ie'
> POST /timestamps HTTP/1.1
> Host: localhost:8181
> Authorization: Basic aGFycnkucXVpZ2xleTJAbWFpbC5kY3UuaWU6b2s=
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 118
>
* upload completely sent off: 118 out of 118 bytes
< HTTP/1.1 302 Found
HTTP/1.1 302 Found
< Server: Apache-Coyote/1.1
Server: Apache-Coyote/1.1
< X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
Pragma: no-cache
< Expires: 0
Expires: 0
< X-Frame-Options: DENY
X-Frame-Options: DENY
< Set-Cookie: JSESSIONID=A8EDFA6339DA76B11E0CDF6BB566A748; Path=/; HttpOnly
Set-Cookie: JSESSIONID=A8EDFA6339DA76B11E0CDF6BB566A748; Path=/; HttpOnly
< Location: http://localhost:8181/login
Location: http://localhost:8181/login
< Content-Length: 0
Content-Length: 0
< Date: Sat, 28 May 2016 16:35:53 GMT
Date: Sat, 28 May 2016 16:35:53 GMT
I am getting a 302 redirect. Also every line is repeated twice - I'm not sure why this is.
My Spring Security Config is:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(UserDetailsService userDetailsService, AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/venue/new", "/static/**").permitAll()
.anyRequest().authenticated();
http.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
http.csrf().disable();
}
}
If someone could understand why I'm being redirected and advise on what to change in order to be able to POST to the JPA entity, that would be great! Thanks
To authenticate with cURL you need to setup HTTP Basic authentication:
http.httpBasic();

Resources