cookie 本意是小甜点, 在web开发中是用来维系状态的一种技术,可以用它实现记住用户名、密码这些参数的操作,就如我们每次登陆页面,跳转到其他页面时我们还是登陆的状态,这就用到了cookie记住用户名密码这一项功能(也就是服务器要向浏览器返回cookie),它的缺点是有安全风险,因为信息是存储在浏览器端的。
- 服务器要向浏览器返回cookie
Cookie cookie的名字 = new Cookie(名, 值); 创建cookie对象
response.addCookie(cookie的名字); 把cookie加入到相应中,返回给浏览器
- 浏览器再发送请求时,会把这些cookie值重新发送给服务器
Cookie[] cookies = request.getCookies(); 返回的是一个cookie数组,里面包含各种cookie信息
- maxAge 用来设置 cookie 的寿命
默认不设置(-1)表示浏览器关闭寿命就到期
指定一个正整数(单位秒),指定cookie存活多久
设置为 0,表示由服务器端删除该cookie
- httpOnly 用来设置是否禁止 js 代码访问 cookie(true 禁止,false 可以)
1、添加cookie、设置寿命、
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(urlPatterns = "/cookieResp")
public class CookieRespServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie up = new Cookie("up", "zhangsan:123");
up.setMaxAge(3600); // 一小时
up.setHttpOnly(true); // 不允许 js 代码操作这个 cookie , 否则,可以使用 document.cookie 访问到cookie
resp.addCookie(up);//添加cookie
resp.getWriter().print("cookie added...");//浏览器输出cookie added...
}
}
结果
2、遍历服务器输出cookie内容
@WebServlet("/cookieReq")
public class CookieReqServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie[] cookies = req.getCookies();//获得cookie
//遍历cookie
for (Cookie cookie : cookies) {
//cookie.getName() 获得cookie名,cookie.getValue() 获得cookie值
System.out.println(cookie.getName() + " " + cookie.getValue());
}
}
}
结果
可以看到我存进去的cookie up zhangsan:123,其他的cookie是浏览器传过来的。
session也是用来维系状态的一种技术,功能和cookie差不多,也是可以用来存储用户名、密码,但是它把这些状态信息存储在服务器端,安全性要比 cookie 高很多,可以用session实现在一个浏览器保持登录状态,不需要重复登录,也可以说是自动登录。
session的应用
- 存储信息
HttpSession session = request.getSession(); // 拿到 session 对象
session.setAttribute("名", 值); // 存储信息
- 获取信息
HttpSession session = request.getSession(); // 拿到 session 对象
session.getAttribute("名"); // 返回上一次存储的值
- 删除信息
session.removeAttribute("名"); // 移除指定的session,返回被移除的值
session.invalidate(); // 让session失效(全部清空)
第一次调用 request.getSession() 创建 session对象,如果隔了 30 分钟没有向服务器发送请求,session 会自动失效。
如果要改变失效时间,可以在 web.xml中:
过期时间,如:30(单位是分钟)
跟浏览器的关系:一个浏览器对应服务器端的一个 session 对象,他们存储的信息互不干扰
- 安全性上, session的安全性高,cookie的信息存在浏览器端所以不安全
- 存储的类型, session 存储的类型是 Object, cookie 只能存字符串(并且需要进行编码处理)
- 存储大小, session 理论上没有限制(但不建议存储太多内容), cookie 的限制:每个cookie不能超过4k,每个网站cookie个数也有限制的
- 失效时间, session 两次请求间隔30分钟, cookie 默认关闭浏览器失效,还可以通过 maxAge 调整的更长
- 请求转发
request.getRequestDispatcher("跳转路径").forward(request, response);
- 请求重定向
response.sendRedirect("跳转路径");
如:重定向到 index.jsp 如果没有加/ 是相对路径
http://localhost:8080/user/index.jsp如果加了 / ,就是相对于主机名和端口号 也就是直接在路径前加 http://localhost:8080
结论:如果路径中有多层目录,建议以 / 写一个完整路径
二者的区别:
请求转发的地址不会改动,始终是刚开始的地址, 请求重定向在跳转后,地址栏会变为目标地址
请求转发是一次请求,跳转操作在服务器内部发生;请求重定向是两次请求,跳转操作是在浏览器与服务器之间发生
请求转发可以使用 request.setAttribute 进行值的传递;请求重定向需要使用 session.setAttribute 进行值的传递
- request作用范围限于一次请求
request.setAttribute(key, value);
request.getAttribute(key) ${key}
request.removeAttribute(key);
- session作用范围同一个浏览器的多次请求之间(一次会话)
session.setAttribute(key, value);
session.getAttribute(key) ${key}
session.removeAttribute(key);
- 页面作用域,作用范围限于当前页面
page
- 应用程序作用域,作用于整个应用程序
application
- 作用于顺序
page < request < sesssion < application
${ key } 这类型的EL会从小的作用域向大的作用域依次查找,直到找到为止
也可以利用前缀精确地找某个作用域:
pageScope page 作用域
requestScope 请求作用域
sessionScope 会话作用域
applicationScope 应用程序作用域
request.getSession(); // 首次请求,在服务器端创建出 session 对象, session会有一个id值(唯一的)
第一次返回响应时,由tomcat 返回一个特殊cookie (jsessionid=session的id值) 返回给浏览器
后续的请求,浏览器就根据这个 jsessionid的值找到服务器端的session对象
1、作用域显示( page 、request 、sesssion 、application )
scope.jsp上显示
<%@ page contentType="text/html;charset=UTF-8" %>
Title
<%
// 向 page 作用域
pageContext.setAttribute("key1", "value1");
// 向 request 作用域
request.setAttribute("key2", "value2");
// 向 session 作用域
session.setAttribute("key3", "value3");
// 向 application 作用域
application.setAttribute("key4", "value4");
%>
<%--显示key对应的value值--%>
page: ${key1}
request: ${key2}
session: ${key3}
application: ${key4}
结果
s2.jsp上显示,也就是在另一个页面上显示
scope.jsp
<%@ page contentType="text/html;charset=UTF-8" %>
Title
<%
// 向 page 作用域
pageContext.setAttribute("key1", "value1");
// 向 request 作用域
request.setAttribute("key2", "value2");
// 向 session 作用域
session.setAttribute("key3", "value3");
// 向 application 作用域
application.setAttribute("key4", "value4");
//发送请求到s2.jsp,这个和下面一个都可以 request.getRequestDispatcher("s2.jsp").forward(request, response);
response.sendRedirect("s2.jsp");
%>
s2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
<%--显示key对应的value值--%>
page: ${key1}
request: ${key2}
session: ${key3}
application: ${key4}
结果
从以上对比可以看出page的作用范围限于当前页面,因此它的值只出现在当前页面上;request作用范围限于一次请求,因此它的值只出现在当前页面上;session作用范围同一个浏览器的多次请求之间,因此在另一个页面上也可以显示它的值;aplication应用程序作用于整个应用程序,因此此程序范围内都可以显示该值。
2、登录与退出,如果不登录就进不到欢迎界面
存储用户信息代码,要序列化,用户密码是敏感信息,因此不许需要在toString中写出
import java.io.Serializable;
public class User implements Serializable {
private String username;
private String password;
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{" +
"username='" + username + '\'' +
'}';
}
}
连接数据库代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class JdbcUtils {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "root");
return conn;
}
}
查询用户密码和用户名的代码
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserDao {
/**
* 根据用户名查询用户对象
* @param username 用户名
* @return 查询到了,返回用户对象,如果用户不存在返回 null
*/
public User findByUsername(String username) {
try(Connection conn = JdbcUtils.getConnection()) {
try(PreparedStatement stmt = conn.prepareStatement("select * from user where uname=?")) {
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
if(rs.next()) {
// 查询到了该用户的用户名和密码,并存入user中,然后返回该user
User user = new User();
user.setUsername(rs.getString("uname"));
user.setPassword(rs.getString("password"));
return user;
} else {
return null;
}
}
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
登录界面(login.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
登录
<%--提交给/login,也就是LoginServlet,验证是否有该用户--%>
${error}
验证用户名与密码(LoginServlet)
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(urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
private UserDao userDao = new UserDao();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
User user = userDao.findByUsername(username);
// 数据库没有此用户
if (user == null) {
req.setAttribute("error", "用户不存在");
req.getRequestDispatcher("login.jsp").forward(req, resp);
return;
}
// 如果数据库的密码 不等于 输入密码
if (!user.getPassword().equals(password)) {
req.setAttribute("error", "密码错误");
req.getRequestDispatcher("login.jsp").forward(req, resp);
return;
}
// 通过验证
// 将登录标记存入 session 作用域
req.getSession().setAttribute("username", username);
req.getRequestDispatcher("welcome.jsp").forward(req, resp);
}
}
登录成功界面,如果没有登录不能直接打开该界面(welcome.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 获取 session 中的 username 变量
Object username = session.getAttribute("username");
// 没有经过验证
if(username == null) {
/*
请求转发的方式,跳转到login.jsp
request.setAttribute("error", "您尚未登录");
request.getRequestDispatcher("login.jsp").forward(request, response);
*/
// 请求重定向方式
session.setAttribute("error", "您尚未登录");
response.sendRedirect("login.jsp");
return;
}
%>
Title
登录成功!!! 欢迎 ${username} 来到本网站
注销(安全退出)
退出功能代码(LogoutServlet)
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(urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 让 session 失效
session.invalidate();
// 跳转至登录页面
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}
结果
登录界面
登陆成功
登录失败(显示密码不存在或用户名不存在)
虽然这两种都是维系状态的一种技术,但是cookie有安全风险,因为信息是存储在浏览器端的,而session把这些状态信息存储在服务器端,因此安全性要比 cookie 高很多,并且使用session可以实现自动登录,这样可以方便跳转页面时,不需要在登录页面 。