- 博主简介:想进大厂的打工人
- 博主主页:@xyk:
- 所属专栏: JavaEE初阶
目录
文章目录
一、什么是Cookie
二、什么是Session
三、Cookie和Session有什么不同
四、关于Sessionid
五、关于浏览器禁止Cookie
六、如何考虑分布式Session问题?
七、Servlet模拟登录
HTTP Cookie是浏览器在本地存储数据的一种机制,是服务器通过 Set-Cookie 字段发送到浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再次发送请求时被携带并一起发送到服务器上,它通常被用来保存当前用户的登录状态。
Cookie 主要用于以下三个方面:
会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
个性化设置(如用户自定义设置、主题等)
浏览器行为跟踪(如跟踪分析用户行为等)
那么在Cookie保存用户身份标识,这样的应用场景中,此时身份标识如何分配?以及身份信息具体如何存储?都是需要服务器的支持,这时候Session(会话)就出现了~
Session(会话)就是服务器用来实现用户身份区分的一种机制,通常是和Cookie配合使用的,Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的页面之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去,当客户端关闭会话,或者Session超时失效时会话结束。
1.作用范围不同,Cookie保存在客户端,Session保存在服务器端
2.存取方式不同,Cookie只能保存ASCII,Session可以存任意数据类型,一般我们可以在Session中存一些常用变量信息或者User对象等
3.有效期不同,Cookie可以设置长时间保持,比如我们经常使用的默认登录,Session一般失效时间较短,客户端关闭或者超时都会失效
4.隐私策略不同,Cookie在客户端,比较容易被获取,Session在服务端,安全性相对较好
5.存储大小不同,单个Cookie保持数据不能超过4K,Session可存储远高于Cookie
当浏览器第一次访问服务器时,服务器会根据用户提交的相关信息,创建一个Session对象,返回一个Cookie并且生成一个唯一标识 Sessionid 一起返回,可以理解为服务器端有一个全局的哈希表,Sessionid作为key,Session对象作为value。
当浏览器第二次访问服务器,请求会自动找到Cookie中的Sessionid信息,并且去服务器端的全局哈希表中查询是否存在Session对象,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
既然服务端是根据 Cookie 中的信息判断用户是否登录,那么如果浏览器中禁止了 Cookie,如何保障整个机制的正常运转?
1.每次请求中都携带一个 Sessionid的参数,也可以Post的方式提交,也可以在请求的地址后面拼接xxx?SessionID=123456...
2.Token 机制。Token 机制多用于 App 客户端和服务器交互的模式,也可以用于 Web 端做用户状态管理。
Token 的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。Token 机制和 Cookie 和 Session 的使用机制比较类似。
当用户第一次登录后,服务器根据提交的用户信息生成一个 Token,响应时将 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次登录验证。
在互联网公司为了可以支撑更大的流量,后端往往需要多台服务器共同来支撑前端用户请求,那如果用户在 A 服务器登录了,第二次请求跑到服务 B 就会出现登录失效问题。
分布式 Session 一般会有以下几种解决方案:
Nginx ip_hash 策略,服务端使用 Nginx 代理,每个请求按访问 IP 的 hash 分配,这样来自同一 IP 固定访问一个后台服务器,避免了在服务器 A 创建 Session,第二次分发到服务器 B 的现象。
Session 复制,任何一个服务器上的 Session 发生改变(增删改),该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点。
共享 Session,服务端无状态话,将用户的 Session 等信息使用缓存中间件来统一管理,保障分发到每一个服务器的响应结果都一致。
建议采用第三种方案。
在HttpServletRequest类中,可以使用getSession来获取或者创建会话,getCookies可以获取请求中的Cookie列表
方法 | 描述 |
HttpSession getSession() |
在服务器中获取会话. 参数如果为 true, 则当不存在会话时新建会话; 参数如果 为 false, 则当不存在会话时返回 null |
Cookie[] getCookies() |
返回一个数组, 包含客户端发送该请求的所有的 Cookie 对象. 会自动把 Cookie 中的格式解析成键值对. |
调用getSession方法所做的事情:
getSession 有一个 boolean 类型的参数, 如果参数是 true, 它有如下行为:
1.读取 cookie 里的 sessionId 字段.
2.根据 sessionld 来查询对应的 HttpSession 对象在服务器上是否存在.
3.如果不存在, 就创建一个新的会话, 即创建一个新的 HttpSession 对象, 并生成一个唯一的 sessionId, 会以新生成的 sessionId 作为 Key, 生成的 HttpSession 对象作为 Value, 以键值对形式储存到类似于 Hash 的结构中, 然后将 sessionId 设置到响应报文中的 set-Cookie 字段返回给浏览器.
4.如果存在就直接返回查询到的 HttpSession 对象.
如果参数是 false, 行为如下:
1.读取 cookie 里的 sessionId 字段.
2.根据 sessionld 来查询对应的 HttpSession 对象在服务器上是否存在.
3.如果不存在, 直接返回 null.
4.如果存在就直接返回查询到的 HttpSession 对象.
HttpSession对象
这个对象也可以看作是一个哈希表,是以键值对的形式存储数据的,我们可以往 HttpSession 中存任何我们需要的信息
方法 | 描述 |
Object getAttribute(String name) |
该方法返回在该 session 会话中具有指定名称的对象,如果没有 指定名称的对象,则返回 null. |
void setAttribute(String name, Object value) |
该方法使用指定的名称绑定一个对象到该 session 会话 |
boolean isNew() | 判定当前是否是新创建出的会话 |
我们这个涉及到两个页面,第一个是登录页面,第二个是登录成功后要跳转的主页面
登录页面包含两个输入框 (用来输入用户名和密码) 和一个登录按钮, 点击登录按钮就会发起一个 HTTP 请求, 服务器处理这个请求的时候就会验证用户名和密码, 验证通过就会跳转到主页, 主页就简单的显示出当前用户的用户名就行了.
涉及两个Servlet,一个是登录LoginServlet,一个是IndexServlet
第一步,约定前后端接口
约定用户名zhangsan,密码123,使用Post请求,响应采用302重定向
获取主页,采用Get请求,响应返回一个页面
前端页面:
登录界面
LoginServlet:
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 先从请求中拿到用户名和密码.
// 为了保证读出来的参数也能支持中文, 要记得设置请求的编码方式是 utf8
req.setCharacterEncoding("utf8");
String username = req.getParameter("username");
String password = req.getParameter("password");
//2.验证用户名密码是否正确
if (username == null || password == null || username.equals("") || password.equals("")){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前输入的用户名或密码不能为空!");
return;
}
// 此处假定用户名只能是 zhangsan 或者 lisi. 密码都是 123
// 正常的登录逻辑, 验证用户名密码都是从数据库读取的.
if (!username.equals("zhangsan") && !username.equals("lisi")){
//用户名有问题
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("用户名或密码有误");
return;
}
if (!password.equals("123")){
//密码有问题
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("用户名或密码有误");
return;
}
// 3. 用户名和密码验证 ok, 接下来就创建一个会话.
// 当前用户处于未登录的状态, 此时请求的 cookie 中没有 sessionId
// 此处的 getSession 是无法从服务器的 哈希表 中找到该 session 对象的.
// 由于此处把参数设为 true 了, 所以就允许 getSession 在查询不到的时候, 创建新的 session 对象和 sessionId
// 并且会自动的把这个 sessionId 和 session 对象存储的 哈希表 中.
// 同时返回这个 session 对象, 并且在接下来的响应中会自动把这个 sessionId 返回给客户端浏览器.
HttpSession session = req.getSession(true);
// 接下来可以让刚刚创建好的 session 对象存储咱们自定义的数据. 就可以在这个对象中存储用户的身份信息.
session.setAttribute("username",username);
// 4. 登录成功之后, 自动跳转到 主页
resp.sendRedirect("index");
}
}
IndexServlet:
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 此处禁止创建会话. 如果没找到, 认为用户是未登录的状态!!
// 如果找到了才认为是登录状态.
HttpSession session = req.getSession(false);
if (session == null){
//未登录状态
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前用户未登录!");
return;
}
String username = (String) session.getAttribute("username");
if (username == null){
// 虽然有会话对象, 但是里面没有必要的属性, 也认为是登录状态异常.
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前用户未登录!");
return;
}
// 如果上述检查都 ok, 接下来就直接生成一个动态页面.
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("欢迎你!"+username);
}
}
存session对象:
当再次访问时,可以看见Cookie中携带Sessionid,不用重新登录了~
关于 IDEA 集成的 Tomcat 环境有一些需要注意的点, 正常来说, 上面说的 sessionId 并不会一直存在下去, 比如 Tomcat 服务器重新启动的时候, 原来服务器在内存中维护的会话 Hash 表就应该没有了, 此时再次访问, 就应该出现 sessionld 查不到, 就被识别成 “未登录” 状态了。
但是有些版本 Smart Tomcat 为了方便程序猿调试程序, 会在停止服务器的时候把会话持久化保存, 并且在下次启动的时候自动把会话恢复到内存中, 在这种情况下会话是不丢失的.
但如果是手动部署程序到 Tomcat, 则会话默认还是在内存中, 重启服务器是会丢失的.