暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

深入理解Cookie机制

Whoooops 2021-04-15
358

目录

Cookie机制
什么是Cookie
Cookie的不可跨域名性
Unicode编码:保存中文
BASE64编码:保存二进制图片
设置Cookie的所有属性
Cookie的有效期
Cookie的修改、删除
Cookie的域名
Cookie的路径
Cookie的安全属性
JavaScript操作Cookie
案例:永久登录
Session机制
什么是Session
实现用户登录
Session的生命周期
Session的有效期
Session的常用方法
Session对浏览器的要求
URL地址重写
Session中禁止使用Cookie
Cookie与Session的区别



什么是cookie

HTTP协议本身是无状态的。无状态是指Web浏览器与Web服务器之间不需要建立持久的连接,这意味着当一个客户端向服务器端发出请求,然后Web服务器返回响应(Response),连接就被关闭了,在服务器端不保留连接的有关信息。也就是说,HTTP请求只能由客户端发起,而服务器不能主动向客户端发送数据。


客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。


打个比方,我们去银行办理储蓄业务,第一次给你办了张银行卡,里面存放了身份证、密码、手机等个人信息。当你下次再来这个银行时,银行机器能识别你的卡,从而能够直接办理业务。



cookie机制

当用户第一次访问并登陆一个网站的时候,cookie的设置以及发送会经历以下4个步骤:


客户端发送一个请求到服务器 --》 服务器发送一个HttpResponse响应到客户端,其中包含Set-Cookie的头部 --》 客户端保存cookie,之后向服务器发送请求时,HttpRequest请求中会包含一个Cookie的头部 --》服务器返回响应数据


为了探究这个过程,写了代码进行测试,如下:

