[ofbiz-framework] branch POC-for-CSRF-Token-OFBIZ-11306 created (now 69b5510)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

[ofbiz-framework] branch POC-for-CSRF-Token-OFBIZ-11306 created (now 69b5510)

jleroux@apache.org
This is an automated email from the ASF dual-hosted git repository.

jleroux pushed a change to branch POC-for-CSRF-Token-OFBIZ-11306
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git.


      at 69b5510  Creates new POC-for-CSRF-Token-OFBIZ-11306 branch

This branch includes the following new commits:

     new 69b5510  Creates new POC-for-CSRF-Token-OFBIZ-11306 branch

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Reply | Threaded
Open this post in threaded view
|

[ofbiz-framework] 01/01: Creates new POC-for-CSRF-Token-OFBIZ-11306 branch

jleroux@apache.org
This is an automated email from the ASF dual-hosted git repository.

jleroux pushed a commit to branch POC-for-CSRF-Token-OFBIZ-11306
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git

commit 69b551038801e53c1cd3b8c03ddf1fab0198c3fc
Author: Jacques Le Roux <[hidden email]>
AuthorDate: Wed Feb 26 15:41:23 2020 +0100

    Creates new POC-for-CSRF-Token-OFBIZ-11306 branch
   
    To share with James and others and later when OK to create a PR
---
 .../humanres/template/category/CategoryTree.ftl    |  16 +-
 .../category/ftl/CatalogAltUrlSeoTransform.java    |   8 +-
 .../product/category/ftl/UrlRegexpTransform.java   |  13 +-
 .../product/template/category/CategoryTree.ftl     |   2 +-
 .../java/org/apache/ofbiz/common/CommonEvents.java |   3 +-
 .../common/webcommon/WEB-INF/common-controller.xml |   7 +-
 framework/security/config/security.properties      |  23 +-
 .../apache/ofbiz/security/CsrfDefenseStrategy.java |  93 ++++++
 .../java/org/apache/ofbiz/security/CsrfUtil.java   | 358 +++++++++++++++++++++
 .../ofbiz/security/ICsrfDefenseStrategy.java       |  55 ++++
 .../ofbiz/security/NoCsrfDefenseStrategy.java      |  50 +++
 .../org/apache/ofbiz/security/CsrfUtilTests.java   | 264 +++++++++++++++
 framework/webapp/dtd/site-conf.xsd                 |  14 +
 .../ofbiz/webapp/control/ConfigXMLReader.java      |   3 +
 .../ofbiz/webapp/control/ControlEventListener.java |   3 +
 .../ofbiz/webapp/control/RequestHandler.java       |  33 +-
 .../ofbiz/webapp/ftl/CsrfTokenAjaxTransform.java   |  75 +++++
 .../webapp/ftl/CsrfTokenPairNonAjaxTransform.java  |  76 +++++
 .../ofbiz/webapp/freemarkerTransforms.properties   |   2 +
 .../webtools/groovyScripts/entity/CheckDb.groovy   |   7 +-
 .../webtools/groovyScripts/entity/EntityRef.groovy |   6 +
 framework/webtools/template/entity/CheckDb.ftl     |  28 +-
 .../webtools/template/entity/EntityRefList.ftl     |   9 +-
 framework/webtools/template/entity/ViewGeneric.ftl |   5 +-
 .../webapp/webtools/WEB-INF/controller.xml         |   2 +-
 .../java/org/apache/ofbiz/widget/WidgetWorker.java |  14 +
 .../widget/renderer/macro/MacroFormRenderer.java   |  14 +-
 themes/bluelight/template/Header.ftl               |   6 +-
 .../common-theme/template/includes/ListLocales.ftl |   2 +-
 .../template/macro/CsvFormMacroLibrary.ftl         |   2 +-
 .../template/macro/FoFormMacroLibrary.ftl          |   2 +-
 .../template/macro/HtmlFormMacroLibrary.ftl        |   8 +-
 .../template/macro/TextFormMacroLibrary.ftl        |   2 +-
 .../template/macro/XlsFormMacroLibrary.ftl         |   2 +-
 .../template/macro/XmlFormMacroLibrary.ftl         |   2 +-
 .../webapp/common/js/util/OfbizUtil.js             |  12 +-
 themes/flatgrey/template/Header.ftl                |   6 +-
 themes/rainbowstone/template/includes/Header.ftl   |   4 +
 .../rainbowstone/template/includes/TopAppBar.ftl   |   2 +-
 themes/tomahawk/template/AppBarClose.ftl           |   2 +-
 themes/tomahawk/template/Header.ftl                |   4 +
 41 files changed, 1179 insertions(+), 60 deletions(-)

