Bläddra i källkod

嵌入工作台功能提交

peicj 1 vecka sedan
förälder
incheckning
68776613eb

+ 18 - 2
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java

@@ -185,11 +185,27 @@ public class SysIndexController extends BaseController
 
         // current user
         SysUser currentUser = ShiroUtils.getSysUser();
+        if (currentUser == null)
+        {
+            if (embed)
+            {
+                mmap.put("errorMsg", "登录会话无效,请重新外呼登录");
+                mmap.put("errorCode", "NO_SESSION");
+                return "error/embedWorkbench";
+            }
+            return "redirect:/login";
+        }
         // extension number
         CcExtNum ccExtNum = ccExtNumService.selectCcExtNumByUserCodeTwo(currentUser.getLoginName());
         if (ccExtNum == null || ccExtNum.getExtNum() == null) {
-            // embed 下不要静默炸页;退回登录并尽量保留 embed
-            return "redirect:/login?embed=1&lang=zh_CN";
+            // embed 下禁止 redirect:/login(会再次触发 LOGIN_READY,被 HIS 误判为 Cookie 回弹)
+            if (embed)
+            {
+                mmap.put("errorMsg", "未绑定分机号,请联系系统管理员");
+                mmap.put("errorCode", "NO_EXT");
+                return "error/embedWorkbench";
+            }
+            return "redirect:/login";
         }
 
         String extnum = ccExtNum.getExtNum().toString();

+ 7 - 5
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@@ -24,6 +24,7 @@ import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.text.Convert;
 import com.ruoyi.common.utils.ServletUtils;
 import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.shiro.web.filter.embed.EmbedRequestUtils;
 import com.ruoyi.framework.web.service.ConfigService;
 
 import java.util.Arrays;
