Servlet之Session单次会话登录案例


    Java-Servlet中提供两种会话管理技术——Cookie和HttpSession。
    两者在实现原理上并无差别,因为Session技术也是通过Cookie记录JSSIONID字段与值的方式实现的;但在服务支持方面则有所不同,Cookie技术是通过客户端浏览器(即:服务请求者)实现会话的维护,直至Cookie信息被销毁或者浏览器客户端Cookie缓存信息被手动清理时,才结束与服务器端的一次会话过程;Session技术则是通过服务器端(即:服务提供者)来实现会话状态的维持。
    下面以Session单次会话登录为例,诠释两种会话管理技术的区别,以及应用场景。

为什么需要会话管理技术?

    原因:HTTP(超文本传输协议)是一个无状态的协议。
    如何理解无状态?类似于“瞬时记忆”,即:客户端浏览器需要服务器端提供某种服务支持时,主动向服务器端发起HTTP请求,服务器端向客户端提供完服务之后,两者之间的连接通道就会被销毁。下次浏览器客户端再想要获取同样的服务支持时,只能够重新建立连接通道。

会话管理技术的应用场景?

    试想这样一个场景1:用户A在login.html登录页面,输入账户名和密码,借助浏览器Browser登录到网站B(由服务器端JavaEE程序C提供服务支持)首页index.html;在没有网站页面访问控制的情况下,用户AA直接通过浏览器客户端进入到index.html页面,这显然都是合理的操作。

在这里插入图片描述

在这里插入图片描述
    再试想这样一个场景:用户A在login.html登录页面,输入账户名和密码,借助浏览器Browser登录到网站B(由服务器端JavaEE程序C提供服务支持)首页index.html,查看包含个人隐私信息;而在没有网站页面访问控制的情况下,用户AA直接通过浏览器客户端进入到index.html页面,也看到了用户A的隐私信息,那这就显然都是不合理的操作,因为严格意义上来讲,每个用户都不希望自己的隐私信息被他/她人偷窥。

    剖析这个问题出现的原因:在于HTTP连接是无状态的。即:在进入index.html主页面时,无论是浏览器客户端B,还是服务器端JavaEE程序C,都无法从当前一次HTTP请求中获取信息凭证,对用户A和AA的身份进行校验,以此来选择是否授予用户A和AA访问index.html页面的权限。
    这时候,就需要会话管理技术来维持客户端和服务器端的HTTP会话连接状态,并提供相应的信息凭证,供其对用户XXX的身份进行校验,然后授予用户相应的页面访问权限。其中:①Cookie技术提供了对客户端浏览器维持会话状态的支持;②Session技术提供了对服务器端维护会话状态的支持。

在这里插入图片描述

Cookie和Session

Cookie会话管理技术

Cookie的具象形式

    Cookie,是由服务端发送给客户端的特殊信息,这些特殊信息以key-value键值对的形式出现的。通常每一个浏览器在磁盘上都有其存储Cookie信息的物理文件,例如:Google Chrome浏览器的Cookie信息文件被保存在C:\Users\adminUser\AppData\Local\Google\Chrome\User Data\Default目录下,文件名为:Cookies
在这里插入图片描述

Cookie的作用机制

    客户端每向服务器端发送一次请求,都会携带Cookies文件中保存的特殊信息;然后服务器端通过解析Cookie中的信息,来进行相应的身份信息甄别或者是其它判断,从而实现不同的权限控制操作。
    具体流程如下:/font>

[1] 客户端A首次登录到支持Cookie技术的网站B(网站B由部署在Web容器C中的JavaEE后端程序D支持),并向服务
器端提交了账户名-uname、密码-pswd等个人信息;

[2] 由于Web容器(以Tomcat为例)是单进程多线程的机制,所以Web容器每捕捉到一个来自客户端的HTTP请求,会
自动将HTTP请求信息封装为HttpServletRequest对象,并创建HttpServletResponse对象;再根据其URL访问
路径,从web.xml配置文件中找到对应的Servlet实现类,创建类的对象,然后才会将此次HTTP请求、
HttpServletRequest对象和HttpServletResponse对象交由Servlet对象处理;

[3]Web容器C将HTTP请求交给JavaEE后端程序D的Servlet实现子类E,E根据客户端A提交的账户名-uname、
密码-pswd信息进行登录验证。如果验证成功,则从HttpServletRequest对象中获取Cookie对象,并将用户
信息uname写入Cookie中,最后打包到HttpServletResponse对象中返回给客户端;

[4]客户端A成功登录到网站B之后,想要访问网站B的主页index.html,于是,再次向服务器端发送请求,此时,
这个请求中就包含了用户A的身份信息,JavaEE后端程序的Servlet子类在接收到请求任务之后,就很容易通过
解读Cookie对象,获取用户A的身份信息,并根据身份标识从数据库中获取相应的数据,响应给浏览器端,以
保证index.html页面内容的正常渲染。

