cookie setSecure详解

1. 前言

       最近项目用Sparrow Health System检测漏洞,发现存在一个setSecure安全漏洞问题,于是网上搜索了一下,这里记录一下。

2. 问题

      在cas中或其他web开发中,会碰到安全cookie的概念,因为CAS中TGT是存放在安全cookie中的。下面是安全cookie 的理解:

  • Set-Cookie 的 secure 属性就是处理这方面的情况用的,它表示创建的 cookie 只能在 HTTPS 连接中被浏览器传递到服务器端进行会话验证,如果是 HTTP 连接则不会传递该信息,所以绝对不会被窃听到。

     在setSecure(true); 的情况下,只有https才传递到服务器端。http是不会传递的。

    setSecure(true)意味着"指示浏览器仅通过 HTTPS 连接传回 cookie。这可以确保 cookie ID 是安全的,且仅用于使用 HTTPS 的网站。如果启用此功能,则 HTTP 上的会话 Cookie 将不再起作用。"

    j2ee servlet的接口中也定义了Cookie对象,也有其方法setSecue(false);

   关于setSecure的问题,在http连接下: 

  •   当setSecure(true)时,浏览器端的cookie会不会传递到服务器端? 
  •   当setSecure(true)时,服务器端的cookie会不会传递到浏览器端? 
  •    答案:1)不会 ; 2)会

    原理:服务器端的Cookie对象是java中的对象,请不要和浏览器端的cookie文件混淆了。服务器端的Cookie对象是方便java程序员包装一个浏览器端的cookie文件。一旦包装好,就放到response对象中,在转换成http头文件。在传递到浏览器端。这时就会在浏览器的临时文件中创建一个cookie文件。 
     但我们再次访问网页时,才查看浏览器端的cookie文件中的secure值,如果是true,但是http连接,这个cookie就不会传到服务器端。当然这个过程对浏览器是透明的。其他人是不会知道的。

    总结如下:cookie的secure值为true时,在http中是无效的;在https中才有效

3. Cookie设置

        实际上,Servlet提供的HttpSession本质上就是通过一个名为JSESSIONID的Cookie来跟踪用户会话的。除了这个名称外,其他名称的Cookie我们可以任意使用。

如果我们想要设置一个Cookie,例如,记录用户选择的语言,可以编写一个LanguageServlet

@WebServlet(urlPatterns = "/pref")
public class LanguageServlet extends HttpServlet {

    private static final Set<String> LANGUAGES = Set.of("en", "zh");

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String lang = req.getParameter("lang");
        if (LANGUAGES.contains(lang)) {
            // 创建一个新的Cookie:
            Cookie cookie = new Cookie("lang", lang);
            // 该Cookie生效的路径范围:
            cookie.setPath("/");
            // 该Cookie有效期:
            cookie.setMaxAge(8640000); // 8640000秒=100天
            // 将该Cookie添加到响应:
            resp.addCookie(cookie);
        }
        resp.sendRedirect("/");
    }
}

        创建一个新Cookie时,除了指定名称和值以外,通常需要设置setPath("/"),浏览器根据此前缀决定是否发送Cookie。如果一个Cookie调用了setPath("/user/"),那么浏览器只有在请求以/user/开头的路径时才会附加此Cookie。通过setMaxAge()设置Cookie的有效期,单位为秒,最后通过resp.addCookie()把它添加到响应。

        如果访问的是https网页,还需要调用setSecure(true),否则浏览器不会发送该Cookie。

        因此,务必注意:浏览器在请求某个URL时,是否携带指定的Cookie,取决于Cookie是否满足以下所有要求:

  • URL前缀是设置Cookie时的Path;
  • Cookie在有效期内;
  • Cookie设置了setSecure(true)时必须以https访问。

我们可以在浏览器看到服务器发送的Cookie:

       如果我们要读取Cookie,例如,在IndexServlet中,读取名为lang的Cookie以获取用户设置的语言,可以写一个方法如下:

