1. Session 的创建和销毁
1). 什么时候创建 HttpSession 对象
①. 对于 JSP: 是否浏览器访问服务端的任何一个 JSP, 服务器都会立即创建一个 HttpSession 对象呢?
不一定。
> 若当前的 JSP 是客户端访问的当前 WEB 应用的第一个资源,且 JSP 的 page 指定的 session 属性值为 false,
则服务器就不会为 JSP 创建一个 HttpSession 对象;
> 若当前 JSP 不是客户端访问的当前 WEB 应用的第一个资源,且其他页面已经创建一个 HttpSession 对象,
则服务器也不会为当前 JSP 页面创建一个 HttpSession 对象,而回会把和当前会话关联的那个 HttpSession 对象返回给当前的 JSP 页面.
②. 对于 Serlvet: 若 Serlvet 是客户端访问的第一个 WEB 应用的资源,
则只有调用了 request.getSession() 或 request.getSession(true) 才会创建 HttpSession 对象
page 指定的 session 属性:
1). 默认情况下, 第一次访问一个 WEB 应用的一个 JSP 页面时, 该页面都必须有一个和这个请求相关联的 Session 对象.
因为 page 指定的 session 属性默认为 true
2). 若把 session 属性改为 false, JSP 页面不会要求一定有一个 Session 对象和当前的 JSP 页面相关联
所以若第一次访问当前 WEB 应用的 JSP 页面时, 就不会创建一个 Session 对象.
3). 创建一个 Session 对象: 若 page 指定的 session 设置为 false 或 在 Servlet 中可以通过以下 API 获取 Session 对象.
request.getSession(flag): 若 flag 为 true, 则一定会返回一个 HttpSession 对象, 如果已经有和当前 JSP 页面关联的 HttpSession
对象, 直接返回; 如果没有, 则创建一个新的返回. flag 为 false: 若有关联的, 则返回; 若没有, 则返回 null
request.getSession(): 相当于 request.getSession(true);
4). Session 对象的销毁:
①. 直接调用 HttpSession 的 invalidate()
②. HttpSession 超过过期时间.
> 返回最大时效: getMaxInactiveInterval() 单位是秒
> 设置最大时效: setMaxInactiveInterval(int interval)
> 可以在 web.xml 文件中配置 Session 的最大时效, 单位是分钟.
<session-config>
<session-timeout>30</session-timeout>
</session-config>
5). 在 Serlvet 中如何获取 HttpSession 对象?
> request.getSession(boolean create):
create 为 false, 若没有和当前 JSP 页面关联的 HttpSession 对象, 则返回 null; 若有, 则返回 true
create 为 true, 一定返回一个 HttpSession 对象. 若没有和当前 JSP 页面关联的 HttpSession 对象, 则服务器创建一个新的
HttpSession 对象返回, 若有, 直接返回关联的.
2. 使用 HttpSession 实现验证码 点击查看
1). 基本原理: 和表单重复提交一致:
> 在原表单页面, 生成一个验证码的图片, 生成图片的同时, 需要把该图片中的字符串放入到 session 中.
> 在原表单页面, 定义一个文本域, 用于输入验证码.
> 在目标的 Servlet 中: 获取 session 和 表单域 中的 验证码的 值
> 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 验证码 属性清除
> 若不一致, 则直接通过重定向的方式返回原表单页面, 并提示用户 "验证码错误"
3. 表单的重复提交 详情看下面代码
1). 重复提交的情况:
①. 在表单提交到一个 Servlet, 而 Servlet 又通过请求转发的方式响应一个 JSP(HTML) 页面,
此时地址栏还保留着 Serlvet 的那个路径, 在响应页面点击 "刷新"
②. 在响应页面没有到达时重复点击 "提交按钮".
③. 点击 "返回", 再点击 "提交"
2). 不是重复提交的情况: 点击 "返回", "刷新" 原表单页面, 再 "提交"。
3). 如何避免表单的重复提交: 在表单中做一个标记, 提交到 Servlet 时, 检查标记是否存在且是否和预定义的标记一致, 若一致, 则受理请求,
并销毁标记, 若不一致或没有标记, 则直接响应提示信息: "重复提交"
①. 仅提供一个隐藏域: <input type="hidden" name="token" value="atguigu"/>. 行不通: 没有方法清除固定的请求参数.
②. 把标记放在 request 中. 行不通, 因为表单页面刷新后, request 已经被销毁, 再提交表单是一个新的 request.
③. 把标记放在 session 中. 可以!
> 在原表单页面, 生成一个随机值 token
> 在原表单页面, 把 token 值放入 session 属性中
> 在原表单页面, 把 token 值放入到 隐藏域 中.
> 在目标的 Servlet 中: 获取 session 和 隐藏域 中的 token 值
> 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 token 属性清除
> 若不一致, 则直接响应提示页面: "重复提交"
4. 使用绝对路径:使用相对路径可能会有问题, 但使用绝对路径肯定没有问题.
1). 绝对路径: 相对于当前 WEB 应用的路径. 在当前 WEB 应用的所有的路径前都添加 contextPath 即可.
2). / 什么时候代表站点的根目录, 什么时候代表当前 WEB 应用的根目录
若 / 需要服务器进行内部解析, 则代表的就是 WEB 应用的根目录. 若是交给浏览器了, 则 / 代表的就是站点的根目录
若 / 代表的是 WEB 应用的根目录, 就不需要加上 contextPath 了.
5.代码区
package com.atguigu.javaweb; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class TokenProcessor { private static final String TOKEN_KEY = "COM.ATGUIGU.TOKEN_KEY"; private static final String TRANSACTION_TOKEN_KEY = "TRANSACTION_TOKEN_KEY"; private static TokenProcessor instance = new TokenProcessor(); private long previous; protected TokenProcessor() { super(); } public static TokenProcessor getInstance() { return instance; } public synchronized boolean isTokenValid(HttpServletRequest request) { return this.isTokenValid(request, false); } public synchronized boolean isTokenValid(HttpServletRequest request, boolean reset) { HttpSession session = request.getSession(false); if (session == null) { return false; } String saved = (String) session.getAttribute(TRANSACTION_TOKEN_KEY); if (saved == null) { return false; } if (reset) { this.resetToken(request); } String token = request.getParameter(TOKEN_KEY); if (token == null) { return false; } return saved.equals(token); } public synchronized void resetToken(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return; } session.removeAttribute(TRANSACTION_TOKEN_KEY); } public synchronized String saveToken(HttpServletRequest request) { HttpSession session = request.getSession(); String token = generateToken(request); if (token != null) { session.setAttribute(TRANSACTION_TOKEN_KEY, token); } return token; } public synchronized String generateToken(HttpServletRequest request) { HttpSession session = request.getSession(); return generateToken(session.getId()); } public synchronized String generateToken(String id) { try { long current = System.currentTimeMillis(); if (current == previous) { current++; } previous = current; byte[] now = new Long(current).toString().getBytes(); MessageDigest md = MessageDigest.getInstance("MD5"); md.update(id.getBytes()); md.update(now); return toHex(md.digest()); } catch (NoSuchAlgorithmException e) { return null; } } private String toHex(byte[] buffer) { StringBuffer sb = new StringBuffer(buffer.length * 2); for (int i = 0; i < buffer.length; i++) { sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16)); sb.append(Character.forDigit(buffer[i] & 0x0f, 16)); } return sb.toString(); } }
package com.atguigu.javaweb; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class TokenServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // HttpSession session = request.getSession(); // Object token = session.getAttribute("token"); // String tokenValue = request.getParameter("token"); // System.out.println(token); // System.out.println(tokenValue); // // if(token != null && token.equals(tokenValue)){ // session.removeAttribute("token"); // }else{ // response.sendRedirect(request.getContextPath() + "/token/token.jsp"); // return; // } boolean valid = TokenProcessor.getInstance().isTokenValid(request); if(valid){ TokenProcessor.getInstance().resetToken(request); }else{ response.sendRedirect(request.getContextPath() + "/token.jsp"); return; } String name = request.getParameter("name"); //访问数据库服务器... System.out.println("name: " + name); //request.getRequestDispatcher("/token/success.jsp").forward(request, response); response.sendRedirect(request.getContextPath() + "/success.jsp"); } }
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>Validate</display-name> <servlet> <description></description> <display-name>TokenServlet</display-name> <servlet-name>TokenServlet</servlet-name> <servlet-class>com.atguigu.javaweb.TokenServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>TokenServlet</servlet-name> <url-pattern>/tokenServlet</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> </web-app>
<%@page import="com.atguigu.javaweb.TokenProcessor"%> <%@page import="java.util.Date"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <%-- String tokenValue = new Date().getTime() + ""; session.setAttribute("token", tokenValue); --%> <form action="<%= request.getContextPath() %>/tokenServlet" method="post"> <input type="hidden" name="COM.ATGUIGU.TOKEN_KEY" value="<%= TokenProcessor.getInstance().saveToken(request) %>"/> name: <input type="text" name="name"/> <input type="submit" value="Submit"/> </form> </body> </html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>Success Page</h4>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>对不起, 已经提交过了!</h4>
</body>
</html>