目录
1.Cookie
概念
优势和不足
2.Session
3.用Session实现登陆
概念
代码
4.使用Filter实现过滤
在使用浏览器的时候,浏览器如何保存我们的个人信息呢?有两种方法,一个是Cookie,一个是Session,区别在于,前者是在客户端保存,即我们的电脑或手机等设备上本地保存;后者是在服务器端保存,不需要占用我们本地的空间。两者共同点是都有一个保存期限。比如使用Session的网页,登陆一个网站,第一次登陆之后,可能后面几天不需要再次登录,进去就是自己的页面,但是再过一段时间会提示身份验证过期,需要重新登陆,这就是服务器端的Session保存期限到了,销毁了。
1.Cookie
概念
cookie的作用是把信息存在客户端,然后通过用户的访问传给服务器。
如下,第一次访问的时候,客户端向服务器端发送第一次请求request1,服务器端收到之后,返回第一次请求response1,并且返回cookies,expire(cookie的期限)等信息,客户端收到这些信息之后,在本地创建cookies。在后面的一次次request请求中,客户端会携带着cookies一起发送请求,然后服务器端也会更新cookies并返回。
此外,除了服务器端建立cookies信息,也可以通过JavaScript,在服务器端创建cookies。
如下,通过网页的开发人员工具,可以看到cookie信息。
也可以看到,在浏览器设置里面的,针对cookie有许许多多的设置。
优势和不足
cookie是在本地创建,所以不需要占用服务器的资源。
但是与此同时,如果我们清除了本地的cookie,那么它就不存在了。比如,一个使用cookie的网站,第一次登录之后,选择七天免账号密码登陆,然后第二天,我把浏览器的cookie等等数据清除了,清除之后再次登陆,发现不是免账号密码,还是需要重新登陆,这就是因为cookie数据被清除,服务器端无法读取对应信息。
此外,使用cookie其实安全性并不高,上面说到,cookie可以在客户端修改,那么如果一些别有用心的黑客通过修改cookie来欺骗服务器,对服务器端构成威胁。
所以,现在很多网站使用cookie不是那么多。
2.Session
相较cookie,session(会话)的安全性更高。其将信息存储在了服务器端。
在客户端向服务器端发送request之后,服务器端会返回response,同时返回一个id号,并且在服务器端的内存开辟空间,存储这个id和相关data 。这个id号是标识了当前用户的。在该用户接下来的每一次访问,都会带着这个id,服务器端都会在内存里查找这个id是否存在。而和这个用户相关的信息,也是存储在服务器端。当然,Session也是有时间限制,只要服务器端对应的 id 没有过期,这个会话就还保留着。
典型的例子是,去网上购物商城买东西,在第一个网店买了本书,加入购物车;第二个网店买鞋子,加入购物车;第三个网店买衣服,加入购物车。最后结算的时候是一起结算的。很明显,访问的是三个不同的网店,但是结帐的时候它怎么会知道我选了哪些商品?明显有一个地方存储我的这些信息,这个地方就是服务器端。
如下,一次request代表一次请求,一次reponse代表一次返回,而一个会话(Session)是多次请求、返回,反反复复交流才叫一个Session。Session是由很多request和response组成的,而会话的 id 就存在服务器端,对应的信息也存在服务器端。
这种模型,一方面对于客户端的依赖少了。另一方面,数据存在服务器端,客户端无法修改,安全性更高。
3.用Session实现登陆
概念
在客户端访问的时候,tomcat会自动在服务器端创建一个Session存储相关信息,如下,Actor 1 绑定的 Session 是 1 (圆圈),Actor 2 绑定的 Session 是 2。Session对象和用户的每次访问都是绑定在一起的。
比如在Session里面创建一个 flag,标记用户是否登陆,如果未登陆就让他登陆,已经登陆,就可以使用网站里的资源,就可以实现我们平常使用的登陆功能。
代码
创建一个Java类,名字为 LoginServlet.java ,其中的代码如下。
一些解释也在注释里面。中间用到的UserRepo里面的 auth方法下面放了一张图片。
package cn.edu.swu.inout;
import cn.edu.swu.user.User;
import cn.edu.swu.user.UserRepo;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.SQLException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
public static final String LOGIN_TOKEN="USER_LOGIN_TOKEN";
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
this.doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
String userName = request.getParameter("user");
String password = request.getParameter("password");
//这里的逻辑是
//如果账号密码不为空,那么就直接登陆
//如果为空,有可能已经登陆,有可能直接没输入,那么看session,session两种情况,一个是有数据,一个是没有数据
//如果没有数据,那么跳到login.html页面
//如果有数据,说明之前登陆过,那么直接跳到admin页面
if (userName != null && password != null) {
//为什么要有这个判断,因为点了登陆之后,他是跳到login的,即这个loginservlet,所以要在这里有
this.doLogin(request, response);
} else {
HttpSession session = request.getSession();
if (session == null || session.getAttribute(LoginServlet.LOGIN_TOKEN) != Boolean.TRUE) {
response.sendRedirect("./login.html");
} else {
response.sendRedirect("./admin.html");
}
}
}
public void doLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
String userName = request.getParameter("user");
String password = request.getParameter("password");
try {
User user = UserRepo.getInstance().auth(userName, password);
if (user != null) {
HttpSession session=request.getSession();//创建一个session
session.setAttribute(LOGIN_TOKEN,Boolean.TRUE);//这就相当于flag,标记是否登陆,为true则已经登陆,否则未登录
response.sendRedirect("./admin.html");
} else {
response.sendRedirect("./index.html");
}
} catch (SQLException | IOException e) {
throw new RuntimeException(e);
}
}
}
如下,这个方法就是在数据库里面,通过账号密码 寻找相匹配的用户并返回。
但是,像这样子些,如果我得到了后台的网页地址,直接输入网址就可以进入,相当于绕过了登陆,并且进行各种操作。这样非常不安全,所以,要使用 Filter 进行过滤。
4.使用Filter实现过滤
在这里我们进行简单的操作,使用 Filter 对所有资源进行过滤,这样子所有资源都无法直接访问,也包括一些图片资源之类的。当然,像一些图片资源、登陆界面、初始界面等等,可以直接“放行”,不需要对其进行限制。
除了直接“放行”的资源,其余资源通过Session来判断是否放心。如果没有Session,那么肯定就是未登陆过,就需要登陆。如果有Session,有的网站会一进入就创建Session,所以有两种情况,一种是已经登陆,一种是没有登陆,但是自动创建Session,两者区分要用Session里面的flag来判定(flag是上文说过的来标记是否登陆过的信息)。 逻辑和代码如下,由于只是简单模拟,整个逻辑并不特别复杂。
使用 Filter 进行过滤之后,即使得到了后台网址,也不可以直接访问,要先登录,安全性大大提高。就好比我们平时登陆校园网,如果直接输入同学给的内部链接,不可以直接访问操作界面,跳出来的是登陆界面。
package cn.edu.swu.user;
import cn.edu.swu.inout.LoginServlet;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/*")
//这里是所有网页都生效
public class AuthFilter extends HttpFilter {
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpSession session = request.getSession();
System.out.println("auth filter");
String uri = request.getRequestURI();
System.out.println(uri);
if ( uri.endsWith("login.html") || uri.endsWith("index.html") || uri.endsWith("verifyCode") ||
uri.endsWith("png") || uri.endsWith("jpg") || uri.endsWith("css") || uri.endsWith("login")) {
chain.doFilter(request, response);
return;
}
if (session == null) {
System.out.println("auth failed");
response.sendRedirect("./login.html");
} else {
Boolean toke = (Boolean) session.getAttribute(LoginServlet.LOGIN_TOKEN);
if (toke == Boolean.TRUE) {
//这里还要判断的原因是,有的网站,即使不登陆也要验证session,所以这里验证一下session里的LOGIN_TOKEN
//是否为true,为true则是登陆过,否则未登陆过。因为只有之前登陆正确才会设置它
System.out.println("登录验证成功");
chain.doFilter(request, response);
} else {
System.out.println("auth failed");
response.sendRedirect("./login.html");
}
}
}
}