Sachin Sharma
Sachin Sharma

Reputation: 1496

How to change Cookie Processor to LegacyCookieProcessor in tomcat 8

My code is working on tomcat 8 version 8.0.33 but on 8.5.4 i get : An invalid domain [.mydomain] was specified for this cookie.

I have found that Rfc6265CookieProcessor is introduced in tomcat 8 latest versions.

It says on official doc that this can be reverted to LegacyCookieProcessor in context.xml but i don't know how.

Please let me know how to do this.

Thanks

Upvotes: 27

Views: 47867

Answers (7)

Nestor Milyaev
Nestor Milyaev

Reputation: 6595

LegacyCookieProcessor got deprecated in Tomcat 9. After searching heaven and earth I had to implement my own based on Tomcat's 10.x+ Rfc6265CookieProcessor, with a lax domain validation. Hope that'll be useful to someone else:

/*
Implementing the LegacyCookieProcessor class to support the legacy cookie parsing mechanism.
That is implemented as a light version of the Rfc6265CookieProcessor class, with lax domain validation.
*/
package com.xx.auth.config;

import jakarta.servlet.http.HttpServletRequest;
import java.text.FieldPosition;
import java.util.BitSet;
import java.util.Date;
import java.util.Map.Entry;
import org.apache.tomcat.util.http.Rfc6265CookieProcessor;
import org.apache.tomcat.util.http.SameSiteCookies;
import org.apache.tomcat.util.http.parser.HttpParser;
import org.apache.tomcat.util.res.StringManager;

public final class LegacyCookieProcessor extends Rfc6265CookieProcessor {

  private static final StringManager STRING_MANAGER =
      StringManager.getManager(Rfc6265CookieProcessor.class.getPackage().getName());
  private static final BitSet domainValid = new BitSet(128);

  @Override
  public String generateHeader(jakarta.servlet.http.Cookie cookie, HttpServletRequest request) {
    var header = new StringBuffer();
    header.append(cookie.getName());
    header.append('=');
    var value = cookie.getValue();
    if (value != null && !value.isEmpty()) {
      this.validateCookieValue(value);
      header.append(value);
    }

    int maxAge = cookie.getMaxAge();
    if (maxAge > -1) {
      header.append("; Max-Age=");
      header.append(maxAge);
      header.append("; Expires=");
      if (maxAge == 0) {
        header.append(ANCIENT_DATE);
      } else {
        (COOKIE_DATE_FORMAT.get())
            .format(
                new Date(System.currentTimeMillis() + (long) maxAge * 1000L),
                header,
                new FieldPosition(0));
      }
    }

    var domain = cookie.getDomain();
    if (domain != null && !domain.isEmpty()) {
      this.validateDomain(domain);
      header.append("; Domain=");
      header.append(domain);
    }

    var path = cookie.getPath();
    if (path != null && !path.isEmpty()) {
      this.validatePath(path);
      header.append("; Path=");
      header.append(path);
    }

    if (cookie.getSecure()) {
      header.append("; Secure");
    }

    if (cookie.isHttpOnly()) {
      header.append("; HttpOnly");
    }

    var cookieSameSite = cookie.getAttribute("SameSite");
    if (cookieSameSite == null) {
      var sameSiteCookiesValue = this.getSameSiteCookies();
      if (!sameSiteCookiesValue.equals(SameSiteCookies.UNSET)) {
        header.append("; SameSite=");
        header.append(sameSiteCookiesValue.getValue());
      }
    } else {
      header.append("; SameSite=");
      header.append(cookieSameSite);
    }

    for (Entry<String, String> entry : cookie.getAttributes().entrySet()) {
      switch (entry.getKey()) {
        case "Comment":
        case "Domain":
        case "Max-Age":
        case "Path":
        case "Secure":
        case "HttpOnly":
        case "SameSite":
          break;
        default:
          this.validateAttribute(entry.getKey(), entry.getValue());
          header.append("; ");
          header.append(entry.getKey());
          header.append('=');
          header.append(entry.getValue());
      }
    }
    return header.toString();
  }

  private void validateCookieValue(String value) {
    int start = 0;
    int end = value.length();
    if (end > 1 && value.charAt(0) == '"' && value.charAt(end - 1) == '"') {
      start = 1;
      --end;
    }

    char[] chars = value.toCharArray();

    for (int i = start; i < end; ++i) {
      char c = chars[i];
      if (c < '!' || c == '"' || c == ',' || c == ';' || c == '\\' || c == 127) {
        throw new IllegalArgumentException(
            STRING_MANAGER.getString(
                "rfc6265CookieProcessor.invalidCharInValue", Integer.toString(c)));
      }
    }
  }

  private void validateDomain(String domain) {
    int i = 0;
    int prev;
    int cur = -1;

    for (char[] chars = domain.toCharArray(); i < chars.length; ++i) {
      prev = cur;
      cur = chars[i];
      if (!domainValid.get(cur)) {
        throw new IllegalArgumentException(
            STRING_MANAGER.getString("rfc6265CookieProcessor.invalidDomain", domain));
      }

      if (prev == 45 && cur == 46) {
        throw new IllegalArgumentException(
            STRING_MANAGER.getString("rfc6265CookieProcessor.invalidDomain", domain));
      }
    }
  }

  private void validatePath(String path) {
    char[] chars = path.toCharArray();

    for (char ch : chars) {
      if (ch < ' ' || ch > '~' || ch == ';') {
        throw new IllegalArgumentException(
            STRING_MANAGER.getString("rfc6265CookieProcessor.invalidPath", path));
      }
    }
  }