@@ -88,15 +89,16 @@ public class SysLoginController extends BaseController
     @ResponseBody
     public AjaxResult ajaxLogin(String username, String password, String extNum, String groupId, String myGateway, Boolean rememberMe)
     {
-        // 校验分机号
+        boolean embed = EmbedRequestUtils.isEmbed(ServletUtils.getRequest());
+        // 校验分机号(HIS iframe 嵌入必须绑定分机;禁止登录成功后再被 main 踢回登录)
         CcExtNum ccExtNum = ccExtNumService.selectCcExtNumByUserCodeTwo(username);
         if (null == ccExtNum) {
-            if(StringUtils.isNotEmpty(username) && username.startsWith("Runtian_")){
-                //润天的有可能账号被覆盖,先保证使用
-            }else{
+            if (!embed && StringUtils.isNotEmpty(username) && username.startsWith("Runtian_")) {
+                // 非嵌入:保留润天账号兼容
+            } else {
                 return AjaxResult.error("未绑定分机号,请联系系统管理员!");
             }
-        }else{
+        } else {
             if (!ccExtNum.getExtNum().toString().equals(extNum)) {
                 return AjaxResult.error("username="+username+"的分机号不正确!线上:"+ccExtNum.getExtNum()+",传入的分机号:" + extNum);
             }

+ 40 - 9
ruoyi-admin/src/main/resources/static/ruoyi/login.js

@@ -59,7 +59,8 @@ function login(extra) {
             "validateCode": validateCode,
             "rememberMe": rememberMe,
             "groupId": groupId,
-            "myGateway": myGateway
+            "myGateway": myGateway,
+            "embed": isEmbedMode() ? "1" : ""
         },
         xhrFields: { withCredentials: true },
         beforeSend: function () {
@@ -72,6 +73,7 @@ function login(extra) {
                 var nextUrl = getLoginSuccessUrl();
                 // 先通知父页,再由【本 iframe 内】同源跳转工作台。
                 // 切勿让父页改 iframe.src(跨源会导致会话 Cookie 丢失 → 踢回登录死循环)
+                try { sessionStorage.setItem("his_ipcc_login_jump", "1"); } catch (e) {}
                 postEmbedToParent("HIS_IPCC_LOGIN_RESULT", { ok: true, nextUrl: nextUrl });
                 if (isEmbedMode()) {
                     window.location.replace(nextUrl);
@@ -80,6 +82,7 @@ function login(extra) {
                 }
             } else {
                 window.__hisIpccLoginJumping = false;
+                try { sessionStorage.removeItem("his_ipcc_login_jump"); } catch (e) {}
                 $('.imgcode').click();
                 $(".code").val("");
                 if (!isEmbedMode()) {
@@ -92,6 +95,8 @@ function login(extra) {
             }
         },
         error: function(xhr, status, error) {
+            window.__hisIpccLoginJumping = false;
+            try { sessionStorage.removeItem("his_ipcc_login_jump"); } catch (e) {}
             if (!isEmbedMode()) {
                 $.modal.closeLoading();
                 $.modal.msg(error || "登录异常");
@@ -162,15 +167,9 @@ function postEmbedToParent(type, payload) {
     } catch (e) {}
 }
 
-function initEmbedLoginBridge() {
-    if (!isEmbedMode()) return;
-    // 防重复绑定
-    if (window.__hisIpccLoginBridgeBound) {
-        postEmbedToParent("HIS_IPCC_LOGIN_READY", {});
-        return;
-    }
+function bindEmbedLoginMessage() {
+    if (window.__hisIpccLoginBridgeBound) return;
     window.__hisIpccLoginBridgeBound = true;
-    postEmbedToParent("HIS_IPCC_LOGIN_READY", {});
     window.addEventListener("message", function(event) {
         var data = event.data;
         if (!data || data.channel !== "HIS_IPCC_EMBED") return;
@@ -199,3 +198,35 @@ function initEmbedLoginBridge() {
         }
     });
 }
+
+function initEmbedLoginBridge() {
+    if (!isEmbedMode()) return;
+    bindEmbedLoginMessage();
+
+    // 登录成功后又回到登录页:真会话丢失(Cookie 未带上)
+    var bounced = false;
+    try { bounced = sessionStorage.getItem("his_ipcc_login_jump") === "1"; } catch (e) {}
+    if (bounced) {
+        try { sessionStorage.removeItem("his_ipcc_login_jump"); } catch (e) {}
+        window.__hisIpccLoginJumping = false;
+        postEmbedToParent("ERROR", {
+            msg: "工作台会话未带上(Cookie 丢失或被拒),请检查同主机:8899 反代是否剥离 Domain/Secure",
+            code: "SESSION_COOKIE_LOST",
+            type: "SESSION_COOKIE_LOST"
+        });
+        postEmbedToParent("HIS_IPCC_LOGIN_RESULT", {
+            ok: false,
+            msg: "工作台会话未带上(Cookie 丢失或被拒)",
+            code: "SESSION_COOKIE_LOST"
+        });
+        // 延迟 READY,避免立刻触发父页「登录回弹」误判;供整轮重登使用
+        setTimeout(function () {
+            postEmbedToParent("HIS_IPCC_LOGIN_READY", {});
+        }, 1500);
+        return;
+    }
+
+    // 正常进入登录页:工作台就绪后清跳转标记
+    try { sessionStorage.removeItem("his_ipcc_login_jump"); } catch (e) {}
+    postEmbedToParent("HIS_IPCC_LOGIN_READY", {});
+}

+ 28 - 0
ruoyi-admin/src/main/resources/templates/error/embedWorkbench.html

@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>工作台不可用</title>
+    <style>
+        html, body { margin: 0; padding: 0; background: transparent; font-family: sans-serif; }
+        .box { padding: 12px; color: #a00; font-size: 13px; }
+    </style>
+</head>
+<body>
+<div class="box" th:text="${errorMsg}">工作台不可用</div>
+<script th:inline="javascript">
+(function () {
+    var msg = /*[[${errorMsg}]]*/ '工作台不可用';
+    var code = /*[[${errorCode}]]*/ 'WORKBENCH_ERROR';
+    try {
+        if (window.parent && window.parent !== window) {
+            var payload = { ok: false, msg: msg, code: code };
+            window.parent.postMessage({ channel: 'HIS_IPCC_EMBED', type: 'ERROR', payload: { msg: msg, code: code, type: code } }, '*');
+            window.parent.postMessage({ channel: 'HIS_IPCC_EMBED', type: 'HIS_IPCC_LOGIN_RESULT', payload: payload }, '*');
+        }
+    } catch (e) {}
+})();
+</script>
+</body>
+</html>

+ 1 - 0
ruoyi-admin/src/main/resources/templates/main.html

@@ -818,6 +818,7 @@
                     }
                 });
             });
+            try { sessionStorage.removeItem('his_ipcc_login_jump'); } catch (e) {}
             hisIpccPost('READY', { extNum: _embedExtNum, myGateway: _embedMyGateway || '' });
         }
 

+ 27 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SessionCookieFilterConfig.java

@@ -0,0 +1,27 @@
+package com.ruoyi.framework.config;
+
+import javax.servlet.DispatcherType;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import com.ruoyi.framework.web.filter.SessionCookieRewriteFilter;
+
+/**
+ * 注册会话 Cookie 改写过滤器(须早于 Shiro,才能包住 Set-Cookie)
+ */
+@Configuration
+public class SessionCookieFilterConfig
+{
+    @Bean
+    public FilterRegistrationBean<SessionCookieRewriteFilter> sessionCookieRewriteFilterRegistration()
+    {
+        FilterRegistrationBean<SessionCookieRewriteFilter> registration = new FilterRegistrationBean<>();
+        registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
+        registration.setFilter(new SessionCookieRewriteFilter());
+        registration.addUrlPatterns("/*");
+        registration.setName("sessionCookieRewriteFilter");
+        registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 20);
+        return registration;
+    }
+}

+ 30 - 3
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java

@@ -16,6 +16,7 @@ import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.apache.shiro.web.servlet.Cookie;
 import org.apache.shiro.web.servlet.SimpleCookie;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
@@ -28,6 +29,7 @@ import com.ruoyi.common.utils.spring.SpringUtils;
 import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
 import com.ruoyi.framework.shiro.realm.UserRealm;
 import com.ruoyi.framework.shiro.rememberMe.CustomCookieRememberMeManager;
+import com.ruoyi.framework.shiro.web.filter.embed.EmbedAwareUserFilter;
 import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
 import com.ruoyi.framework.shiro.session.OnlineSessionFactory;
 import com.ruoyi.framework.shiro.web.CustomShiroFilterFactoryBean;
@@ -222,8 +224,16 @@ public class ShiroConfig
         manager.setDeleteInvalidSessions(true);
         // 设置全局session超时时间
         manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
-        // 去掉 JSESSIONID
+        // 去掉 URL 重写 ;JSESSIONID=(仍使用 Cookie 传递会话)
         manager.setSessionIdUrlRewritingEnabled(false);
+        // 显式会话 Cookie:host-only + SameSite=Lax,保证同主机:8899 iframe 登录后能带到工作台
+        SimpleCookie sessionIdCookie = new SimpleCookie("JSESSIONID");
+        sessionIdCookie.setHttpOnly(true);
+        sessionIdCookie.setPath(StringUtils.isNotEmpty(path) ? path : "/");
+        // 不设置 Domain → host-only;避免 yml/反代写成上游 IP 导致浏览器拒收
+        sessionIdCookie.setSameSite(Cookie.SameSiteOptions.LAX);
+        manager.setSessionIdCookie(sessionIdCookie);
+        manager.setSessionIdCookieEnabled(true);
         // 定义要使用的无效的Session定时调度器
         manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));
         // 是否定时检查session
