Cookie&Session10_Session2
Session:主菜
1.概念
服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中。HttpSession
2. 快速入门
HttpSession也是域对象
a. 获取HttpSession对象:
HttpSession session = request.getSession();
b. 使用HttpSession对象:
Object getAttribute(String name)
void setAttribute(String name, Object value)
void removeAttribute(String name)
package cn.itcast.session; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/sessionDemo1") public class SessionDemo1 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //使用session共享数据 //1. 获取session HttpSession session = request.getSession(); //2. 存储数据 session.setAttribute("msg","hello session"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
package cn.itcast.session; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/sessionDemo2") public class SessionDemo2 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //使用session共享数据 //1. 获取session HttpSession session = request.getSession(); //2. 获取数据 Object msg = session.getAttribute("msg"); System.out.println(msg); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
浏览器访问http://localhost/day10/sessionDemo1,然后访问http://localhost/day10/sessionDemo2,检查idea控制台输出:hello session。
关闭浏览器,重新打开浏览器访问http://localhost/day10/sessionDemo2,检查idea控制台输出:null
3. 原理
Session的实现是依赖于Cookie的。
浏览器访问http://localhost/day10/sessionDemo1,检查响应头
浏览器访问http://localhost/day10/sessionDemo2,检查请求头
两个的JSESSIONID是一样的。
4. 当客户端关闭后,服务器不关闭,两次获取session是否为同一个?
a. 默认情况下不是。因为客户端关闭意味着会话结束了
b. 如果需要相同,则可以创建Cookie,键为JSESSIONID,设置最大存活时间,让cookie持久化保存。
Cookie c = new Cookie("JSESSIONID",session.getId());
c.setMaxAge(60*60);
response.addCookie(c);
代码实现一:默认情况下关闭客户端,两次获取session不同
package cn.itcast.session; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/sessionDemo3") public class SessionDemo3 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 获取session HttpSession session = request.getSession(); System.out.println(session); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
浏览器访问http://localhost/day10/sessionDemo3,关闭浏览器,打开浏览器重复访问,检查idea控制台输出session对象的地址栏不同,可以证明不是同一个session对象。当然如果不关闭浏览器重复访问,则是同一个session对象
代码实现二:
package cn.itcast.session; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; @WebServlet("/sessionDemo3") public class SessionDemo3 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 获取session HttpSession session = request.getSession(); System.out.println(session); //期望客户端关闭后,session也能相同 Cookie cookie = new Cookie("JSESSIONID", session.getId()); cookie.setMaxAge(60*60); response.addCookie(cookie); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
浏览器访问http://localhost/day10/sessionDemo3,关闭浏览器,打开浏览器重复访问,检查idea控制台输出的session对象的地址是一样的,表示是同一个session。
5. 客户端不关闭,服务器关闭后,两次获取的session是同一个吗?
不是同一个,但是要确保数据不丢失。tomcat自动完成以下工作
* session的钝化(序列化过程):
* 在服务器正常关闭之前,将session对象序列化到硬盘上
* session的活化(反序列化过程):
* 在服务器启动后,将session文件转化为内存中的session对象即可。
idea不能演示出session的钝化和活化。要使用本地的tomcat进行演示完成。
演示一:
package cn.itcast.session; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; @WebServlet("/sessionDemo4") public class SessionDemo4 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 获取session HttpSession session = request.getSession(); System.out.println(session); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
浏览器访问http://localhost/day10/sessionDemo4,检查idea控制台输出session对象的地址值:org.apache.catalina.session.StandardSessionFacade@34de66e7。关闭tomcat服务器后启动tomcat服务器,浏览器再次访问,检查idea控制台输出:org.apache.catalina.session.StandardSessionFacade@72a5ebd7。
由此可见,两次获取的session不是同一个。
演示二:
idea不能演示出session的钝化和活化。要使用本地的tomcat进行演示完成。
本地的tomcat演示步骤:
把项目的真实路径,即out文件中的项目路径下的项目文件打包成war包,放到tomcat的webapps,startup.bat启动tomcat后,浏览器访问http://localhost:8080/day10/sessionDemo1,然后访问http://localhost:8080/day10/sessionDemo2,可以看到tomcat终端输出:hello session。
这时点击shupdown.bat正常关闭tomcat,可以看到tomcat的work-Catilina-localhost-day10目录下就会生成一个SESSIONS.ser文件。文件中放的是session对象。
把tomcat服务器重动,这个文件会被自动读取,并且这个文件被删除掉,把文件中的内容还原到内存中了。虽然对象的地址值不一样,但是sessionId是一样的。这时候访问http://localhost:8080/day10/sessionDemo1,仍旧能输出hello session。
idea演示步骤:
启动tomat,浏览器访问http://localhost:8080/day10/sessionDemo1,idea中关闭tomat服务后,CATALINA_BASE下的work-Catalina-localhost-day10下会生成SESSIONS.ser文件。idea中启动tomcat可以发现会先把CATALINA_BASE下的work文件夹删除,然后新建一个work,这样就找不到原来的SESSIONS.ser文件了。
6. session什么时候被销毁?
a. 服务器关闭
b. session对象调用invalidate() 。
c. session默认失效时间 30分钟
tomcat-conf-web.xml中选择性配置修改
<session-config>
<session-timeout>30</session-timeout>
</session-config>
7. session的特点
a. session用于存储一次会话的多次请求的数据,存在服务器端
b. session可以存储任意类型,任意大小的数据
8. session与Cookie的区别:
a. session存储数据在服务器端,Cookie在客户端
b. session没有数据大小限制,Cookie有
c. session数据安全,Cookie相对于不安全
9. 案例:验证码
需求:
a. 访问带有验证码的登录页面login.jsp
b. 用户输入用户名,密码以及验证码。
* 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误
* 如果验证码输入有误,跳转登录页面,提示:验证码错误
* 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您
分析:
实现:
package cn.itcast.domain; /** * 用户的实体类 */ public class User { private int id; private String username; private String password; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
druid.properties放在src下
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///day08 username=root password=root initialSize=5 maxActive=10 maxWait=3000
package cn.itcast.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; /** * JDBC工具类 使用Druid连接池 */ public class JDBCUtils { private static DataSource ds; static { try { //1. 加载配置文件 Properties pro = new Properties(); //使用ClassLoader加载配置i文件,获取字节输入流 InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"); pro.load(is); //2. 初始化连接池对象 ds = DruidDataSourceFactory.createDataSource(pro); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /** * 获取连接池对象 */ public static DataSource getDataSource(){ return ds; } /** * 获取连接Connection对象 */ public static Connection getConnection() throws SQLException { return ds.getConnection(); } }
package cn.itcast.dao; import cn.itcast.domain.User; import cn.itcast.util.JDBCUtils; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; /** * 操作数据库中User表的类 */ public class UserDao { //声明JDBCTemplate对象公用 private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource()); /** * 登录方法 * @param loginUser 只有用户名和密码 * @return user包含用户全部数据,没有查询到返回null */ public User login(User loginUser){ try { //1. 编写sql String sql ="select * from user where username=? and password=?"; //2. 调用query方法 User user = template.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), loginUser.getUsername(), loginUser.getPassword()); return user; } catch (DataAccessException e) { e.printStackTrace();//记录日志 return null; } } }
login.jsp
<%-- Created by IntelliJ IDEA. User: ajing Date: 2022/2/26 Time: 20:42 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>login</title> <script> window.onload = function(){ document.getElementById("img").onclick = function () { this.src="/day10/checkCodeServlet?time="+ new Date().getTime(); } } </script> <style> div{ color: red; } </style> </head> <body> <form action="/day10/loginServlet" method="post"> <table> <tr> <td>用户名</td> <td><input type="text" name="username"></td> </tr> <tr> <td>密码</td> <td><input type="text" name="password"></td> </tr> <tr> <td>验证码</td> <td><input type="text" name="checkCode"></td> </tr> <tr> <td colspan="2"><img id="img" src="/day10/checkCodeServlet"></td> </tr> <tr> <td colspan="2"><input type="submit" value="登录"></td> </tr> </table> </form> <div><%=request.getAttribute("cc_error")%></div> <div><%=request.getAttribute("login_error")%></div> </body> </html>
success.jsp
<%-- Created by IntelliJ IDEA. User: ajing Date: 2022/2/26 Time: 21:15 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1><%=request.getSession().getAttribute("user")%>,欢迎您</h1> </body> </html>
package cn.itcast.servlet; import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Random; @WebServlet("/checkCodeServlet") public class CheckCodeServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int width = 100; int height = 50; //1. 创建一个对象,在内存中图片(代表验证码图片对象) BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);//在浏览器输出一张宽100高50的黑色背景的图片 //2. 美化图片 //2.1 填充背景色 Graphics g = image.getGraphics();//获取画笔对象 g.setColor(Color.PINK);//设置画笔颜色 g.fillRect(0,0,width,height); //2.2 画边框 g.setColor(Color.BLUE); g.drawRect(0,0,width-1,height-1); String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; //生成随机角标 Random ran = new Random(); StringBuilder sb = new StringBuilder(); for (int i = 1; i <= 4; i++) { int index = ran.nextInt(str.length()); //获取字符 char ch = str.charAt(index);//随机字符 sb.append(ch); //2.3 写验证码 g.drawString(ch+"",width/5*i,height/2); } String checkCode_session = sb.toString(); //将验证码存入session request.getSession().setAttribute("checkCode_session",checkCode_session); //2.4 画干扰线 g.setColor(Color.GREEN); //随机生成坐标点 for (int i = 0; i < 10; i++) { int x1 = ran.nextInt(width); int x2 = ran.nextInt(width); int y1 = ran.nextInt(height); int y2 = ran.nextInt(height); g.drawLine(x1,y1,x2,y2); } // g.drawLine(1,1,30,30); //3. 将图片输出到页面展示 ImageIO.write(image,"jpg",response.getOutputStream()); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request,response); } }
package cn.itcast.servlet; import cn.itcast.dao.UserDao; import cn.itcast.domain.User; import org.apache.commons.beanutils.BeanUtils; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Map; @WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 设置request编码 request.setCharacterEncoding("utf-8"); //2. 获取参数Map Map<String, String[]> map = request.getParameterMap(); //3. 创建User对象 User loginUser = new User(); UserDao dao = new UserDao(); try { BeanUtils.populate(loginUser,map); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } //3. 先获取生产的验证码 HttpSession session = request.getSession(); String checkCode_session = (String) session.getAttribute("checkCode_session"); //3.先判断验证码是否正确 if(checkCode_session.equalsIgnoreCase(map.get("checkCode")[0])){ //忽略大小写比较 //验证码正确 //判断用户名和密码是否一致 //4. 调用UserDao的login方法 User user = dao.login(loginUser); if(user != null){ //登录成功 //存储信息:用户信息 session.setAttribute("user",user.getUsername()); //重定向到success.jsp response.sendRedirect(request.getContextPath()+"/success.jsp"); }else { //登录失败 //存储提示信息到request request.setAttribute("login_error","用户名或密码错误"); //转发到登录页面 request.getRequestDispatcher("/login.jsp").forward(request,response); } }else{ //验证码不一致 //存储提示信息到request request.setAttribute("cc_error","验证码错误"); //转发到登录页面 request.getRequestDispatcher("/login.jsp").forward(request,response); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
浏览器访问http://localhost/day10/login.jsp,检查浏览器展示:
输入错误的验证码,点击登录,检查浏览器展示:
输入正确的验证码,输入错误的用户名和密码,点击登录,检查浏览器展示:
输入正确的验证码,输入正确的用户名和密码,点击登录,检查浏览器展示:
解决bug:
1、null展示的问题
修改login.jsp:
<div><%=request.getAttribute("cc_error") == null ? "":request.getAttribute("cc_error")%></div> <div><%=request.getAttribute("login_error") == null ? "" : request.getAttribute("login_error")%></div>
2、输入正确的用户名、密码和验证码,从成功页后退到登录页面验证码没有变,也就是没有重新请求。这样我用原来的验证码仍旧会登录成功。这样不太安全。
修改LoginServlet中的部分代码
HttpSession session = request.getSession(); String checkCode_session = (String) session.getAttribute("checkCode_session"); //删除session中存储的验证码 session.removeAttribute("checkCode_session"); //3.先判断验证码是否正确 if(checkCode_session != null && checkCode_session.equalsIgnoreCase(map.get("checkCode")[0])){
这时候后退后虽然验证码看着没变,但是用这个验证码点击登录会报:验证码错误