目录
1.会话跟踪
什么是会话呢?从用户进入一个网站浏览开始,到关闭浏览器为止称为一次会话。
会话包含以下意思:
- 会话是一段时间,这段时间默认从打开浏览器开始到关闭浏览器为止。
- 一次会话可以包含多次请求与响应。
而Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了。要跟踪该会话,必须引入一种机制。
Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。
2.Cookie
Cookie意为“甜饼”,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。
由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
查看某个网站颁发的Cookie很简单。在浏览器地址栏输入**javascript:alert (document. cookie)**就可以了(需要有网才能查看)。JavaScript脚本会弹出一个对话框显示本网站颁发的所有Cookie的内容。
如果浏览器不支持Cookie(如大部分手机中的浏览器)或者把Cookie禁用了,Cookie功能就会失效。
不同的浏览器采用不同的方式保存Cookie。
3.Cookie常用的方法
Java中把Cookie封装成了javax.servlet.http.Cookie类。每个Cookie都是该Cookie类的对象。服务器通过操作Cookie类对象对客户端Cookie进行操作。通过request.getCookies()获取客户端提交的所有Cookie(以Cookie[]数组形式返回),通过response.addCookie(Cookie cookie)向客户端设置Cookie。
Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie对象保存一个属性对,一个request或者response同时使用多个Cookie。因为Cookie类位于包javax.servlet.http.*下面,所以JSP中不需要import该类。
Cookie在Java中是用javax.servlet.http.Cookie类表示的。Cookie不是JSP的内置对象,Cookie常用的方法如下:
4.Cookie在java中如何使用
任务1:显示上次访问时间
创建动态web项目,命名为cookie,在cookie中创建write.jsp用于写入Cookie,创建read.jsp用于读取cookie,实现记录用户上次访问时间的业务。
write.jsp代码
<%
//创建Cookie对象
Cookie cookieUserName =new Cookie("username","songjinxiang");
//设置存活时间
cookieUserName.setMaxAge(60*60*10);
//添加cookie
response.addCookie(cookieUserName);
//创建Cookie对象,中文需要编码
Cookie cookieName =new Cookie("name",java.net.URLEncoder.encode("林冲", "utf-8"));
//设置存活时间
cookieName.setMaxAge(60*60);
//添加cookie
response.addCookie(cookieName);
//创建cookie对象
Cookie cookieTime =new Cookie("time",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
//添加cookie
response.addCookie(cookieTime);
%>
Cookie为什么要设置最大存活时间呢????
如果没有为Cookie指定存活时间,则Cookie保存在客户端的内存中,当浏览器关闭Cookie即消亡。如果指定了存活时间,则Cookie保存在客户端的文件中,即使浏览器关闭,在存活时间内Cookie内容依然存在。
read.jsp代码
<%
Cookie[] cookies = request.getCookies();
if(cookies != null){
for(int i = 0;i < cookies.length;i++){
Cookie cookie = cookies[i];
if("name".equals(cookie.getName())){
out.print(java.net.URLDecoder.decode(cookie.getValue(),"utf-8"));
out.print("你好,你上次访问时间是");
}
if("time".equals(cookie.getName())){
out.print(cookie.getValue());
out.print("<br />");
}
}
}
%>
代码解析
- request对象的getCookies()方法用于从请求报头中获取当前站点向客户端写入过的所有有效Cookie,返回Cookie数组。
- 通过迭代数组可以找到需要的Cookie对象,getName()获取Cookie的键,getValue()获取Cookie的值。
- URLDecoder.decode()用于对Cookie中存储的汉字解码。
5.Unicode编码:保存中文
Cookie中使用Unicode字符时需要对Unicode字符进行编码,否则会乱码。
//创建Cookie对象,中文需要编码
Cookie cookieName =new Cookie("myname",java.net.URLEncoder.encode("林冲", "utf-8"));
response.addCookie(cookieName);
//============================================================
//取值需要解码
Cookie[] cookies = request.getCookies();
for (Cookie c:cookies) {
if(c.getName().equals("myname"))
out.println(c.getName()+":"+java.net.URLDecoder.decode(c.getValue(),"UTF-8"));
}
提示:Cookie中保存中文只能编码。一般使用UTF-8编码即可。不推荐使用GBK等中文编码,因为浏览器不一定支持,而且JavaScript也不支持GBK编码。
6.Cookie的有效期
Cookie的maxAge决定着Cookie的有效期,单位为秒(Second)。Cookie中通过getMaxAge()方法与setMaxAge(int maxAge)方法来读写maxAge属性。
如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化,即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效。
下面代码中的Cookie信息将永远有效。
// 新建Cookie
Cookie cookie = new Cookie("username","songjinxiang");
// 设置生命周期为MAX_VALUE
cookie.setMaxAge(Integer.MAX_VALUE);
// 输出到客户端
response.addCookie(cookie);
下面代码的Cookie信息将保存10天
// 新建Cookie
Cookie cookie = new Cookie("username","songjinxiang");
// 设置生命周期为10天
cookie.setMaxAge(60*60*24*10);
// 输出到客户端
response.addCookie(cookie);
如果maxAge为负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该Cookie即失效。maxAge为负数的Cookie,为临时性Cookie,不会被持久化,不会被写到Cookie文件中。Cookie信息保存在浏览器内存中,因此关闭浏览器该Cookie就消失了。Cookie默认的maxAge值为–1。
如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。
Cookie机制没有提供删除Cookie的方法,因此通过设置该Cookie即时失效实现删除Cookie的效果。失效的Cookie会被浏览器从Cookie文件或者内存中删除。
// 新建Cookie
Cookie cookie = new Cookie("username","songjinxiang");
//如果设置为负值的话,则为浏览器进程Cookie(内存中保存),关闭浏览器就失效;
cookie.setMaxAge(-1);
//如果设置为0,则立即删除该Cookie。
cookie.setMaxAge(0);
// 输出到客户端
response.addCookie(cookie);
7.Cookie的修改、删除
Cookie并不提供修改、删除操作。如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。
如果要删除某个Cookie,只需要新建一个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie。注意是0而不是负数。负数代表其他的意义。读者可以通过上例的程序进行验证,设置不同的属性。
// 新建同名Cookie的值,覆盖原有的cookie,达到修改的效果
Cookie cookie = new Cookie("username","litong");
//也可设置不同的失效时间20天
cookie.setMaxAge(60*60*24*20);
// 输出到客户端
response.addCookie(cookie);
// 新建Cookie
Cookie cookie = new Cookie("username","songjinxiang");
//如果设置为0,则立即删除该Cookie。
cookie.setMaxAge(0);
// 输出到客户端
response.addCookie(cookie);
注意:修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,例如name、path、domain等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。
8.Cookie的域名
Cookie是不可跨域名的。域名www.google.com颁发的Cookie不会被提交到域名www.baidu.com去。这是由Cookie的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的Cookie。
正常情况下,同一个一级域名下的两个二级域名如www.helloweenvsfei.com和images.helloweenvsfei.com也不能交互使用Cookie,因为二者的域名并不严格相同。如果想所有helloweenvsfei.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数。
// 新建Cookie
Cookie cookie = new Cookie("time","20220108");
// 设置域名
cookie.setDomain("www.westos.org");
// 设置路径
cookie.setPath("/");
// 设置有效期
cookie.setMaxAge(Integer.MAX_VALUE);
// 输出到客户端
response.addCookie(cookie);
9.Cookie的不可跨域名性
很多网站都会使用Cookie。例如,Google会向客户端颁发Cookie,Baidu也会向客户端颁发Cookie。那浏览器访问Google会不会也携带上Baidu颁发的Cookie呢?或者Google能不能修改Baidu颁发的Cookie呢?
答案是否定的。Cookie具有不可跨域名性。根据Cookie规范,浏览器访问Google只会携带Google的Cookie,而不会携带Baidu的Cookie。Google也只能操作Google的Cookie,而不能操作Baidu的Cookie。
Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作Google的Cookie而不会操作Baidu的Cookie,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google与Baidu的域名不一样,因此Google不能操作Baidu的Cookie。
需要注意的是,虽然网站images.google.com与网站www.google.com同属于Google,但是域名不一样,二者同样不能互相操作彼此的Cookie。
注意:用户登录网站www.google.com之后会发现访问images.google.com时登录信息仍然有效,而普通的Cookie是做不到的。这是因为Google做了特殊处理。
10.Cookie的路径
domain属性决定运行访问Cookie的域名,而path属性决定允许访问Cookie的路径(ContextPath)。例如,如果只允许/myWeb/下的程序使用Cookie,可以这么写。
// 新建Cookie
Cookie cookie = new Cookie("time","20080808");
// 设置路径
cookie.setPath("项目部署名称/myWeb/");
// 输出到客户端
response.addCookie(cookie);
注意:页面只能获取它属于的Path的Cookie。例如/session/test/a.jsp不能获取到路径为/session/abc/的Cookie。使用时一定要注意。
11.Cookie的安全属性
HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。
下面的代码设置secure属性为true:
// 新建Cookie
Cookie cookie = new Cookie("time", "20080808");
// 设置安全属性
cookie.setSecure(true);
// 输出到客户端
response.addCookie(cookie);
提示:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。
12.JavaScript操作Cookie
Cookie是保存在浏览器端的,因此浏览器具有操作Cookie的先决条件。浏览器可以使用脚本程序如JavaScript或者VBScript等操作Cookie。这里以JavaScript为例介绍常用的Cookie操作。例如下面的代码会输出本页面所有的Cookie。
//JavaScript 中,创建 cookie
document.cookie="username=John Doe";
//cookie 添加一个过期时间(以 UTC 或 GMT 时间)。默认情况下,cookie 在浏览器关闭时删除:
document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT";
//可以使用 path 参数告诉浏览器 cookie 的路径。默认情况下,cookie 属于当前页面。
document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/";
//javascript读取 cookie
var x = document.cookie;
由于JavaScript能够任意地读写Cookie,有些好事者便想使用JavaScript程序去窥探用户在其他网站的Cookie。不过这是徒劳的,W3C组织早就意识到JavaScript对Cookie的读写所带来的安全隐患并加以防备了,W3C标准的浏览器会阻止JavaScript读写任何不属于自己网站的Cookie。换句话说,A网站的JavaScript程序读写B网站的Cookie不会有任何结果。
13.永久登录(重要、重要)
如果用户是在自己家的电脑上上网,登录时就可以记住他的登录信息,下次访问时不需要再次登录,直接访问即可。实现方法是把登录信息如账号、密码等保存在Cookie中,并控制Cookie的有效期,下次访问时再验证Cookie中的登录信息即可。
保存登录信息有多种方案。最直接的是把用户名与密码都保持到Cookie中,下次访问时检查Cookie中的用户名与密码,与数据库比较。这是一种比较危险的选择,一般不把密码等重要信息保存到Cookie中。
还有一种方案是把密码加密后保存到Cookie中,下次访问时解密并与数据库比较。这种方案略微安全一些。如果不希望保存密码,还可以把登录的时间戳保存到Cookie与数据库中,到时只验证用户名与登录时间戳就可以了。
这几种方案验证账号时都要查询数据库。
本例将采用另一种方案,只在登录时查询一次数据库,以后访问验证登录信息时不再查询数据库。实现方式是把账号按照一定的规则加密后,连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。把账号保存到名为account的Cookie中,把账号连同密钥用MD5算法加密后保存到名为ssid的Cookie中。验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。
写Cookie
// 获取account参数
String account =request.getParameter("account");
// 获取password参数
String password =request.getParameter("password");
// 获取timeout参数
int timeout = newInteger(request.getParameter("timeout"));
// 把账号、密钥使用MD5加密后保存
String ssid =MDigest5.md5(account + MDigest5.KEY);
// 新建Cookie
CookieaccountCookie = new Cookie("account", account);
// 设置有效期
accountCookie.setMaxAge(Integer.MAX_VALUE);
// 新建Cookie
Cookie ssidCookie =new Cookie("ssid", ssid);
// 设置有效期
ssidCookie.setMaxAge(Integer.MAX_VALUE);
// 输出到客户端
response.addCookie(accountCookie);
// 输出到客户端
response.addCookie(ssidCookie);
// 重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容
response.sendRedirect(request.getRequestURI() + "?" + System.
currentTimeMillis());
return;
下次访问时只需要判断账号的加密规则是否正确
// 是否登录
boolean login = false;
// 账号
String account = null;
// SSID标识
String ssid = null;
// 如果Cookie不为空
if(request.getCookies() !=null){
// 遍历Cookie
for(Cookie cookie :request.getCookies()){
// 如果Cookie名为 account
if(cookie.getName().equals("account"))
// 保存account内容
account = cookie.getValue();
if(cookie.getName().equals("ssid"))
// 保存SSID内容
ssid = cookie.getValue();
}
}
// 如果account、SSID都不为空
if(account != null && ssid !=null){
// 如果加密规则正确, 则视为已经登录
login =ssid.equals(MDigest5.md5(account + MDigest5.KEY));
}
}
MD5加密
package com.xawl.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MDigest5 {
//加密盐
private static final String KEY="west8309";
/**
* 返回MD5加密串(默认32位)
* @param plainText 需要加密的字符串
* @return 返回MD5加密串
*/
private static String md5(String plainText) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(plainText.getBytes());
byte b[] = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
}
return buf.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
}
<%@ include file="ckuserlogin.jsp"%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
恭喜登录成功!
</body>
</html>
<%@ page import="com.xawl.util.MDigest5" %>
<%
//登录成功后访问的页面(只有登录成功后才以访问页面),需要做登录状态的认证。
String accout = "";
String ssid = "";
boolean isLogin = false;
Cookie[] cookies = request.getCookies();
if(cookies!=null)
for (Cookie cookie:cookies) {
//获得账号
if("act".equals(cookie.getName())){
accout = cookie.getValue();
}
if("ssid".equals(cookie.getName())){
ssid = cookie.getValue();
}
}
if(!"".equals(accout) && !"".equals(ssid)){
isLogin = ssid.equals(MDigest5.getMD5(accout+MDigest5.KEY));
}
if(isLogin==false){
response.sendRedirect("login.jsp");
return;
}
%>
package com.xawl.controller;
import com.xawl.iservice.IUserService;
import com.xawl.pojo.Userinfo;
import com.xawl.service.UserService;
import com.xawl.util.MDigest5;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;
@WebServlet(name = "LoginController",urlPatterns = "/dologin")
public class LoginController extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 实现思路
* 1.设置编码,设置响应类型编码,解决乱码问题
* 2.获得数据(用户名、密码、验证码)
* 3.验证数据的合法性,不合格返回登录页面
* 4.到数据库查找用户名
* 5.处理返回结果,登录成功的话,转向成页面,不成功返回登录页面提示用户
*/
//1.设置编码,设置响应类型编码,解决乱码问题
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//2.获得数据(用户名、密码、验证码)
String username = request.getParameter("username");
String userpwd = request.getParameter("userpwd");
String usercode = request.getParameter("usercode");
//3.验证数据的合法性,不合格返回登录页面
//定义出错的标志位
boolean isErr = false;
if(username==null || "".equals(username.trim())){
request.setAttribute("userErr","*用户不能为空");
isErr = true;
}
if(userpwd==null || "".equals(userpwd.trim())){
request.setAttribute("pwdErr","*密码不能为空");
isErr = true;
}
if(usercode==null || "".equals(usercode.trim())){
request.setAttribute("codeErr","*验证码不能为空");
isErr = true;
}
else{
Object usercode1 = request.getSession().getAttribute("usercode");
//判断验证码是否失效
if(usercode1==null){
request.setAttribute("codeErr","*验证码失效,请重新生成验证码");
isErr = true;
}else {
//判断验证码是否正确
String mycode = usercode1.toString().replace(" ","");
if(!usercode.equalsIgnoreCase(mycode)){
request.setAttribute("codeErr","*验证码不正确");
isErr = true;
}
}
}
//如果不合格,返回登录页面
if(isErr){
request.getRequestDispatcher("login.jsp").forward(request,response);
return;
}
//4.到数据库查找用户名
IUserService userService = new UserService();
try {
Userinfo user = userService.findByUsername(username);
//判断是否存在此用户
if(user==null){ //此用户不存在
request.setAttribute("userErr","*用户名不正确");
isErr = true;
}
else{//通过用户名查找到了此用户
//判断用户是否被锁定
if(user.getUserstate()!=1){//锁定
request.setAttribute("userErr","*此用户名已被锁定");
isErr = true;
}
else {
//密码加密MD5(非对称,不可逆)
String usermd5 = MDigest5.getMD5(userpwd + MDigest5.KEY);
if (user.getUserpwd().equals(usermd5)) {//密码正确
// 用cookie保存登录状态
saveUserLoginSate(user,response);
//登录成功,地址重定向
response.sendRedirect("main.jsp");
return;
} else {
request.setAttribute("pwdErr", "*密码不正确");
isErr = true;
}
}
}
} catch (SQLException ex) {
ex.printStackTrace();
request.setAttribute("loginErr","*连接服务器出错");
isErr = true;
}
System.out.println("bbbbbbbbbbbbbb");
//登录不成功
if(isErr){
request.getRequestDispatcher("login.jsp").forward(request,response);
return;
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
/**
* 保存用户状态
* @param user
*/
public void saveUserLoginSate(Userinfo user,HttpServletResponse response){
//账号
String username = user.getUsername();
//登录时间
String loginTime =System.currentTimeMillis()+"";
//密钥
String ssid = MDigest5.getMD5(username+MDigest5.KEY);
//创建账号Cookie
Cookie account_ck = new Cookie("act",username);
//设置过期(永久)
account_ck.setMaxAge(Integer.MAX_VALUE);
//写入
response.addCookie(account_ck);
//创建登录时间Cookie
Cookie ltime_ck = new Cookie("ltime",loginTime);
//设置过期(永久)
ltime_ck.setMaxAge(Integer.MAX_VALUE);
//写入
response.addCookie(ltime_ck);
//创建密钥Cookie
Cookie ssid_ck = new Cookie("ssid",ssid);
//设置过期(永久)
ssid_ck.setMaxAge(Integer.MAX_VALUE);
//写入
response.addCookie(ssid_ck);
}
}