@@ -314,6 +324,8 @@ public class ShiroConfig
         // filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());
 
         Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
+        // 替换默认 user:未登录踢回时保留 embed=1
+        filters.put("user", embedAwareUserFilter());
         filters.put("onlineSession", onlineSessionFilter());
         filters.put("syncOnlineSession", syncOnlineSessionFilter());
         filters.put("captchaValidate", captchaValidateFilter());
@@ -367,10 +379,15 @@ public class ShiroConfig
     public SimpleCookie rememberMeCookie()
     {
         SimpleCookie cookie = new SimpleCookie("rememberMe");
-        cookie.setDomain(domain);
+        // Domain 为空时不要 setDomain,保持 host-only(与会话 Cookie 一致)
+        if (StringUtils.isNotEmpty(domain))
+        {
+            cookie.setDomain(domain);
+        }
         cookie.setPath(path);
         cookie.setHttpOnly(httpOnly);
         cookie.setMaxAge(maxAge * 24 * 60 * 60);
+        cookie.setSameSite(Cookie.SameSiteOptions.LAX);
         return cookie;
     }
 
@@ -404,11 +421,21 @@ public class ShiroConfig
         kickoutSessionFilter.setMaxSession(maxSession);
         // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序
         kickoutSessionFilter.setKickoutAfter(kickoutAfter);
-        // 被踢出后重定向到的地址;
+        // 被踢出后重定向到的地址(不带 embed,避免影响线上主流登录页iframe 内由 login.html 自动补 embed=1)
         kickoutSessionFilter.setKickoutUrl("/login?kickout=1");
         return kickoutSessionFilter;
     }
 
+    /**
+     * 未登录跳转:保留 HIS iframe embed 参数
+     */
+    public EmbedAwareUserFilter embedAwareUserFilter()
+    {
+        EmbedAwareUserFilter filter = new EmbedAwareUserFilter();
+        filter.setLoginUrl(loginUrl);
+        return filter;
+    }
+
     /**
      * thymeleaf模板引擎和shiro框架的整合
      */

+ 24 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/embed/EmbedAwareUserFilter.java