private String parseLanguageFromCookie(HttpServletRequest req) {
    // 获取请求附带的所有Cookie:
    Cookie[] cookies = req.getCookies();
    // 如果获取到Cookie:
    if (cookies != null) {
        // 循环每个Cookie:
        for (Cookie cookie : cookies) {
            // 如果Cookie名称为lang:
            if (cookie.getName().equals("lang")) {
                // 返回Cookie的值:
                return cookie.getValue();
            }
        }
    }
    // 返回默认值:
    return "en";
}

可见,读取Cookie主要依靠遍历HttpServletRequest附带的所有Cookie。

 

4. setSecure测试

下面我自己再实验一下:

4.1 http访问测试

我自己的电脑用的Tomcat,首先,把cookie-secure的值设为false,创建一个session:

 1 public void service(HttpServletRequest request, HttpServletResponse response) {
 2         // 创建cookie
 3         Cookie cookie = new Cookie("key", "value");
 4         // 关键地方:cookie设为false
 5         cookie.setSecure(false);
 6         response.addCookie(cookie);
 7         // 从request中取得cookie,并输出
 8         Cookie[] cookies = request.getCookies();
 9         for (Cookie c : cookies) {
10             System.out.println(c.getValue());
11         }
12         RequestDispatcher dispatcher = request
13                 .getRequestDispatcher("index.jsp");
14         try {
15             dispatcher.forward(request, response);
16         } catch (ServletException e) {
17             e.printStackTrace();
18         } catch (IOException e) {
19             e.printStackTrace();
20         }
21     }

好,我们访问一下这个地址:http://localhost:8080/servlet/index.do

用Fiddler来看一下客户端发出的信息:

好,我们再访问一下前面的地址:

       这个时候我们可以看到客户端发出的cookie里面,有key=value,说明客户端把cookie发给了服务器端。

4.2 https访问测试

       那如果我们在服务器端设置cookie.setSecure(true)呢?

先上代码:

 1 public void service(HttpServletRequest request, HttpServletResponse response) {
 2         // 创建cookie
 3         Cookie cookie = new Cookie("key", "value");
 4         // 关键地方:cookie设为false
 5         cookie.setSecure(true);
 6         response.addCookie(cookie);
 7         // 从request中取得cookie,并输出
 8         Cookie[] cookies = request.getCookies();
 9         if (cookies != null) {
10             for (Cookie c : cookies) {
11                 System.out.println(c.getValue());
12             }
13         }
14         RequestDispatcher dispatcher = request
15                 .getRequestDispatcher("index.jsp");
16         try {
17             dispatcher.forward(request, response);
18         } catch (ServletException e) {
19             e.printStackTrace();
20         } catch (IOException e) {
21             e.printStackTrace();
22         }
23     }

第一次http访问

这是request的信息。

服务器的返回信息如下:

我们可以看到服务器发给客户端的信息中有这么一行:Set-Cookie:key=value; Secure

多了一个Secure。

第二次http访问:

我们发现key=value这个cookie已经没有了。

第三次https访问:

那我们用https协议头来试着访问一下看看:

我们看到key=value又发给了服务器端了。

5. 另外

      cookie中的数据通常会包含用户的隐私数据,首先要保证数据的保密性,其次要保证数据不能被伪造或者篡改,基于这两点,我们通常需要对cookie内容进行加密,加密方式一般使用对称加密(单密钥,如DES)或非对称加密(一对密钥,如RSA),密钥需要保存在服务器端一个安全的地方,这样,别人不知道密钥时,无法对数据进行解密,也无法伪造或篡改数据。

       另外,像上文提到的,重要的cookie数据需要设置成HttpOnly的,避免跨站脚本获取你的cookie,保证了cookie在浏览器端的安全性

       还有我们可以设置cookie只作用于安全的协议(https),JavaEE中可以通过Cookie类的setSecure(boolean flag)来设置,设置后,该cookie只会在https下发送,而不会再http下发送,保证了cookie在服务器端的安全性,
 