diff --git a/applications/humanres/template/category/CategoryTree.ftl b/applications/humanres/template/category/CategoryTree.ftl
index 10a08ac..f14bbfc 100644
--- a/applications/humanres/template/category/CategoryTree.ftl
+++ b/applications/humanres/template/category/CategoryTree.ftl
@@ -61,18 +61,18 @@ var rawdata = [
         "plugins" : [ "themes", "json_data","ui" ,"cookies", "types", "crrm", "contextmenu"],
             "json_data" : {
                 "data" : rawdata,
-                          "ajax" : { "url" : "<@ofbizUrl>getHRChild</@ofbizUrl>", "type" : "POST",
-                          "data" : function (n) {
-                            return {
+                "ajax" : { "url" : "<@ofbizUrl>getHRChild</@ofbizUrl>", "type" : "POST",
+                    "data" : function (n) {
+                            return {
                                 "partyId" : n.attr ? n.attr("id").replace("node_","") : 1 ,
                                 "additionParam" : "','category" ,
                                 "hrefString" : "viewprofile?partyId=" ,
                                 "onclickFunction" : "callDocument"
-                        };
+                        };
                     },
-                              success : function(data) {
-                                  return data.hrTree;
-                              }
+                    success : function(data) {
+                        return data.hrTree;
+                    }
                 }
             },
             "types" : {
@@ -92,7 +92,7 @@ var rawdata = [
   }
   
   function callDocument(id,type) {
-    window.location = "viewprofile?partyId=" + id;
+    window.location = "viewprofile?partyId=" + id + "&<@csrfTokenPair>viewprofile</@csrfTokenPair>";
   }
   
   function callEmplDocument(id,type) {
diff --git a/applications/product/src/main/java/org/apache/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java b/applications/product/src/main/java/org/apache/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
index b421681..653067b 100644
--- a/applications/product/src/main/java/org/apache/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
+++ b/applications/product/src/main/java/org/apache/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
@@ -25,12 +25,14 @@ import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.ofbiz.security.CsrfUtil;
 import org.apache.ofbiz.base.util.Debug;
 import org.apache.ofbiz.base.util.UtilValidate;
 import org.apache.ofbiz.base.util.template.FreeMarkerWorker;
 import org.apache.ofbiz.entity.Delegator;
 import org.apache.ofbiz.entity.GenericEntityException;
 import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.entity.util.EntityQuery;
 import org.apache.ofbiz.entity.util.EntityUtilProperties;
 import org.apache.ofbiz.product.category.CatalogUrlFilter;
 import org.apache.ofbiz.product.category.CategoryContentWrapper;
@@ -48,7 +50,6 @@ import freemarker.template.SimpleNumber;
 import freemarker.template.SimpleScalar;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateTransformModel;
-import org.apache.ofbiz.entity.util.EntityQuery;
 
 public class CatalogAltUrlSeoTransform implements TemplateTransformModel {
     public final static String module = CatalogUrlSeoTransform.class.getName();
@@ -129,6 +130,11 @@ public class CatalogAltUrlSeoTransform implements TemplateTransformModel {
                                 url = CatalogUrlFilter.makeCategoryUrl(request, previousCategoryId, productCategoryId, productId, viewSize, viewIndex, viewSort, searchString);
                             }
                         }
+
+                        // add / update csrf token to link when required
+                        String tokenValue = CsrfUtil.generateTokenForNonAjax(request, "product");
+                        url = CsrfUtil.addOrUpdateTokenInUrl(url, tokenValue);
+
                         // make the link
                         if (fullPath) {
                             try {
diff --git a/applications/product/src/main/java/org/apache/ofbiz/product/category/ftl/UrlRegexpTransform.java b/applications/product/src/main/java/org/apache/ofbiz/product/category/ftl/UrlRegexpTransform.java
index 48ac9b3..642d25f 100644
--- a/applications/product/src/main/java/org/apache/ofbiz/product/category/ftl/UrlRegexpTransform.java
+++ b/applications/product/src/main/java/org/apache/ofbiz/product/category/ftl/UrlRegexpTransform.java
@@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 import org.apache.ofbiz.base.component.ComponentConfig;
+import org.apache.ofbiz.security.CsrfUtil;
 import org.apache.ofbiz.base.util.Debug;
 import org.apache.ofbiz.base.util.template.FreeMarkerWorker;
 import org.apache.ofbiz.entity.Delegator;
@@ -134,8 +135,16 @@ public class UrlRegexpTransform implements TemplateTransformModel {
                         }
 
                         RequestHandler rh = RequestHandler.from(request);
-                        String link = rh.makeLink(request, response, buf.toString(), fullPath, secure || request.isSecure() , encode, controlPath);
-                        out.write(seoUrl(link, userLogin == null));
+                        String seoUrl = seoUrl(rh.makeLink(request, response, buf.toString(), fullPath,
+                                secure || request.isSecure(), encode, controlPath), userLogin == null);
+                        String requestURI = buf.toString();
+
+                        // add / update csrf token to link when required
+                        String tokenValue = CsrfUtil.generateTokenForNonAjax(request,
+                                controlPath + (requestURI.startsWith("/") ? requestURI : "/"+requestURI));
+                        seoUrl = CsrfUtil.addOrUpdateTokenInUrl(seoUrl, tokenValue);
+
+                        out.write(seoUrl);
                     } else if (!webSiteId.isEmpty()) {
                         Delegator delegator = FreeMarkerWorker.unwrap(env.getVariable("delegator"));
                         if (delegator == null) {
diff --git a/applications/product/template/category/CategoryTree.ftl b/applications/product/template/category/CategoryTree.ftl
index dce62c7..dd4ca21 100644
--- a/applications/product/template/category/CategoryTree.ftl
+++ b/applications/product/template/category/CategoryTree.ftl
@@ -65,7 +65,7 @@ var rawdata = [
             "plugins" : [ "themes", "json_data","ui" ,"cookies", "types"],
             "json_data" : {
                 "data" : rawdata,
-                "ajax" : { "url" : "<@ofbizUrl>getChild</@ofbizUrl>",
+                "ajax" : { "url" : "getChild",
                            "type" : "POST",
                            "data" : function (n) {
                                         return {
diff --git a/framework/common/src/main/java/org/apache/ofbiz/common/CommonEvents.java b/framework/common/src/main/java/org/apache/ofbiz/common/CommonEvents.java
index 2135444..aa42d61 100644
--- a/framework/common/src/main/java/org/apache/ofbiz/common/CommonEvents.java
+++ b/framework/common/src/main/java/org/apache/ofbiz/common/CommonEvents.java
@@ -77,7 +77,8 @@ public class CommonEvents {
         "thisRequestUri",
         "org.apache.tomcat.util.net.secure_protocol_version",
         "userLogin",
-        "impersonateLogin"
+        "impersonateLogin",
+        "requestMapMap" // requestMapMap is used by CSRFUtil
     };
 
     /** Simple event to set the users per-session locale setting. The user's locale
diff --git a/framework/common/webcommon/WEB-INF/common-controller.xml b/framework/common/webcommon/WEB-INF/common-controller.xml
index 80407c6..6526111 100644
--- a/framework/common/webcommon/WEB-INF/common-controller.xml
+++ b/framework/common/webcommon/WEB-INF/common-controller.xml
@@ -75,7 +75,7 @@ under the License.
         <response name="error" type="view" value="login"/>
     </request-map>
     <request-map uri="logout">
-        <security https="true" auth="true"/>
+        <security https="true" auth="true" csrf-token="false"/>
         <event type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="logout"/>
         <response name="success" type="request-redirect" value="main"/>
         <response name="error" type="view" value="main"/>
@@ -237,7 +237,8 @@ under the License.
     <!--========================== AJAX events =====================-->
     <!-- Get states related to a country -->
     <request-map uri="getAssociatedStateList">
-        <security https="true" auth="false"/>
+        <!-- depended on by ecommerce during checkout, so set the csrf-token to false -->
+        <security https="true" auth="false" csrf-token="false"/>
         <event type="service" invoke="getAssociatedStateList"/>
         <response name="success" type="request" value="json"/>
         <response name="error" type="request" value="json"/>
@@ -317,7 +318,7 @@ under the License.
 
     <!-- Set TimeZone from user's browser -->
     <!-- XXX The auth setting is inconsistent with the one in the service for a good reason, see OFBIZ-10471 for an explanation -->
-    <request-map uri="SetTimeZoneFromBrowser">
+    <request-map uri="SetTimeZoneFromBrowser" method="post">
         <security https="false" auth="false"/>
         <event type="service" invoke="SetTimeZoneFromBrowser"/>
         <response name="success" type="request" value="json"/>
diff --git a/framework/security/config/security.properties b/framework/security/config/security.properties
index 5a44fe2..55c2b6a 100644
--- a/framework/security/config/security.properties
+++ b/framework/security/config/security.properties
@@ -1,4 +1,4 @@
-###############################################################################
+##############################################################################
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information
@@ -149,5 +149,24 @@ security.jwt.token.expireTime=1800
 # -- To make this work you also have to configure a secret key with security.token.key
 security.internal.sso.enabled=false
 
-# -- The secret key for the JWT token signature. Read Passwords and JWT (JSON Web Tokens) usage documentation to choose the way you want to store this key
+# -- The secret key for the JWT token signature. Read Passwords and JWT (JSON Web Tokens) usage documentation to choose the way you want to store this key
 security.token.key=security.token.key
+
+# -- The cache size for the Tokens Maps that stores the CSRF tokens.
+# -- RemoveEldestEntry is used when it's get above csrf.cache.size
+# -- Default is 5000
+# -- TODO: separate tokenMap from partyTokenMap
+csrf.cache.size=
+
+# -- Parameter name for CSRF token. Default is "csrf" if not specified
+csrf.tokenName.nonAjax=
+
+# -- The csrf.entity.request.limit is used to show how to avoid cluttering the Tokens Maps cache with URIs starting with "entity/"
+# -- It can be useful with large Database contents, ie with a large numbers of tuples, like "entity/edit/Agreement/10000, etc.
+# -- The same principle can be extended to other cases similar to "entity/" URIs (harcoded or using similar properties).
+# -- Default is 3
+csrf.entity.request.limit=
+
+# csrf defense strategy. Default is org.apache.ofbiz.security.CsrfDefenseStrategy if not specified.
+# use org.apache.ofbiz.security.NoCsrfDefenseStrategy to disable CSRF check totally.
+csrf.defense.strategy=
diff --git a/framework/security/src/main/java/org/apache/ofbiz/security/CsrfDefenseStrategy.java b/framework/security/src/main/java/org/apache/ofbiz/security/CsrfDefenseStrategy.java
new file mode 100644
index 0000000..5b72990
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/ofbiz/security/CsrfDefenseStrategy.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.ofbiz.security;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.UtilProperties;
+import org.apache.ofbiz.webapp.control.RequestHandlerExceptionAllowExternalRequests;
+
+public class CsrfDefenseStrategy implements ICsrfDefenseStrategy {
+
+    public static final String module = CsrfDefenseStrategy.class.getName();
+    private static SecureRandom secureRandom = null;
+    private static final String prng = "SHA1PRNG";
+    private static final String CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+    private static int csrfEntityErequestLimit =  (int) Long.parseLong(UtilProperties.getPropertyValue("security", "csrf.entity.request.limit", "3"));
+
+    static{
+        try {
+            secureRandom = SecureRandom.getInstance(prng);
+        } catch (NoSuchAlgorithmException e) {
+            Debug.logError(e, module);
+        }
+    }
+
+    @Override
+    public String generateToken() {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 1; i < 12 + 1; i++) {
+            int index = secureRandom.nextInt(CHARSET.length());
+            char c = CHARSET.charAt(index);
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public int maxSubFolderInRequestUrlForTokenMapLookup(String requestUri){
+        if (requestUri.startsWith("entity/")){
+            return csrfEntityErequestLimit;
+        }
+        return 0;
+    }
+
+    @Override
+    public boolean modifySecurityCsrfToken(String requestUri, String requestMapMethod, String securityCsrfToken) {
+        // main request URI is exempted from CSRF token check
+        if (requestUri.equals("main")) {
+            return false;
+        } else {
+            return !"false".equals(securityCsrfToken);
+        }
+    }
+
+
+    @Override
+    public boolean keepTokenAfterUse(String requestUri, String requestMethod) {
+        // to allow back and forth browser buttons to work,
+        // token value is unchanged when request.getMethod is GET
+        if ("GET".equals(requestMethod)) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void invalidTokenResponse(String requestUri, HttpServletRequest request) throws RequestHandlerExceptionAllowExternalRequests {
+        request.setAttribute("_ERROR_MESSAGE_",
+                "Invalid or missing CSRF token to path '" + request.getPathInfo() + "'. Click <a href='"
+                        + request.getContextPath() + "'>here</a> to continue.");
+        throw new RequestHandlerExceptionAllowExternalRequests();
+    }
+}
diff --git a/framework/security/src/main/java/org/apache/ofbiz/security/CsrfUtil.java b/framework/security/src/main/java/org/apache/ofbiz/security/CsrfUtil.java
new file mode 100644
index 0000000..eaf5635
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/ofbiz/security/CsrfUtil.java
@@ -0,0 +1,358 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.ofbiz.security;
+
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.ws.rs.core.MultivaluedHashMap;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.cxf.jaxrs.model.URITemplate;
+import org.apache.ofbiz.base.component.ComponentConfig;
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.UtilGenerics;
+import org.apache.ofbiz.base.util.UtilProperties;
+import org.apache.ofbiz.base.util.UtilValidate;
+import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.webapp.control.ConfigXMLReader;
+import org.apache.ofbiz.webapp.control.RequestHandler;
+import org.apache.ofbiz.webapp.control.RequestHandlerException;
+import org.apache.ofbiz.webapp.control.RequestHandlerExceptionAllowExternalRequests;
+import org.apache.ofbiz.webapp.control.WebAppConfigurationException;
+
+public class CsrfUtil {
+
+    public static final String module = CsrfUtil.class.getName();
+    public static String tokenNameNonAjax = UtilProperties.getPropertyValue("security", "csrf.tokenName.nonAjax", "csrf");
+    public static ICsrfDefenseStrategy strategy;
+    private static int cacheSize =  (int) Long.parseLong(UtilProperties.getPropertyValue("security", "csrf.cache.size", "5000"));
+    private static LinkedHashMap<String, Map<String, Map<String, String>>> csrfTokenCache = new LinkedHashMap<String, Map<String, Map<String, String>>>() {
+        private static final long serialVersionUID = 1L;
+        protected boolean removeEldestEntry(Map.Entry<String, Map<String, Map<String, String>>> eldest) {
+            return size() > cacheSize; // TODO use also csrf.cache.size here?
+        }
+    };
+
+    private CsrfUtil() {
+    }
+
+    static {
+        try {
+            String className = UtilProperties.getPropertyValue("security", "csrf.defense.strategy", CsrfDefenseStrategy.class.getCanonicalName());
+            Class<?> c = Class.forName(className);
+            strategy = (ICsrfDefenseStrategy)c.newInstance();
+        } catch (Exception e){
+            Debug.logError(e, module);
+            strategy = new CsrfDefenseStrategy();
+        }
+    }
+
+    public static Map<String, String> getTokenMap(HttpServletRequest request, String targetContextPath) {
+        
+        HttpSession session = request.getSession();
+        GenericValue userLogin = (GenericValue) session.getAttribute("userLogin");
+        String partyId = null;
+        if (userLogin != null && userLogin.get("partyId") != null) {
+            partyId = userLogin.getString("partyId");
+        }
+
+        Map<String, String> tokenMap = null;
+        if (UtilValidate.isNotEmpty(partyId)) {
+            Map<String, Map<String, String>> partyTokenMap = csrfTokenCache.get(partyId);
+            if (partyTokenMap == null) {
+                partyTokenMap = new HashMap<String, Map<String, String>>();
+                csrfTokenCache.put(partyId, partyTokenMap);
+            }
+
+            tokenMap = partyTokenMap.get(targetContextPath);
+            if (tokenMap == null) {
+                tokenMap = new LinkedHashMap<String, String>() {
+                    private static final long serialVersionUID = 1L;
+                    protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
+                        return size() > cacheSize;
+                    }
+                };
+                partyTokenMap.put(targetContextPath, tokenMap);
+            }
+        } else {
+            tokenMap = UtilGenerics.cast(session.getAttribute("CSRF-Token"));
+            if (tokenMap == null) {
+                tokenMap = new LinkedHashMap<String, String>() {
+                    private static final long serialVersionUID = 1L;
+                    protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
+                        return size() > cacheSize;
+                    }
+                };
+                session.setAttribute("CSRF-Token", tokenMap);
+            }
+        }
+        return tokenMap;
+    }
+
+    private static String generateToken() {
+        return strategy.generateToken();
+    }
+
+    /**
+     * Reduce number of subfolder from request uri, if needed, before using it to generate CSRF token.
+     * @param requestUri
+     * @return
+     */
+    static String getRequestUriWithSubFolderLimit(String requestUri){
+        int limit = CsrfUtil.strategy.maxSubFolderInRequestUrlForTokenMapLookup(requestUri);
+        if (limit<1){
+            return requestUri;
+        }
+        while(StringUtils.countMatches(requestUri, "/")+1>limit){
+            requestUri = requestUri.substring(0, requestUri.lastIndexOf("/"));
+        }
+        return requestUri;
+    }
+
+    static String getRequestUriFromPath(String pathOrRequestUri){
+        String requestUri = pathOrRequestUri;
+        // remove any query string
+        if (requestUri.contains("?")) {
+            // e.g. "/viewprofile?partyId=Company" to "/viewprofile"
+            requestUri = requestUri.substring(0, requestUri.indexOf("?"));
+        }
+        String controlServletPart = "/control/";
+        if (requestUri.contains(controlServletPart)) {
+            // e.g. "/partymgr/control/viewprofile" to "viewprofile"
+            requestUri = requestUri.substring(requestUri.indexOf(controlServletPart) + controlServletPart.length());
+        }
+        if (requestUri.startsWith("/")) {
+            // e.g. "/viewprofile" to "viewprofile"
+            requestUri = requestUri.substring(1);
+        }
+        if (requestUri.contains("#")){
+            // e.g. "view/entityref_main#org.apache.ofbiz.accounting.budget" to "view/entityref_main"
+            requestUri = requestUri.substring(0, requestUri.indexOf("#"));
+        }
+        return requestUri;
+    }
+
+    /**
+     * Generate CSRF token for non-ajax request if required and add it as key to token map in session When token map
+     * size limit is reached, the eldest entry will be deleted each time a new entry is added.
+     * Token only generated for up to 3 subfolders in the path so 'entity/find/Budget/0001' & 'entity/find/Budget/0002'
+     * should share the same CSRF token.
+     *
+     * @param request
+     * @param pathOrRequestUri
+     * @return csrf token
+     */
+    public static String generateTokenForNonAjax(HttpServletRequest request, String pathOrRequestUri) {
+        if (UtilValidate.isEmpty(pathOrRequestUri)
+                || pathOrRequestUri.startsWith("javascript")
+                || pathOrRequestUri.startsWith("#") ) {
+            return "";
+        }
+        
+        if (pathOrRequestUri.contains("&#x2f;")) {
+            pathOrRequestUri = pathOrRequestUri.replaceAll("&#x2f;", "/");
+        }
+
+        String requestUri = getRequestUriWithSubFolderLimit(getRequestUriFromPath(pathOrRequestUri));
+        
+        Map<String, String> tokenMap = null;
+
+        ConfigXMLReader.RequestMap requestMap = null;
+        // TODO when  OFBIZ-11354 will be done this will need to be removed even if it should be OK as is
+        if (pathOrRequestUri.contains("/control/")) {
+            tokenMap = getTokenMap(request, "/" + RequestHandler.getRequestUri(pathOrRequestUri));
+            requestMap = findRequestMap(pathOrRequestUri);
+        } else {
+            tokenMap = getTokenMap(request, request.getContextPath());
+            Map<String, ConfigXMLReader.RequestMap> requestMapMap = UtilGenerics
+                    .cast(request.getAttribute("requestMapMap"));
+            requestMap = findRequestMap(requestMapMap, pathOrRequestUri);
+        }
+        if (requestMap == null) {
+            Debug.logError("Cannot find the corresponding request map for path: " + pathOrRequestUri, module);
+        }
+        String tokenValue = "";
+        if (requestMap != null && requestMap.securityCsrfToken) {
+            if (tokenMap.containsKey(requestUri)) {
+                tokenValue = tokenMap.get(requestUri);
+            } else {
+                tokenValue = generateToken();
+                tokenMap.put(requestUri, tokenValue);
+            }
+        }
+        return tokenValue;
+    }
+
+    static ConfigXMLReader.RequestMap findRequestMap(String _urlWithControlPath){
+
+        String requestUri = getRequestUriFromPath(_urlWithControlPath);
+
+        List<ComponentConfig.WebappInfo> webappInfos = ComponentConfig.getAllWebappResourceInfos().stream()
+                .filter(line -> line.contextRoot.contains(RequestHandler.getRequestUri(_urlWithControlPath)))
+                .collect(Collectors.toList());
+
+        ConfigXMLReader.RequestMap requestMap = null;
+        if (UtilValidate.isNotEmpty(webappInfos)) {
+            try {
+                if (StringUtils.countMatches(requestUri, "/")==1){
+                    requestMap = ConfigXMLReader.getControllerConfig(webappInfos.get(0)).getRequestMapMap()
+                            .get(requestUri.substring(0, requestUri.indexOf("/")));
+                } else {
+                    requestMap = ConfigXMLReader.getControllerConfig(webappInfos.get(0)).getRequestMapMap()
+                            .get(requestUri);
+                }
+            } catch (WebAppConfigurationException | MalformedURLException e) {
+                Debug.logError(e, module);
+            }
+        }
+        return requestMap;
+    }
+
+    static ConfigXMLReader.RequestMap findRequestMap(Map<String, ConfigXMLReader.RequestMap> requestMapMap,
+            String _urlWithoutControlPath) {
+        String path = _urlWithoutControlPath;
+        if (_urlWithoutControlPath.startsWith("/")) {
+            path = _urlWithoutControlPath.substring(1);
+        }
+        int charPos = path.indexOf("?");
+        if (charPos != -1) {
+            path = path.substring(0, charPos);
+        }
+        MultivaluedHashMap<String, String> vars = new MultivaluedHashMap<>();
+        for (Map.Entry<String, ConfigXMLReader.RequestMap> entry : requestMapMap.entrySet()) {
+            URITemplate uriTemplate = URITemplate.createExactTemplate(entry.getKey());
+            // Check if current path the URI template exactly.
+            if (uriTemplate.match(path, vars) && vars.getFirst(URITemplate.FINAL_MATCH_GROUP).equals("/")) {
+                return entry.getValue();
+            }
+        }
+        // the path could be request uri with orderride
+        if (path.contains("/")) {
+            return requestMapMap.get(path.substring(0, path.indexOf("/")));
+        }
+        return null;
+    }
+
+    /**
+     * generate csrf token for AJAX and add it as value to token cache
+     *
+     * @param request
+     * @return csrf token
+     */
+    public static String generateTokenForAjax(HttpServletRequest request) {
+        HttpSession session = request.getSession();
+        String tokenValue = (String) session.getAttribute("X-CSRF-Token");
+        if (tokenValue == null) {
+            tokenValue = generateToken();
+            session.setAttribute("X-CSRF-Token", tokenValue);
+        }
+        return tokenValue;
+    }
+
+    /**
+     * get csrf token for AJAX
+     *
+     * @param session
+     * @return csrf token
+     */
+    public static String getTokenForAjax(HttpSession session) {
+        return (String) session.getAttribute("X-CSRF-Token");
+    }
+
+    public static String addOrUpdateTokenInUrl(String link, String csrfToken) {
+        if (link.contains(CsrfUtil.tokenNameNonAjax)) {
+            return link.replaceFirst("\\b"+CsrfUtil.tokenNameNonAjax+"=.*?(&|$)", CsrfUtil.tokenNameNonAjax+"=" + csrfToken + "$1");
+        } else if (!"".equals(csrfToken)) {
+            if (link.contains("?")) {
+                return link + "&"+CsrfUtil.tokenNameNonAjax+"=" + csrfToken;
+            } else {
+                return link + "?"+CsrfUtil.tokenNameNonAjax+"=" + csrfToken;
+            }
+        }
+        return link;
+    }
+
+    public static String addOrUpdateTokenInQueryString(String link, String csrfToken) {
+        if (UtilValidate.isNotEmpty(link)) {
+            if (link.contains(CsrfUtil.tokenNameNonAjax)) {
+                return link.replaceFirst("\\b"+CsrfUtil.tokenNameNonAjax+"=.*?(&|$)", CsrfUtil.tokenNameNonAjax+"=" + csrfToken + "$1");
+            } else {
+                if (UtilValidate.isNotEmpty(csrfToken)) {
+                    return link + "&"+CsrfUtil.tokenNameNonAjax+"=" + csrfToken;
+                } else {
+                    return link;
+                }
+            }
+        } else {
+            return CsrfUtil.tokenNameNonAjax+"=" + csrfToken;
+        }
+    }
+
+    public static void checkToken(HttpServletRequest request, String _path)
+            throws RequestHandlerException, RequestHandlerExceptionAllowExternalRequests {
+        String path = _path;
+        if (_path.startsWith("/")) {
+            path = _path.substring(1);
+        }
+        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With")) && !"GET".equals(request.getMethod())) {
+            String csrfToken = request.getHeader("X-CSRF-Token");
+            HttpSession session = request.getSession();
+            if ((UtilValidate.isEmpty(csrfToken) || !csrfToken.equals(CsrfUtil.getTokenForAjax(session)))
+                    && !"/SetTimeZoneFromBrowser".equals(request.getPathInfo())) { // TODO maybe this can be improved...
+                throw new RequestHandlerException(
+                        "Invalid or missing CSRF token for AJAX call to path '" + request.getPathInfo() + "'");
+            }
+        } else {
+            Map<String, String> tokenMap = CsrfUtil.getTokenMap(request, request.getContextPath());
+            String csrfToken = request.getParameter(CsrfUtil.tokenNameNonAjax);
+            String limitPath = getRequestUriWithSubFolderLimit(path);
+            if (UtilValidate.isNotEmpty(csrfToken) && tokenMap.containsKey(limitPath)
+                    && csrfToken.equals(tokenMap.get(limitPath))) {
+                if (!CsrfUtil.strategy.keepTokenAfterUse(path,request.getMethod())) {
+                    tokenMap.remove(limitPath);
+                }
+            } else {
+                CsrfUtil.strategy.invalidTokenResponse(path, request);
+            }
+        }
+    }
+
+    public static void cleanupTokenMap(HttpSession session) {
+        GenericValue userLogin = (GenericValue) session.getAttribute("userLogin");
+        String partyId = null;
+        if (userLogin != null && userLogin.get("partyId") != null) {
+            partyId = userLogin.getString("partyId");
+            Map<String, Map<String, String>> partyTokenMap = csrfTokenCache.get(partyId);
+            if (partyTokenMap != null) {
+                String contextPath = session.getServletContext().getContextPath();
+                partyTokenMap.remove(contextPath);
+                if (partyTokenMap.isEmpty()) {
+                    csrfTokenCache.remove(partyId);
+                }
+            }
+        }
+    }
+}
diff --git a/framework/security/src/main/java/org/apache/ofbiz/security/ICsrfDefenseStrategy.java b/framework/security/src/main/java/org/apache/ofbiz/security/ICsrfDefenseStrategy.java
new file mode 100644
index 0000000..322afb5
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/ofbiz/security/ICsrfDefenseStrategy.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.ofbiz.security;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ofbiz.webapp.control.RequestHandlerExceptionAllowExternalRequests;
+
+public interface ICsrfDefenseStrategy {
+
+    String generateToken();
+
+    /**
+     * Limit the number of subfolders in request uri to reduce the number of CSRF tokens needed.
+     * @param requestUri
+     * @return
+     */
+    int maxSubFolderInRequestUrlForTokenMapLookup(String requestUri);
+
+    /**
+     * Override security csrf-token value in request map
+     * @param requestUri
+     * @param requestMapMethod  get, post or all
+     * @param securityCsrfToken
+     * @return
+     */
+    boolean modifySecurityCsrfToken(String requestUri, String requestMapMethod, String securityCsrfToken);
+
+    /**
+     * Whether to reuse the token after it is consumed
+     * @param requestUri
+     * @param requestMethod GET, POST, or PUT
+     * @return
+     */
+    boolean keepTokenAfterUse(String requestUri, String requestMethod);
+
+    void invalidTokenResponse(String requestUri, HttpServletRequest request) throws RequestHandlerExceptionAllowExternalRequests;
+
+}
\ No newline at end of file
diff --git a/framework/security/src/main/java/org/apache/ofbiz/security/NoCsrfDefenseStrategy.java b/framework/security/src/main/java/org/apache/ofbiz/security/NoCsrfDefenseStrategy.java
new file mode 100644
index 0000000..279310c
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/ofbiz/security/NoCsrfDefenseStrategy.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.ofbiz.security;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class NoCsrfDefenseStrategy implements ICsrfDefenseStrategy {
+
+    @Override
+    public String generateToken() {
+        return null;
+    }
+
+    @Override
+    public int maxSubFolderInRequestUrlForTokenMapLookup(String requestUri){
+        return 0;
+    }
+
+    @Override
+    public boolean modifySecurityCsrfToken(String requestUri, String requestMapMethod, String securityCsrfToken) {
+        // all SecurityCsrfToken checks in request maps are read as false
+        return false;
+    }
+
+    @Override
+    public boolean keepTokenAfterUse(String requestUri, String requestMethod) {
+        return false;
+    }
+
+    @Override
+    public void invalidTokenResponse(String requestUri, HttpServletRequest request) {
+
+    }
+}
\ No newline at end of file
diff --git a/framework/security/src/test/java/org/apache/ofbiz/security/CsrfUtilTests.java b/framework/security/src/test/java/org/apache/ofbiz/security/CsrfUtilTests.java
new file mode 100644
index 0000000..53d0096
--- /dev/null
+++ b/framework/security/src/test/java/org/apache/ofbiz/security/CsrfUtilTests.java
@@ -0,0 +1,264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ofbiz.security;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.webapp.control.ConfigXMLReader;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class CsrfUtilTests {
+
+    @Test
+    public void testGetTokenMap(){
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        HttpSession session = mock(HttpSession.class);
+        when(request.getSession()).thenReturn(session);
+
+        // prepare the token map to be retrieved from session
+        Map<String,String> tokenMap = new LinkedHashMap<String, String>();
+        tokenMap.put("uri_1","abcd");
+        when(session.getAttribute("CSRF-Token")).thenReturn(tokenMap);
+
+        // without userLogin in session, test token map is retrieved from session
+        Map<String, String> resultMap = CsrfUtil.getTokenMap(request, "");
+        assertEquals("abcd", resultMap.get("uri_1"));
+
+        // add userLogin to session
+        GenericValue userLogin = mock(GenericValue.class);
+        when(userLogin.get("partyId")).thenReturn("10000");
+        when(userLogin.getString("partyId")).thenReturn("10000");
+        when(session.getAttribute("userLogin")).thenReturn(userLogin);
+
+        // with userLogin in session, test token map is not retrieved from session
+        resultMap = CsrfUtil.getTokenMap(request, "/partymgr");
+        assertNull(resultMap.get("uri_1"));
+
+    }
+
+    @Test
+    public void testGetRequestUriWithSubFolderLimit(){
+        CsrfUtil.strategy = new CsrfDefenseStrategy();
+
+        // limit only when request uri starts with 'entity'
+        String limitRequestUri = CsrfUtil.getRequestUriWithSubFolderLimit("entity/find/Budget/0002");
+        assertEquals("entity/find/Budget", limitRequestUri);
+
+        limitRequestUri = CsrfUtil.getRequestUriWithSubFolderLimit("a/b/c/d");
+        assertEquals("a/b/c/d", limitRequestUri);
+    }
+
+    @Test
+    public void testGetRequestUriFromPath(){
+        String requestUri = CsrfUtil.getRequestUriFromPath("/viewprofile?partyId=Company");
+        assertEquals("viewprofile", requestUri);
+
+        requestUri = CsrfUtil.getRequestUriFromPath("/partymgr/control/viewprofile");
+        assertEquals("viewprofile", requestUri);
+
+        requestUri = CsrfUtil.getRequestUriFromPath("view/entityref_main#org.apache.ofbiz.accounting.budget");
+        assertEquals("view/entityref_main", requestUri);
+    }
+
+
+    @Test
+    public void testGenerateTokenForNonAjax() throws ParserConfigurationException {
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        HttpSession session = mock(HttpSession.class);
+        when(request.getSession()).thenReturn(session);
+
+        // add userLogin to session
+        GenericValue userLogin = mock(GenericValue.class);
+        when(userLogin.get("partyId")).thenReturn("10000");
+        when(userLogin.getString("partyId")).thenReturn("10000");
+        when(session.getAttribute("userLogin")).thenReturn(userLogin);
+
+        String token = CsrfUtil.generateTokenForNonAjax(request, "");
+        assertEquals("", token);
+
+        token = CsrfUtil.generateTokenForNonAjax(request, "javascript:");
+        assertEquals("", token);
+
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        DocumentBuilder builder = dbf.newDocumentBuilder();
+        Document doc = builder.newDocument();
+
+        Map<String, ConfigXMLReader.RequestMap> requestMapMap = new HashMap<>();
+        {
+            Element requestMapElement = doc.createElement("request-map");
+            requestMapElement.setAttribute("uri", "checkLogin");
+            ConfigXMLReader.RequestMap requestMap = new ConfigXMLReader.RequestMap(requestMapElement);
+            requestMapMap.put(requestMap.uri, requestMap);
+        }
+        {
+            Element requestMapElement = doc.createElement("request-map");
+            requestMapElement.setAttribute("uri", "entity/find/{entityName}/{pkValues: .*}");
+            ConfigXMLReader.RequestMap requestMap = new ConfigXMLReader.RequestMap(requestMapElement);
+            requestMapMap.put(requestMap.uri, requestMap);
+        }
+        when(request.getAttribute("requestMapMap")).thenReturn(requestMapMap);
+
+        token = CsrfUtil.generateTokenForNonAjax(request, "checkLogin");
+        assertNotEquals("", token);
+
+        CsrfUtil.strategy = new CsrfDefenseStrategy();
+
+        token = CsrfUtil.generateTokenForNonAjax(request, "entity/find/Budget/0001");
+        assertNotEquals("", token);
+
+        String token2 = CsrfUtil.generateTokenForNonAjax(request, "entity/find&#x2f;Budget/0001");
+        // test support for treating "&#x2f;" as "/"
+        assertEquals(token2, token);
+
+        token2 = CsrfUtil.generateTokenForNonAjax(request, "entity/find/Budget/0002");
+        // token only generated for up to 3 subfolders in the path
+        assertEquals(token2, token);
+    }
+
+    @Test
+    public void testFindRequestMapWithoutControlPath() throws ParserConfigurationException {
+
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        DocumentBuilder builder = dbf.newDocumentBuilder();
+        Document doc = builder.newDocument();
+
+        Map<String, ConfigXMLReader.RequestMap> requestMapMap = new HashMap<>();
+        {
+            Element requestMapElement = doc.createElement("request-map");
+            requestMapElement.setAttribute("uri", "checkLogin");
+            ConfigXMLReader.RequestMap requestMap = new ConfigXMLReader.RequestMap(requestMapElement);
+            requestMapMap.put(requestMap.uri, requestMap);
+        }
+        // REST request like /entity/find/AccommodationClass
+        {
+            Element requestMapElement = doc.createElement("request-map");
+            requestMapElement.setAttribute("uri", "entity/find/{entityName}");
+            ConfigXMLReader.RequestMap requestMap = new ConfigXMLReader.RequestMap(requestMapElement);
+            requestMapMap.put(requestMap.uri, requestMap);
+        }
+        // View override like /view/ModelInduceFromDb
+        {
+            Element requestMapElement = doc.createElement("request-map");
+            requestMapElement.setAttribute("uri", "view");
+            ConfigXMLReader.RequestMap requestMap = new ConfigXMLReader.RequestMap(requestMapElement);
+            requestMapMap.put(requestMap.uri, requestMap);
+        }
+        {
+            Element requestMapElement = doc.createElement("request-map");
+            requestMapElement.setAttribute("uri", "ModelInduceFromDb");
+            ConfigXMLReader.RequestMap requestMap = new ConfigXMLReader.RequestMap(requestMapElement);
+            requestMapMap.put(requestMap.uri, requestMap);
+        }
+
+        // test usual request
+        ConfigXMLReader.RequestMap requestMap = CsrfUtil.findRequestMap(requestMapMap, "/checkLogin");
+        assertEquals(requestMap.uri, "checkLogin");
+
+        // test usual request
+        requestMap = CsrfUtil.findRequestMap(requestMapMap, "checkLogin");
+        assertEquals(requestMap.uri, "checkLogin");
+
+        // test REST request
+        requestMap = CsrfUtil.findRequestMap(requestMapMap, "/entity/find/AccommodationClass");
+        assertEquals(requestMap.uri, "entity/find/{entityName}");
+
+        // test view orderride
+        requestMap = CsrfUtil.findRequestMap(requestMapMap, "/view/ModelInduceFromDb");
+        assertEquals(requestMap.uri, "view");
+
+    }
+
+    @Test
+    public void testGenerateTokenForAjax() {
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        HttpSession session = mock(HttpSession.class);
+        when(request.getSession()).thenReturn(session);
+        when(session.getAttribute("X-CSRF-Token")).thenReturn("abcd");
+
+        String token = CsrfUtil.generateTokenForAjax(request);
+        assertEquals("abcd", token);
+    }
+
+    @Test
+    public void testGetTokenForAjax(){
+        HttpSession session = mock(HttpSession.class);
+        when(session.getAttribute("X-CSRF-Token")).thenReturn("abcd");
+
+        String token = CsrfUtil.getTokenForAjax(session);
+        assertEquals("abcd", token);
+    }
+
+    @Test
+    public void testAddOrUpdateTokenInUrl(){
+        CsrfUtil.tokenNameNonAjax = "csrfToken";
+
+        // test link without csrfToken
+        String url = CsrfUtil.addOrUpdateTokenInUrl("https://localhost:8443/catalog/control/login", "abcd");
+        assertEquals("https://localhost:8443/catalog/control/login?csrfToken=abcd", url);
+
+        // test link with query string and without csrfToken
+        url = CsrfUtil.addOrUpdateTokenInUrl("https://localhost:8443/partymgr/control/EditCommunicationEvent?communicationEventId=10000", "abcd");
+        assertEquals("https://localhost:8443/partymgr/control/EditCommunicationEvent?communicationEventId=10000&csrfToken=abcd", url);
+
+        // test link with csrfToken
+        url = CsrfUtil.addOrUpdateTokenInUrl("https://localhost:8443/catalog/control/login?csrfToken=abcd", "efgh");
+        assertEquals("https://localhost:8443/catalog/control/login?csrfToken=efgh", url);
+
+        // test link with csrfToken amd empty csrfToken replacement
+        url = CsrfUtil.addOrUpdateTokenInUrl("https://localhost:8443/catalog/control/login?csrfToken=abcd", "");
+        assertEquals("https://localhost:8443/catalog/control/login?csrfToken=", url);
+    }
+
+    @Test
+    public void testAddOrUpdateTokenInQueryString(){
+        CsrfUtil.tokenNameNonAjax = "csrfToken";
+
+        String queryString = CsrfUtil.addOrUpdateTokenInQueryString("", "abcd");
+        assertEquals(queryString, "csrfToken=abcd");
+
+        queryString = CsrfUtil.addOrUpdateTokenInQueryString("csrfToken=abcd&a=b", "efgh");
+        assertEquals(queryString, "csrfToken=efgh&a=b");
+
+        queryString = CsrfUtil.addOrUpdateTokenInQueryString("csrfToken=abcd&a=b", "");
+        assertEquals(queryString, "csrfToken=&a=b");
+
+        queryString = CsrfUtil.addOrUpdateTokenInQueryString("a=b", "abcd");
+        assertEquals(queryString, "a=b&csrfToken=abcd");
+
+        queryString = CsrfUtil.addOrUpdateTokenInQueryString("a=b", "");
+        assertEquals(queryString, "a=b");
+    }
+}
diff --git a/framework/webapp/dtd/site-conf.xsd b/framework/webapp/dtd/site-conf.xsd
index fc9a966..01d0046 100644
--- a/framework/webapp/dtd/site-conf.xsd
+++ b/framework/webapp/dtd/site-conf.xsd
@@ -305,6 +305,20 @@ under the License.
                 </xs:documentation>
             </xs:annotation>
         </xs:attribute>
+        <xs:attribute name="csrf-token" use="optional" default="">
+            <xs:annotation>
+                <xs:documentation>
+                    If true csrf token is expected. If false no csrf token check. Default to "".
+                </xs:documentation>
+            </xs:annotation>
+            <xs:simpleType>
+                <xs:restriction base="xs:token">
+                    <xs:enumeration value=""/>
+                    <xs:enumeration value="true"/>
+                    <xs:enumeration value="false"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
     </xs:attributeGroup>
     <xs:element name="metric">
         <xs:annotation>
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
index 0802582..b542036 100644
--- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
@@ -54,6 +54,7 @@ import org.apache.ofbiz.base.util.cache.UtilCache;
 import org.apache.ofbiz.base.util.collections.MapContext;
 import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
 import org.apache.ofbiz.base.util.collections.MultivaluedMapContextAdapter;
+import org.apache.ofbiz.security.CsrfUtil;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -450,6 +451,7 @@ public class ConfigXMLReader {
         public Event event;
         public boolean securityHttps = true;
         public boolean securityAuth = false;
+        public boolean securityCsrfToken = true;
         public boolean securityCert = false;
         public boolean securityExternalView = true;
         public boolean securityDirectRequest = true;
@@ -481,6 +483,7 @@ public class ConfigXMLReader {
                 this.securityCert = "true".equals(securityElement.getAttribute("cert"));
                 this.securityExternalView = !"false".equals(securityElement.getAttribute("external-view"));
                 this.securityDirectRequest = !"false".equals(securityElement.getAttribute("direct-request"));
+                this.securityCsrfToken = CsrfUtil.strategy.modifySecurityCsrfToken(this.uri, this.method, securityElement.getAttribute("csrf-token"));
             }
             // Check for event
             Element eventElement = UtilXml.firstChildElement(requestMapElement, "event");
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlEventListener.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlEventListener.java
index 22fd632..54cb206 100644
--- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlEventListener.java
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlEventListener.java
@@ -26,6 +26,7 @@ import javax.servlet.http.HttpSession;
 import javax.servlet.http.HttpSessionEvent;
 import javax.servlet.http.HttpSessionListener;
 
+import org.apache.ofbiz.security.CsrfUtil;
 import org.apache.ofbiz.base.util.Debug;
 import org.apache.ofbiz.base.util.UtilDateTime;
 import org.apache.ofbiz.base.util.UtilGenerics;
@@ -71,6 +72,8 @@ public class ControlEventListener implements HttpSessionListener {
     public void sessionDestroyed(HttpSessionEvent event) {
         HttpSession session = event.getSession();
 
+        CsrfUtil.cleanupTokenMap(session);
+
         // Finalize the Visit
         boolean beganTransaction = false;
         try {
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
index 0e91bb1..1fe488a 100644
--- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
@@ -45,6 +45,7 @@ import javax.ws.rs.core.MultivaluedHashMap;
 
 import org.apache.cxf.jaxrs.model.URITemplate;
 import org.apache.ofbiz.base.location.FlexibleLocation;
+import org.apache.ofbiz.security.CsrfUtil;
 import org.apache.ofbiz.base.util.Debug;
 import org.apache.ofbiz.base.util.SSLUtil;
 import org.apache.ofbiz.base.util.StringUtil;
@@ -428,6 +429,13 @@ public class RequestHandler {
         if (Debug.verboseOn()) Debug.logVerbose("[Processing Request]: " + requestMap.uri + showSessionId(request), module);
         request.setAttribute("thisRequestUri", requestMap.uri); // store the actual request URI
 
+        // Store current requestMap map to be referred later when generating csrf token
+        request.setAttribute("requestMapMap", getControllerConfig().getRequestMapMap());
+
+        // Perform CSRF token check when request not on chain
+        if (chain==null && originalRequestMap.securityCsrfToken) {
+                CsrfUtil.checkToken(request, path);
+        }
 
         // Perform security check.
         if (requestMap.securityAuth) {
@@ -578,8 +586,13 @@ public class RequestHandler {
                 if (UtilValidate.isNotEmpty(queryString)) {
                     redirectTarget += "?" + queryString;
                 }
-                
-                callRedirect(makeLink(request, response, redirectTarget), response, request, ccfg.getStatusCode());
+                String link = makeLink(request, response, redirectTarget);
+
+                // add / update csrf token to link when required
+                String tokenValue = CsrfUtil.generateTokenForNonAjax(request,redirectTarget);
+                link = CsrfUtil.addOrUpdateTokenInUrl(link, tokenValue);
+
+                callRedirect(link, response, request, ccfg.getStatusCode());
                 return;
             }
         }
@@ -659,10 +672,22 @@ public class RequestHandler {
                 callRedirect(url + this.makeQueryString(request, nextRequestResponse), response, request, redirectSC);
             } else if ("request-redirect".equals(nextRequestResponse.type)) {
                 if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]: Response is a Request redirect." + showSessionId(request), module);
-                callRedirect(makeLinkWithQueryString(request, response, "/" + nextRequestResponse.value, nextRequestResponse), response, request, redirectSC);
+                String link = makeLinkWithQueryString(request, response, "/" + nextRequestResponse.value, nextRequestResponse);
+
+                // add / update csrf token to link when required
+                String tokenValue = CsrfUtil.generateTokenForNonAjax(request, nextRequestResponse.value);
+                link = CsrfUtil.addOrUpdateTokenInUrl(link, tokenValue);
+
+                callRedirect(link, response, request, redirectSC);
             } else if ("request-redirect-noparam".equals(nextRequestResponse.type)) {
                 if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]: Response is a Request redirect with no parameters." + showSessionId(request), module);
-                callRedirect(makeLink(request, response, nextRequestResponse.value), response, request, redirectSC);
+                String link = makeLink(request, response, nextRequestResponse.value);
+
+                // add token to link when required
+                String tokenValue = CsrfUtil.generateTokenForNonAjax(request, nextRequestResponse.value);
+                link = CsrfUtil.addOrUpdateTokenInUrl(link, tokenValue);
+
+                callRedirect(link, response, request, redirectSC);
             } else if ("view".equals(nextRequestResponse.type)) {
                 if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]: Response is a view." + showSessionId(request), module);
 
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/CsrfTokenAjaxTransform.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/CsrfTokenAjaxTransform.java
new file mode 100644
index 0000000..6a2d89e
--- /dev/null
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/CsrfTokenAjaxTransform.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.ofbiz.webapp.ftl;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ofbiz.security.CsrfUtil;
+
+import freemarker.core.Environment;
+import freemarker.ext.beans.BeanModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateTransformModel;
+
+/**
+ * CsrfTokenAjaxTransform - Freemarker Transform for csrf token in Ajax call
+ */
+public class CsrfTokenAjaxTransform implements TemplateTransformModel {
+
+    public final static String module = CsrfTokenAjaxTransform.class.getName();
+
+    @Override
+    public Writer getWriter(Writer out, @SuppressWarnings("rawtypes") Map args)
+            throws TemplateModelException, IOException {
+
+        return new Writer(out) {
+
+            @Override
+            public void close() throws IOException {
+                try {
+                    Environment env = Environment.getCurrentEnvironment();
+                    BeanModel req = (BeanModel) env.getVariable("request");
+                    if (req != null) {
+                        HttpServletRequest request = (HttpServletRequest) req.getWrappedObject();
+                        String tokenValue = CsrfUtil.generateTokenForAjax(request);
+                        out.write(tokenValue);
+                    }
+                    return;
+                } catch (Exception e) {
+                    throw new IOException(e.getMessage());
+                }
+            }
+
+            @Override
+            public void flush() throws IOException {
+                out.flush();
+            }
+
+            @Override
+            public void write(char cbuf[], int off, int len) {
+
+            }
+        };
+
+    }
+}
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/CsrfTokenPairNonAjaxTransform.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/CsrfTokenPairNonAjaxTransform.java
new file mode 100644
index 0000000..d51bd61
--- /dev/null
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/CsrfTokenPairNonAjaxTransform.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.ofbiz.webapp.ftl;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ofbiz.security.CsrfUtil;
+
+import freemarker.core.Environment;
+import freemarker.ext.beans.BeanModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateTransformModel;
+
+/**
+ * CsrfTokenPairNonAjaxTransform - Freemarker Transform for csrf token in non-Ajax call
+ */
+public class CsrfTokenPairNonAjaxTransform implements TemplateTransformModel {
+
+    public final static String module = CsrfTokenPairNonAjaxTransform.class.getName();
+
+    @Override
+    public Writer getWriter(Writer out, @SuppressWarnings("rawtypes") Map args)
+            throws TemplateModelException, IOException {
+
+        final StringBuffer buf = new StringBuffer();
+
+        return new Writer(out) {
+
+            @Override
+            public void close() throws IOException {
+                try {
+                    Environment env = Environment.getCurrentEnvironment();
+                    BeanModel req = (BeanModel) env.getVariable("request");
+                    if (req != null) {
+                        HttpServletRequest request = (HttpServletRequest) req.getWrappedObject();
+                        String tokenValue = CsrfUtil.generateTokenForNonAjax(request, buf.toString());
+                        out.write(CsrfUtil.tokenNameNonAjax +"="+tokenValue);
+                    }
+                    return;
+                } catch (Exception e) {
+                    throw new IOException(e.getMessage());
+                }
+            }
+
+            @Override
+            public void write(char cbuf[], int off, int len) {
+                buf.append(cbuf, off, len);
+            }
+
+            @Override
+            public void flush() throws IOException {
+                out.flush();
+            }
+        };
+    }
+}
diff --git a/framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties b/framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties
index 46f7979..65cf04e 100644
--- a/framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties
+++ b/framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties
@@ -29,3 +29,5 @@ ofbizNumber=org.apache.ofbiz.webapp.ftl.OfbizNumberTransform
 setRequestAttribute=org.apache.ofbiz.webapp.ftl.SetRequestAttributeMethod
 renderWrappedText=org.apache.ofbiz.webapp.ftl.RenderWrappedTextTransform
 setContextField=org.apache.ofbiz.webapp.ftl.SetContextFieldTransform
+csrfTokenAjax=org.apache.ofbiz.webapp.ftl.CsrfTokenAjaxTransform
+csrfTokenPair=org.apache.ofbiz.webapp.ftl.CsrfTokenPairNonAjaxTransform
diff --git a/framework/webtools/groovyScripts/entity/CheckDb.groovy b/framework/webtools/groovyScripts/entity/CheckDb.groovy
index fd822de..567714f 100644
--- a/framework/webtools/groovyScripts/entity/CheckDb.groovy
+++ b/framework/webtools/groovyScripts/entity/CheckDb.groovy
@@ -16,10 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import org.apache.ofbiz.entity.Delegator
-import org.apache.ofbiz.security.Security
+
+
 import org.apache.ofbiz.entity.jdbc.DatabaseUtil
-import org.apache.ofbiz.entity.model.ModelEntity
 
 controlPath = parameters._CONTROL_PATH_
 
@@ -114,7 +113,7 @@ if (security.hasPermission("ENTITY_MAINT", session)) {
         miter = messages.iterator()
         context.miters = miter
     }
-    context.encodeURLCheckDb = response.encodeURL(controlPath + "/view/checkdb")
+    context.checkDbURL = "view/checkdb"
     context.groupName = groupName ?: "org.apache.ofbiz"
     context.entityName = entityName ?: ""
 }
diff --git a/framework/webtools/groovyScripts/entity/EntityRef.groovy b/framework/webtools/groovyScripts/entity/EntityRef.groovy
index 17933db..279e448 100644
--- a/framework/webtools/groovyScripts/entity/EntityRef.groovy
+++ b/framework/webtools/groovyScripts/entity/EntityRef.groovy
@@ -16,6 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import org.apache.ofbiz.security.CsrfUtil;
+
 controlPath = parameters._CONTROL_PATH_
 list = "$controlPath/view/entityref_list"
 main = "$controlPath/view/entityref_main"
@@ -29,5 +31,9 @@ if (search) {
     list = "$list?forstatic=$forstatic"
     main = "$main?forstatic=$forstatic"
 }
+tokenList = CsrfUtil.generateTokenForNonAjax(request, "view/entityref_list")
+tokenMain = CsrfUtil.generateTokenForNonAjax(request, "view/entityref_main")
+list = CsrfUtil.addOrUpdateTokenInUrl(list, tokenList)
+main = CsrfUtil.addOrUpdateTokenInUrl(main, tokenMain)
 context.encodeUrlList = response.encodeURL(list)
 context.encodeUrlMain = response.encodeURL(main)
diff --git a/framework/webtools/template/entity/CheckDb.ftl b/framework/webtools/template/entity/CheckDb.ftl
index ac81459..91cf8d3 100644
--- a/framework/webtools/template/entity/CheckDb.ftl
+++ b/framework/webtools/template/entity/CheckDb.ftl
@@ -17,7 +17,7 @@ specific language governing permissions and limitations
 under the License.
 -->
 <h3>${uiLabelMap.WebtoolsCheckUpdateDatabase}</h3>
-<form class="basic-form" class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -61,7 +61,7 @@ under the License.
    }
 </script>
 <h3>${uiLabelMap.WebtoolsRemoveAllTables}</h3>
-<form class="basic-form" class="basic-form" method="post" action="${encodeURLCheckDb}" name="TablesRemoveForm">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>" name="TablesRemoveForm">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -80,7 +80,7 @@ under the License.
       </tbody>
    </table>
 </form>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}" name="TableRemoveForm">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>" name="TableRemoveForm">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -107,7 +107,7 @@ under the License.
    </table>
 </form>
 <h3>${uiLabelMap.WebtoolsCreateRemoveAllPrimaryKeys}</h3>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -125,7 +125,7 @@ under the License.
       </tbody>
    </table>
 </form>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -143,7 +143,7 @@ under the License.
    </table>
 </form>
 <h3>${uiLabelMap.WebtoolsCreateRemovePrimaryKey}</h3>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -168,7 +168,7 @@ under the License.
       </tbody>
    </table>
 </form>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -197,7 +197,7 @@ under the License.
    </table>
 </form>
 <h3>${uiLabelMap.WebtoolsCreateRemoveAllDeclaredIndices}</h3>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -214,7 +214,7 @@ under the License.
       </tbody>
    </table>
 </form>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -232,7 +232,7 @@ under the License.
    </table>
 </form>
 <h3>${uiLabelMap.WebtoolsCreateRemoveAllForeignKeyIndices}</h3>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -249,7 +249,7 @@ under the License.
       </tbody>
    </table>
 </form>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -268,7 +268,7 @@ under the License.
 </form>
 <h3>${uiLabelMap.WebtoolsCreateRemoveAllForeignKeys}</h3>
 <p>${uiLabelMap.WebtoolsNoteForeighKeysMayAlsoBeCreated}</p>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -285,7 +285,7 @@ under the License.
       </tbody>
    </table>
 </form>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
@@ -303,7 +303,7 @@ under the License.
    </table>
 </form>
 <h3>${uiLabelMap.WebtoolsUpdateCharacterSetAndCollate}</h3>
-<form class="basic-form" method="post" action="${encodeURLCheckDb}">
+<form class="basic-form" method="post" action="<@ofbizUrl>${checkDbURL}</@ofbizUrl>">
    <table class="basic-table" cellspacing="0">
       <tbody>
          <tr>
diff --git a/framework/webtools/template/entity/EntityRefList.ftl b/framework/webtools/template/entity/EntityRefList.ftl
index 1ace17f..55e2387 100644
--- a/framework/webtools/template/entity/EntityRefList.ftl
+++ b/framework/webtools/template/entity/EntityRefList.ftl
@@ -1,3 +1,4 @@
+
 <#--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -54,9 +55,9 @@ under the License.
                 <div class="section-header">${uiLabelMap.WebtoolsEntityPackages}</div>
                 <#list packageNames as packageName>
                     <#if forstatic>
-                        <a href="<@ofbizUrl>view/entityref_main?forstatic=true#${packageName}</@ofbizUrl>" target="entityFrame">${packageName}</a><br />
+                        <a href="<@ofbizUrl>view/entityref_main?forstatic=true</@ofbizUrl>#${packageName}" target="entityFrame">${packageName}</a><br />
                     <#else>
-                        <a href="<@ofbizUrl>view/entityref_main#${packageName}</@ofbizUrl>" target="entityFrame">${packageName}</a><br />
+                        <a href="<@ofbizUrl>view/entityref_main</@ofbizUrl>#${packageName}" target="entityFrame">${packageName}</a><br />
                     </#if>
                 </#list>
             </#if>
@@ -65,9 +66,9 @@ under the License.
                 <div class="section-header">${uiLabelMap.WebtoolsEntitiesAlpha}</div>
                 <#list entitiesList as entity>
                     <#if forstatic>
-                        <a href="<@ofbizUrl>view/entityref_main?forstatic=true#${entity.entityName}</@ofbizUrl>" target="entityFrame">${entity.entityName}</a>
+                        <a href="<@ofbizUrl>view/entityref_main?forstatic=true</@ofbizUrl>#${entity.entityName}" target="entityFrame">${entity.entityName}</a>
                     <#else>
-                        <a href="<@ofbizUrl>view/entityref_main#${entity.entityName}${entity.url!}</@ofbizUrl>" target="entityFrame">${entity.entityName}</a>
+                        <a href="<@ofbizUrl>view/entityref_main</@ofbizUrl>#${entity.entityName}${entity.url!}" target="entityFrame">${entity.entityName}</a>
                     </#if>
                     <br />
                 </#list>
diff --git a/framework/webtools/template/entity/ViewGeneric.ftl b/framework/webtools/template/entity/ViewGeneric.ftl
index 7c163db..e907835 100644
--- a/framework/webtools/template/entity/ViewGeneric.ftl
+++ b/framework/webtools/template/entity/ViewGeneric.ftl
@@ -38,6 +38,7 @@ function ShowTab(lname) {
 }
 </script>
 
+<#assign currentFindString = currentFindString?replace("&#x2f;", "/")!>
 <div class="screenlet">
   <div class="screenlet-title-bar">
     <ul>
@@ -53,13 +54,13 @@ function ShowTab(lname) {
       <a href='<@ofbizUrl>entity/find/${entityName}</@ofbizUrl>' class="buttontext">${uiLabelMap.WebtoolsBackToFindScreen}</a>
       <#if enableEdit = "false">
         <#if hasCreatePermission>
-          <form action="<@ofbizUrl>entity/edit/${currentFindString}</@ofbizUrl>" method="get">
+          <form action="<@ofbizUrl>entity/edit/${currentFindString}</@ofbizUrl>" method="post">
             <input type="submit" value="${uiLabelMap.CommonEdit}" />
           </form>
         </#if>
         <#if value?has_content>
           <#if hasDeletePermission>
-            <form action='<@ofbizUrl>entity/change/${currentFindString}</@ofbizUrl>' method="delete" name="updateForm">
+            <form action='<@ofbizUrl>entity/change/${currentFindString}</@ofbizUrl>' method="post" name="updateForm">
               <input type="hidden" value="DELETE" name="_method"/>
               <#list pkNamesValuesMap.keySet() as pkName>
                 <input type="hidden" value="${pkNamesValuesMap.get(pkName)}" name="${pkName}"/>
diff --git a/framework/webtools/webapp/webtools/WEB-INF/controller.xml b/framework/webtools/webapp/webtools/WEB-INF/controller.xml
index 6e4976c..72eec1e 100644
--- a/framework/webtools/webapp/webtools/WEB-INF/controller.xml
+++ b/framework/webtools/webapp/webtools/WEB-INF/controller.xml
@@ -51,7 +51,7 @@ under the License.
     <request-map uri="entity/create/{entityName}" method="get"><security https="true" auth="true"/><response name="success" type="view" value="EditGeneric"/></request-map>
 
     <!-- form for modifying a record  -->
-    <request-map uri="entity/edit/{entityName}/{pkValues: .*}" method="get"><security https="true" auth="true"/><response name="success" type="view" value="EditGeneric" /></request-map>
+    <request-map uri="entity/edit/{entityName}/{pkValues: .*}" method="post"><security https="true" auth="true"/><response name="success" type="view" value="EditGeneric" /></request-map>
 
     <!--view relations for a given entity -->
     <request-map uri="entity/relations/{entityName}" method="get"><security https="true" auth="true"/><response name="success" type="view" value="ViewRelations" /></request-map>
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/WidgetWorker.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/WidgetWorker.java
index 2f043bb..e359770 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/WidgetWorker.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/WidgetWorker.java
@@ -27,6 +27,7 @@ import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.ofbiz.security.CsrfUtil;
 import org.apache.ofbiz.base.util.UtilCodec;
 import org.apache.ofbiz.base.util.UtilGenerics;
 import org.apache.ofbiz.base.util.UtilHttp;
@@ -115,6 +116,19 @@ public final class WidgetWorker {
         } else {
             externalWriter.append(localWriter.toString());
         }
+
+        String tokenValue = CsrfUtil.generateTokenForNonAjax(request, target);
+        if (UtilValidate.isNotEmpty(tokenValue)){
+            String currentString = externalWriter.toString();
+            if(currentString.startsWith("<form")) {
+                currentString = currentString.substring(currentString.lastIndexOf("\"")+1);
+            }
+            if (currentString.indexOf('?') == -1) {
+                externalWriter.append("?" + CsrfUtil.tokenNameNonAjax + "=" + tokenValue);
+            } else {
+                externalWriter.append("&amp;" + CsrfUtil.tokenNameNonAjax + "=" + tokenValue);
+            }
+        }
     }
 
     public static void appendContentUrl(Appendable writer, String location, HttpServletRequest request) throws IOException {
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
index 2a7ee14..02797ad 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
@@ -40,6 +40,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
+import org.apache.ofbiz.security.CsrfUtil;
 import org.apache.ofbiz.base.util.Debug;
 import org.apache.ofbiz.base.util.StringUtil;
 import org.apache.ofbiz.base.util.UtilCodec;
@@ -1425,6 +1426,10 @@ public final class MacroFormRenderer implements FormStringRenderer {
             }
         }
         String focusFieldName = modelForm.getFocusFieldName();
+
+        // Generate CSRF name & value for form
+        String csrfNameValue = CsrfUtil.tokenNameNonAjax + " " +CsrfUtil.generateTokenForNonAjax(request, targ);
+
         StringWriter sr = new StringWriter();
         sr.append("<@renderFormOpen ");
         sr.append(" linkUrl=\"");
@@ -1455,7 +1460,9 @@ public final class MacroFormRenderer implements FormStringRenderer {
         sr.append(Integer.toString(viewSize));
         sr.append("\" useRowSubmit=");
         sr.append(Boolean.toString(useRowSubmit));
-        sr.append(" />");
+        sr.append(" csrfNameValue=\"");
+        sr.append(csrfNameValue);
+        sr.append("\" />");
         executeMacro(writer, sr.toString());
     }
 
@@ -2413,6 +2420,11 @@ public final class MacroFormRenderer implements FormStringRenderer {
             viewSizeParam = "VIEW_SIZE" + "_" + paginatorNumber;
         }
         String str = (String) context.get("_QBESTRING_");
+
+        // refresh any csrf token in the query string for pagination
+        String tokenValue = CsrfUtil.generateTokenForNonAjax(request, targetService);
+        str = CsrfUtil.addOrUpdateTokenInQueryString(str, tokenValue);
+
         // strip legacy viewIndex/viewSize params from the query string
         String queryString = UtilHttp.stripViewParamsFromQueryString(str, "" + paginatorNumber);
         // strip parameterized index/size params from the query string
diff --git a/themes/bluelight/template/Header.ftl b/themes/bluelight/template/Header.ftl
index 16fcea2..f4bbff9 100644
--- a/themes/bluelight/template/Header.ftl
+++ b/themes/bluelight/template/Header.ftl
@@ -28,6 +28,10 @@ under the License.
 <html lang="${docLangAttr}" dir="${langDir}" xmlns="http://www.w3.org/1999/xhtml">
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <#assign csrfDefenseStrategy = Static["org.apache.ofbiz.entity.util.EntityUtilProperties"].getPropertyValue("security", "csrf.defense.strategy", delegator)>
+    <#if csrfDefenseStrategy != "org.apache.ofbiz.security.NoCsrfDefenseStrategy">
+        <meta name="csrf-token" content="<@csrfTokenAjax/>"/>
+    </#if>
     <title>${layoutSettings.companyName}: <#if (titleProperty)?has_content>${uiLabelMap[titleProperty]}<#else>${title!}</#if></title>
     <#if layoutSettings.shortcutIcon?has_content>
       <#assign shortcutIcon = layoutSettings.shortcutIcon/>
@@ -194,7 +198,7 @@ under the License.
             <#--if webSiteId?? && requestAttributes._CURRENT_VIEW_?? && helpTopic??-->
             <#if parameters.componentName?? && requestAttributes._CURRENT_VIEW_?? && helpTopic??>
               <#include "component://common-theme/template/includes/HelpLink.ftl" />
-              <li><a class="help-link <#if pageAvail?has_content> alert</#if>" href="javascript:lookup_popup1('showHelp?helpTopic=${helpTopic}&amp;portalPageId=${(parameters.portalPageId!)?html}','help' ,500,500);" title="${uiLabelMap.CommonHelp}"></a></li>
+              <li><a class="help-link <#if pageAvail?has_content> alert</#if>" href="javascript:lookup_popup1('<@ofbizUrl>showHelp?helpTopic=${helpTopic}&amp;portalPageId=${(parameters.portalPageId!)?html}</@ofbizUrl>','help' ,500,500);" title="${uiLabelMap.CommonHelp}"></a></li>
             </#if>
             <#if userLogin??>
               <#if "Y" == (userPreferences.COMPACT_HEADER)?default("N")>
diff --git a/themes/common-theme/template/includes/ListLocales.ftl b/themes/common-theme/template/includes/ListLocales.ftl
index 647090f..82c7ca7 100644
--- a/themes/common-theme/template/includes/ListLocales.ftl
+++ b/themes/common-theme/template/includes/ListLocales.ftl
@@ -36,7 +36,7 @@ under the License.
       </#if>
       <tr <#if altRow>class="alternate-row"</#if>>
         <td lang="${langAttr}" dir="${langDir}">
-          <a href="<@ofbizUrl>setSessionLocale</@ofbizUrl>?newLocale=${availableLocale.toString()}">
+          <a href="<@ofbizUrl>setSessionLocale?newLocale=${availableLocale.toString()}</@ofbizUrl>">
               ${availableLocale.getDisplayName(availableLocale)}
               &nbsp;&nbsp;&nbsp;-&nbsp;&nbsp;&nbsp; [${langAttr}]</a>
         </td>
diff --git a/themes/common-theme/template/macro/CsvFormMacroLibrary.ftl b/themes/common-theme/template/macro/CsvFormMacroLibrary.ftl
index cadd70e..b371b19 100644
--- a/themes/common-theme/template/macro/CsvFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/CsvFormMacroLibrary.ftl
@@ -54,7 +54,7 @@ under the License.
 <#macro renderEmptyFormDataMessage message></#macro>
 <#macro renderSingleFormFieldTitle></#macro>
 
-<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField></#macro>
+<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField csrfNameValue></#macro>
 <#macro renderFormClose></#macro>
 <#macro renderMultiFormClose></#macro>
 
diff --git a/themes/common-theme/template/macro/FoFormMacroLibrary.ftl b/themes/common-theme/template/macro/FoFormMacroLibrary.ftl
index 948203f..29189db 100644
--- a/themes/common-theme/template/macro/FoFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/FoFormMacroLibrary.ftl
@@ -80,7 +80,7 @@ under the License.
 <#macro renderEmptyFormDataMessage message></#macro>
 <#macro renderSingleFormFieldTitle><!--title form--></#macro>
 
-<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField></#macro>
+<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField csrfNameValue></#macro>
 <#macro renderFormClose></#macro>
 <#macro renderMultiFormClose></#macro>
 
diff --git a/themes/common-theme/template/macro/HtmlFormMacroLibrary.ftl b/themes/common-theme/template/macro/HtmlFormMacroLibrary.ftl
index 060fd20..9e6c62c 100644
--- a/themes/common-theme/template/macro/HtmlFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/HtmlFormMacroLibrary.ftl
@@ -243,8 +243,14 @@ under the License.
 </#macro>
 <#macro renderSingleFormFieldTitle></#macro>
 
-<#macro renderFormOpen linkUrl formType name viewIndexField viewSizeField viewIndex viewSize targetWindow="" containerId="" containerStyle="" autocomplete="" useRowSubmit="" focusFieldName="" hasRequiredField="">
+<#macro renderFormOpen linkUrl formType name viewIndexField viewSizeField viewIndex viewSize targetWindow="" containerId="" containerStyle="" autocomplete="" useRowSubmit="" focusFieldName="" hasRequiredField="" csrfNameValue="">
   <form method="post" action="${linkUrl}"<#if formType=="upload"> enctype="multipart/form-data"</#if><#if targetWindow?has_content> target="${targetWindow}"</#if><#if containerId?has_content> id="${containerId}"</#if> <#if focusFieldName?has_content> data-focus-field="${focusFieldName}"</#if> class="<#if containerStyle?has_content>${containerStyle}<#else>basic-form</#if><#if hasRequiredField?has_content> requireValidation</#if>" onsubmit="javascript:submitFormDisableSubmits(this)"<#if au [...]
+    <#if csrfNameValue?has_content>
+      <#assign result = csrfNameValue?matches(r"(\w+) (\w+)")>
+      <#if result>
+        <input type="hidden" name="${result?groups[1]}" value="${result?groups[2]}"/>
+      </#if>
+    </#if>
     <#if useRowSubmit?has_content && useRowSubmit>
       <input type="hidden" name="_useRowSubmit" value="Y"/>
       <#if linkUrl?index_of("VIEW_INDEX") &lt;= 0 && linkUrl?index_of(viewIndexField) &lt;= 0>
diff --git a/themes/common-theme/template/macro/TextFormMacroLibrary.ftl b/themes/common-theme/template/macro/TextFormMacroLibrary.ftl
index 228611e..0e97938 100644
--- a/themes/common-theme/template/macro/TextFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/TextFormMacroLibrary.ftl
@@ -54,7 +54,7 @@ under the License.
 <#macro renderEmptyFormDataMessage message></#macro>
 <#macro renderSingleFormFieldTitle></#macro>
 
-<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField></#macro>
+<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField csrfNameValue></#macro>
 <#macro renderFormClose></#macro>
 <#macro renderMultiFormClose></#macro>
 
diff --git a/themes/common-theme/template/macro/XlsFormMacroLibrary.ftl b/themes/common-theme/template/macro/XlsFormMacroLibrary.ftl
index 0998073..0472f2d 100644
--- a/themes/common-theme/template/macro/XlsFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/XlsFormMacroLibrary.ftl
@@ -59,7 +59,7 @@ under the License.
 
 <#macro renderSingleFormFieldTitle></#macro>
 
-<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField></#macro>
+<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField csrfNameValue></#macro>
 <#macro renderFormClose></#macro>
 <#macro renderMultiFormClose></#macro>
 
diff --git a/themes/common-theme/template/macro/XmlFormMacroLibrary.ftl b/themes/common-theme/template/macro/XmlFormMacroLibrary.ftl
index b8cbc51..acc2f28 100644
--- a/themes/common-theme/template/macro/XmlFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/XmlFormMacroLibrary.ftl
@@ -62,7 +62,7 @@ under the License.
 <#macro renderEmptyFormDataMessage message></#macro>
 <#macro renderSingleFormFieldTitle></#macro>
 
-<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField></#macro>
+<#macro renderFormOpen linkUrl formType targetWindow containerId containerStyle autocomplete name viewIndexField viewSizeField viewIndex viewSize useRowSubmit focusFieldName hasRequiredField csrfNameValue></#macro>
 <#macro renderFormClose></#macro>
 <#macro renderMultiFormClose></#macro>
 
diff --git a/themes/common-theme/webapp/common/js/util/OfbizUtil.js b/themes/common-theme/webapp/common/js/util/OfbizUtil.js
index f268519..7636760 100644
--- a/themes/common-theme/webapp/common/js/util/OfbizUtil.js
+++ b/themes/common-theme/webapp/common/js/util/OfbizUtil.js
@@ -25,6 +25,16 @@ var AJAX_REQUEST_TIMEOUT = 5000;
 
 // Add observers on DOM ready.
 $(document).ready(function() {
+    // add CSRF token to jQuery AJAX calls to the same domain
+    jQuery.ajaxPrefilter(function(options, _, jqXHR) {
+      var token;
+      if (!options.crossDomain) {
+        token = jQuery("meta[name='csrf-token']").attr("content")
+        if (token) {
+          return jqXHR.setRequestHeader("X-CSRF-Token", token);
+        }
+      }
+    });
     //initializing UI combobox dropdown by overriding its methods.
     ajaxAutoCompleteDropDown();
     // bindObservers will add observer on passed html section when DOM is ready.
@@ -1218,7 +1228,7 @@ function getJSONuiLabels(requiredLabels, callback) {
     }
 }
 /**
- * Read the requiered uiLabel from the uiLabelXml Resource
+ * Read the required uiLabel from the uiLabelXml Resource
  * @param uiResource String
  * @param errUiLabel String
  * @returns String with Label
diff --git a/themes/flatgrey/template/Header.ftl b/themes/flatgrey/template/Header.ftl
index 8920f07..bbe4eb3 100644
--- a/themes/flatgrey/template/Header.ftl
+++ b/themes/flatgrey/template/Header.ftl
@@ -24,6 +24,10 @@ under the License.
 <html lang="${docLangAttr}" dir="${langDir}" xmlns="http://www.w3.org/1999/xhtml">
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <#assign csrfDefenseStrategy = Static["org.apache.ofbiz.entity.util.EntityUtilProperties"].getPropertyValue("security", "csrf.defense.strategy", delegator)>
+    <#if csrfDefenseStrategy != "org.apache.ofbiz.security.NoCsrfDefenseStrategy">
+        <meta name="csrf-token" content="<@csrfTokenAjax/>"/>
+    </#if>
     <title>${layoutSettings.companyName}: <#if (titleProperty)?has_content>${uiLabelMap[titleProperty]}<#else>${title!}</#if></title>
     <#if layoutSettings.shortcutIcon?has_content>
       <#assign shortcutIcon = layoutSettings.shortcutIcon/>
@@ -156,7 +160,7 @@ under the License.
           <#---if webSiteId?? && requestAttributes._CURRENT_VIEW_?? && helpTopic??-->
           <#if parameters.componentName?? && requestAttributes._CURRENT_VIEW_?? && helpTopic??>
             <#include "component://common-theme/template/includes/HelpLink.ftl" />
-            <li><a <#if pageAvail?has_content>class="alert"</#if> href="javascript:lookup_popup1('showHelp?helpTopic=${helpTopic}&amp;portalPageId=${(parameters.portalPageId!)?html}','help' ,500,500);">${uiLabelMap.CommonHelp}</a></li>
+            <li><a <#if pageAvail?has_content>class="alert"</#if> href="javascript:lookup_popup1('<@ofbizUrl>showHelp?helpTopic=${helpTopic}&amp;portalPageId=${(parameters.portalPageId!)?html}</@ofbizUrl>','help' ,500,500);">${uiLabelMap.CommonHelp}</a></li>
           </#if>
           </ul>
       </li>
diff --git a/themes/rainbowstone/template/includes/Header.ftl b/themes/rainbowstone/template/includes/Header.ftl
index bb1ad5e..93a8500 100644
--- a/themes/rainbowstone/template/includes/Header.ftl
+++ b/themes/rainbowstone/template/includes/Header.ftl
@@ -24,6 +24,10 @@ under the License.
 <html lang="${docLangAttr}" dir="${langDir}" xmlns="http://www.w3.org/1999/xhtml">
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <#assign csrfDefenseStrategy = Static["org.apache.ofbiz.entity.util.EntityUtilProperties"].getPropertyValue("security", "csrf.defense.strategy", delegator)>
+    <#if csrfDefenseStrategy != "org.apache.ofbiz.security.NoCsrfDefenseStrategy">
+        <meta name="csrf-token" content="<@csrfTokenAjax/>"/>
+    </#if>
     <title>${layoutSettings.companyName}: <#if (titleProperty)?has_content>${uiLabelMap[titleProperty]}<#else>${title!}</#if></title>
 <#if layoutSettings.shortcutIcon?has_content>
     <#assign shortcutIcon = layoutSettings.shortcutIcon/>
diff --git a/themes/rainbowstone/template/includes/TopAppBar.ftl b/themes/rainbowstone/template/includes/TopAppBar.ftl
index e82c73b..90cc979 100644
--- a/themes/rainbowstone/template/includes/TopAppBar.ftl
+++ b/themes/rainbowstone/template/includes/TopAppBar.ftl
@@ -238,7 +238,7 @@ under the License.
         <div id="main-nav-bar-right">
             <div id="company-logo"></div>
             <#if parameters.componentName?exists && requestAttributes._CURRENT_VIEW_?exists && helpTopic?exists>
-                <a class="dark-color" title="${uiLabelMap.CommonHelp}" href="javascript:lookup_popup1('showHelp?helpTopic=${helpTopic}&amp;portalPageId=${(parameters.portalPageId!)?html}','help' ,500,500);"><img class="appbar-btn-img" id="help-btn" src="/rainbowstone/images/help.svg" alt="Help"></a>
+                <a class="dark-color" title="${uiLabelMap.CommonHelp}" href="javascript:lookup_popup1('<@ofbizUrl>showHelp?helpTopic=${helpTopic}&amp;portalPageId=${(parameters.portalPageId!)?html}</@ofbizUrl>','help' ,500,500);"><img class="appbar-btn-img" id="help-btn" src="/rainbowstone/images/help.svg" alt="Help"></a>
             </#if>
 
             <#include "component://rainbowstone/template/includes/Avatar.ftl"/>
diff --git a/themes/tomahawk/template/AppBarClose.ftl b/themes/tomahawk/template/AppBarClose.ftl
index 7475102..c79b4fb 100644
--- a/themes/tomahawk/template/AppBarClose.ftl
+++ b/themes/tomahawk/template/AppBarClose.ftl
@@ -75,7 +75,7 @@ under the License.
       <#--if webSiteId?? && requestAttributes._CURRENT_VIEW_?? && helpTopic??-->
       <#if parameters.componentName?? && requestAttributes._CURRENT_VIEW_?? && helpTopic??>
         <#include "component://common-theme/template/includes/HelpLink.ftl" />
-        <li><a class="help-link <#if pageAvail?has_content> alert</#if>" href="javascript:lookup_popup1('showHelp?helpTopic=${helpTopic}&amp;portalPageId=${(parameters.portalPageId!)?html}','help' ,500,500);" title="${uiLabelMap.CommonHelp}"></a></li>
+        <li><a class="help-link <#if pageAvail?has_content> alert</#if>" href="javascript:lookup_popup1('<@ofbizUrl>showHelp?helpTopic=${helpTopic}&amp;portalPageId=${(parameters.portalPageId!)?html}</@ofbizUrl>','help' ,500,500);" title="${uiLabelMap.CommonHelp}"></a></li>
       </#if>
       <li><a href="<@ofbizUrl>logout</@ofbizUrl>">${uiLabelMap.CommonLogout}</a></li>
       <li><a href="<@ofbizUrl>ListVisualThemes</@ofbizUrl>">${uiLabelMap.CommonVisualThemes}</a></li>
diff --git a/themes/tomahawk/template/Header.ftl b/themes/tomahawk/template/Header.ftl
index 3376614..d01ae9c 100644
--- a/themes/tomahawk/template/Header.ftl
+++ b/themes/tomahawk/template/Header.ftl
@@ -28,6 +28,10 @@ under the License.
 <html lang="${docLangAttr}" dir="${langDir}" xmlns="http://www.w3.org/1999/xhtml">
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <#assign csrfDefenseStrategy = Static["org.apache.ofbiz.entity.util.EntityUtilProperties"].getPropertyValue("security", "csrf.defense.strategy", delegator)>
+    <#if csrfDefenseStrategy != "org.apache.ofbiz.security.NoCsrfDefenseStrategy">
+        <meta name="csrf-token" content="<@csrfTokenAjax/>"/>
+    </#if>
     <title>${layoutSettings.companyName}: <#if (titleProperty)?has_content>${uiLabelMap[titleProperty]}<#else>${title!}</#if></title>
     <#if layoutSettings.shortcutIcon?has_content>
       <#assign shortcutIcon = layoutSettings.shortcutIcon/>