Same-Site flag for session cookie in Spring Security - cookies
Is it possible to set Same-site Cookie flag in Spring Security?
And if not, is it on a roadmap to add support, please? There is already support in some browsers (i.e. Chrome).
New Tomcat version support SameSite cookies via TomcatContextCustomizer. So you should only customize tomcat CookieProcessor, e.g. for Spring Boot:
#Configuration
public class MvcConfiguration implements WebMvcConfigurer {
#Bean
public TomcatContextCustomizer sameSiteCookiesConfig() {
return context -> {
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
cookieProcessor.setSameSiteCookies(SameSiteCookies.NONE.getValue());
context.setCookieProcessor(cookieProcessor);
};
}
}
For SameSiteCookies.NONE be aware, that cookies are also Secure (SSL used), otherwise they couldn't be applied.
By default since Chrome 80 cookies considered as SameSite=Lax!
See SameSite Cookie in Spring Boot and SameSite cookie recipes.
For nginx proxy it could be solved easily in nginx config:
if ($scheme = http) {
return 301 https://$http_host$request_uri;
}
proxy_cookie_path / "/; secure; SameSite=None";
UPDATE from #madbreaks:
proxy_cookie_flags iso proxy_cookie_path
proxy_cookie_flags ~ secure samesite=none;
Instead of a Filter, In your Authentication Success Handler, you can mention in this way.
#Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
clearAuthenticationAttributes(request);
addSameSiteCookieAttribute(response);
handle(request, response);
}
private void addSameSiteCookieAttribute(HttpServletResponse response) {
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
// there can be multiple Set-Cookie attributes
for (String header : headers) {
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
}
}
It was mentioned in one of the answers. Couldn't find the link after I've implemented it.
All possible solutions here failed for me. Every time I tried a filter or interceptor, the Set-Cookie header had not yet been added. The only way I was able to make this work was by adding Spring Session and adding this bean into one of my #Configuration files:
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("none");
return serializer;
}
Anyway hope this helps someone else in my same situation.
You can always set cookie values by yourself in the Java world if you can get an instance of the HttpServletResponse.
Then you can do:
response.setHeader("Set-Cookie", "key=value; HttpOnly; SameSite=strict")
In spring-security you can easily do this with a filter, here is an example:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Set-Cookie", "locale=de; HttpOnly; SameSite=strict");
chain.doFilter(request, response);
}
}
Add this filter to your SecurityConfig like this:
http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class)
Or via XML:
<http>
<custom-filter after="BASIC_AUTH_FILTER" ref="myFilter" />
</http>
<beans:bean id="myFilter" class="org.bla.CustomFilter"/>
It isn't possible. There is support for this feature in Spring Session: https://spring.io/blog/2018/10/31/spring-session-bean-ga-released
I came up with a solution similar to Ron's one. But there is one important thing to note:
Cookies for cross-site usage must specify SameSite=None; Secure
to enable inclusion in third party context.
So I've included Secure attribute in header. Also, you don't have to override all three methods when you don't use them. It is only required when you are implementing HandlerInterceptor.
import org.apache.commons.lang.StringUtils;
public class CookiesInterceptor extends HandlerInterceptorAdapter {
final String sameSiteAttribute = "; SameSite=None";
final String secureAttribute = "; Secure";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
addEtagHeader(request, response);
Collection<String> setCookieHeaders = response.getHeaders(HttpHeaders.SET_COOKIE);
if (setCookieHeaders == null || setCookieHeaders.isEmpty())
return;
setCookieHeaders
.stream()
.filter(StringUtils::isNotBlank)
.map(header -> {
if (header.toLowerCase().contains("samesite")) {
return header;
} else {
return header.concat(sameSiteAttribute);
}
})
.map(header -> {
if (header.toLowerCase().contains("secure")) {
return header;
} else {
return header.concat(secureAttribute);
}
})
.forEach(finalHeader -> response.setHeader(HttpHeaders.SET_COOKIE, finalHeader));
}
}
I used xml in my project so I had to add this to my configuration file:
<mvc:interceptors>
<bean class="com.zoetis.widgetserver.mvc.CookiesInterceptor"/>
</mvc:interceptors>
Using the interceptor in SpringBoot.
I'm looking for a resolution for adding SameSite as you, and I only want to add the attribute to the existing "Set-Cookie" instead of creating a new "Set-Cookie".
I have tried several ways to meet this requirement, including:
adding a custom filter as #unwichtich said,
and more I overrode basicAuthenticationFilter. It does add the SameSite attribute. While the timing when Spring will add the "Set-Cookie" is hard to catch. I thought in onAuthenticationSuccess() method, the response must have this header, but it doesn't. I'm not sure whether it's the fault of my custom basicAuthenticationFilter's order.
using cookieSerializer, but the spring-session version comes up to a problem. Seems only the latest version support it, but I still can't figure out the version number should be added into the dependency list.
Unfortunately, none of them above can add the samesite well as expected.
Finally, I found the interceptor in spring can help me to make it.
It took me a week to get it. Hope this can help you if anyone has the same problem.
#Component
public class CookieServiceInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
//check whether it has "set-cookie" in the response, if it has, then add "SameSite" attribute
//it should be found in the response of the first successful login
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) { // there can be multiple Set-Cookie attributes
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
}
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {
}
}
and you also need to make this interceptor work in your application, which means you should add a bean as below:
#Autowired
CookieServiceInterceptor cookieServiceInterceptor;
#Bean
public MappedInterceptor myInterceptor() {
return new MappedInterceptor(null, cookieServiceInterceptor);
}
This interceptor has a flaw, it can't add samesite when the request is redirected(ex.return 302) or failed(ex. return 401), while it makes my app fail when SSO. Eventually, I have to use the Tomcat cookie, because I don't embed tomcat in my springboot app. I add
<Context>
<CookieProcessor sameSiteCookies="none" />
</Context>
in a context.xml under /META-INF of my app. It will add SameSite attribute in set-cookie header for each response. Note that this behavior is possible since Tomcat 9.0.21 and 8.5.42. according to https://stackoverflow.com/a/57622508/4033979
For Spring Webflux (reactive environment) this worked for me:
#Configuration
#EnableSpringWebSession
public class SessionModule {
#Bean
public ReactiveSessionRepository<MapSession> reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
#Bean
public WebSessionIdResolver webSessionIdResolver() {
CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();
resolver.setCookieName("SESSION");
resolver.addCookieInitializer((builder) -> {
builder.path("/")
.httpOnly(true)
.secure(true)
.sameSite("None; Secure");
});
return resolver;
}
}
You can add cookie by yourself by using ResponseCookie and adding it to your HttpServletResponse.
ResponseCookie cookie = ResponseCookie.from("cookiename", "cookieValue")
.maxAge(3600) // one hour
.domain("test.com")
.sameSite("None")
.secure(true)
.path("/")
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
I have tested this solution for spring-webmvc without spring-security, but I think it should also work for spring-boot.
Using the SessionRepositoryFilter bean from spring-session-core
You can extend default java HttpSession with a spring Session and replace JSESSIONID cookie with a custom one, like this:
Set-Cookie: JSESSIONID=NWU4NzY4NWUtMDY3MC00Y2M1LTg1YmMtNmE1ZWJmODcxNzRj; Path=/; Secure; HttpOnly; SameSite=None
Additional spring Session cookie flags can be set using DefaultCookieSerializer:
#Configuration
#EnableSpringHttpSession
public class WebAppConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
servletContext
.addFilter("sessionRepositoryFilter", DelegatingFilterProxy.class)
.addMappingForUrlPatterns(null, false, "/*");
}
#Bean
public MapSessionRepository sessionRepository() {
final Map<String, Session> sessions = new ConcurrentHashMap<>();
MapSessionRepository sessionRepository =
new MapSessionRepository(sessions) {
#Override
public void save(MapSession session) {
sessions.entrySet().stream()
.filter(entry -> entry.getValue().isExpired())
.forEach(entry -> sessions.remove(entry.getKey()));
super.save(session);
}
};
sessionRepository.setDefaultMaxInactiveInterval(60*5);
return sessionRepository;
}
#Bean
public SessionRepositoryFilter<?> sessionRepositoryFilter(MapSessionRepository sessionRepository) {
SessionRepositoryFilter<?> sessionRepositoryFilter =
new SessionRepositoryFilter<>(sessionRepository);
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName("JSESSIONID");
cookieSerializer.setSameSite("None");
cookieSerializer.setUseSecureCookie(true);
CookieHttpSessionIdResolver cookieHttpSessionIdResolver =
new CookieHttpSessionIdResolver();
cookieHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
sessionRepositoryFilter.setHttpSessionIdResolver(cookieHttpSessionIdResolver);
return sessionRepositoryFilter;
}
}
I have extended a bit MapSessionRepository implementation, since it does NOT support firing SessionDeletedEvent or SessionExpiredEvent - I have added clearing of expired sessions before adding new ones. I think this might be enough for a small application.
Apparently, with spring boot you can write this and it gets picked up.
#Configuration
public static class WebConfig implements WebMvcConfigurer {
#Bean
public CookieSameSiteSupplier cookieSameSiteSupplier(){
return CookieSameSiteSupplier.ofNone();
}
}
Or ... even simpler, spring boot since 2.6.0 supports setting it in application.properties.
Spring documentation about SameSite Cookies
server.servlet.session.cookie.same-site = none
Related
Camel exchange expired via jetty continuation
Is there a possibility in Apache Camel to register a handler for managing exchanges that cannot be written to jetty endpoint http response because continuation timeout has been reached?
I'll just add my notes on that because I made it available in my project by modifying CamelContinuationServlet in the if (continuation.isExpired()) block like this if (continuation.isExpired()) { String id = (String) continuation.getAttribute(EXCHANGE_ATTRIBUTE_ID); // remember this id as expired expiredExchanges.put(id, id); log.warn("Continuation expired of exchangeId: {}", id); consumer.getBinding().doWriteExceptionResponse(new TimeoutException(), response); return; } in combination with a custom HttpBinding called ErrorHandlingHttpBinding in my code like this public class ErrorHandlingHttpBinding extends DefaultHttpBinding { #Override public void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException { if (exception instanceof TimeoutException) { response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); response.getWriter().write("Continuation timed out..."); } else { super.doWriteExceptionResponse(exception, response); } } } registered as spring bean with id="errorHandlingHttpBinding" and referred in the component string as jetty:http://localhost:21010/?useContinuation=true&continuationTimeout=1&httpBindingRef=errorHandlingHttpBinding.
No this is not possible. Maybe you need to set a higher timeout if you have some slow processing exchanges. You are welcome to dive in the Jetty APIs to see if you can find a hook for such a onTimeout event and see what it takes to support that in camel-jetty.
Cross Origin Filter with embedded Jetty
I'm trying to get a CrossOriginFilter working with a couple of embedded Jetty servers, both running on our internal network. Both are running servlets, but I need server A's web page to be able to post to server B's servlets. I think I need to add ACCESS_CONTROL_ALLOW_ORIGIN to a CrossOriginFilter but finding out how to do this with an embedded Jetty instance with no web.xml isn't proving to be easy. I get the following error message in the browser when trying to access server b's serlvets No 'Access-Control-Allow-Origin' header is present on the requested resource Im using angularjs to post to the other server's servlets in a controller. And here is the code for one of the servers (both are pretty much the same) Server server = new Server(httpPort); ResourceHandler resource_handler = new ResourceHandler(); resource_handler.setDirectoriesListed(true); resource_handler.setWelcomeFiles(new String[] { "index.html" }); resource_handler.setResourceBase("./http/"); ServletHandler handler = new ServletHandler(); handler.addServletWithMapping(ServerPageRoot.class, "/servlet/*"); FilterHolder holder = new FilterHolder(CrossOriginFilter.class); holder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*"); holder.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*"); holder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,POST,HEAD"); holder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "X-Requested-With,Content-Type,Accept,Origin"); handler.addFilter(holder ); HandlerList handlers = new HandlerList(); handlers.setHandlers(new Handler[] { resource_handler, handler,new DefaultHandler() }); server.setHandler(handlers); server.start();
A few points: Don't use ServletHandler naked like that. The ServletHandler is an internal class that ServletContextHandler uses. The ServletContextHandler is what provides the needed ServletContext object and state for the various servlets and filters you are using. The ServletContextHandler also provides a place for the overall Context Path declaration The ServletContextHandler is also the place for Welcome Files declaration. Don't use ResourceHandler, when you have DefaultServlet available, its far more capable and feature rich. Example: Server server = new Server(httpPort); // Setup the context for servlets ServletContextHandler context = new ServletContextHandler(); // Set the context for all filters and servlets // Required for the internal servlet & filter ServletContext to be sane context.setContextPath("/"); // The servlet context is what holds the welcome list // (not the ResourceHandler or DefaultServlet) context.setWelcomeFiles(new String[] { "index.html" }); // Add a servlet context.addServlet(ServerPageRoot.class,"/servlet/*"); // Add the filter, and then use the provided FilterHolder to configure it FilterHolder cors = context.addFilter(CrossOriginFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST)); cors.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*"); cors.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*"); cors.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,POST,HEAD"); cors.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "X-Requested-With,Content-Type,Accept,Origin"); // Use a DefaultServlet to serve static files. // Alternate Holder technique, prepare then add. // DefaultServlet should be named 'default' ServletHolder def = new ServletHolder("default", DefaultServlet.class); def.setInitParameter("resourceBase","./http/"); def.setInitParameter("dirAllowed","false"); context.addServlet(def,"/"); // Create the server level handler list. HandlerList handlers = new HandlerList(); // Make sure DefaultHandler is last (for error handling reasons) handlers.setHandlers(new Handler[] { context, new DefaultHandler() }); server.setHandler(handlers); server.start();
managed to get it working by doing FilterHolder holder = new FilterHolder(CrossOriginFilter.class); holder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*"); holder.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*"); holder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,POST,HEAD"); holder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "X-Requested-With,Content-Type,Accept,Origin"); holder.setName("cross-origin"); FilterMapping fm = new FilterMapping(); fm.setFilterName("cross-origin"); fm.setPathSpec("*"); handler.addFilter(holder, fm );
Maybe this will help someone even though it is not a good answer to the original question. I realized that you can easaly enable cross origin request sharing in an embedded jetty instance by manipulating the headers directly in your handler. The response object below is an instance of HttpServletResponse (which is passed to the handler). Example: response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Credentials", "true"); response.addHeader("Access-Control-Allow-Methods", "POST, GET"); response.addHeader("Access-Control-Allow-Headers", "Content-Type");
I tried all the way of above answers and other similar ones. But always, I came across same error message. Finally I reach a correct answer for my situation. I use Jersey with Jetty and I am not using web.xml. If you try all methods and you don't enable the CORS support, maybe you can try this solution below. First, define a filter (you can define another one which directly implements Filter class) import java.io.IOException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.core.Response; public class CorsFilter implements ContainerRequestFilter, ContainerResponseFilter { private static boolean isPreflightRequest(ContainerRequestContext request) { return request.getHeaderString("Origin") != null && request.getMethod().equalsIgnoreCase("OPTIONS"); } #Override public void filter(ContainerRequestContext request) throws IOException { // If it's a preflight request, we abort the request if (isPreflightRequest(request)) { request.abortWith(Response.ok().build()); return; } } #Override public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException { // if there is no Origin header, we don't do anything. if (request.getHeaderString("Origin") == null) { return; } // If it is a preflight request, then we add all // the CORS headers here. if (isPreflightRequest(request)) { response.getHeaders().add("Access-Control-Allow-Credentials", "true"); response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD"); response.getHeaders().add("Access-Control-Allow-Headers", // Whatever other non-standard/safe headers (see list above) // you want the client to be able to send to the server, // put it in this list. And remove the ones you don't want. "X-Requested-With,Content-Type,Content-Length,Authorization," + "Accept,Origin,Cache-Control,Accept-Encoding,Access-Control-Request-Headers," + "Access-Control-Request-Method,Referer,x-csrftoken,ClientKey"); } response.getHeaders().add("Access-Control-Allow-Origin", "*"); } } Register this filter to resource config import java.io.IOException; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; public class AppServer { public static void main(String[] args) throws Exception { Server jettyServer = new Server(); // Add port ServerConnector jettyServerConnector = new ServerConnector(jettyServer); jettyServerConnector.setPort(Integer.parseInt("9090")); jettyServer.addConnector(jettyServerConnector); // Define main servlet context handler ServletContextHandler jettyServletContextHandler = new ServletContextHandler(); jettyServletContextHandler.setContextPath("/service"); // Define main resource (webapi package) support ResourceConfig webapiResourceConfig = new ResourceConfig(); webapiResourceConfig.packages("com.example.service"); ServletContainer webapiServletContainer = new ServletContainer(webapiResourceConfig); ServletHolder webapiServletHolder = new ServletHolder(webapiServletContainer); jettyServletContextHandler.addServlet(webapiServletHolder, "/webapi/*"); // Add Cors Filter webapiResourceConfig.register(CorsFilter.class, 1); try { jettyServer.start(); jettyServer.dump(System.err); jettyServer.join(); } catch (Throwable t) { t.printStackTrace(System.err); } finally { jettyServer.destroy(); } } } That's it. This solution solved my problem. Maybe it can be useful for others.
Tomcat 7 sessionid cookie disable http-only and secure
I have a web application which is running on a Tomcat 7 server. The cookie with session id has by default the flags HttpOnly and Secure. I want to disable this flags for the JSESSIONID cookie. But it wont work. I have changed this in my web.xml file but it is not working. <session-config> <session-timeout>20160</session-timeout> <cookie-config> <http-only>false</http-only> <secure>false</secure> </cookie-config> </session-config> I know this is a security risk because a attacker is able to steal the cookie and hijack the session if he has found a xss vuln. The JSESSIONID cookie should be send with HTTP and HTTPS and with AJAX requests. Edit: I have successfuly disabled the HttpOnly flag by adding the following option to the conf/context.xml file: <Context useHttpOnly="false"> .... </Context>
If you read the code from tomcat you will find: // Always set secure if the request is secure if (scc.isSecure() || secure) { cookie.setSecure(true); } So trying to deactivate Secure flag on JSESSIONID cookie with sessionCookieConfig.setSecure(false); in a listener or <cookie-config><secure>false</secure></cookie-config> in the web.xml WON'T WORK as Tomcat force the secure flag to true if the request is secure (ie came from an https url or the SSL port). A solution is to use a request filter to modify the JSESSIONID cookie on the server response immediately after the session creation. This is my implementation (very basic): public class DisableSecureCookieFilter implements javax.servlet.Filter { #Override public void init(FilterConfig filterConfig) throws ServletException { } #Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if(request instanceof HttpServletRequest && response instanceof HttpServletResponse) { request = new ForceUnsecureSessionCookieRequestWrapper((HttpServletRequest) request, (HttpServletResponse) response); } chain.doFilter(request, response); } #Override public void destroy() { } public static class ForceUnsecureSessionCookieRequestWrapper extends HttpServletRequestWrapper { HttpServletResponse response; public ForceUnsecureSessionCookieRequestWrapper(HttpServletRequest request, HttpServletResponse response) { super(request); this.response = response; } #Override public HttpSession getSession(boolean create) { if(create) { HttpSession session = super.getSession(create); updateCookie(response.getHeaders("Set-Cookie")); return session; } return super.getSession(create); } #Override public HttpSession getSession() { HttpSession session = super.getSession(); if(session != null) { updateCookie(response.getHeaders("Set-Cookie")); } return session; } protected void updateCookie(Collection<String> cookiesAfterCreateSession) { if(cookiesAfterCreateSession != null && !response.isCommitted()) { // search if a cookie JSESSIONID Secure exists Optional<String> cookieJSessionId = cookiesAfterCreateSession.stream() .filter(cookie -> cookie.startsWith("JSESSIONID") && cookie.contains("Secure")) .findAny(); if(cookieJSessionId.isPresent()) { // remove all Set-Cookie and add the unsecure version of the JSessionId Cookie response.setHeader("Set-Cookie", cookieJSessionId.get().replace("Secure", "")); // re-add all other Cookies cookiesAfterCreateSession.stream() .filter(cookie -> !cookie.startsWith("JSESSIONID")) .forEach(cookie -> response.addHeader("Set-Cookie", cookie)); } } } } } and in the web.xml : <filter> <filter-name>disableSecureCookieFilter</filter-name> <filter-class>com.xxxx.security.DisableSecureCookieFilter</filter-class> </filter> <filter-mapping> <filter-name>disableSecureCookieFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> Remember that enabling insecure cookies bypass an important https security! (I had to do that for a smooth transition from http to https)
I did not find a solution in Tomcat to this but if you're using apache as a reverse proxy you can do: Header edit* Set-Cookie "(JSESSIONID=.*)(; Secure)" "$1" with mod_headers which will munge the header on the way back out to remove the secure flag. Not pretty but works if this is critical.
In addition to George Powell's solution above for Apache, if you are on IIS, you can solve it as follows: Install the IIS URL Rewrite module Add the following to your web.config <rewrite> <outboundRules> <rule name="RemoveSecureJessionID"> <match serverVariable="RESPONSE_Set-Cookie" pattern="^(.*JSESSIONID.*)Secure;(.*)$" /> <action type="Rewrite" value="{R:1}{R:2}" /> </rule> </outboundRules> </rewrite> This solution was from Pete Freitag's blog As stated above, since the recent Chrome update (Jan 2017), this has become an issue.
Accessing Session in HttpRequest Pipelines
I'm trying to access a query string parameter and save it to a Session variable. Since the solution I'm working on has several base layouts, the simplest approach would be to add this to a pipeline handler. However, my code is failing because args.Context.Session is null: public class SaveQueryStringToSession : HttpRequestProcessor { public override void Process(HttpRequestArgs args) { Assert.ArgumentNotNull((object)args, "args"); string queryString = WebUtil.GetQueryString("parm1"); if (queryString.Length <= 0) return; args.Context.Session["parm1"] = queryString; } } This occurs when this method is inserted into either the HttpRequestBegin or HttpRequestEnd pipeline. Curious to know why, and if there is a standard workaround or pattern to use here. (Yes, I will add a null check. No need to point that out.) I'm running Sitecore Sitecore.NET 6.4.1 (rev. 110720) on IIS 7.5 (Integrated .Net 2.0) Possibly relevant links: What is the first global.asax event that Session is accessible assuming the context handler is IRequiresSessionState or IReadOnlySessionState? http://intothecore.cassidy.dk/2009/02/session-state-and-integrated-pipeline.html
The HttpRequestBegin pipeline is wired up to the HttpApplication.BeginRequest event, and this event is fired before the HttpSession object has been instantiated. Using the HttpRequestEnd pipeline does not work because the HttpSession object has already been disposed by the time the HttpApplication.EndRequest event is fired. The session becomes available after the PostAcquireRequestState event is fired. To intercept this, create a class that implements IHttpModule, and add it to the <httpModules> element in Web.config. The HttpModule code will need to check if the request requires session state, as attempting to read the session for a static resource request will throw an exception. Here is HttpModule code that accesses the Session and QueryString: public class MyHttpModule :IHttpModule { public void Init(HttpApplication context) { context.PostAcquireRequestState += RequestHandler; } public void Dispose() { // } public void RequestHandler(object sender, EventArgs e) { var app = (HttpApplication) sender; if (app.Context.Handler is IRequiresSessionState) { var session = app.Session; var queryString = app.Request.QueryString["test"]; session["test"] = queryString; } } } It is worth noting that Sitecore's HttpRequestBegin and HttpRequestEnd pipelines are wired to ASP.NET via an HttpModule: <add type="Sitecore.Nexus.Web.HttpModule,Sitecore.Nexus" name="SitecoreHttpModule" /> Thanks to #ddysart for pointing me in the right direction, and to this answer for the correct event to listen for.
Actually instead of httpRequestBegin or HttpRequestEnd you can use httpRequestProcessed during which sitecore process the HttpRequest so you can access the Session. You will be able to use the same code you have provided earlier. public class SaveQueryStringToSession : HttpRequestProcessor { public override void Process(HttpRequestArgs args) { Assert.ArgumentNotNull((object)args, "args"); string queryString = WebUtil.GetQueryString("parm1"); if (queryString.Length <= 0) return; args.Context.Session["parm1"] = queryString; } }
Servlet Filter as Security Proxy for Web Services
Good time. Suppose there are 8 web-services in the one application. 5 of them require authorization (a client must to provide a JSESSIONID cookie and a corresponding session must not be invalidated), other 3 can be called without the jsessionid cookie. My naive solution is to write a servlet filter which intercepts requests and retrieve their pathInfos (all the services have the same url structure: /service/serviceSuffix). There is a enum which contains the serviceSuffix of each web service that requires authorization. When the request is retrieved the pathInfo is collected; if this pathInfo is contained in the enum and there is the corresponding valid session the request is sent ahead to the filter chain. Otherwise, an error is sent back to a client. After a while I've realized that it is needed to add the possibility to retrieve the wsdl and xsds for the concrete service. So that, two more check were added. public class SecurityFilter implements Filter { public static final String WSDL = "wsdl"; public static final String XSD = "xsd="; /** * Wittingly left empty */ public void init(FilterConfig filterConfig) throws ServletException {} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest servletRequest = (HttpServletRequest) request; HttpServletResponse servletResponse = (HttpServletResponse)response; String pathInfo = servletRequest.getPathInfo(); String queryString = servletRequest.getQueryString(); if (pathInfo != null && SecureWebServices.contains(pathInfo)) { if (queryString != null && (queryString.equals(WSDL) || queryString.startsWith(XSD))) { // wsdl or xsd is requested chain.doFilter(request, response); } else { // a web service's method is called HttpSession requestSession = servletRequest.getSession(false); if (requestSession != null) { // the session is valid chain.doFilter(request, response); } else { servletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } } } else { chain.doFilter(request, response); } } /** * Wittingly left empty */ public void destroy() {} } It seems that it is not very secure, because if the request's pathInfo is not in the enum, this request is passed on (just in case of some unexpected system calls). Could you, please, suggest what to do, how to increase the security level. I want to build a configurable system (that is why I have the enum. It is possible just to add a path there to secure the web service and it is not required to duplicate the security code in the each web service). How to increase
Maybe I do not understand but. jsessionid has nothink to do with security. you simply just get it. Next I am not sure if you want authentication or authorization. The code as provided will not provide you with security features. I suppose you are interested in authentication anyway. Security logic can be provided with standard web container features. Just send in authentication data in the header of request and you are done. web container can be configured to secure only selected resources (urls)