在这里插入图片描述
在这里插入图片描述

Cookie类的解析

    Cookie会话管理技术,具体到代码层面,需要javax.servlet.http包下color=blue>javax.servlet.http类的支持。
    

---->Servlet源码中对Cookie描述如下:
    Creates a cookie, a small amount of information sent by a servlet to a Web browser, saved by the browser, and later sent back to the server. 
    一个Cookie对象有一个名称(name)、值(value),也包含一些可选属性:comment, path and domain qualifiers, a maximum age, and a version number.但是一些浏览器在处理这些可选项时存在漏洞,因此要慎重使用这些可选项来提升Servlet的交互性。
    
    【1】服务器--->浏览器:Servlet对象通过调用HttpServletResponse.addCookie(javax.servlet.http.Cookie)方法向浏览器发送Cookie,addCookie方法是通过向响应头(Response Header)添加字段的方式实现Cookie的发送任务的。
    【2】浏览器--->服务器:浏览器通过向HTTP请求头添加字段的方式返回Cookie给服务器端,这样,在Servlet类中,就可以通过HttpServletRequest.getCookies()方法取到Cookie。
    影响:Cookie会对使用它的网页页面缓存产生影响,
    
    Cookie类的源码如下:其所有方法都是围绕Cookie的必选参数name-value对,以及可选参数comment、domain、maxAge、path等可选项设定的setter和getter方法。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package javax.servlet.http;

import java.io.Serializable;
import java.security.AccessController;
import java.util.Locale;

public class Cookie implements Cloneable, Serializable {
    private static final CookieNameValidator validation;
    private static final long serialVersionUID = 1L;
    private final String name;
    private String value;
    private int version = 0;
    private String comment;
    private String domain;
    private int maxAge = -1;
    private String path;
    private boolean secure;
    private boolean httpOnly;

    public Cookie(String name, String value) {
        validation.validate(name);
        this.name = name;
        this.value = value;
    }

    public void setComment(String purpose) {
        this.comment = purpose;
    }

    public String getComment() {
        return this.comment;
    }

    public void setDomain(String pattern) {
        this.domain = pattern.toLowerCase(Locale.ENGLISH);
    }

    public String getDomain() {
        return this.domain;
    }

    public void setMaxAge(int expiry) {
        this.maxAge = expiry;
    }

    public int getMaxAge() {
        return this.maxAge;
    }

    public void setPath(String uri) {
        this.path = uri;
    }

    public String getPath() {
        return this.path;
    }

    public void setSecure(boolean flag) {
        this.secure = flag;
    }

    public boolean getSecure() {
        return this.secure;
    }

    public String getName() {
        return this.name;
    }

    public void setValue(String newValue) {
        this.value = newValue;
    }

    public String getValue() {
        return this.value;
    }

    public int getVersion() {
        return this.version;
    }

    public void setVersion(int v) {
        this.version = v;
    }

    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException var2) {
            throw new RuntimeException(var2);
        }
    }

    public void setHttpOnly(boolean httpOnly) {
        this.httpOnly = httpOnly;
    }

    public boolean isHttpOnly() {
        return this.httpOnly;
    }

    static {
        boolean strictServletCompliance;
        String propStrictNaming;
        String propFwdSlashIsSeparator;
        if (System.getSecurityManager() == null) {
            strictServletCompliance = Boolean.getBoolean("org.apache.catalina.STRICT_SERVLET_COMPLIANCE");
            propStrictNaming = System.getProperty("org.apache.tomcat.util.http.ServerCookie.STRICT_NAMING");
            propFwdSlashIsSeparator = System.getProperty("org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR");
        } else {
            strictServletCompliance = (Boolean)AccessController.doPrivileged(() -> {
                return Boolean.valueOf(System.getProperty("org.apache.catalina.STRICT_SERVLET_COMPLIANCE"));
            });
            propStrictNaming = (String)AccessController.doPrivileged(() -> {
                return System.getProperty("org.apache.tomcat.util.http.ServerCookie.STRICT_NAMING");
            });
            propFwdSlashIsSeparator = (String)AccessController.doPrivileged(() -> {
                return System.getProperty("org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR");
            });
        }

        boolean strictNaming;
        if (propStrictNaming == null) {
            strictNaming = strictServletCompliance;
        } else {
            strictNaming = Boolean.parseBoolean(propStrictNaming);
        }

        boolean allowSlash;
        if (propFwdSlashIsSeparator == null) {
            allowSlash = !strictServletCompliance;
        } else {
            allowSlash = !Boolean.parseBoolean(propFwdSlashIsSeparator);
        }

        if (strictNaming) {
            validation = new RFC2109Validator(allowSlash);
        } else {
            validation = new RFC6265Validator();
        }

    }
}