@@ -0,0 +1,24 @@
+package com.ruoyi.framework.shiro.web.filter.embed;
+
+import java.io.IOException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import org.apache.shiro.web.filter.authc.UserFilter;
+import org.apache.shiro.web.util.WebUtils;
+
+/**
+ * 未登录跳转登录页时保留 embed=1,避免 iframe 内丢失嵌入态后反复 LOGIN_READY。
+ */
+public class EmbedAwareUserFilter extends UserFilter
+{
+    @Override
+    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException
+    {
+        String loginUrl = getLoginUrl();
+        if (EmbedRequestUtils.isEmbed(request))
+        {
+            loginUrl = EmbedRequestUtils.withEmbedParams(loginUrl);
+        }
+        WebUtils.issueRedirect(request, response, loginUrl);
+    }
+}

+ 63 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/embed/EmbedRequestUtils.java

@@ -0,0 +1,63 @@
+package com.ruoyi.framework.shiro.web.filter.embed;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * HIS iframe 嵌入模式识别(embed=1 / Referer / 自定义头)
+ */
+public final class EmbedRequestUtils
+{
+    public static final String EMBED_PARAM = "embed";
+    public static final String EMBED_HEADER = "X-HIS-IPCC-Embed";
+
+    private EmbedRequestUtils()
+    {
+    }
+
+    public static boolean isEmbed(ServletRequest request)
+    {
+        if (request == null)
+        {
+            return false;
+        }
+        if ("1".equals(request.getParameter(EMBED_PARAM)))
+        {
+            return true;
+        }
+        if (!(request instanceof HttpServletRequest))
+        {
+            return false;
+        }
+        HttpServletRequest req = (HttpServletRequest) request;
+        if ("1".equals(req.getHeader(EMBED_HEADER)))
+        {
+            return true;
+        }
+        String referer = req.getHeader("Referer");
+        if (StringUtils.isNotEmpty(referer) && referer.contains("embed=1"))
+        {
+            return true;
+        }
+        return false;
+    }
+
+    /** 登录 URL 补齐 embed=1&lang=zh_CN,避免踢回登录后丢失嵌入态 */
+    public static String withEmbedParams(String loginUrl)
+    {
+        String url = StringUtils.isEmpty(loginUrl) ? "/login" : loginUrl;
+        if (url.contains("embed=1"))
+        {
+            return url;
+        }
+        StringBuilder sb = new StringBuilder(url);
+        sb.append(url.contains("?") ? "&" : "?");
+        sb.append("embed=1");
+        if (!url.contains("lang="))
+        {
+            sb.append("&lang=zh_CN");
+        }
+        return sb.toString();
+    }
+}

+ 7 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java

@@ -14,6 +14,7 @@ import com.ruoyi.common.enums.OnlineStatus;
 import com.ruoyi.common.utils.ShiroUtils;
 import com.ruoyi.framework.shiro.session.OnlineSession;
 import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
