Cookie 和 Session
状态管理
现有问题
- Http 协议是无状态的,不能保存每次提交的信息。
- 如果用户发来一个新的请求,服务器知道它是否与上次的请求有联系。
- 对于那些需要多次提交的数据才能完成的Web 操作,比如说登录来说,就成问题了。
状态管理概念:
将浏览器与web 服务器之间多次交互当做一个整体来处理,并且将多次交互所涉及的数据(即状态)保存下来。
简单来说:就是 记录客户端和服务器之间多次交互的状态。
状态管理分类:
- 客户端状态管理:将状态保存在客户端,代表性的是 Cookie 技术。
- 服务器状态管理技术:将状态保存在服务器端,代表性的是 Session 技术(服务器传递 sessionID 时需要使用Cookie 的方式 和 application)
Cookie
1、什么是 Cookie#
- Cookie 是在浏览器访问 web 服务器的某个资源时,由 web 服务器在 HTTP 响应头中附带传送给 浏览器的一小段数据。
- 一旦 浏览器 保存了某个Cookie ,那么它 以后每次访问该 web 服务器时,都应该在 Http 请求头中将这个 Cookie 回传给服务器。
- 一个 Cookie 主要有 该信息的名称 (name)和 值(value)组成。
创建 Cookie ,然后响应的时候携带,让客户端保存,再次请求的时候,携带 Cookie 信息。让请求的 Servlet 获取。
2、创建 Cookie#
// 创建 Cookie
Cookie cookie = new Cookie("code",code);
// 设置 Cookie 的路径(哪些可以访问)
cookie.setMaxAge(-1); // 取值有三种:>0 单位:秒; =0 浏览器关闭; <0 内存存储,默认 -1 ;
// 将 Cookie 响应给客户端,可以是一个也可以是多个
response.addCookie(cookie); // 添加到 response 对象中,响应时发送给客户端
3、获取 Cookie#
// 通过 request 对象获取所有的 cookie
Cookie[] cookies = req.getCookies();
// 通过 循环遍历数组Cookie
/* for (Cookie cookie : cookies) {
System.out.println(cookie.getName()+":"+cookie.getValue());
}*/
// 但是有可能传入 的cookie 是一个空,那么如何遍历呢,或者说如何避免空指针异常呢?
// 简单:加个判断就好
if (cookies != null){
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + ":" + cookie.getValue());
}
}
3、修改 Cookie#
只需要保证 Cookie 的名称和路径是一致的。这时会认为修改操作。
// 创建 Cookie
Cookie cookie = new Cookie("code","code");
cookie.setPath("/webs"); // 必须是一致的,不一样就新建了个 cookie
cookie.setMaxAge(-1);
response.addCookie(cookie);
【注意】:如果改变 name 和有效路径会新建 cookie;而改变 cookie 的 value 、有效期,会覆盖原有的 cookie。
很简单。。。
4、Cookie 的编码与解码#
Cookie 默认不支持中文,只能包含 ASCII 字符,所以 cookie 需要对 Unicode 字符进行编码,否则会出现乱码。
- 编码使用
java.net.URLEncoder
类中的,encode(String str,String encoding)
方法。(对指定的字符串,用指定的编码格式编码) - 解码使用
java.net.URLDecoder
类的decode(String str,String encoding)
方法。
创建带中文的 cookie(编码)#
Cookie cookie = new Cookie("姓名", "张三"); // cookie 不支持
cookie.setPath("/webproject/get");
cookie.setMaxAge(60 * 60);
resp.addCookie(cookie);
需要设置 编码格式
Cookie cookie = new Cookie(
URLEncoder.encode("姓名","utf-8"),
URLEncoder.encode("张三","utf-8"));
cookie.setPath("/webproject/get");
cookie.setMaxAge(60 * 60);
resp.addCookie(cookie);
读取带中文的 cookie (解码)#
同时也需要设置,解码方式:(不然出现的是%E5%A7%93%E5%90%8D:%E5%BC%A0%E4%B8%89)
if (cookies != null) {
for (Cookie cookie : cookies) {
System.out.println(
URLDecoder.decode(cookie.getName(), "utf-8")
+ ":" + URLDecoder.decode(cookie.getValue(), "utf-8"));
}
}
总结#
优点:
- 可配置到期规则
- 简单性:Cookie 是一种基于文本的轻量结构,包含简单的键值对。
- 数据持久性:cookie 默认在过期之前是可以一直存在客户端浏览器上。
缺点:
- 大小收到限制:大多数浏览器对cookie 的大小有 4k,到8k 字节的限制。
- 有些用户配置禁用,就会限制 cookie 功能
- 潜在安全风险,cookie 可能会被篡改,会对安全性造成风险或者导致依赖于cookie 的应用程序失败。
Session
概述#
- Session 用于记录用户的状态,Session 指的是在一段时间内,单个客户端与 web 服务器的一连串相关的交互过程。
- 在一个 Session 中,客户可能会多次请求访问同一个资源,也有可能请求访问各种不同的服务器资源。
Session 原理#
- 服务器会为每一次会话分配一个 Session 对象。
- 同一个浏览器发起的多次请求,同属于一次会话。
- 首次使用到 Session 时,服务器会自动创建 Session,并创建 Cookie 存储 SessionID ,并且回传给客户端。
注意:Session 是服务端创建的。
首次请求:在响应中设置 cookie
未关闭浏览器,再次请求:请求中携带 Session
Session 的使用#
session 是服务器的一个 map 集合。
- Session 作用域:拥有存储数据的空间,作用范围是一次会话有效。
- 一次会话:是使用统一浏览器发送的多次请求,一旦浏览器关闭,则会话结束。
- 可以将数据存入 Session 中,在一次会话的任意位置进行获取。
- 可传递任何数据(基本数据类型,对象,集合,数组)
浏览器在没有关闭的情况下,访问同一个资源,拿到的是同一个 SessionID
1、获取 Session 对象#
// 获取 Session 对象
HttpSession session = request.getSession();
// 唯一标识
System.out.println("session :"+session.getId());
2、Session 保存数据#
setAttribute(属性名,Object)保存数据到 session 中
session.setAttribute("key",value); // 以键值对形式存储在 session 作用域中
3、Session 获取数据#
getAttribute(属性名);
获取 session 中的数据
session.setAttribute("key"); // 通过String 类型的key 访问Object 类型的 value
4、Session 移除数据#
removeAttribute(属性名);
从session 中移除数据
removeAttribute("key"); // 通过键移除session 作用域中的值
Session 和 Request 应用区别#
区别#
-
request 是一次请求有效,请求改变,则 request 改变。
-
session 是一次会话有效,只要浏览器不关闭,那么 Session 是不会改变的。
实例
@WebServlet(value = "/ss")
public class SessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取 Session 对象
HttpSession session = req.getSession();
System.out.println("session :"+session.getId());
session.setAttribute("name", "Asia");
// 用 request 域传递数据
req.setAttribute("password", "123");
resp.sendRedirect("/webproject/getvalue");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
获取值的 Servlet
@WebServlet(value = "/getvalue")
public class GetValue extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取 Session 对象
HttpSession session = req.getSession();
// 获取 Session 数据
String name = (String) session.getAttribute("name");
System.out.println("name:" + name);
// 获取 request 域中的 数据,这里因为采取的是定向的方式,所以是获取不到的
String password = (String) req.getAttribute("password");
System.out.println(password);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
结果:(先访问 /ss,然后重定向到 /getvalue )以证明request 和 session 的区别。
session :F41D767BD803DDEBDE5E9AB2F6C9B819
name:Asia
null
Session 的生命周期#
- 开始:第一次使用到Session 的请求产生,则创建 Session
- 结束:
- 浏览器关闭,失效
- Session 超时,失效
session.setMaxInactiveInterval(60 * 60);
- 手动销毁,失效
session.invalidate();
// 登录退出、注销
@WebServlet(value = "/gets")
public class getSessionLife extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取 Session 对象
HttpSession session = req.getSession();
// 设置 session 有效时间
// session.setMaxInactiveInterval(10); // 以秒为单位
System.out.println("第一次获取 sessionID:" + session.getId());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
@WebServlet(value = "/secget")
public class SecondGetLife extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取 Session 对象
HttpSession session = req.getSession();
System.out.println("第二次获取 sessionID :" + session.getId()); // 因为我是在一次会话请求的这个 Servlet,同时我还设置了 10 秒超时,所以 10 秒后,超时了,第二次获取到的 session 和第一次是不一样的。
// 手动销毁失效 手动销毁后,在此获取到 session 是 新session
session.invalidate();
/*
* 第一次获取 sessionID:969168F6CD8A47168209EB8D13E438E0
第二次获取 sessionID :969168F6CD8A47168209EB8D13E438E0
再次访问第一次获取的 servlet: sessionID:99C5CAFCE6BD0287F7CBC8B6621A92AB
* */
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
浏览器禁用 Cookie 解决方案#
浏览器禁用 cookie 的后果
现在大家都很喜欢将 cookie 全部禁用了,防止在看学习资料的时候,被别人发现。
服务器默认情况下,会使用 cookie 的方式将 sessionID 发送给浏览器,我们禁止了,sessionID 就不会保存,那我们多次获取到的 sessionID 就是不同了,这就属于一次会话,多个 sessionID ,这当然是不行的了。
解决方法:URL 重写
浏览器在访问服务器上的某个地址时,不再使用原来的那个地址,而是使用经过改写的地址(即在原来的地址后面加上了 sessionID)
实现:
response.encodeRedirectURL(String url)
生成重写的 URL
@WebServlet(value = "/gets")
public class getSessionLife extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取 Session 对象
HttpSession session = req.getSession();
// 设置 session 有效时间
// session.setMaxInactiveInterval(10); // 以秒为单位
// 生成 重写 URL
String newURI = resp.encodeRedirectURL("/webproject/secget");
System.out.println("第一次获取 sessionID:" + session.getId());
System.out.println(newURI);
resp.sendRedirect(newURI);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
/*
第一次获取 sessionID:62DFA4B0640D24471A0B00FAB4D3C948
/webproject/secget;jsessionid=62DFA4B0640D24471A0B00FAB4D3C948
第二次获取 sessionID :62DFA4B0640D24471A0B00FAB4D3C948
*/
做到了 即使客户端禁用了 cookie ,我仍旧可以传 serssionID 来保证,两次请求都属于同一个 session 会话。
Session 应用场景#
1、保存用户登录信息#
写一个管理员的实体类,mapper,service,controller,挨个写,在 controller 中做判断。
if (mgr != null){
// 登录成功
// 将管理员信息存在 Session 里面 (这样我可以在任何位置,访问管理员的信息)
// 有效期是一次会话
HttpSession session = req.getSession();
session.setAttribute("mgr", mgr);
// 跳转,目标,方式(通过重定向的方式,跳转到 showallController)
resp.sendRedirect("/webproject/showAllController");
} else {
// 登录失败
resp.sendRedirect("/webproject/loginMgr.html");
}
在 showAllController 中,也要做判断,如果登录过的话,可以直接访问,没有登录,重定向到登录页面,这样做是为了防止,用户直接访问 showAllController.
// 通过 HttpSession 完成权限的控制
HttpSession session = req.getSession();
Manager mgr = (Manager) session.getAttribute("mgr");
// 如果登录了,执行业务,没有登录,则登录失败,跳转到登录页面。
if (mgr != null){
System.out.println("进入 /showAllController doGet");
AdminService adminService = new AdminServiceImpl();
List<User> userList = adminService.showAllUser();
// request 作用域存数据
req.setAttribute("user", userList);
// 通过转发,跳转到显示结果的 servlet
req.getRequestDispatcher("/showAlljsp").forward(req, resp);
} else{
// 登录失败,转到登录页面登录
resp.sendRedirect("/webproject/loginMgr.html");
}
同时因为是一次会话,所以我们可以在任意位置,获取,session 的信息,session 存的是管理员信息,所以可以做到,显示 welcome ,管理员! 这样的效果。
2、保存验证码#
- 导入 validateCode.jar
- 创建生成验证码 的 servlet
思路:
创建生成验证码的 servlet ,创建验证码图片和响应给客户端,这时候,在 logMgr.html 页面已经有验证码了,接下来,我们就需要在 loginMrgController 里面首先收参,然后判断验证码,是否正确,接着再进行后续业务的比较。
【注意】:创建 验证码 生成的 servlet 的时候,将 验证码获取,然后存在 session 里面,然后在 loginMgrController 里面,获取session 中存的 验证码,然后进行比较。
login.html
<form action="/webproject/loginMgr" method="post">
用户名:<input type="text" name="username"><br>
密 码:<input type="password" name="password"><br>
验证码:<input type="text" name="inputVcode"/><img src="/webproject/createCode"><br> <!--这里已经对createCode 做了一次请求,响应。再次点击登录,那就是两次请求,所以,不能用request 存验证码数据-->
<input type="submit" value="登录">
</form>
验证码生成 servlet
@WebServlet(value = "/createCode")
public class CreateCodeController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 创建验证码图片
ValidateCode vc = new ValidateCode(150, 30, 5, 10);
// 2. 验证码图片响应给客户端
vc.write(resp.getOutputStream());
// 3. 创建session 保存 验证码
// 获取验证码生成 的5个字符 在 ValidateCode中 有个 getcode方法
String codes = vc.getCode();
HttpSession session = req.getSession();
session.setAttribute("code",codes);
}
loginMgrController servlet
@WebServlet(value = "/loginMgr")
public class LoginMgrController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 处理乱码
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// 2. 收参
String username = req.getParameter("username");
String password = req.getParameter("password");
String inputVcode = req.getParameter("inputVcode");
// 首先判断,验证码,是否正确,然后,接着判断业务逻辑
// 从session 中 获取随机生成的验证码,然后我们在调用业务方法,进行后续的比较
String code = (String) req.getSession().getAttribute("code");
if (inputVcode != null && inputVcode.equalsIgnoreCase(code)) {
// 3. 调用业务方法
ManagerServiceImpl managerService = new ManagerServiceImpl();
Manager mgr = managerService.login(username, password);
// 4. 处理结果,流程跳转
if (mgr != null) {
// 登录成功
// 将管理员信息存在 Session 里面 (这样我可以在任何位置,访问管理员的信息)
// 有效期是一次会话
HttpSession session = req.getSession();
session.setAttribute("mgr", mgr);
// 跳转,目标,方式(通过重定向的方式,跳转到 showallController)
resp.sendRedirect("/webproject/showAllController");
} else {
// 登录失败
resp.sendRedirect("/webproject/loginMgr.html");
}
}else {
resp.sendRedirect("/webproject/loginMgr.html");
}
}
作者:走马
出处:https://www.cnblogs.com/zou-ma/p/16303376.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
本博文版权归本博主所有,未经授权不得转载
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?