Session会话管理技术

    Session会话管理技术,是通过服务端来实现会化状态的维持。落实到代码上,需要javax.servlet.http包下HttpSession接口的支持。


HttpSession接口解读

    HttpSession:Provides a way to identify a user across more than one page request or visit to a Web site and to store information about that user.——HttpSession提供了一种跨页面请求或者访问网站页面时,存储和读取在服务器端存储的用户信息——以识别不同用户(身份)的方式。

  • HttpSession对象的创建与与存储方式?
    HttpSession是位于javax.servlet.http包下的一个interface,无法通过new关键字创建对象。
    其对象创建:由Servlet容器(web容器/Tomcat)完成,用于表示HTTP客户端和HTTP服务器端之间的一个Session会话。
  • 如何理解一次Session会话?
    A session usually corresponds to one user, who may visit a site many times.
    一个会话通常对应于一个用户,该用户可能会多次访问一个站点。
    而一个会话,就是指HTTP客户端和HTTP服务器端之间的一次Request请求和Response响应过程。
    The server can maintain a session in many ways such as using cookies or rewriting URLs.——服务器端有多种方式可以来维持一次会话,例如:Cookie或者重写URL
  • HttpSession接口的作用?
    This interface allows servlets to
    • View and manipulate information about a session, such as the session identifier, * creation time, and last accessed time(查看和操作有关会话的信息,如会话标识符、创建时间和最后一次访问的时间)
    • Bind objects to sessions, allowing user information to persist across multiple user connections(将一个Session对象绑定到会话,从而允许用户信息在多个用户连接之间持久存在 )
    • 通常使用HttpSession对象保存当前登录的用户、用户权限以及其它信息等
  • HttpSession-会话信息保存的实现机制?
    When an application stores an object in or removes an object from a session, the session checks whether the object implements HttpSessionBindingListener.
    当应用程序在会话中存储对象或从会话中删除对象时,会话将检查该对象是否实现了Httpsessionbindinglistener接口.
    If it does, the servlet notifies the object that it has been bound to or unbound from the session. Notifications are sent after the binding methods complete. For session that are invalidated or expire, notifications are sent after the session has been invalidated or expired.
    如果是,servlet将通知对象它已被绑定或从会话取消绑定。在绑定方法完成后,将发送通知。对于无效或过期的会话,会在会话失效或过期后发送通知。
  • HttpSession的作用范围?
    作用范围:在整个ServletContext范围内(即当前整个web应用程序范围内)有效。——即:一个web应用的HttpSession对象是被所有Servlet所共享的。
    Session information is scoped only to the current web application (ServletContext), so information stored in one context will not be directly visible in another.
  • HttpSession-会话结束条件?
    情况1:浏览器客户端的Cookie中没有携带字段为JESSIONID的属性;-浏览器关闭/手动清除Cookie(Cookie默认会在浏览器关闭之后被清除,JESSIONID自然也被清除)
    情况2:服务器丢失HttpSession;-Tomcat服务器重启/到达最大不活动时间30mins(用户无操作超30分钟)/手动销毁HttpSession

HttpSession接口对象的创建与禁用

    通过以上的解读知:HttpSession是一个接口,无法通过new关键字主动创建对象。但是,HttpSession接口的对象创建工作由Web容器完成,即:每当客户端浏览器和服务器端建立了一次新的HTTP连接时,就会自动创建HttpSession接口的对象,这个HttpSession对象可以通过HttpServletRequest参数的getSession()方法获取。例如:

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doPost(req, resp);
        //设置request解析参数的编码格式
        req.setCharacterEncoding("UTF-8");
        //获取Session
        HttpSession session = req.getSession();
        //获取用户信息
        User user = (User)session.getAttribute("user");
        //判断用户是否为空
        if (user!=null){
            //获取用户名
            String usernmae = user.getUsernmae();
            if (null!=usernmae){
                //跳转到index.html
                req.getRequestDispatcher("WEB-INF/pages/index.html").forward(req,resp);
            }
        }else {
            //用户信息为空,跳转到登录页面
            resp.sendRedirect(req.getContextPath()+"/login.html");
        }
    }
    但是,也可以通过调用HttpSession接口的invalidate()方法,禁用Session会话信息,从而释放与当前HttpSession对象绑定的所有信息。
invalidate
public void invalidate()
Invalidates this session then unbinds any objects bound to it.
Throws:
java.lang.IllegalStateException - if this method is called on an already invalidated session

Session单次会话登录案例

