Session与Cookie(自定义Session)

时间:2022-12-02 21:41:56

一、会话管理

  会话管理: 管理浏览器客户端 和 服务器端之间会话过程中产生的会话数据。

  域对象: 实现资源之间的数据共享。

  request域对象

  context域对象

  会话技术:  

    Cookie技术:会话数据保存在浏览器客户端。

    Session技术:会话数据保存在服务器端。存放在内存里面,客户端与服务器端之前的通讯使用SessionId

二、Cookie技术

2.1 特点:Cookie技术:会话数据保存在浏览器客户端。

2.2 Cookie技术核心

  Cookie类:用于存储会话数据

    1)构造Cookie对象

      New  Cookie(java.lang.String name, java.lang.String value)

    2)设置cookie

      void setPath(java.lang.String uri)   :设置cookie的有效访问路径

      void setMaxAge(int expiry) : 设置cookie的有效时间

      void setValue(java.lang.String newValue) :设置cookie的值

    3)发送cookie到浏览器端保存

      void response.addCookie(Cookie cookie)  : 发送cookie

    4)服务器接收cookie

      Cookie[] request.getCookies()  : 接收cookie

2.3 Cookie原理

  1)服务器创建cookie对象,把会话数据存储到cookie对象中。

    new Cookie("name","value");

  2)  服务器发送cookie信息到浏览器

    response.addCookie(cookie);

    举例: set-cookie: name=eric  (隐藏发送了一个set-cookie名称的响应头)

  3)浏览器得到服务器发送的cookie,然后保存在浏览器端。

  4)浏览器在下次访问服务器时,会带着cookie信息

    举例: cookie: name=eric  (隐藏带着一个叫cookie名称的请求头)

  5)服务器接收到浏览器带来的cookie信息

    request.getCookies();

2.4 Coolie的细节  

  1)void setPath(java.lang.String uri)   :设置cookie的有效访问路径。有效路径指的是cookie的有效路径保存在哪里,那么浏览器在有效路径下访问服务器时就会带着cookie信息,否则不带cookie信息。

  2)void setMaxAge(int expiry) : 设置cookie的有效时间。

    正整数:表示cookie数据保存浏览器的缓存目录(硬盘中),数值表示保存的时间。

    负整数:表示cookie数据保存浏览器的内存中。浏览器关闭cookie就丢失了!!

    零:表示删除同名的cookie数据

  3)Cookie数据类型只能保存非中文字符串类型的。可以保存多个cookie,但是浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。

2.5 Cookie 问题

  1) cookie不能跨浏览器

  2)浏览器关闭,默认失效cookie

  3)127.0.0.1和localhost,操作cookie时不共享

2.6 Cookie的局限:

  1)Cookie只能存字符串类型,不能保存对象

  2)只能存非中文

  3)1个cookie的容量不超过4KB

2.7 案例-显示用户上次访问时间

@WebServlet("/LastAccessTime")
public class LastAccessTime extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");// 防止浏览器显示乱码
String lastAccessTime = null;
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
String name = cookie.getName();
if (name.equals("lastAccessTime")) {
lastAccessTime = cookie.getValue();
break;
}
}
if (StringUtils.isEmpty(lastAccessTime)) {
resp.getWriter().print("您是首次访问!");
} else {
resp.getWriter().print("你上次访问时间:" + lastAccessTime);
}
// 保存访问时间
// 创建cookie 将当前时间作为cookie保存到浏览器
String currenttime = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss").format(new Date());
Cookie cookie = new Cookie("lastAccessTime", currenttime);
cookie.setMaxAge(60 * 60 * 24);
// 发送cookie
resp.addCookie(cookie);
}
String addCookie(String key, String value, HttpServletResponse resp) {
return key;
}
}

三、Session技术

3.1 由于Cookie的局限性和问题,想要突破这些限制只能使用session技术!

  Session特点:会话数据保存在服务器端。(内存中)

3.2 Session 技术核心

  HTTPSession类:用于保存会话数据

  1)创建或得到session对象

    HttpSession getSession()

    HttpSession getSession(boolean create)   //true,没有session,会创建。False则不会创建

  2)设置session对象

    void setMaxInactiveInterval(int interval)  : 设置session的有效时间

    void invalidate()     : 销毁session对象

    java.lang.String getId()  : 得到session编号

  3)保存会话数据到session对象

    void setAttribute(java.lang.String name, java.lang.Object value)  : 保存数据

    java.lang.Object getAttribute(java.lang.String name)  : 获取数据

    void removeAttribute(java.lang.String name) : 清除数据