  private void validateAttribute(String name, String value) {
    if (!HttpParser.isToken(name)) {
      throw new IllegalArgumentException(
          STRING_MANAGER.getString("rfc6265CookieProcessor.invalidAttributeName", name));
    } else {
      char[] chars = value.toCharArray();

      for (char ch : chars) {
        if (ch < ' ' || ch > '~' || ch == ';') {
          throw new IllegalArgumentException(
              STRING_MANAGER.getString(
                  "rfc6265CookieProcessor.invalidAttributeValue", name, value));
        }
      }
    }
  }

  static {
    char c;
    for (c = '0'; c <= '9'; ++c) {
      domainValid.set(c);
    }

    for (c = 'a'; c <= 'z'; ++c) {
      domainValid.set(c);
    }

    for (c = 'A'; c <= 'Z'; ++c) {
      domainValid.set(c);
    }
    domainValid.set(45);
    domainValid.set(46);
  }
}

And then of course you need to wire that up as in:

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TomcatCookieConfig
    implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

  @Override
  public void customize(TomcatServletWebServerFactory factory) {
    factory.addContextCustomizers(
        context -> context.setCookieProcessor(new LegacyCookieProcessor()));
  }
}

Upvotes: 0

Sandeep Poojary
Sandeep Poojary

Reputation: 31

SameSite issue in tomcat version < 8.5.47 has resolved

In Tomcat 8.5.47 and bellow (Tomcat 8 versions), setting CookieProcessor tag to enable same site (as given bellow) in context.xml does not work due to a bug in Tomcat.

<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" sameSiteCookies="none" />

If you find in this situation where it is not a easy thing to upgrade tomcat immediately (which I faced recently), or if you find any other case where you just need custom processing in cookies; You can write your own CookieProcessor class to get around.

Please find a custom CookieProcessor implementation and details of it's deployment steps here.

In my case I wrote a custom CookieProcessor based on LegacyCookieProcessor source code that allows tomcat 8.5.47 to enable SameSite attribute in cookies.

Upvotes: 3

PatrickV
PatrickV

Reputation: 11

As mentioned by @atul, this issue persists in Tomcat 9. It will most likely persist moving forward with all future versions of Tomcat, since this is the new standard.

Using the legacy cookie processor (by adding the line above to the context.xml file) is working well for us. However, the true 'fix' is to adjust how your cookie is formed in the first place. This will need to be done in your application, not in Tomcat.

The new cookie processor does not allow the domain to start with a . (dot). Adjusting your cookie (if possible) to start with a value other than that will fix this problem without reverting to the old, legacy cookie processor.

Also, it should be obvious, but I didn't see it mentioned above: after updating the context.xml file, you need to restart the Tomcat service for the change to take effect.

Cheers!

Upvotes: 1

IamVickyAV
IamVickyAV

Reputation: 1555

Case 1: You are using Standalone Tomcat & have access to change files in tomcat server

Please follow answer by @linzkl

Case 2: You are using Standalone Tomcat but you don't have access to change files in tomcat server

Create a new file called context.xml under src/main/webapp/META-INF folder in your application & paste the content given below

<?xml version="1.0" encoding="UTF-8"?> 
<Context>
  <WatchedResource>WEB-INF/web.xml</WatchedResource>
  <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
  <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource> 
  <CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />
</Context>

When you deploy your application in Standalone Tomcat, the context.xml file you placed under META-INF folder will override the context.xml file given in tomcat/conf/context.xml

Note: If you are following this solution, you have to do it for every single application because META-INF/context.xml is application specific

Case 3: You are using Embedded Tomcat

Create a new bean for WebServerFactoryCustomizer

@Bean
WebServerFactoryCustomizer<TomcatServletWebServerFactory> cookieProcessorCustomizer() {
    return new WebServerFactoryCustomizer<TomcatServletWebServerFactory>() {

        @Override
        void customize(TomcatServletWebServerFactory tomcatServletWebServerFactory) {
            tomcatServletWebServerFactory.addContextCustomizers(new TomcatContextCustomizer() {
                @Override
                public void customize(Context context) {
                    context.setCookieProcessor(new LegacyCookieProcessor());
                }
            });
        }
    };
}

Upvotes: 15

Atul
Atul

Reputation: 3357

The problem is still with Tomcat9. Same process need to follow for Tomcat 9 to set the class.

Add the class in context.xml file.

If you are using eclipse to run the application, need to set in the context.xml file in the server folder. Refer the below screenshot for more reference.

enter image description here

Hope this helps someone.

Upvotes: 4

smos
smos

Reputation: 148

Enabling the LegacyCookieProcessor which is used in previous versions of Tomcat has solved the problem in my application. As linzkl mentioned this is explained in Apache's website https://tomcat.apache.org/tomcat-8.0-doc/config/cookie-processor.html.

The reason is that the new version of Tomcat does not understand the . (dot) in front of the domain name of the Cookie being used.

Also, make sure to check this post when you are using Internet Explorer. Apparently, it's very likely to break.

You can find context.xml in the following path.

tomcat8/conf/context.xml

<?xml version="1.0" encoding="UTF-8”?>
<!-- The contents of this file will be loaded for each web application —>
<Context>
<!-- Default set of monitored resources. If one of these changes, the    -->
<!-- web application will be reloaded.                                   -->

<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!-- <Manager pathname="" /> -->
<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor"/>
</Context>

Upvotes: 11

linzkl
linzkl

Reputation: 336

You can try in context.xml

<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />

reference: https://tomcat.apache.org/tomcat-8.0-doc/config/cookie-processor.html

Upvotes: 32

Related Questions