我在doGet方法中,new了一个Cookie对象并将其加入到了HttpResponse对象中

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {


    Cookie cookie = new Cookie("mcrwayfun",System.currentTimeMillis()+"");
    // 设置生命周期为MAX_VALUE
    cookie.setMaxAge(Integer.MAX_VALUE);
    resp.addCookie(cookie);
    }

    可见Response Headers中包含Set-Cookie头部,而Request Headers中包含了Cookie头部。name和value正是上述设置的。


    cookie属性项



    属性项属性项介绍
    NAME/VALUE键值对,可以设置要保存的 Key/Value,注意这里的 NAME 不能和其他属性项的名字一样。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码。
    Expires过期时间,在设置的某个时间点后该 Cookie 就会失效
    Domain生成该 Cookie 的域名,如 domain="www.baidu.com"
    Path该 Cookie 是在当前的哪个路径下生成的,如 path=/wp-admin/
    Secure如果设置了这个属性,那么只会在 SSH 连接时才会回传该 Cookie


    Expires

    该属性用来设置Cookie的有效期。Cookie中的maxAge用来表示该属性,单位为秒。Cookie中通过getMaxAge()和setMaxAge(int maxAge)来读写该属性。maxAge有3种值,分别为正数,负数和0。

    如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化,即写到对应的Cookie文件中(每个浏览器存储的位置不一致)。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效。下面代码中的Cookie信息将永远有效。


      Cookie cookie = new Cookie("mcrwayfun",System.currentTimeMillis()+"");
      // 设置生命周期为MAX_VALUE,永久有效
      cookie.setMaxAge(Integer.MAX_VALUE);
      resp.addCookie(cookie);

      当maxAge为0时,表示立即删除Cookie

        Cookie[] cookies = req.getCookies();
        Cookie cookie = null;


        // get Cookie
        for (Cookie ck : cookies) {


        if ("mcrwayfun".equals(ck.getName())) {
        cookie = ck;
        break;
        }
        }


        if (null != cookie) {
        // 删除一个cookie
        cookie.setMaxAge(0);
        resp.addCookie(cookie);
        }

        那么maxAge设置为负值和0到底有什么区别呢?

        maxAge设置为0表示立即删除该Cookie,maxAge设置为负数,Expires属性会改变,但Cookie仍然会存在一段时间直到关闭浏览器或者重新打开浏览器。

        修改或者删除Cookie

        HttpServletResponse提供的Cookie操作只有一个addCookie(Cookie cookie),所以想要修改Cookie只能使用一个同名的Cookie来覆盖原先的Cookie。如果要删除某个Cookie,则只需要新建一个同名的Cookie,并将maxAge设置为0,并覆盖原来的Cookie即可。


        新建的Cookie,除了value、maxAge之外的属性,比如name、path、domain都必须与原来的一致才能达到修改或者删除的效果。否则,浏览器将视为两个不同的Cookie不予覆盖。


        值得注意的是,从客户端读取Cookie时,包括maxAge在内的其他属性都是不可读的,也不会被提交。浏览器提交Cookie时只会提交name和value属性,maxAge属性只被浏览器用来判断Cookie是否过期,而不能用服务端来判断。


        我们无法在服务端通过cookie.getMaxAge()来判断该cookie是否过期,maxAge只是一个只读属性,值永远为-1。当cookie过期时,浏览器在与后台交互时会自动筛选过期cookie,过期了的cookie就不会被携带了。


        Cookie的域名

        Cookie是不可以跨域名的,隐私安全机制禁止网站非法获取其他网站的Cookie。


        Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作Google的Cookie而不会操作Baidu的Cookie,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google与Baidu的域名不一样,因此Google不能操作Baidu的Cookie


        正常情况下,同一个一级域名下的两个二级域名也不能交互使用Cookie,比如test1.mcrwayfun.com和test2.mcrwayfun.com,因为二者的域名不完全相同。如果想要mcrwayfun.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数为.mcrwayfun.com,这样使用test1.mcrwayfun.com和test2.mcrwayfun.com就能访问同一个cookie


        Unicode编码:保存中文

        中文与英文字符不同,中文属于Unicode字符,在内存中占4个字符,而英文属于ASCII字符,内存中只占2个字节。Cookie中使用Unicode字符时需要对Unicode字符进行编码,否则会乱码。

        提示:Cookie中保存中文只能编码。一般使用UTF-8编码即可。不推荐使用GBK等中文编码,因为浏览器不一定支持,而且JavaScript也不支持GBK编码。



        记录用户访问次数

        Java中把Cookie封装成了javax.servlet.http.Cookie类。每个Cookie都是该Cookie类的对象。服务器通过操作Cookie类对象对客户端Cookie进行操作。通过request.getCookie()获取客户端提交的所有Cookie(以Cookie[]数组形式返回),通过response.addCookie(Cookie cookie)向客户端设置Cookie。

        Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie对象保存一个属性对,一个request或者response同时使用多个Cookie。因为Cookie类位于包javax.servlet.http.*下面,所以JSP中不需要import该类。


        Cookie的路径


        path属性决定允许访问Cookie的路径。比如,设置为"/"表示允许所有路径都可以使用Cookie。path属性需要使用符号“/”结尾。name相同但domain不同的两个Cookie也是两个不同的Cookie。

        • domain表示的是cookie所在的域,默认为请求的地址,如网址为www.test.com/test/test.aspx,那么domain默认为www.test.com。而跨域访问,如域A为t1.test.com,域B为t2.test.com,那么在域A生产一个令域A和域B都能访问的cookie就要将该cookie的domain设置为.test.com;如果要在域A生产一个令域A不能访问而域B能访问的cookie就要将该cookie的domain设置为t2.test.com。

        • path表示cookie所在的目录,默认为/,就是根目录。在同一个服务器上有目录如下:/test/,/test/cd/,/test/dd/,现设一个cookie1的path为/test/,cookie2的path为/test/cd/,那么test下的所有页面都可以访问到cookie1,而/test/和/test/dd/的子页面不能访问cookie2。这是因为cookie能让其path路径下的页面访问。

        • 浏览器会将domain和path都相同的cookie保存在一个文件里,cookie间用*隔开。


        Cookie的安全属性

        HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。下面的代码设置secure属性为true:

          `Cookie cookie =` `new` `Cookie(``"time"``,` `"20080808"``);` `// 新建Cookie`


          `cookie.setSecure(``true``);` `// 设置安全属性`


          `response.addCookie(cookie);` `// 输出到客户端`

          提示:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。

            `<script>document.write(document.cookie);</script>`

            由于JavaScript能够任意地读写Cookie,有些好事者便想使用JavaScript程序去窥探用户在其他网站的Cookie。不过这是徒劳的,W3C组织早就意识到JavaScript对Cookie的读写所带来的安全隐患并加以防备了,W3C标准的浏览器会阻止JavaScript读写任何不属于自己网站的Cookie。换句话说,A网站的JavaScript程序读写B网站的Cookie不会有任何结果。


            案例:永久登录

            如果用户是在自己家的电脑上上网,登录时就可以记住他的登录信息,下次访问时不需要再次登录,直接访问即可。实现方法是把登录信息如账号、密码等保存在Cookie中,并控制Cookie的有效期,下次访问时再验证Cookie中的登录信息即可。

            保存登录信息有多种方案。最直接的是把用户名与密码都保持到Cookie中,下次访问时检查Cookie中的用户名与密码,与数据库比较。这是一种比较危险的选择,一般不把密码等重要信息保存到Cookie中。


            还有一种方案是把密码加密后保存到Cookie中,下次访问时解密并与数据库比较。这种方案略微安全一些。如果不希望保存密码,还可以把登录的时间戳保存到Cookie与数据库中,到时只验证用户名与登录时间戳就可以了。


            这几种方案验证账号时都要查询数据库。

            本例将采用另一种方案,只在登录时查询一次数据库,以后访问验证登录信息时不再查询数据库。实现方式是把账号按照一定的规则加密后,连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。本例把账号保存到名为account的Cookie中,把账号连同密钥用MD1算法加密后保存到名为ssid的Cookie中。验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。相关代码如下:loginCookie.jsp:


              <%@ page language="java" pageEncoding="UTF-8" isErrorPage="false" %>
              <%! // JSP方法
              private static final String KEY =":cookie@helloweenvsfei.com"; // 密钥

              public final static String calcMD1(String ss) { // MD1 加密算法
              String s = ss == null ? "" : ss; // 若为null返回空
              char hexDigits[] = { '0','1', '2', '3', '4', '1', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; // 字典
              try {
              byte[] strTemp = s.getBytes(); // 获取字节
              MessageDigestmdTemp = MessageDigest.getInstance("MD1"); // 获取MD1
              mdTemp.update(strTemp); // 更新数据
              byte[] md =mdTemp.digest(); // 加密
              int j =md.length; // 加密后的长度
              char str[] = new char[j * 2]; // 新字符串数组
              int k =0; // 计数器k
              for (int i = 0; i< j; i++) { // 循环输出
              byte byte0 = md[i];
              str[k++] = hexDigits[byte0 >>> 4 & 0xf];
              str[k++] = hexDigits[byte0 & 0xf];
              }
              return new String(str); // 加密后字符串
              } catch (Exception e){return null; }
              }
              %>
              <%
              request.setCharacterEncoding("UTF-8"); // 设置request编码
              response.setCharacterEncoding("UTF-8"); // 设置response编码

              String action =request.getParameter("action"); // 获取action参数

              if("login".equals(action)) { // 如果为login动作
              String account =request.getParameter("account"); // 获取account参数
              String password =request.getParameter("password"); // 获取password参数
              int timeout = new Integer(request.getParameter("timeout")); // 获取timeout参数

              String ssid =calcMD1(account + KEY); // 把账号、密钥使用MD1加密后保存

              Cookie accountCookie = new Cookie("account", account); // 新建Cookie
              accountCookie.setMaxAge(timeout); // 设置有效期

              Cookie ssidCookie =new Cookie("ssid", ssid); // 新建Cookie
              ssidCookie.setMaxAge(timeout); // 设置有效期

              response.addCookie(accountCookie); // 输出到客户端
              response.addCookie(ssidCookie); // 输出到客户端

              // 重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容
              response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());
              return;
              } else if("logout".equals(action)) { // 如果为logout动作
              CookieaccountCookie = new Cookie("account", ""); // 新建Cookie,内容为空
              accountCookie.setMaxAge(0); // 设置有效期为0,删除

              Cookie ssidCookie =new Cookie("ssid", ""); // 新建Cookie,内容为空
              ssidCookie.setMaxAge(0); // 设置有效期为0,删除
              response.addCookie(accountCookie); // 输出到客户端
              response.addCookie(ssidCookie); // 输出到客户端
              // 重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容
              response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());
              return;
              }
              boolean login = false; // 是否登录
              String account = null; // 账号
              String ssid = null; // SSID标识

              if(request.getCookies() !=null) { // 如果Cookie不为空
              for(Cookie cookie : request.getCookies()) { // 遍历Cookie
              if(cookie.getName().equals("account")) // 如果Cookie名为 account
              account = cookie.getValue(); // 保存account内容
              if(cookie.getName().equals("ssid")) // 如果为SSID
              ssid = cookie.getValue(); // 保存SSID内容
              }
              }
              if(account != null && ssid !=null) { // 如果account、SSID都不为空
              login = ssid.equals(calcMD1(account + KEY)); // 如果加密规则正确, 则视为已经登录
              }
              %>
              <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
              <legend><%= login ? "欢迎您回来" : "请先登录"%></legend>
              <% if(login){%>
              欢迎您, ${cookie.account.value }. &nbsp;&nbsp;
              <a href="${pageContext.request.requestURI }?action=logout">
              注销</a>
              <% } else { %>
              <form action="${ pageContext.request.requestURI }?action=login" method="post">
              <table>
              <tr><td>账号:</td>
              <td><input type="text"name="account" style="width: 200px; "></td>
              </tr>
              <tr>
              <td>密码:</td>
              <td><inputtype="password" name="password"></td>
              </tr>
              <tr>
              <td>有效期:</td>
              <td>
              <inputtype="radio" name="timeout" value="-1"checked> 关闭浏览器即失效 <br/>
              <input type="radio" name="timeout" value="<%= 30 *24 * 60 * 60 %>"> 30天内有效<br/>
              <input type="radio" name="timeout" value= "<%= Integer.MAX_VALUE %>"> 永久有效<br/>
              </td>
              </tr>
              <tr>
              <td><input type="submit"value=" 登 录 " class= "button"></td>
              </tr>
              </table>
              </form>
              <% } %>

              登录时可以选择登录信息的有效期:关闭浏览器即失效、30天内有效与永久有效。通过设置Cookie的age属性来实现。


              提示:该加密机制中最重要的部分为算法与密钥。由于MD1算法的不可逆性,即使用户知道了账号与加密后的字符串,也不可能解密得到密钥。因此,只要保管好密钥与算法,该机制就是安全的。


              文章转载自Whoooops,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论