3.3 Session原理

  服务器端创建session,通过响应头将sessionId保存到客户端本地。客户端通过请求头,将sessionId传递到服务器端。

  关闭浏览器只会清楚客户端的sessionID,服务器端的session由创建时的时间失效决定。

代码解读:HttpSession session = request.getSession();

  1)第一次访问创建session对象,给session对象分配一个唯一的ID,叫JSESSIONID

     new HttpSession();

  2)把JSESSIONID作为Cookie的值发送给浏览器保存

    Cookie cookie = new Cookie("JSESSIONID", sessionID);

    response.addCookie(cookie);

  3)第二次访问的时候,浏览器带着JSESSIONID的cookie访问服务器

  4)服务器得到JSESSIONID,在服务器的内存中搜索是否存放对应编号的session对象。

    if(找到){

      return map.get(sessionID);

    }else{

      new HttpSession();

    }

  5)如果找到对应编号的session对象,直接返回该对象

  6)如果找不到对应编号的session对象,创建新的session对象,继续走1的流程

  结论:通过JSESSION的cookie值在服务器找session对象!

3.4 Session细节

  1)java.lang.String getId()  : 得到session编号

  2)两个getSession方法:

    getSession(true) / getSession()  : 创建或得到session对象。没有匹配的session编号,自动创建新的session对象。

    getSession(false):              得到session对象。没有匹配的session编号,返回null

  3)void setMaxInactiveInterval(int interval)  : 设置session的有效时间

    session对象销毁时间:

    3.1 默认情况30分服务器自动回收

    3.2 修改session回收时间

    3.3 全局修改session有效时间

<!-- 修改session全局有效时间:分钟 -->
<session-config>
<session-timeout>1</session-timeout>
</session-config>

    3.4.手动销毁session对象  

      void invalidate()     : 销毁session对象

  4)如何避免浏览器的JSESSIONID的cookie随着浏览器关闭而丢失的问题

  /**
  * 手动发送一个硬盘保存的cookie给浏览器
  */
  Cookie c = new Cookie("JSESSIONID",session.getId());
  c.setMaxAge(60*60);
  response.addCookie(c);

总结:

  1)会话管理: 浏览器和服务器会话过程中的产生的会话数据的管理。

  2)Cookie技术:

    new Cookie("name","value")

    response.addCookie(coookie)

    request.getCookies()

  3)Session技术

    request.getSession();

    setAttrbute("name","会话数据");

    getAttribute("会话数据")

四、自定义缓存

4.1定义缓存实体类

public class Cache {
public Cache(String key, Object value, Long timeOut) {
super();
this.key = key;
this.value = value;
this.timeOut = timeOut;
}
public Cache() {
}
/**
* key
*/
private String key;
/**
* 缓存数据
*/
private Object value;
/**
* 超时时间
*/
private Long timeOut;
}

4.2 定义缓存类

/**
* @classDesc: 功能描述:(缓存map)
*/
public class CacheManager {
private Map<String, Cache> cacheMap = new HashMap<>();
/**
* @methodDesc: 功能描述:(往缓存存值)
*/
public void put(String key, Object oj) {
put(key, oj, null);
}
/**
* @methodDesc: 功能描述:(往缓存存值)
*/
public synchronized void put(String key, Object oj, Long timeOut) {
if (oj == null) {
return;
}
Cache cache = new Cache();
cache.setKey(key);
if (timeOut != null)
cache.setTimeOut(timeOut + System.currentTimeMillis());
cache.setValue(oj);
cacheMap.put(key, cache);
}
/**
* @methodDesc: 功能描述:(删除)
*/
public synchronized void deleteCache(String key) {
cacheMap.remove(key);
}
/**
* @methodDesc: 功能描述:(获取缓存中数据)
*/
public synchronized Object get(String key) {
Cache cache = cacheMap.get(key);
Object oj = null;
if (cache != null) {
oj = cache.getValue();
}
return oj;
}
/**
* @methodDesc: 功能描述:(检查数据是否在有效期内)
*/
public synchronized void checkValidityData() {
for (String key : cacheMap.keySet()) {
Cache cache = cacheMap.get(key);
Long timeOut = cache.getTimeOut();
if (timeOut == null) {
return;
}
long currentTime = System.currentTimeMillis();
long endTime = timeOut;
long result = (currentTime - endTime);
if (result > 0) {
System.out.println("清除:"+key);
cacheMap.remove(key);
}
}
}
public static void main(String[] args) throws InterruptedException {
CacheManager cacheManager = new CacheManager();
// cacheManager.put("lisi", 0);
cacheManager.put("zhangsan", "jj", 5000l);
// 多线程,验证session的有效时间
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
public void run() {
cacheManager.checkValidityData();
}
}, 5000, TimeUnit.MILLISECONDS);
Thread.sleep(5000);
System.out.println(cacheManager.get("zhangsan"));
}
}