6. 例子

import javax.servlet.http.Cookie; //導入方法依賴的package包/類
/**
 * Creates a new session cookie for the given session ID
 *
 * @param context     The Context for the web application
 * @param sessionId   The ID of the session for which the cookie will be
 *                    created
 * @param secure      Should session cookie be configured as secure
 */
public static Cookie createSessionCookie(Context context,
        String sessionId, boolean secure) {

    SessionCookieConfig scc =
        context.getServletContext().getSessionCookieConfig();

    // NOTE: The priority order for session cookie configuration is:
    //       1. Context level configuration
    //       2. Values from SessionCookieConfig
    //       3. Defaults

    Cookie cookie = new Cookie(
            SessionConfig.getSessionCookieName(context), sessionId);
   
    // Just apply the defaults.
    cookie.setMaxAge(scc.getMaxAge());
    cookie.setComment(scc.getComment());
   
    if (context.getSessionCookieDomain() == null) {
        // Avoid possible NPE
        if (scc.getDomain() != null) {
            cookie.setDomain(scc.getDomain());
        }
    } else {
        cookie.setDomain(context.getSessionCookieDomain());
    }

    // Always set secure if the request is secure
    if (scc.isSecure() || secure) {
        cookie.setSecure(true);
    }

    // Always set httpOnly if the context is configured for that
    if (scc.isHttpOnly() || context.getUseHttpOnly()) {
        cookie.setHttpOnly(true);
    }
   
    String contextPath = context.getSessionCookiePath();
    if (contextPath == null || contextPath.length() == 0) {
        contextPath = scc.getPath();
    }
    if (contextPath == null || contextPath.length() == 0) {
        contextPath = context.getEncodedPath();
    }
    if (context.getSessionCookiePathUsesTrailingSlash()) {
        // Handle special case of ROOT context where cookies require a path of
        // '/' but the servlet spec uses an empty string
        // Also ensure the cookies for a context with a path of /foo don't get
        // sent for requests with a path of /foobar
        if (!contextPath.endsWith("/")) {
            contextPath = contextPath + "/";
        }
    } else {
        // Only handle special case of ROOT context where cookies require a
        // path of '/' but the servlet spec uses an empty string
        if (contextPath.length() == 0) {
            contextPath = "/";
        }
    }
    cookie.setPath(contextPath);

    return cookie;
}

/**
 * 往客戶端寫入Cookie
 * @param name cookie參數名
 * @param value cookie參數值
 * @param maxAge 有效時間,int(單位秒),0:刪除Cookie,-1:頁麵關閉時刪除cookie
 * @param path 與cookie一起傳輸的虛擬路徑
 * @param domain 與cookie關聯的域
 * @param isSecure 是否在https請求時才進行傳輸
 * @param isHttpOnly 是否隻能通過http訪問
 */
public void addCookie(String name, String value, int maxAge, String path, String domain, boolean isSecure, boolean isHttpOnly)
{
    Cookie cookie = new Cookie(name, value);
    cookie.setMaxAge(maxAge);
    cookie.setPath(path);
    if(maxAge > 0 && domain != null)
    {
        cookie.setDomain(domain);
    }
    cookie.setSecure(isSecure);
    try
    {
        Cookie.class.getMethod("setHttpOnly", boolean.class);
        cookie.setHttpOnly(isHttpOnly);
    }
    catch(Exception e)
    {
        System.out.println("MyCookie ignore setHttpOnly Method");
    }
    response.addCookie(cookie);
}

参考

cookie setSecure详解_wangyunpeng0319的博客-CSDN博客_cookie.setsecure

使用Session和Cookie - 廖雪峰的官方网站

COOKIE的SECURE属性 - 画水 - 博客园