前两天,有个刚入职的兄弟问了一个问题:他们公司有两个网站,领导想让他实现两个网站之间"记住密码"功能的互认,即对于同一个客户端(浏览器)只要访问A网站时选择了记住密码,那么下次他不管访问A网站还是B网站都会记住密码,想让给他提供一个思路或者解决方案。
我想记住密码正是Cookie最擅长的,今天正好有时间,就来谈谈Cookie,帮他整理一下思路,也希望能帮助到更多的人。
首先来说一下Cookie简单使用案例:
我们都很熟悉Cookie,就是服务器向客户端保存的纯文本信息。网站通常用Cookie来向客户端保存一些字符串信息,以便需要的时候再获取,经典应用就是实现网站的记住密码或者免登录功能:
客户端向网站的服务器发送登录请求,并携带账号和密码数据;
网站的服务器校验账号密码正确以后,返回响应并把账号密码以cookie的形式写到客户端;
之后该客户端再次向网站发起请求会自动带上网站存储在本地的cookie;
网站的服务器从Cookie中获取账号密码数据后,返回登陆成功界面或者自动填写账号密码。
看代码:===>
登录请求提交到服务器时:如果账号密码都正确并且也选择了记住密码,就会把账号密码以Cookie的形式写到客户端:LoginServlet.java
public class LoginServlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String loginAct=req.getParameter(\"loginAct\");String loginPwd=req.getParameter(\"loginPwd\");String isRemPwd=req.getParameter(\"isRemPwd\");//登录失败,跳转到失败页面if(!(\"cohouse\".equals(loginAct)&&\"123\".equals(loginPwd))){resp.sendRedirect(\"fail.jsp\");}else{//登录成功,跳转到登录成功页面//假设参数isRemPwd为true标识客户单选择了记住密码//如果选择了记住密码,就往客户端写cookieif(\"true\".equals(isRemPwd)){Cookie actCookie=new Cookie(\"loginAct\",loginAct);actCookie.setMaxAge(10*24*60*60);//记10天resp.addCookie(actCookie);//写到客户端Cookie pwdCookie=new Cookie(\"loginPwd\",loginPwd);pwdCookie.setMaxAge(10*24*60*60);//记10天resp.addCookie(pwdCookie);//写到客户端}resp.sendRedirect(\"success.jsp\");}}}
每次访问网站时:都会获取客户端的Cookie,查看有没有账号密码相关的Cookie,如果有的话,可以进行自动登录,或者把账号密码自动填到输入框里面:login.jsp
<form action=\"LoginServlet\" method=\"post\">用户名:<input type=\"text\" name=\"loginAct\" value=\"${cookie.loginAct.value}\"><br>密码: <input type=\"password\" name=\"loginPwd\" value=\"${cookie.loginPwd.value}\"><br><input type=\"checkbox\" name=\"isRemPwd\">记住密码<br><input type=\"submit\" value=\"登录\"></form>
以上是Cookie最简单的应用,显然还不足以解决那位兄弟跨网站记住密码的问题,下边来看看Cookie的特性:
Cookie的特性:
首先,Cookie不可以跨域名访问
很多网站都会使用Cookie。例如:当你网上购物时,京东会向你的浏览器写Cookie,淘宝当然也会,下次访问时,你都会把这些Cookie再给他们带回去。但这里的“带回去”不可能是简简单单地把你客户端中所有的Cookie都带过去,即如果用户访问淘宝时也会连同京东写的cookie带过去,或者说访问京东时也会连同淘宝写的cookie带过去,如果真是这样的话,那这个世界可就真是乱了套了!不天下大乱估计也差不多!
当然,协议的设计者们不可能这么干。根据浏览器规范的"同源策略",未经允许,Cookie不可以跨域访问。客户端浏览器访问淘宝时,只会携带淘宝的Cookie,而不会携带京东或者其它网站的Cookie。所以淘宝只能操作淘宝的Cookie,京东也只能操作京东的Cookie,这样就保证了网站和客户的隐私安全。
实际上,浏览器会通过域名来判断两个Cookie是不是属于同一个网站,京东和淘宝的域名不一样,因此京东不能操作淘宝的Cookie,反之亦然。
需要强调的是,域名必须严格相同,Cookie才能互相访问。例如,images.jd.com和pages.jd.com虽然都是属于京东、一级域名相同、但是二级域名并不严格相同,两网站的Cookie也不能互相操作彼此。
Cookie跨域访问的解决:正是由于Cookie的这种不可跨域访问性,使得跨网站记住密码功能貌似不能轻易实现。但话又说回来了,规则都是人定的,在这个世界上任何规则都不是绝对的,如果有的Cookie确实需要其它网站访问,也不是不可以,但必须经过Cookie创建者的允许,代码上只需要设置Cookie的domain属性:
Cookie actCookie=new Cookie(\"loginAct\",loginAct);actCookie.setDomain(\"cohouse.com\");//设置域名actCookie.setMaxAge(10*24*60*60);//记10天resp.addCookie(actCookie);//写到客户端Cookie pwdCookie=new Cookie(\"loginPwd\",loginAct);pwdCookie.setDomain(\"cohouse.com\");//设置域名pwdCookie.setMaxAge(10*24*60*60);//记10天resp.addCookie(pwdCookie);//写到客户端
这样,所有并且也只有域名cohouse.com及其名下的二级域名可以使用该Cookie,比如:这里cohouse.com、www.cohouse.com和test.cohouse.com等域名都可以访问actCookie,但是www.jd.com就不能访问。读者可以修改本机C:\\WINDOWS\\system32\\drivers\\etc下的hosts文件来配置多个临时域名,然后使用程序来设置跨域名Cookie验证domain属性。
需要注意的是,如果你使用的是tomcat8.0以前的服务器,domain属性必须以点(".")开头,例如:actCookie.setDomain(".cohouse.com")。另外,设置了domain的两个Cookie,即使name相同但domain不同,也是两个完全不同的Cookie。
另外,domain属性默认为请求地址的域名,如网址为www.cohouse.com/test写Cookie,那么domain默认为www.cohouse.com。
到此为止,那位兄弟要实现的两个网站跨域记住密码的功能就迎刃而解了:
如果他两个网站部署在同一台服务器上,由于域名相同,Cookie采用默认域名,无需手动设置domain属性,只要Cookie路径允许(Cookie的路径后边会详细说),就可以轻易而一举实现了。如果他两个网站不是部署在同一台服务器上(这种概率很大),两台服务器的域名完全不同,则可以生成两个Cookie,domain属性分别为两个域名,写到客户端。
其次,Cookie的有效期
每一个Cookie都有着明确的有效期。Cookie的maxAge属性决定着Cookie的有效期,单位为秒。Cookie提供两个方法getMaxAge()和setMaxAge()分别来读取和设置maxAge的值。
Cookie cookie=new Cookie(\"loginAct\",\"cohouse\");//创建Cookiecookie.setMaxAge(10*24*60*60);//设置最大生命周期为10天resp.addCookie(cookie);//写到客户端
如果maxAge为正数,表示该Cookie写到客户端之后maxAge秒自动失效。浏览器会maxAge为正数的Cookie持久化,即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效。
如果maxAge为0,表示该Cookie写到客户端之后马上失效,通常用来删除Cookie。
如果maxAge为负数,表示该Cookie为临时性Cookie,仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该Cookie即失效。Cookie的maxAge默认值是-1。
需要注意的是,我们在客户端操作Cookie时,maxAge属性是不可读的,maxAge属性值也不会被再次携带到服务器。浏览器访问服务器时只会携带Cookie的name属性和value属性。换句话说,maxAge仅仅用来给浏览器判断Cookie是否过期使用的,别无它用!所以,我们在打印所有Cookie的maxAge属性时,一律都是-1:
<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head><title>打印所有Cookie</title></head><body><%Cookie[] cs=request.getCookies();for(Cookie c:cs){out.write(\"<h5>\"+c.getName()+\":\"+c.getValue()+\":\"+c.getMaxAge()+\"<h5>\");}%></body></html>
第三,Cookie的路径
domain属性决定能够访问Cookie的域名,而path属性决定允许访问Cookie的资源路径(ContextPath)。例如:有三个目录如下:/test/、/test/aa/、/test/bb/,现在生成两个Cookie,path分别为/test/和/test/aa/:
Cookie cookie1=new Cookie(\"cookie1\",\"11111111\");cookie1.setMaxAge(10*24*60*60);//记10天cookie1.setPath(req.getContextPath()+\"/test/\");resp.addCookie(cookie1);Cookie cookie2=new Cookie(\"cookie2\",\"22222222\");cookie2.setMaxAge(10*24*60*60);//记10天cookie2.setPath(req.getContextPath()+\"/test/aa\");resp.addCookie(cookie2);
那么,/test/目录及其子目录下所有页面都可以访问到cookie1,而/test/目录和/test/bb/目录不能访问cookie2。这是因为Cookie只能被它的路径及其子路径下的页面访问,不能被其它的路径访问。
Cookie的默认路径,即如果不设置path,则Cookie默认的path值是“/项目名/当前路径的上一级”。例如:如果当前路径是/myweb/test/SetCookieServlet(myweb是项目名),则生成的Cookie默认路径就是/myweb/test/。
Cookie就是这样通过domain属性和path属性结合控制其访问权限,前者从域的层面进行宏观控制,后者从域内部路径的层面进行更精确控制,两者结合起来,灵活运用,方能得心应手。
第四,JavaScript操作Cookie
Cookie是保存在浏览器端的,因此浏览器理所当然地能够操作Cookie。浏览器可以使用JavaScript、VBScript等脚本程序操作Cookie。例如:以使用JavaScript为例打印出本页面所有的Cookie:
<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head><title>JavaScript打印所有Cookie</title><script type=\"text/javascript\">document.write(document.cookie);</script></head><body></body></html>
显然,JavaScript是通过document对象的cookie属性来操作Cookie的。说明一下,document.cookie是一个特定格式的字符串,它使用分号(“;”)连接多个cookie信息,我们可以通过该字符串在js中完成对cookie的增删改查。
最后强调一点,JavaScript同样也只能操作本页面所能访问到的Cookie,即受Cookie的domain和path属性值的限制,而不能够任意地读写各个网站的Cookie,否则也会乱套。这种基于规则的自由才是真正的自由,不是吗?
以上,就是我基于那位兄弟的问题而产生的一些思考和总结,把知识分享出去,帮助别人,成就自己,跟大家共勉。JAVA技术交流QQ群29259682,欢迎来我的咖啡屋玩JAVA品咖啡。