五、自定义Token

5.1 什么是token

  token其实就是一个令牌,具有随机性,类似于sessionId。在对接一些第三方平台的时候,为了能够保证数据安全性,通常会使用一些令牌进行交互。

5.2 如何自定义token

  token生成规则,只要保证token生成一个不重复的唯一字符串即可。使用jdk自带的uuid生成规则。

5.3 什么是UUID

  UUID含义是通用唯一识别码 (Universally Unique Identifier),这是一个软件建构的标准,也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。

  UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过*控制端来做辨识资讯的指定。如此一来,每个人都可以建立不与其它人冲突的 UUID。

5.4 UUID的组成

  UUID保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字
  UUID由以下几部分的组合:
    (1)当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
    (2)时钟序列。
    (3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
  UUID的唯一缺陷在于生成的结果串会比较长。关于UUID这个标准使用最普遍的是微软的GUID(Globals Unique Identifiers)。在ColdFusion中可以用CreateUUID()函数很简单地生成UUID,其格式为:xxxxxxxx-xxxx-
xxxx-xxxxxxxxxxxxxxxx(8-4-4-16),其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。而标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
(8-4-4-4-12)。

5.5 UUID代码

  UUID。randomUUID().toString()

六、表单重复提交解决方案(防止http重复提交) 

6.1 场景描述:

  网络延时:在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交。

  重复刷新:表单提交后用户点击【刷新】按钮导致表单重复提交。

  后退再提交:点击浏览器的【后退】按钮回退到表单页面后进行再次提交

6.2 解决方案

  6.2.1 使用javascript 解决

<%@ 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>
<title>Form表单</title>
<script type="text/javascript">
var isFlag = false; //表单是否已经提交标识,默认为false
function submitFlag() {
if (isFlag == false) {
isFlag = true;
return true;
} else {
return false;
}
}
</script>
</head>
<body>
<form action="${pageContext.request.contextPath}/DoFormServlet"
method="post" onsubmit="return submitFlag()">
用户名:<input type="text" name="userName"> <input type="submit"
value="提交" id="submit">
</form>
</body>
</html>

  6.2.2 使用后端提交解决

  对于【重复刷新】、【后退再提交】导致表单重复提交的问题,前端无法解决,只能在服务器端解决。

  在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。

  在下列情况下,服务器端将拒绝处理用户提交的表单请求:

  1. 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
  2. 当前用户的Session中不存在Token(令牌)
  3. 用户提交的表单数据中没有Token(令牌)

  转发代码:

@WebServlet("/ForwardServlet")
public class ForwardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().setAttribute("sesionToken", TokenUtils.getToken());
req.getRequestDispatcher("form.jsp").forward(req, resp);
}
}

  转发页面:

<%@ 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>
<title>Form表单</title> </head> <body>
<form action="${pageContext.request.contextPath}/DoFormServlet"
method="post" onsubmit="return dosubmit()">
<input type="hidden" name="token" value="${sesionToken}"> 用户名:<input type="text"
name="userName"> <input type="submit" value="提交" id="submit">
</form>
</body>
</html>

  后端代码:

@WebServlet("/DoFormServlet")
public class DoFormServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
boolean flag = isFlag(req);
if (!flag) {
resp.getWriter().write("已经提交...");
System.out.println("数据已经提交了..");
return;
}
String userName = req.getParameter("userName");
try {
Thread.sleep(300);
} catch (Exception e) {
}
System.out.println("往数据库插入数据...." + userName);
resp.getWriter().write("success");
} public boolean isFlag(HttpServletRequest request) {
HttpSession session = request.getSession();
String sesionToken = (String) session.getAttribute("sesionToken");
String token = request.getParameter("token");
if (!(token.equals(sesionToken))) {
return false;
}
session.removeAttribute("sesionToken");
return true;
}
}

