session存储在服务器端,session在用户第一次访问时创建,访问jsp、servlet等程序时才会创建Session,只访问html、image等静态资源并不会创建,可调用request.getSession(true)强制生成Session。
服务器会把长时间没有活动的Session从内存中清除,tomcat中session的默认失效时间为30分钟,可调用调用session的invalidate方法强制清楚。
第一:session调用了session.invalidate()方法.
第二:前后两次请求超出了session指定的生命周期时间.
Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session"活跃(active)"了一次。
(1)Session中的数据保存在服务器端;
(2)Session中可以保存任意类型的数据;
(2)Session默认的生命周期是20分钟,可以手动设置更长或更短的时间。
至于设置可以用代码设置或者在Web.config中配置,不过不建议将Session的超时时间设置过长,因为默认情况下Session在内存中保存,设置时间过长保存的数据过大的话会导致内存不足。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Session 是另一种记录浏览器状态的机制。不同的是Cookie保存在浏览器中,Session保存在服务器中。用户使用浏览器访问服务器的时候,服务器把用户的信息以某种的形式记录在服务器,这就是Session
如果说Cookie是检查用户身上的”通行证“来确认用户的身份,那么Session就是通过检查服务器上的”客户明细表“来确认用户的身份的。Session相当于在服务器中建立了一份“客户明细表”。
Session比Cookie使用方便,Session可以解决Cookie解决不了的事情【Session可以存储对象,Cookie只能存储字符串。】。
从上面的API看出,Session有着request和ServletContext类似的方法。其实Session也是一个域对象。Session作为一种记录浏览器状态的机制,只要Session对象没有被销毁,Servlet之间就可以通过Session对象实现通讯
//得到Session对象
HttpSession httpSession = request.getSession();
//设置Session属性
httpSession.setAttribute("name", "看完博客就要点赞!!");
//获取到从Servlet4的Session存进去的值
HttpSession httpSession = request.getSession();
String value = (String) httpSession.getAttribute("name");
System.out.println(value);
image
Session在用户第一次访问服务器Servlet,jsp等动态资源就会被自动创建,Session对象保存在内存里,这也就为什么上面的例子可以直接使用request对象获取得到Session对象。
如果访问HTML,IMAGE等静态资源Session不会被创建。
Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,无论是否对Session进行读写,服务器都会认为Session活跃了一次。
由于会有越来越多的用户访问服务器,因此Session也会越来越多。为了防止内存溢出,服务器会把长时间没有活跃的Session从内存中删除,这个时间也就是Session的超时时间。
Session的超时时间默认是30分钟,有三种方式可以对Session的超时时间进行修改
第一种方式:在tomcat/conf/web.xml文件中设置,时间值为20分钟,所有的WEB应用都有效
20
image
20
//设置Session最长超时时间为60秒,这里的单位是秒
httpSession.setMaxInactiveInterval(60);
System.out.println(httpSession.getMaxInactiveInterval());
image
image
response.setContentType("text/html;charset=UTF-8");
PrintWriter printWriter = response.getWriter();
printWriter.write("网页上所有的书籍:" + "
");
//拿到数据库所有的书
LinkedHashMap linkedHashMap = DB.getAll();
Set> entry = linkedHashMap.entrySet();
//显示所有的书到网页上
for (Map.Entry stringBookEntry : entry) {
Book book = stringBookEntry.getValue();
String url = "/ouzicheng/Servlet6?id=" + book.getId();
printWriter.write(book.getName());
printWriter.write("购买");
printWriter.write("
");
}
//得到用户想买书籍的id
String id = request.getParameter("id");
//根据书籍的id找到用户想买的书
Book book = (Book) DB.getAll().get(id);
//获取到Session对象
HttpSession httpSession = request.getSession();
//由于用户可能想买多本书的,所以我们用一个容器装着书籍
List list = (List) httpSession.getAttribute("list");
if (list == null) {
list = new ArrayList();
//设置Session属性
httpSession.setAttribute("list",list);
}
//把书籍加入到list集合中
list.add(book);
//得到用户想买书籍的id
String id = request.getParameter("id");
//根据书籍的id找到用户想买的书
Book book = (Book) DB.getAll().get(id);
//获取到Session对象
HttpSession httpSession = request.getSession();
//创建List集合
List list = new ArrayList();
list.add(book);
httpSession.setAttribute("list", list);
//得到用户想买书籍的id
String id = request.getParameter("id");
//根据书籍的id找到用户想买的书
Book book = (Book) DB.getAll().get(id);
//获取到Session对象
HttpSession httpSession = request.getSession();
//由于用户可能想买多本书的,所以我们用一个容器装着书籍
List list = (List) httpSession.getAttribute("list");
if (list == null) {
list = new ArrayList();
//设置Session属性
httpSession.setAttribute("list",list);
}
//把书籍加入到list集合中
list.add(book);
String url = "/ouzicheng/Servlet7";
response.sendRedirect(url);
//要得到用户购买过哪些书籍,得到Session的属性遍历即可
HttpSession httpSession = request.getSession();
List list = (List) httpSession.getAttribute("list");
if (list == null || list.size() == 0) {
printWriter.write("对不起,你还没有买过任何商品");
} else {
printWriter.write("您购买过以下商品:");
printWriter.write("
");
for (Book book : list) {
printWriter.write(book.getName());
printWriter.write("
");
}
}
image
//得到Session对象
HttpSession httpSession = request.getSession();
//设置Session属性
httpSession.setAttribute("name", "看完博客就要点赞!!");
String value = (String) request.getSession().getAttribute("name");
printWriter.write(value);
image
image
image
现在问题来了:服务器是如何实现一个session为一个用户浏览器服务的?换个说法:为什么服务器能够为不同的用户浏览器提供不同session?
HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一个用户。于是乎:服务器向用户浏览器发送了一个名为JESSIONID的Cookie,它的值是Session的id值。其实Session依据Cookie来识别是否是同一个用户。
简单来说:Session 之所以可以识别不同的用户,依靠的就是Cookie
该Cookie是服务器自动颁发给浏览器的,不用我们手工创建的。该Cookie的maxAge值默认是-1,也就是说仅当前浏览器使用,不将该Cookie存在硬盘中
我们来捋一捋思路流程:当我们访问Servlet4的时候,服务器就会创建一个Session对象,执行我们的程序代码,并自动颁发个Cookie给用户浏览器
image
image
上面说了Session是依靠Cookie来识别用户浏览器的。如果我的用户浏览器禁用了Cookie了呢?绝大多数的手机浏览器都不支持Cookie,那我的Session怎么办?
image
image
image
一看,Session好像不能用了。但是Java Web提供了解决方法:URL地址重写
HttpServletResponse类提供了两个URL地址重写的方法:
需要值得注意的是:这两个方法会自动判断该浏览器是否支持Cookie,如果支持Cookie,重写后的URL地址就不会带有jsessionid了【当然了,即使浏览器支持Cookie,第一次输出URL地址的时候还是会出现jsessionid(因为没有任何Cookie可带)】
下面我们就以上面“购物”的例子来做试验吧!首先我们来看看禁用掉Cookie对原来的小例子有什么影响。
访问Servlet1,随便点击一本书籍购买
image
image
原因也非常简单,没有Cookie传递给服务器,服务器每次创建的时候都是新的Session,导致最后获取到的List集合一定是空的。
不同Servlet获取到的Session的id号都是不同的。
image
下面我们就对URL进行重写,看看能不能恢复没有禁掉Cookie之前的效果。
原则:把Session的属性带过去【传递给】另外一个Servlet,都要URL地址重写
在跳转到显示购买过商品的Servlet的时候,URL地址重写。
String url = "/ouzicheng/Servlet7";
response.sendRedirect(response.encodeURL(url));
image
image
Java Web规范支持通过配置禁用Cookie
禁用自己项目的Cookie
image
禁用全部web应用的Cookie
image
注意:该配置只是让服务器不能自动维护名为jsessionid的Cookie,并不能阻止Cookie的读写。
private String username = null;
private String password = null;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
....各种set、get方法
private static List list = new ArrayList<>();
//装载些数据进数据库
static {
list.add(new User("aaa","111"));
list.add(new User("bbb","222"));
list.add(new User("ccc","333"));
}
//通过用户名和密码查找用户
public static User find(String username, String password) {
for (User user : list) {
if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
return user;
}
}
return null;
}
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = UserDB.find(username, password);
//如果找不到,就是用户名或密码出错了。
if (user == null) {
response.getWriter().write("you can't login");
return;
}
//标记着该用户已经登陆了!
HttpSession httpSession = request.getSession();
httpSession.setAttribute("user", user);
//跳转到其他页面,告诉用户成功登陆了。
response.sendRedirect(response.encodeURL("index.jsp"));
image
image
image
image
重复提交的危害:
首先我们来看一下常见的重复提交。
下面的gif是后退再提交,在处理提交请求的Servlet中刷新
image
下面的gif是网络延迟,多次点击提交按钮
image
对于网络延迟造成的多次提交数据给服务器,其实是客户端的问题。于是,我们可以使用javaScript来防止这种情况
要做的事情也非常简单:当用户第一次点击提交按钮时,把数据提交给服务器。当用户再次点击提交按钮时,就不把数据提交给服务器了。
监听用户提交事件。只能让用户提交一次表单!
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
表单提交
image
由于网络延迟造成的多次提交数据给服务器,我们还可以使用javaScript代码这样解决:当我点击过一次提交按钮时,我就把提交的按钮隐藏起来。不能让用户点击了!
想要让按钮隐藏起来,也很简单。只要获取到按钮的节点,就可以控制按钮的隐藏或显示了!
image
在处理表单的Servlet中刷新和后退再提交这两种方式不能只靠客户端来限制了。也就是说javaScript代码无法阻止这两种情况的发生。
于是乎,我们就想得用其他办法来阻止表单数据重复提交了。我们现在学了Session,Session可以用来标识一个用户是否登陆了。Session的原理也说了:不同的用户浏览器会拥有不同的Session。而request和ServletContext为什么就不行呢?request的域对象只能是一次http请求,提交表单数据的时候request域对象的数据取不出来。ServletContext代表整个web应用,如果有几个用户浏览器同时访问,ServletContext域对象的数据会被多次覆盖掉,也就是说域对象的数据就毫无意义了。
可能到这里,我们会想到:在提交数据的时候,存进Session域对象的数据,在处理提交数据的Servlet中判断Session域对象数据????。究竟判断Session什么?判断Session域对象的数据不为null?没用呀,既然已经提交过来了,那肯定不为null。
此时,我们就想到了,在表单中还有一个隐藏域,可以通过隐藏域把数据交给服务器。
我们向Session域对象的存入数据究竟是什么呢?简单的一个数字?好像也行啊。因为只要Session域对象的数据和jsp隐藏域带过去的数据对得上号就行了呀,反正在Servlet上判断完是否重复提交,会立马把Session的数据移除掉的。更专业的做法是:向Session域对象存入的数据是一个随机数【Token--令牌】。
生成一个独一无二的随机数
/*
* 产生随机数就应该用一个对象来生成,这样可以避免随机数的重复。
* 所以设计成单例
* */
public class TokenProcessor {
private TokenProcessor() {
}
private final static TokenProcessor TOKEN_PROCESSOR = new TokenProcessor();
public static TokenProcessor getInstance() {
return TOKEN_PROCESSOR;
}
public static String makeToken() {
//这个随机生成出来的Token的长度是不确定的
String token = String.valueOf(System.currentTimeMillis() + new Random().nextInt(99999999));
try {
//我们想要随机数的长度一致,就要获取到数据指纹
MessageDigest messageDigest = MessageDigest.getInstance("md5");
byte[] md5 = messageDigest.digest(token.getBytes());
//如果我们直接 return new String(md5)出去,得到的随机数会乱码。
//因为随机数是任意的01010101010,在转换成字符串的时候,会查gb2312的码表,gb2312码表不一定支持该二进制数据,得到的就是乱码
//于是乎经过base64编码成了明文的数据
BASE64Encoder base64Encoder = new BASE64Encoder();
return base64Encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
//生出随机数
TokenProcessor tokenProcessor = TokenProcessor.getInstance();
String token = tokenProcessor.makeToken();
//将随机数存进Session中
request.getSession().setAttribute("token", token);
//跳转到显示页面
request.getRequestDispatcher("/login.jsp").forward(request, response);
String serverValue = (String) request.getSession().getAttribute("token");
String clientValue = request.getParameter("token");
if (serverValue != null && clientValue != null && serverValue.equals(clientValue)) {
System.out.println("处理请求");
//清除Session域对象数据
request.getSession().removeAttribute("token");
}else {
System.out.println("请不要重复提交数据!");
}
image
实现原理是非常简单的:
一次性校验码其实就是为了防止暴力猜测密码
在讲response对象的时候,我们使用response对象输出过验证码,但是没有去验证!
验证的原理也非常简单:生成验证码后,把验证码的数据存进Session域对象中,判断用户输入验证码是否和Session域对象的数据一致。
生成验证码图片,并将验证码存进Session域中
//在内存中生成图片
BufferedImage bufferedImage = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
//获取到这张图片
Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();
//设置背景色为白色
graphics2D.setColor(Color.white);
graphics2D.fillRect(0, 0, 80, 20);
//设置图片的字体和颜色
graphics2D.setFont(new Font(null, Font.BOLD, 20));
graphics2D.setColor(Color.BLUE);
//生成随机数
String randomNum = makeNum();
//往这张图片上写数据,横坐标是0,纵坐标是20
graphics2D.drawString(randomNum, 0, 20);
//将随机数存进Session域中
request.getSession().setAttribute("randomNum", randomNum);
//控制浏览器不缓存该图片
response.setHeader("Expires", "-1");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
//通知浏览器以图片的方式打开
response.setHeader("Content-type", "image/jpeg");
//把图片写给浏览器
ImageIO.write(bufferedImage, "jpg", response.getOutputStream());
private String makeNum() {
Random random = new Random();
//生成0-6位的随机数
int num = random.nextInt(999999);
//验证码的数位全都要6位数,于是将该随机数转换成字符串,不够位数就添加
String randomNum = String.valueOf(num);
//使用StringBuffer来拼凑字符串
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 6 - randomNum.length(); i++) {
stringBuffer.append("0");
}
return stringBuffer.append(randomNum).toString();
}
//获取用户输入验证码的数据
String client_randomNum = request.getParameter("randomNum");
//获取Session中的数据
String session_randomNum = (String) request.getSession().getAttribute("randomNum");
//判断他俩数据是否相等,用户是否有输入验证码,Session中是否为空
if (client_randomNum == null || session_randomNum == null || !client_randomNum.equals(session_randomNum)) {
System.out.println("验证码错误了!!!");
return ;
}
//下面就是验证用户名和密码...................
image
image
对于校验码实现思路是这样子的:
如果仅仅使用Cookie或仅仅使用Session可能达不到理想的效果。这时应该尝试一下同时使用Session和Cookie
那么,什么时候才需要同时使用Cookie和Session呢?
在上一篇博客中,我们使用了Session来进行简单的购物,功能也的确实现了。现在有一个问题:我在购物的途中,不小心关闭了浏览器。当我再返回进去浏览器的时候,发现我购买过的商品记录都没了!!为什么会没了呢?原因也非常简单:服务器为Session自动维护的Cookie的maxAge属性默认是-1的,当浏览器关闭掉了,该Cookie就自动消亡了。当用户再次访问的时候,已经不是原来的Cookie了。
我们现在想的是:即使我不小心关闭了浏览器了,我重新进去网站,我还能找到我的购买记录。
要实现该功能也十分简单,问题其实就在:服务器为Session自动维护的Cookie的maxAge属性是-1,Cookie没有保存在硬盘中。我现在要做的就是:把Cookie保存在硬盘中,即使我关闭了浏览器,浏览器再次访问页面的时候,可以带上Cookie,从而服务器识别出Session。
第一种方式:只需要在处理购买页面上创建Cookie,Cookie的值是Session的id返回给浏览器即可
Cookie cookie = new Cookie("JSESSIONID",session.getId());
cookie.setMaxAge(30*60);
cookie.setPath("/ouzicheng/");
response.addCookie(cookie);
第二种方式: 在server.xml文件中配置,将每个用户的Session在服务器关闭的时候序列化到硬盘或数据库上保存。但此方法不常用,知道即可!
下面看一下效果
image
越努力越幸运,一直努力,一直幸运
作者:Java3y
链接:https://www.jianshu.com/p/716331793ce5
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。