+import com.ruoyi.framework.shiro.web.filter.embed.EmbedRequestUtils;
 
 /**
  * 自定义访问控制
@@ -89,7 +90,12 @@ public class OnlineSessionFilter extends AccessControlFilter
     @Override
     protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException
     {
-        WebUtils.issueRedirect(request, response, loginUrl);
+        String url = loginUrl;
+        if (EmbedRequestUtils.isEmbed(request))
+        {
+            url = EmbedRequestUtils.withEmbedParams(url);
+        }
+        WebUtils.issueRedirect(request, response, url);
     }
 
     public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO)

+ 183 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/filter/SessionCookieRewriteFilter.java

@@ -0,0 +1,183 @@
+package com.ruoyi.framework.web.filter;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import com.ruoyi.framework.shiro.web.filter.embed.EmbedRequestUtils;
+
+/**
+ * 仅改写会话类 Cookie(JSESSIONID / rememberMe),不影响业务 Cookie。
+ * <p>
+ * 目标:在无法改 Nginx 时,尽量让浏览器收下会话 Cookie,供 HIS 同主机:8899 iframe 使用。
+ * 非嵌入主流访问:只清理「Domain=IP」这类明显错误,不强制改其它合法 Domain。
+ */
+public class SessionCookieRewriteFilter implements Filter
+{
+    private static final Pattern COOKIE_NAME = Pattern.compile("^\\s*([^=\\s;]+)\\s*=");
+    private static final Pattern DOMAIN_ATTR = Pattern.compile("(?i);\\s*Domain=([^;]*)");
+    private static final Pattern IP_DOMAIN = Pattern.compile(
+            "^\\[?[0-9a-fA-F:.]+\\]?$|^\\d{1,3}(?:\\.\\d{1,3}){3}$");
+
+    @Override
+    public void init(FilterConfig filterConfig)
+    {
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException
+    {
+        HttpServletRequest req = (HttpServletRequest) request;
+        HttpServletResponse res = (HttpServletResponse) response;
+        boolean embed = EmbedRequestUtils.isEmbed(req);
+        boolean secure = req.isSecure()
+                || "https".equalsIgnoreCase(req.getHeader("X-Forwarded-Proto"));
+        chain.doFilter(request, new CookieRewriteResponse(res, embed, secure));
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    static boolean isSessionCookieName(String name)
+    {
+        if (name == null)
+        {
+            return false;
+        }
+        String n = name.trim();
+        return "JSESSIONID".equalsIgnoreCase(n)
+                || "rememberMe".equalsIgnoreCase(n)
+                || "SHIROJSESSIONID".equalsIgnoreCase(n);
+    }
+
+    static boolean isIpLiteral(String domain)
+    {
+        if (domain == null)
+        {
+            return false;
+        }
+        String d = domain.trim();
+        if (d.startsWith("."))
+        {
+            d = d.substring(1);
+        }
+        return IP_DOMAIN.matcher(d).matches();
+    }
+
+    /**
+     * @param embedMode HIS iframe 嵌入:会话 Cookie 强制 host-only + SameSite=Lax
+     * @param secureRequest 当前是否 HTTPS(含 X-Forwarded-Proto)
+     */
+    static String rewriteSetCookie(String raw, boolean embedMode, boolean secureRequest)
+    {
+        if (raw == null || raw.isEmpty())
+        {
+            return raw;
+        }
+        Matcher nameMatcher = COOKIE_NAME.matcher(raw);
+        if (!nameMatcher.find())
+        {
+            return raw;
+        }
+        String cookieName = nameMatcher.group(1);
+        if (!isSessionCookieName(cookieName))
+        {
+            // 业务 Cookie 原样返回,不影响主流任务
+            return raw;
+        }
+
+        String value = raw;
+        Matcher domainMatcher = DOMAIN_ATTR.matcher(value);
+        if (domainMatcher.find())
+        {
+            String domainVal = domainMatcher.group(1) == null ? "" : domainMatcher.group(1).trim();
+            // embed:一律 host-only;主流:仅去掉 Domain=IP(反代常见误配)
+            if (embedMode || isIpLiteral(domainVal))
+            {
+                value = domainMatcher.replaceFirst("");
+            }
+        }
+
+        if (!secureRequest)
+        {
+            value = value.replaceAll("(?i);\\s*Secure", "");
+            value = value.replaceAll("(?i)SameSite=None", "SameSite=Lax");
+        }
+
+        // 会话 Cookie 补 SameSite=Lax(同站不同端口 iframe 可带上;不使用 None 以免无 Secure 被丢)
+        if (!value.matches("(?i).*\\bSameSite\\s*=.*"))
+        {
+            value = value + "; SameSite=Lax";
+        }
+        return value;
+    }
+
+    private static final class CookieRewriteResponse extends HttpServletResponseWrapper
+    {
+        private final boolean embedMode;
+        private final boolean secureRequest;
+
+        CookieRewriteResponse(HttpServletResponse response, boolean embedMode, boolean secureRequest)
+        {
+            super(response);
+            this.embedMode = embedMode;
+            this.secureRequest = secureRequest;
+        }
+
+        @Override
+        public void addCookie(Cookie cookie)
+        {
+            if (cookie != null && isSessionCookieName(cookie.getName()))
+            {
+                String domain = cookie.getDomain();
+                if (embedMode || isIpLiteral(domain))
+                {
+                    cookie.setDomain(null);
+                }
+                if (cookie.getPath() == null || cookie.getPath().isEmpty())
+                {
+                    cookie.setPath("/");
+                }
+                if (!secureRequest)
+                {
+                    cookie.setSecure(false);
+                }
+            }
+            super.addCookie(cookie);
+        }
+
+        @Override
+        public void setHeader(String name, String value)
+        {
+            if ("Set-Cookie".equalsIgnoreCase(name))
+            {
+                super.setHeader(name, rewriteSetCookie(value, embedMode, secureRequest));
+                return;
+            }
+            super.setHeader(name, value);
+        }
+
+        @Override
+        public void addHeader(String name, String value)
+        {
+            if ("Set-Cookie".equalsIgnoreCase(name))
+            {
+                super.addHeader(name, rewriteSetCookie(value, embedMode, secureRequest));
+                return;
+            }
+            super.addHeader(name, value);
+        }
+    }
+}