七、Servlet之Filter

7.1 什么是Filter

  Filter 也称之为过滤器,它是 Servlet 技术中最实用的技术,Web 开发人员通过 Filter 技术,对 web 服务器管理的所有 web 资源:例如 Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现 URL 级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

  它主要用于对用户请求进行预处理,也可以对 HttpServletResponse 进行后处理。使用 Filter 的完整流程:Filter 对用户请求进行预处理,接着将请求交给 Servlet 进行处理并生成响应,最后 Filter 再对服务器响应进行后处理。

7.2 Fileter使用

import java.io.IOException;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 使用Filter 打印参数
*
*/
public class FilterDemo implements Filter {
public FilterDemo(){
System.out.println("FilterDemo 构造函数被执行...");
}
/**
* 销毁
*/
public void destroy() {
  System.out.println("destroy");
}
public void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse,
FilterChain paramFilterChain) throws IOException, ServletException {
System.out.println("doFilter");
HttpServletRequest request = (HttpServletRequest) paramServletRequest;
HttpServletResponse response = (HttpServletResponse) paramServletResponse;
// 请求地址
String requestURI = request.getRequestURI();
System.out.println("requestURI:"+requestURI);
// 参数
Map<String, String[]> parameterMap = request.getParameterMap();
for (String key : parameterMap.keySet()) {
String[] arr=parameterMap.get(key);
}
}
/**
* 初始化
*/
public void init(FilterConfig paramFilterConfig) throws ServletException {
System.out.println("init");
}
}

  web.xml配置

<filter>
<filter-name>FilterDemo</filter-name>
<filter-class>com.stu.servlet.FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterDemo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

7.3 使用Filter防止XSS攻击

  7.3.1 什么是XSS攻击

    XSS攻击使用Javascript脚本注入进行攻击,例如在表单中注入: <script>location.href='http://www.baidu.com'</script>

  fromToXss.jsp

<%@ 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>
<form action="XssDemo" method="post">
<input type="text" name="userName"> <input type="submit">
</form>
</body>
</html>
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; @WebServlet("/XssDemo")
public class XssDemo extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
req.setAttribute("userName", userName);
req.getRequestDispatcher("showUserName.jsp").forward(req, resp);
}
}

  show.jsp

<%@ 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>userName:${userName}
</body>
</html>

  73..2 解决方案:使用Filter过滤器过滤注入标签

import java.io.IOException;
import java.util.Map; import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class FilterDemo implements Filter {
public FilterDemo() {
System.out.println("FilterDemo 构造函数被执行...");
}
/**
* 销毁
*/
public void destroy() {
System.out.println("destroy");
}
public void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse,
FilterChain paramFilterChain) throws IOException, ServletException {
System.out.println("doFilter");
HttpServletRequest request = (HttpServletRequest) paramServletRequest;
XssAndSqlHttpServletRequestWrapper xssRequestWrapper = new XssAndSqlHttpServletRequestWrapper(request);
// HttpServletResponse response = (HttpServletResponse)
// paramServletResponse;
// // 请求地址
// String requestURI = request.getRequestURI();
// System.out.println("requestURI:" + requestURI);
// // 参数
// Map<String, String[]> parameterMap = request.getParameterMap();
// for (String key : parameterMap.keySet()) {
// String[] arr = parameterMap.get(key);
// System.out.print("key:");
// for (String string : arr) {
// System.out.println(string);
// }
// }
paramFilterChain.doFilter(xssRequestWrapper, paramServletResponse);
}
/**
* 初始化
*/
public void init(FilterConfig paramFilterConfig) throws ServletException {
System.out.println("init");
}
}

  XssAndSqlHttpServletRequestWrapper

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils; /**
* 防止XSS攻击
*/
public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest request;
public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
public String getParameter(String name) {
String value = request.getParameter(name);
System.out.println("name:" + name + "," + value);
if (!StringUtils.isEmpty(value)) {
// 转换Html
value = StringEscapeUtils.escapeHtml4(value);
}
return value;
}
}