案例需求描述

    通过HttpSession实现控制用户登录、退出index.html的功能,要求:用户信息存储到HttpSession对象中,以便于获取用户的身份信息,控制用户的访问权限。

————配置当前Web项目的欢迎页默认为login.html或者login.jsp
【1】用户登录时,输入账户和密码,服务端程序通过HttpSession对象记录用户身份信息,登录成功后进入index.html页面;
【2】用户退出时,服务器端程序通过调用invalidate()方法,禁用Session;
【3】在用户未登录的情况下,想要访问index.html以及任何其它页面,通过服务器后端程序控制其不允许访问,并跳转到login.html页面。

项目结构

在这里插入图片描述

前端页面准备

登录页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
</head>
<body>
    <form action="loginservlet" method="post">
        <label for="uname">账户:</label><input type="text" placeholder="请输入账户" id="uname" name="username"/>
        <label for="uname">密码:</label><input type="password" placeholder="请输入密码" id="pswd" name="password"/>
        <input type="submit" name="登录" value="登录"/>
    </form>
</body>
</html>

index.html主页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
    <p>welcome to index.html!</p>
</body>
</html>

后端Servlet代码

LoginServlet

package com.xwd.main.servlet;

import com.xwd.main.pojo.User;

import javax.servlet.*;
import java.io.IOException;

/**
 * @ClassName Login
 * @Description: com.xwd.main.servlet
 * @Auther: xiwd
 * @Date: 2022/2/6 - 02 - 06 - 18:27
 * @version: 1.0
 */
@WebServlet(
        name = "loginservlet",
        value = {
                "/loginservlet"
        }
)
public class LoginServlet extends HttpServlet {
    //methods
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doPost(req, resp);
        //设置参数解析编码格式
        req.setCharacterEncoding("UTF-8");
        //获取HttpSession
        HttpSession session = req.getSession();
        //获取请求参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        //判断是否为空
        if (username!=null&&password!=null){
            if ("root".equals(username)&&"root".equals(password)){
                //登陆成功
                //设置session
                User user=new User(username,password);
                session.setAttribute("user",user);
                //跳转到index.html(WEB-INF下的资源)
                req.getRequestDispatcher("WEB-INF/pages/index.html").forward(req,resp);
            }else {
                //登陆失败
                resp.sendRedirect(req.getContextPath()+"/login.html");
            }
        }else {
            resp.sendRedirect(req.getContextPath()+"/login.html");
        }
    }
}

LoginOutServlet

package com.xwd.main.servlet;

import javax.servlet.*;
import java.io.IOException;

/**
 * @ClassName LoginOutServlet
 * @Description: com.xwd.main.servlet
 * @Auther: xiwd
 * @Date: 2022/2/6 - 02 - 06 - 20:26
 * @version: 1.0
 */
@WebServlet(
        name = "loginoutservlet",
        value = {
                "/loginoutservlet"
        }
)
public class LoginOutServlet extends HttpServlet {
    //methods
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doPost(req, resp);
        //设置请求参数解析编码格式
        req.setCharacterEncoding("UTF-8");
        //获取Session对象
        HttpSession session = req.getSession();
        //禁用session
        session.invalidate();
        //自动跳转到login.html页面
        resp.sendRedirect(req.getContextPath()+"/login.html");
    }
}

IndexServlet

package com.xwd.main.servlet;

import com.xwd.main.pojo.User;

import javax.servlet.*;
import java.io.IOException;

/**
 * @ClassName IndexServlet
 * @Description: com.xwd.main.servlet
 * @Auther: xiwd
 * @Date: 2022/2/6 - 02 - 06 - 18:48
 * @version: 1.0
 */
@WebServlet(
        name = "indexservlet",
        value = {
                "/indexservlet"
        }
)
public class IndexServlet extends HttpServlet {
    //methods

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doPost(req, resp);
        //设置request解析参数的编码格式
        req.setCharacterEncoding("UTF-8");
        //获取Session
        HttpSession session = req.getSession();
        //获取用户信息
        User user = (User)session.getAttribute("user");
        //判断用户是否为空
        if (user!=null){
            //获取用户名
            String usernmae = user.getUsernmae();
            if (null!=usernmae){
                //跳转到index.html
                req.getRequestDispatcher("WEB-INF/pages/index.html").forward(req,resp);
            }
        }else {
            //用户信息为空,跳转到登录页面
            resp.sendRedirect(req.getContextPath()+"/login.html");
        }
    }
}

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <welcome-file-list>
        <welcome-file>login.html</welcome-file>
        <welcome-file>login.jsp</welcome-file>
    </welcome-file-list>

</web-app>

案例测试

    访问欢迎页面:

在这里插入图片描述
    登录到index.html主页:
在这里插入图片描述

    退出登录,返回login.html登录页:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是席木木啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值