Cookie&Session
目标:
- 理解什么是会话跟踪技术
- 掌握Cookie的使用
- 掌握Session的使用
- 完善用户登录注册案例的功能
# 会话跟踪技术概述
会话跟踪这个词,我们比较陌生,这四个字我们需要拆开来解释,首先要理解什么是会话,然后再去理解会话跟踪。
- 会话:用户打开浏览器,访问Web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
- 从浏览器发出请求到服务器端响应数据给前端之后,一次会话(在浏览器和服务器之间)就被建立了
- 会话建立后,如果浏览器或服务器端都没有被关闭,则会话就会保持
- 浏览器和服务器就可以继续使用该会话进行发送请求和响应,上述的整个过程就被称之为会话。
下面我们用实际场景来理解下会话,比如我们在访问京东的时候,当打开浏览器进入京东首页后,浏览器和京东的服务器之间就建立了一次会话,后面的搜索商品,查看商品详情,加入购物车等都是在这一次会话中完成的。
思考:下图建立了几个会话?
每个浏览器都会与服务器建立一个会话,加起来总共是3个会话
- 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一个浏览器,以便在同一次会话的多次请求间共享数据。
- 服务器会收到多个请求,这多个请求可能来自多个浏览器,如上图中的6个请求来自3个浏览器
- 服务器需要用来识别请求是否来自同一个浏览器
- 服务器用来识别浏览器的过程,这个过程就是会话跟踪
- 服务器识别浏览器后就可以在同一个会话中的多次请求之间来共享数据
思考:为什么要在一个会话中的多次请求之间共享数据?有了数据共享功能我们可以实现哪些功能?
购物车:加入购物车和去购物车结算是两次请求,但是后面的请求要展示前一次请求所添加的商品,就要用到数据共享
页面展示用户登录信息:很多网站,登录后访问多个功能发送多次请求后,浏览器是哪个都会有当前登录用户的信息用户名、头像等信息
网站登录页面的记住我功能:当用户登录成功后,勾选记住我按钮后下次再登录的时候,网站就会自动填充用户名和密码,简化用户的登录操作,多次登录就会有多次登录请求,它们之间也涉及到共享数据
登录页面的验证码功能:生成验证码和输入验证码点击注册也是两次请求,这两次请求的数据之间要进行对比,相同则允许注册,不同则拒绝注册,该功能的实现也需要在同一次会话中共享数据
通过上面几个例子的讲解,相信大家对会话跟踪技术已经有了简单的理解,该技术在实际开发中也非常重要,接下来我们就来学习下会话跟踪技术,在开始学习之前,我们思考一个问题:为什么浏览器和服务器之间不支持数据共享?
- 浏览器和服务器之间使用的是
HTTP
请求来进行数据传输 HTTP
协议是无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求HTTP
协议设计成无状态的目的是让每次请求之间相互独立,互不影响- 请求和请求之间相互独立后,就无法实现多次请求之间的数据共享
分析完具体的原因后,那么浏览器和服务器之间该如何实现会话跟踪呢?具体的实现方式有:
- 客户端会话跟踪技术:Cookie
- 服务器端会话跟踪技术:Session
这两个技术都可以实现会话跟踪,它们之间最大的区别:Cookie是存储在浏览器端,而Session是存储在服务器端
具体的学习步骤为:
- Cookie的基本使用、原理、使用细节
- Session的基本使用、原理、使用细节
- Cookie和Session的综合案例
小结
在本小节中,我们主要介绍了什么是会话和会话跟踪技术,需要注意的是:
- HTTP协议是无状态的,靠HTTP协议是无法实现会话跟踪
- 要实现会话跟中,需要用到Cookie和Session
接下来,我们先从Cookie开始学习
# Cookie
学习Cookie,我们主要解决下面几个问题:
- 什么是Cookie?
- Cookie如何使用
- Cookie是如何实现的?
- Cookie的使用有哪些注意事项?
# Cookie基本使用
- 概念:
Cookie:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问。
- Cookie的工作流程:
- 服务器端提供了两个
Servlet
,分别是ServletA
和ServletB
- 浏览器发送HTTP请求1给服务器,服务器
ServletA
接收请求并进行业务处理 - 服务器
ServletA
在处理的过程中可以创建一个Cookie
对象并将name=zs
的数据存入Cookie
- 服务器端
ServletA
在响应数据的时候,会把Cookie
对象响应给浏览器 - 浏览器接收到响应数据,会把
Cookie
对象中的数据存储到浏览器内存中,此时浏览器和服务器就建立了一次会话 - 在同一次会话中浏览器再次发送HTTP请求2给服务器
ServletB
,此时浏览器会携带Cookie
对象中的所有数据 ServletB
接收到请求和数据后,就可以获取到存储在Cookie
对象中的数据,这样一个会话中的多次请求之间就实现了数据共享。
- Cookie的基本使用:
对于Cookie
的使用,我们的关注点应该放在后台代码如何操作Cookie
,对于Cookie
的操作主要分为两类:发送Cookie和获取Cookie。
发送Cookie:
- 创建
Cookie
对象,并设置数据:
Cookie cookie = new Cookie("key", "value");
- 发送
Cookie
到客户端:使用Response对象
response.addCookie(cookie);
介绍完发送Cookie
对应的步骤后,下面通过一个案例来学习Cookie
的发送
案例需求:在Servlet
中生成Cookie
对象并存入数据,然后将数据发送给浏览器
具体步骤为:
- 创建Maven项目,并在
pom.xml
中添加所需依赖- 编写
Servlet
类,名称为AServlet
- 在
AServlet
中创建Cookie
对象,存入数据,发送给前端- 启动测试,在浏览器查看
Cookie
对象中的值
- 创建Maven项目,在
pom.xml
添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>Course</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!-- jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
- 编写
AServlet
类,在com.itheima.web
包下创建该类
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 在
AServlet
中创建Cookie
对象,存入数据,发送给前端
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 创建Cookie对象
Cookie cookie = new Cookie("username", "sunyy");
// 2. 发送Cookie
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 启动服务器,访问http://localhost/course/aServlet,在浏览器查看
Cookie
对象中的值,Chrome浏览器查看Cookie
,可以通过使用快捷键F12
或者Ctrl+Shift+i
组合键
获取Cookie
:
获取客户端携带的所有
Cookie
数据,使用Request对象Cookie[] cookies = request.getCookies();
1遍历数组,获取每一个
Cookie
对象:for
循环使用
Cookie
对象的方法获取数据cookie.getName(); cookie.getValue();
1
2
介绍完获取Cookie
对应的步骤后,同样我们通过一个案例来学习如何获取Cookie
:
案例需求:在Servlet
中获取前一个案例存入在Cookie
对象中的数据
实现步骤:
- 编写一个新的
BServlet
类- 在
BServlet
中使用request
对象获取Cookie
数组,遍历数组,从数据中获取指定名称对应的值- 启动测试,在控制台打印出获取到的值
- 编写一个新的
BServlet
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 在
BServlet
中使用request
对象获取Cookie
数组,遍历数组,从数据中获取指定名称对应的值
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取Cookie数组
Cookie[] cookies = request.getCookies();
// 2. 遍历数组
for (Cookie cookie : cookies) {
// 3. 获取数据
String name = cookie.getName();
if ("username".equals(name)) {
String value = cookie.getValue();
System.out.println(name + ": " + value);
break;
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- 启动服务器,访问http://localhost/course/bServlet,查看控制台打印的内容
思考
在访问AServlet
和BServlet
的中间把浏览器关闭,重启浏览器后访问BServlet
能否获取到Cookie
中的数据?
这个问题我们会在Cookie
的使用细节中讲解,大家可以先动手试一下。
小结
在本小节中,主要讲解了Cookie
的基本使用,主要包含两部分内容
- 发送
Cookie
:- 创建
Cookie
对象,并设置值:Cookie cookie = new Cookie("key", "value")
- 发送
Cookie
到客户端使用的是Response
对象:response.addCookie(cookie)
- 创建
- 获取
Cookie
:- 使用
Request
对象获取Cookie
数组:Cookie[] cookies = request.getCookies();
- 遍历
Cookie
数组 - 获取数组中每个
Cookie
对象的值:cookie.getName()
和cookie.getValue()
- 使用
# Cookie原理分析
上面我们介绍了Cookie
的基本使用,那么Cookie
的底层到底是如何实现一次会话多次请求之间共享数据的呢?
Cookie
的实现是基于HTTP协议的,其中涉及到HTTP协议的两个头信息:
- 响应头:
set-cookie
- 请求头:
cookie
- 前面的案例已经实现,
AServlet
给前端发送Cookie
,BServlet
从request
中获取Cookie
数据的功能 AServlet
响应数据的时候,Tomcat服务器基于HTTP协议响应数据- 当Tomcat发现后端需要返回
Cookie
对象,它就会在响应头中添加一行数据Set-Cookie:username=sunyy - 浏览器获取响应结果,从响应头中可以获取到
Set-Cookie
对应的数据username=sunyy
,并将数据存储在浏览器的内存中 - 浏览器再次发送请求给
BServlet
的时候,浏览器会自动在请求头中添加Cookie:username=sunyy发送给BServlet
Request
对象会把请求头中cookie
对应的值封装成一个个Cookie
对象,最终形成一个数组BServlet
通过Request
对象获取到Cookie[]
后,就可以从中获取自己需要的数据
下面,使用上面的案例,我们把上述的结论验证一下:
- 访问
AServlet
对应的地址:http://localhost/course/aServlet,通过F12
或者Ctrl+Shift+i
组合键打开浏览器开发者工具
- 访问
BServlet
对应的地址:http://localhost/course/bServlet
# Cookie的使用细节
本节我们主要讲解两个知识,第一个是Cookie
的存活时间,第二个是Cookie
如何存储中文。
- Cookie的存活时间
前面让大家思考了一个问题:
- 浏览器发送请求给
AServlet
,它会响应一个存有username=sunyy
的Cookie
对象给浏览器 - 浏览器接收到响应数据将
Cookie
存储浏览器内存中 - 当浏览器再次发送请求给
BServlet
,BServlet
就可以使用Request
对象获取到Cookie
数据 - 在发送请求到
BServlet
之前,如果把浏览器关闭再打开进行访问,BServlet
能否同样获取到Cookie
的数据?
注意
浏览器关闭只的是软件关闭,不是关闭选项卡。
针对上面的问题,通过演示发现,BServlet
中无法再获取到Cookie
数据,那么是什么原因导致BServlet
无法获取到Cookie
数据呢?
答案
默认情况下,Cookie
存储在浏览器内存中,当浏览器关闭,内存释放,此时Cookie
将被销毁。
如果使用这种默认情况的Cookie
来共享数据,有些需求就无法实现,比如:
上图这个网站的登录页面有记住我的功能,这个功能大家应该在很多网站都见到过
- 第一次输入用户名和密码并勾选记住我,然后登录
- 下次再登录的时候,用户名和密码就会被自动填充,不需要重新输入登录
- 比如记住我这个功能需要记住用户名和密码一个星期,那么使用默认情况下的
Cookie
就会出现问题 - 因为默认情况,浏览器关闭,
Cookie
会从浏览器内存中删除,对于记住我功能就无法实现
那么,Cookie
怎么才能持久化存储呢?
要想解决这个问题,我们要先看下Cookie
给我们提供了哪些方法,也就是API。
上图中我们找到一个方法:setMaxAge,设置Cookie
的存活时间
setMaxAge(int seconds)
参数值为:
- 正整数:将
Cookie
写入浏览器所在电脑的硬盘,持久化存储,到时间就自动删除。 - 负数:默认值,
Cookie
在当前浏览器内存中,当浏览器关闭,Cookie
即被销毁 - 0:删除对应的
Cookie
下面,我们就在AServlet
中设置下Cookie
的存活时间。
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 创建Cookie对象
Cookie cookie = new Cookie("username", "sunyy");
// 设置Cookie的存活时间为1周,7天
cookie.setMaxAge(24*60*60*7);
// 2. 发送Cookie
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
修改后,启动服务器进行测试,访问http://localhost/course/aServlet
- 访问
AServlet
后,把浏览器关闭重启,再访问BServlet
,如果可以再控制台打印username: sunyy
,说明Cookie
没有随着浏览器关闭被销毁 - 通过浏览器查看
Cookie
的内容,可以看到Cookie
的相关信息
- Cookie存储中文
在Tomcat 8之前Cookie
不能直接存储中文,之后Cookie
支持中文数据,下面的内容大家了解一下即可:
如果我们日后工作中遇到Cookie
存储中文报错的情况可以通过下面的思路进行解决:
- 在
AServlet
中对中文进行URL
编码,采用URLEncoder.encode()
,将编码后的值存储Cookie
- 在
BServlet
中获取Cookie
中的值,获取的值为URL
编码后的值- 将获取的值进行
URL
解码,采用URLDecoder.decode()
就可以获取到对应的中文值
- 在
AServlet
中对中文进行URL
编码
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 创建Cookie对象
String value = "张三";
value = URLEncoder. encode(value, "UTF-8");
Cookie cookie = new Cookie("username", value);
// 设置Cookie的存活时间为1周,7天
cookie.setMaxAge(24*60*60*7);
// 2. 发送Cookie
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- 在
BServlet
中获取值,并对获取到的值进行解码
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取Cookie数组
Cookie[] cookies = request.getCookies();
// 2. 遍历数组
for (Cookie cookie : cookies) {
// 3. 获取数据
String name = cookie.getName();
if ("username".equals(name)) {
String value = cookie.getValue();
value = URLDecoder.decode(value, "UTF-8");
System.out.println(name + ": " + value);
break;
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- 启动服务器,先后访问
AServlet
和BServlet
,查看效果
小结
Cookie
使用细节中,我们将来Cookie
的存活时间和存储中文
- 存活时间,需要掌握
setMaxAge()
的使用 - 存储中文,需要了解
URL
编码和解码的使用
# Session
Cookie
已经能完成一次会话多次请求之间的数据共享,前面我们还提到到过Session
也可以实现同样的功能,那么:
- 什么是
Session
? Session
如何使用?Session
是如何实现的?Session
的使用需要注意哪些内容?
# Session基本使用
- 概念:
Session:服务端会话跟踪技术,将数据保存在服务器端
Session
是存储在服务器端而Cookie
是存储在客户端- 存储在客户端的数据容易被窃取和截获,存在很多不安全的因素
- 存储在服务器端的数据与客户端相比要更安全一些
- Session的工作流程
- 在服务器端的
AServlet
获取一个Session
对象,把数据存入其中 - 在服务器端的
BServlet
获取到相同的Session
对象,从中取出数据 - 这样就可以实现一次会话多次请求之间的数据共享
- 那么问题随之而来,如何保证
AServlet
和BServlet
使用的是同一个Session
对象(后面原理分析章节会进行详细介绍)?
- Session的基本使用
在JavaEE中提供了HttpSession
接口,通过该接口来实现一次会话多次请求之间共享数据。
具体步骤为:
使用
request
对象,获取Session
对象HttpSession session = request.getSession();
1Session
对象提供的功能:- 存储数据到
session
域中
void setAttribute(String name, Object o);
1- 根据
key
,获取值
Object getAttribute(String name);
1- 根据
key
,删除键值对
void removeAttribute(String name);
1- 存储数据到
那么介绍完Session
相关的API后,下面我们通过一个简单的案例来完成Session
的使用。
案例需求:在一个Servlet
中网Session
中存入数据,在另一个Servlet
中获取Session
中存入的数据
- 创建名为
SessionDemo1
的Servlet
类- 创建名为
SessionDemo2
的Servlet
类- 在
SessionDemo1
的方法中:获取Session
对象,存储数据- 在
SessionDemo2
的方法中:获取Session
对象,获取数据- 启动服务器,测试查看效果
- 创建
SessionDemo1
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 创建
SessionDemo2
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 在
SessionDemo1
中:获取Session
对象,存储数据
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取Session对象
HttpSession session = request.getSession();
// 2. 存储数据
session.setAttribute("username", "zhangsan");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 在
SessionDemo2
中:获取Session
对象,获取数据
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取Session独享
HttpSession session = request.getSession();
// 2. 获取数据
Object username = session.getAttribute("username");
System.out.println(username);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 启动服务器,测试查看效果
- 先访问http://localhost/course/demo1,将数据存入
session
- 再访问http://localhost/course/demo2,从
session
中获取数据 - 查看Idea控制台
- 先访问http://localhost/course/demo1,将数据存入
通过上面的案例,我们可以看到Session
可以在一次会话的两次请求之间共享数据。
小结
Session
重点要掌握的:
Session
的获取HttpSession session = request.getSession();
1Session
常用方法的使用void setAttribute(String name, Object o); Object getAttribute(String name);
1
2
3
注意:Session
总可以存储的是对象类型的数据,它可以存储任意类型的数据类型。
# Session原理分析
问题:Session
的底层是如何实现一次会话多个请求之间共享数据的?
Session
要想实现一次会话多次请求之间共享数据,就必须要保证多次请求获取Session
的对象是同一个。
那么我们在上面案例中两个Servlet
获取到的session
是一个对象么?下面我们就来验证一下:在两个Servlet
总分别打印下Session
对象
SessionDemo1:
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取Session对象
HttpSession session = request.getSession();
System.out.println(session);
// 2. 存储数据
session.setAttribute("username", "zhangsan");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SessionDemo2:
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取Session独享
HttpSession session = request.getSession();
System.out.println(session);
// 2. 获取数据
Object username = session.getAttribute("username");
System.out.println(username);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
启动测试,分别访问:http://localhost/course/demo1,http://localhost/course/demo2
通过控制台输出结果可以得到如下结论:
- 两个
Servlet
类中获取的Session
对象是同一个 - 把
demo1
和demo2
请求刷新多次,控制台最终打印的结果都是同一个
那么如果新打开浏览器,或换其他浏览器访问demo1
或者demo2
,打印在控制台的Session
还是同一个对象吗?
测试的结果:如果是不同浏览器或者重新打开浏览器后,打印的Session
就不一样了。
注意
在一台电脑上演示,如果是相同的浏览器必须把浏览器关闭再重新打开,才算新开浏览器。淡然也可以使用不同的浏览器进行测试,像我就是用了IE浏览器。
那么最最核心的问题来了,Session
是如何保证在一次会话中获取的Session
对象是同一个?
demo1
在第一次获取session
对象的时候,session
对象会有一个唯一的标识,假设是id:10
demo1
在session
中存入数据并处理完业务后,需要通过Tomcat服务器响应结果给浏览器Tomcat服务器发现业务处理中使用了
session
对象,就会把session
的唯一标识id:10
当做一个cookie
,添加Set-Cookie:JESSIONID=10
到响应头中,并响应给浏览器浏览器接收到响应结果后,会把响应头中的
cookie
数据存储到浏览器内存中浏览器在同一会话中访问
demo2
的时候,会把cookie
中的数据按照cookie: JESSIONID=10
的格式添加到请求头中并发送给服务器Tomcatdemo2
获取到请求后,从请求头中读取cookie
中的JESSIONID
值为10
,如果找到了,就直接返回该对象,如果没有则新创建session
对象关闭打开浏览器后,因为浏览器的
cookie
被销毁,所以就没有JESSIONID
的数据,服务端获取到的session
就是一个全新的session
对象
由上面的分析过程,我们可以得出一个结论:Session是基于Cookie实现的
# Session使用细节
本节我们会主要讲解两个知识:
Session
的钝化和活化Session
的销毁Session钝化与活化
假设我们在淘宝中买东西,此时访问淘宝的用户很多,虽然session
数量没有上限,但随session
数量的增多,会导致内存无法承受,此时如果有一些session
长时间都没有活动。服务器就会将这些很久没有活动的session
放到硬盘上,让内存给空出来,之后需要再次访问这放到硬盘上session
,从硬盘找那个去除放到内存里面即可,这样用户就不会感觉自己掉线,那么这个过程就是session
的钝化和活化。
Session的钝化和活化就是把session
保存到硬盘,再从硬盘读取到内存中。但是它们和Session序列化、反序列化还不一样,Session
序列化、反序列化是关闭和启动服务器,Session
钝化和活化是没有关闭和启动服务器就完成了。
- Session销毁
session
的销毁会有两种方式:
- 默认情况下,无操作,30分钟自动销毁
这个是小时间,是可以通过配置进行修改的
- 在项目的
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"> <session-config> <session-timeout>100</session-timeout> </session-config> </web-app>
1
2
3
4
5
6
7
8
9- 如果没有配置,默认是30分钟,默认值是在Tomcat的
web.xml
配置文件中写死的
- 在项目的
调用
Session
对象的invalidate()
方法进行销毁- 在
SessionDemo2
中添加session
的销毁方法
package com.itheima.web; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/demo2") public class SessionDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取Session独享 HttpSession session = request.getSession(); System.out.println(session); // 销毁session session.invalidate(); // 2. 获取数据 Object username = session.getAttribute("username"); System.out.println(username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35- 启动访问测试,先访问
demo1
将数据存入session
,再次访问demo2
从session
中获取数据,因为在获取数据前我们销毁了session
,所以会报错。
- 该销毁方法一般会用在用户退出的时候,将
session
销毁
- 在
Cookie和Session小结
Cookie
和Session
都是来完成一次会话内多次请求间共享数据的。
- 区别:
- 存储位置:
Cookie
将数据存储在客户端,Session
将数据存储在服务器端 - 安全性:
Cookie
不安全,Session
安全 - 数据大小:
Cookie
最大3KB,Session
大小无限制 - 存储时间:
Cookie
可以通过setMaxAge()
长期存储,Session
默认30分钟 - 服务器性能:
Cookie
不占服务器资源,Session
占用服务器资源
- 存储位置:
- 应用场景:
- 购物车:使用
Cookie
来存储 - 展示登录用户信息:使用
Session
存储 - 记住我:
Cookie
- 验证码:
Session
- 购物车:使用
- 结论:
Cookie
是用来保证用户在未登录情况下的身份识别Session
一般用来保存用户登录后的数据
# 用户登录注册案例
# 需求分析
需求说明:
- 完成用户登录功能,如果用户勾选记住我,下层访问登录页面时自动填充用户名和密码
- 完成注册功能,并实现验证码功能
# 用户登录功能
- 需求:
- 用户登录成功后,跳转到列表页面,并在页面上展示当前登录的用户名称
- 用户登录失败后,跳转会登录页面,并在页面上展示对应的错误信息
- 实现流程:
- 前端通过表单发送请求和数据给Web层的
LoginServlet
- 在
LoginServlet
中接收请求和数据用户名和密码 LoginServlet
接收到请求和数据后,调用Service
层完成根据用户名和密码查询用户对象- 在
Service
层需要编写UserService
类,在类中实现login
方法,方法中调用Dao
层的UserMapper
- 在
UserMapper
接口中,声明一个根据用户名和密码查询用户的方法 Dao
层把数据查询出来以后,将返回数据封装到User
对象,将对象交给Service
层Service
层将数据返回给Web
层Web
层获取到User
对象后,判断User
对象,如果为Null
,则将错误信息响应给登录页面,如果不为Null
,则跳转到列表页面,并把当前登录用户的信息存入Session
发送到列表页面。
- 具体实现:
环境准备
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itheima</groupId> <artifactId>Course</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--mybatis 依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> <!-- servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!-- jsp --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency> <!-- jstl --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> </dependencies> </project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 起别名 --> <typeAliases> <package name="com.itheima.pojo" /> </typeAliases> <!-- environments:develop配置数据库连接环境信息。 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--数据库连接信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> <mappers> <!--加载sql映射文件--> <!--Mapper代理方式--> <package name="com.itheima.mapper"/> </mappers> </configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30- 建表
-- 创建用户表 CREATE TABLE tb_user( id int primary key auto_increment, username varchar(20) unique, password varchar(32) ); -- 添加数据 INSERT INTO tb_user(username,password) values('zhangsan','123'),('lisi','234'); SELECT * FROM tb_user;
1
2
3
4
5
6
7
8
9
10
11创建实体类
User.java
package com.itheima.pojo;
public class User {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
编写
Dao
层代码UserMapper.java
package com.itheima.mapper; import com.itheima.pojo.User; import org.apache.ibatis.annotations.Param; public interface UserMapper { /** * 根据用户名和密码查询用户 * @param username * @param password * @return */ User select(@Param("username") String username, @Param("password")String password); /** * 根据用户名查询用户对象 * @param username * @return */ User selectByUsername(String username); /** * 添加用户 * @param user */ void add(User user); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.mapper.UserMapper"> <resultMap id="UserMap" type="User"> <id column="id" property="id" /> <result column="username" property="username" /> <result column="password" property="password" /> </resultMap> <sql id="User_Columns"> id, username, password </sql> <insert id="add"> insert into tb_user(username, password) values(#{username}, #{password}) </insert> <select id="select" resultMap="UserMap"> select <include refid="User_Columns" /> from tb_user where username = #{username} and password = #{password} </select> <select id="selectByUsername" resultMap="UserMap"> select <include refid="User_Columns" /> from tb_user where username = #{username} </select> </mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27编写工具类
package com.itheima.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* MyBatis工具类
*/
public class MyBatisUtil {
private static SqlSessionFactory factory;
/**
* 静态代码块会随着类的加载而自动执行,且只执行一次
*/
static {
try {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getSqlSessionFactory() {
return factory;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- 编写
Service
层代码
package com.itheima.service;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
public class UserService {
private SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
/**
* 登录方法
* @param username
* @param password
* @return
*/
public User login(String username, String password) {
// 1. 获取SqlSession
SqlSession sqlSession = factory.openSession();
// 2. 获取UserMapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 3. 调用方法
User user = mapper.select(username, password);
// 4. 释放资源
sqlSession.close();
// 5. 返回结果
return user;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
完成页面和Web层代码的编写
- 将课程资料中登录注册案例下面的静态页面拷贝到我们的
webapp
目录下
- 将
login.html
内容修改为login.jsp
<%@ page contentType="text/html;charset=utf-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> <link href="css/login.css" rel="stylesheet"> </head> <body> <div id="loginDiv" style="height: 350px"> <form action="loginServlet" id="form"> <h1 id="loginMsg">LOGIN IN</h1> <div id="errorMsg">用户名或密码不正确</div> <p>Username:<input id="username" name="username" type="text"></p> <p>Password:<input id="password" name="password" type="password"></p> <p>Remember:<input id="remember" name="remember" type="checkbox"></p> <div id="subDiv"> <input type="submit" class="button" value="login up"> <input type="reset" class="button" value="reset"> <a href="register.html">没有账号?</a> </div> </form> </div> </body> </html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29- 创建
LoginServlet
package com.itheima.web; import com.itheima.pojo.User; import com.itheima.service.UserService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { private UserService service = new UserService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); // 2. 调用service查询 User user = service.login(username, password); // 3. 判断 if (user != null) { // 登录成功,跳转(重定向)到查询所有品牌数据的页面 // 将登录成功后的user对象,存储到session中 HttpSession session = request.getSession(); session.setAttribute("user", user); String contextPath = request.getContextPath(); response.sendRedirect(contextPath + "/selectAllServlet"); } else { // 登录失败 // 存储错误信息到request request.setAttribute("login_msg", "用户名或密码错误"); // 跳转到login.jsp request.getRequestDispatcher("/login.jsp").forward(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54- 在
brand.jsp
中<body>
标签下添加欢迎当前用户的提示信息:
<h1>${user.username},欢迎您!</h1>
1- 修改
login.jsp
,将错误信息使用EL表达式获取
修改前内容:<div id="errorMsg">用户名和密码不正确</div> 修改后内容:<div id="errorMsg">${login_msg}</div>
1
2- 将课程资料中登录注册案例下面的静态页面拷贝到我们的
启动服务器,测试查看效果
- 进入登录页面,输入错误的用户名和密码
- 输入正确的用户名和密码
小结
- 在
LoginServlet
中,将登录成功的用户数据存入session
,在品牌列表页面中获取当前登录用户信息进行展示 - 在
LoginServlet
中,将登录失败的错误信息存入到request
中,如果存入到session
中就会出现这次会话的所有请求都出现登录失败的错误提示信息,因此不用存入到session
中。
# 记住我-设置Cookie
需求:登录成功并勾选了Remeber后,后端返回给前端的Cookie数据就存储好了,接下来在页面获取Cookie中的数据,并把数据设置到登录页面的用户名和密码框中。
对应上面的需求,最大的问题就是:如何自动填充用户名和密码?
实现流程分析:
因为记住我功能要实现的效果是,用户把浏览器关闭即使过一段时间在来访问登录页面,用户名和密码也可以自动填充,所以需要将登录信息存入一个可以长久保存,并且能够在浏览器关闭重新启动后亦然有效的地方,其实就是我们前面讲的Cookie,所以:
- 需要将用户名和密码写入Cookie中,并且持久化存储Cookie
- 接着就是从Cookie中获取数据,并设置到用户名和密码框中
- 那么,何时将用户名和密码写入Cookie呢?
- 用户必须成功登录后才需要写入
- 用户必须在登录页面勾选了记住我的复选框
- 前端需要在发送请求的时候,多携带一个用户是否勾选
Remmeber
的数据 LoginServlet
获取到数据后,调用Service
完成用户名和密码的判断- 登录成功,并且用户在前端勾选了记住我,那么就需要在Cookie中写入用户名和密码,并设置Cookie的存活时间
- 设置成功后,将数据响应给前端
具体实现:
- 设置
login.jsp
记住我复选框的值
<%@ page contentType="text/html;charset=utf-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
<form action="loginServlet" id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg}</div>
<p>Username:<input id="username" name="username" type="text"></p>
<p>Password:<input id="password" name="password" type="password"></p>
<p>Remember:<input id="remember" name="remember" type="checkbox" value="1"></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.html">没有账号?</a>
</div>
</form>
</div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 在
LoginServlet
获取复选框的值并在登录成功后设置Cookie
package com.itheima.web;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
// 获取复选框的数据
String remember = request.getParameter("remember");
// 2. 调用service查询
User user = service.login(username, password);
// 3. 判断
if (user != null) {
// 登录成功,跳转(重定向)到查询所有品牌数据的页面
// 将登录成功后的user对象,存储到session中
HttpSession session = request.getSession();
session.setAttribute("user", user);
if ("1".equals(remember)) {
// 候选了 记住我
// 创建Cookie对象
Cookie ckUsername = new Cookie("username", username);
Cookie ckPassword = new Cookie("password", password);
// 设置Cookie的存活时间
ckUsername.setMaxAge(24 * 60 * 60 * 7);
ckPassword.setMaxAge(24 * 60 * 60 * 7);
// 响应中添加Cookie数据
response.addCookie(ckUsername);
response.addCookie(ckPassword);
}
String contextPath = request.getContextPath();
response.sendRedirect(contextPath + "/selectAllServlet");
} else {
// 登录失败
// 存储错误信息到request
request.setAttribute("login_msg", "用户名或密码错误");
// 跳转到login.jsp
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
- 启动服务器,访问页面查看效果:只有当用户名和密码输入正确,并且勾选了Remember的复选框,在响应头中才可以看到cookie的相关数据
# 记住我-获取Cookie
需求:登录成功并勾选了Remeber后,后端返回给前端的Cookie数据就已经存储好了,接下来就需要在页面获取Cookie中的数据,并把数据设置到登录页面的用户名和密码框中
那么,如何在页面获取Cookie中的值呢?
实现流程分析:
- 在
login.jsp
页面的登录表单用户名输入框使用value
给表单元素添加默认值,value
可以使用${cookie.username.value}
- 在
login.jsp
页面的登录表单密码输入框使用value
给表单元素添加默认值,value
可以使用${cookie.password.value}
具体实现:
- 修改
login.jsp
页面
<%@ page contentType="text/html;charset=utf-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
<form action="loginServlet" id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg}</div>
<p>Username:<input id="username" name="username" type="text" vlaue="${cookie.username.value}"></p>
<p>Password:<input id="password" name="password" type="password" value="${cookie.password.value}"></p>
<p>Remember:<input id="remember" name="remember" type="checkbox" value="1"></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.html">没有账号?</a>
</div>
</form>
</div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 重启服务器,访问登录页面查看效果
# 用户注册功能
需求:
- 注册功能:保存用户信息到数据库
- 验证码功能:
- 展示验证码:展示验证码图片,点击可以切换
- 校验验证码:验证码填写错误,注册失败
实现流程分析:
- 前端通过表单发送请求和数据给Web层的
RegisterServlet
- 在
RegisterServlet
中接收数据用户名和密码 RegisterServlet
接收到请求后,调用Service
层完成用户信息的保存- 在
Service
层需要编写UserService
类,在类中实现register
方法,此时需要判断用户是否已经存在,如果不存在,则保存用户数据,存在在注册失败 - 在
UserMapper
接口中,声明两个方法,一个是根据用户名查询用户信息方法,另一个是保存用户信息方法 - 在
UserService
类中保存成功则返回true
,失败则返回false
,将数据返回给Web层 - Web层获取到结果后,如果返回的是
true
,则提示注册成功,并转发到登录页面,如果返回false
则提示用户名已存在并转发到注册页面
具体实现:
Dao层代码如下
编写Service层代码
package com.itheima.service;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
public class UserService {
private SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
/**
* 登录方法
* @param username
* @param password
* @return
*/
public User login(String username, String password) {
// 1. 获取SqlSession
SqlSession sqlSession = factory.openSession();
// 2. 获取UserMapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 3. 调用方法
User user = mapper.select(username, password);
// 4. 释放资源
sqlSession.close();
// 5. 返回结果
return user;
}
/**
* 注册
* @param user
* @return
*/
public boolean register(User user) {
// 1. 获取SqlSession
SqlSession sqlSession = factory.openSession(true);
// 2. 获取UserMapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 3. 判断用户名是否存在
User user1 = mapper.selectByUsername(user.getUsername());
if (user1 == null) {
// 用户名不存在,注册
mapper.add(user);
}
return user1 == null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
完成页面和Web层代码编写
- 将
register.html
修改为register.jsp
<%@ page contentType="text/html;charset=utf-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>欢迎注册</title> <link href="css/register.css" rel="stylesheet"> </head> <body> <div class="form-div"> <div class="reg-content"> <h1>欢迎注册</h1> <span>已有帐号?</span> <a href="login.jsp">登录</a> </div> <form id="reg-form" action="registerServlet" method="post"> <table> <tr> <td>用户名</td> <td class="inputs"> <input name="username" type="text" id="username"> <br> <span id="username_err" class="err_msg" style="display:none;">用户名不太受欢迎</span> </td> </tr> <tr> <td>密码</td> <td class="inputs"> <input name="password" type="password" id="password"> <br> <span id="password_err" class="err_msg" style="display: none">密码格式有误</span> </td> </tr> <tr> <td>验证码</td> <td class="inputs"> <input name="checkCode" type="text" id="checkCode"> <img src="imgs/a.jpg"> <a href="#" id="changeImg">看不清?</a> </td> </tr> </table> <div class="buttons"> <input value="注 册" type="submit" id="reg_btn"> </div> <br class="clear"> </form> </div> </body> </html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50- 编写
RegisterServlet
package com.itheima.web; import com.itheima.pojo.User; import com.itheima.service.UserService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/registerServlet") public class RegisterServlet extends HttpServlet { private UserService userService = new UserService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); User user = new User(); user.setUsername(username); user.setPassword(password); // 2. 调用Service层方法 boolean flag = userService.register(user); // 3. 判断注册成功与否 if (flag) { // 注册成功,跳转登录页面 request.setAttribute("register_msg", "注册成功,请登录"); request.getRequestDispatcher("/login.jsp").forward(request, response); } else { // 注册失败,跳转到注册页面 request.setAttribute("register_msg", "用户名已存在"); request.getRequestDispatcher("/register.jsp").forward(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47- 注册失败后需要在注册页面展示后台返回的错误信息,需要修改
register.jsp
修改前:<span id="username_err" class="err_msg" style="display:none">用户名不太受欢迎</span> 修改后:<span id="username_err" class="err_msg">${register_msg}</span>
1
2- 如果注册成功,需要把注册成功信息展示到登录页面,所以也需要修改
login.jsp
修改前:<div id="errorMsg">${login_msg}</div> 修改后:<div id="errorMsg">${login_msg}${register_msg}</div>
1
2- 修改
login.jsp
,将注册跳转地址修改为register.jsp
修改前:<a href="register.html">没有账号?</a> 修改后:<a href="register.jsp">没有账号?</a>
1
2- 启动服务器,测试
如果注册的用户名已存在:
如果注册的用户名可用,注册成功:
- 将
# 展示验证码
需求分析:展示验证码,展示验证码图片,点击图片可以切换
验证码的生成是通过工具类来实现的,具体的工具类参考课程资料,针对该工具类我们可以编写一个测试方法:
package com.itheima.util;
import org.junit.Test;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class CheckCodeUtilTest {
@Test
public void testCaptcha() throws IOException {
// 生成验证码图片位置
OutputStream fos = new FileOutputStream("D://itcast//Temp//catcha.jpg");
// captchaCode为最终验证码的数据
String captchaCode = CaptchaUtil.outputVerifyImage(100, 50, fos, 4);
System.out.println(captchaCode);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
生成验证码以后,我们就可以知晓:
- 验证码就是使用Java代码生成的一张图片
- 验证码的作用:防止机器自动注册,攻击服务器
实现流程分析:
- 前端发送请求给
CheckCodeServlet
CheckCodeServlet
接收到请求后,生成验证码图片,将图片用Response
对象的输出流写到前端页面
思考:如何将图片写到前端浏览器?
- Java中已经有工具类生成验证码图片,测试类中只是把这个图片生成到磁盘上
- 生成磁盘的过程中使用的是
OutputStream
流,如何把这个图片生成到页面上? - 前面在介绍
Response
对象的时候,它有一个方法可以获取其字节输出流,getOutputStream()
- 通过上面的介绍,我们可以把写往磁盘的流对象更换成
Response
的字节流,即可完成将图片响应给前端的工作
具体实现:
- 修改
register.jsp
页面,将验证码图片改为从后台获取
<%@ page contentType="text/html;charset=utf-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>欢迎注册</title>
<link href="css/register.css" rel="stylesheet">
</head>
<body>
<div class="form-div">
<div class="reg-content">
<h1>欢迎注册</h1>
<span>已有帐号?</span> <a href="login.jsp">登录</a>
</div>
<form id="reg-form" action="registerServlet" method="post">
<table>
<tr>
<td>用户名</td>
<td class="inputs">
<input name="username" type="text" id="username">
<br>
<span id="username_err" class="err_msg">${register_msg}</span>
</td>
</tr>
<tr>
<td>密码</td>
<td class="inputs">
<input name="password" type="password" id="password">
<br>
<span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
</td>
</tr>
<tr>
<td>验证码</td>
<td class="inputs">
<input name="checkCode" type="text" id="checkCode">
<img id="checkCodeImg" src="checkCodeServlet">
<a href="#" id="changeImg">看不清?</a>
</td>
</tr>
</table>
<div class="buttons">
<input value="注 册" type="submit" id="reg_btn">
</div>
<br class="clear">
</form>
</div>
<script>
document.getElementById("changeImg").onclick = function() {
// 路径后面添加时间戳的目的是避免浏览器缓存静态资源,导致验证码图片不刷新
document.getElementById("checkCodeImg").src = "checkCodeServlet?" + new Date().getMilliseconds();
}
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
- 编写
CheckCodeServlet
类,用来接收请求并生成验证码
package com.itheima.web;
import com.itheima.util.CheckCodeUtil;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 生成验证码
ServletOutputStream os = response.getOutputStream();
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 校验验证码
需求:
- 判断程序生成的验证码和用户输入的验证码是否一样,如果不一样,则阻止注册
- 验证码图片访问和提交注册表单是两次请求,所以要将程序生成的验证码存入
Session
中
思考:为什么要把验证码数据存入到Session
中?
- 生成验证码和校验验证码是两次请求,此处就需要在一个会话的两次请求之间共享数据
- 验证码属于安全类数据,因此我们要选中
Session
来存储验证码数据
实现流程分析:
- 在
CheckCodeServlet
中生成验证码的时候,将验证码数据存入Session
对象 - 前端将验证码和注册数据提交到后台,交给
RegisterServlet
类 RegisterServlet
类接收到请求和数据后,其中就有验证码,然后和Session
中的验证码进行对比- 如果一致,完成注册逻辑,如果不一致,提示错误信息
具体实现:
- 修改
CheckCodeServlet
类,将验证码存入Session
对象
package com.itheima.web;
import com.itheima.util.CheckCodeUtil;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 生成验证码
ServletOutputStream os = response.getOutputStream();
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
// 存入Session
HttpSession session = request.getSession();
session.setAttribute("checkCodeGen", checkCode);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- 在
RegisterServlet
中,获取页面输入的和session
对象中的验证码,进行对比
package com.itheima.web;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
private UserService userService = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = new User();
user.setUsername(username);
user.setPassword(password);
// 获取用户输入的验证码
String checkCode = request.getParameter("checkCode");
// 从Session中获取程序生成的验证码
HttpSession session = request.getSession();
String checkCodeGen = (String) session.getAttribute("checkCodeGen");
// 比对
if (!checkCodeGen.equalsIgnoreCase(checkCode)) {
request.setAttribute("register_msg", "验证码错误");
request.getRequestDispatcher("/register.jsp").forward(request, response);
// 验证码不匹配,不允许注册
return;
}
// 2. 调用Service层方法
boolean flag = userService.register(user);
// 3. 判断注册成功与否
if (flag) {
// 注册成功,跳转登录页面
request.setAttribute("register_msg", "注册成功,请登录");
request.getRequestDispatcher("/login.jsp").forward(request, response);
} else {
// 注册失败,跳转到注册页面
request.setAttribute("register_msg", "用户名已存在");
request.getRequestDispatcher("/register.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
至此,用户的注册登录功能就完成了。