一. 会话技术
-
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。再一次会话中包含多次请求和响应。
-
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自统一浏览器,以便在同一次会话的多次请求间共享数据。
-
会话跟踪方案:
(1) 客户端会话跟踪技术: Cookie
(2) 服务端会话跟踪技术:Session
(3) 令牌技术
二. Cookie(传统方案)
- 服务器端创建Cookie后,自动响应给浏览器,浏览器会将Cookie自动存储在浏览器本地,后续请求中Cookie自动携带到服务器。
java
//设置Cookie
@GetMapping("/ce1")
public Result cookie1(HttpServletResponse response){
//设置Cookie/响应Cookie
response.addCookie(new Cookie("login_username","lizhuangzhuang"));
return Result.success();
}
//获取Cookie
@GetMapping("/ce2")
public Result cookie2(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if(cookie.getName().equals("login_username")){
//输出name为login_username的cookie
System.out.println("login_username: "+cookie.getValue());
}
}
return Result.success();
}




-
优点:HTTP协议中支持的技术
-
缺点:
(1) 移动端APP无法使用Cookie;
(2) 不安全,用户可以自己禁用Cookie;
(3) Cookie不能跨域;(协议、IP/域名、端口任意一个不同即为跨域)


三. Session(传统方案)
- 基于Cookie实现的,只不过Cookie中存储的是Session ID值。
java
@GetMapping("/sn1")
public Result session1(HttpSession session){
log.info("HttpSession-s1: {}", session.hashCode());
//往session中存储数据
session.setAttribute("loginUser", "lizhuangzhuang");
return Result.success();
}
@GetMapping("/sn2")
public Result session2(HttpSession session){
log.info("HttpSession-s2: {}", session.hashCode());
//从session中获取数据
Object loginUser = session.getAttribute("loginUser");
log.info("loginUser: {}", loginUser);
return Result.success(loginUser);
}


-
优点:存储在服务端,安全
-
缺点:服务器集群环境下无法直接使用Session(可能存储在一台服务器上,再另一台服务器无法获取);Cookie的缺点(因为是基于Cookie实现的,也可能被禁用和删除);
四. 令牌 (主流方案)
- 优点:
(1) 支持PC端、移动端
(2) 解决集群环境下的认证问题
(3) 减轻服务器存储压力
- 缺点:需要程序员自己编码实现(编码相对繁琐)
五. JWT令牌
-
全称:JSON Web Token (https://jwt.io/),定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息
-
组成:
第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256", "type":"JWT"}
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如{"id":"1","username":"lizhuangzhuang"}
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload融入,并加入制定秘钥,通过制定签名算法计算而来
Base64: 是一种基于64个可打印字符(A-Z a-z 0-9 + / ) 来表示二进制数据的编码方式。

- JWT令牌-生成/解析
(1) 引入jjwt的依赖
(2) 调用官方提供的工具类 Jwts 来生成或解析jwt令牌
java
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
java
/*
* 生成jwt令牌
* */
@Test
public void testJwt1(){
Map<String,Object> map = new HashMap<>();
map.put("name","lizhuangzhuang");
map.put("age","18");
// 生成签名算法
String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256,"123456") //第一个参数指定签名算法 第二个为秘钥
.addClaims(map) //添加自定义信息
.setExpiration(new Date(System.currentTimeMillis()+3600*1000)) // 设置有效期 1小时
.compact(); // 生成令牌
System.out.println(jwt);
}
秘钥可为字符串 或 base64编码




8WF4okYi-k94WttbNAxRl90HsO9TsaW4YeTzQ2SIYMI:第三部分则是基于秘钥和签名算法加密后的字符串
java
/*
* 解析jwt令牌
* */
@Test
public void testJwt2(){
String toket = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoibGl6aHVhbmd6aHVhbmciLCJhZ2UiOiIxOCIsImV4cCI6MTc2MjUzMjYwMH0.8WF4okYi-k94WttbNAxRl90HsO9TsaW4YeTzQ2SIYMI";
Claims claims = Jwts.parser().setSigningKey("123456").parseClaimsJws(toket).getBody();
System.out.println(claims);
}

一旦令牌数据被篡改,解析令牌时会报错

令牌过期解析是也会报错

注意事项:JWT校验时使用的签名秘钥,必须和生成jwt令牌时使用的秘钥是配套的
java
package com.wyyzs.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
/*
* jwt令牌工具类
* */
public class JwtUtils {
// 秘钥
private static String signKey = "bGl6aHVhbmd6aHVhbmc=";
// 有效期 1小时
private static Long expire = 3600000L;
/**
* 生成JWT令牌
* @return
*/
public static String generateJwt(Map<String,Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, signKey)
.setExpiration(new Date(System.currentTimeMillis() + expire))
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
return claims;
}
}
用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,都需要在请求头hrader中携带到服务端,请求头的名称为token,值为登录时下发的JWT令牌,如果检测到用户未登录,则提示错误信息。
六. 过滤器 Filter
-
Filter过滤器:是javaWeb三大组件(Servlet、Filter、Listtener)之一。
-
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
-
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
-
快速入门
(1) 定义Filter:定义一个类,实现Filter接口,并实现其所有方法
(2) 配置Filter:Filter类上加@WebFilter注解,配置拦截路径.引导类上加@ServletComponentScan 开启Servlet组件支持
java
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebServlet;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@WebFilter(urlPatterns = "/*") // 拦截所有请求
@Slf4j
public class webFilter implements Filter {
/*
* 初始化方法,web服务器启动时执行,只执行一次
* 一般做一些资源/环境的准备工作
* 需要时实现 不需要时可不实现 接口中已经默认实现
* */
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("过滤器初始化init方法执行了.....");
}
/*
* 拦截到请求之后执行 会执行多次
* */
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("拦截到了请求");
// 放行操作 不写服务器则不会返回结果
filterChain.doFilter(servletRequest, servletResponse);
log.info("放行后逻辑执行");
}
/*
* 销毁方法,web服务器关闭的时候执行,只执行一次
* 一般做一些资源释放/环境清理工作
* 需要时实现 不需要时可不实现 接口中已经默认实现
* */
@Override
public void destroy() {
log.info("过滤器销毁方法destroy执行了.....");
}
}



(3). 注意事项:如果过滤器中不执行放行操作,过滤器拦截到请求之后,就不会访问对应的资源;放行:filterChain.doFilter(servletRequest, servletResponse);
(4) 登录案例:
java
import com.wyyzs.utils.JwtUtils;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@Slf4j
@WebFilter(urlPatterns = "/*")
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 1. 获取请求路径
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String url = request.getRequestURI();
// 2. 判断是否登录请求 是登录则放行
if (url.contains("/login")) {
log.info("登录操作--放行");
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// 3.获取请求头中的token
String token = request.getHeader("token");
// 4. 判断Token是否存在,不存在 说明没登录
if (token == null || token.isBlank()) {
log.info("令牌token为空");
// 设置返回状态码 SC_UNAUTHORIZED 401 校验未通过
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// 5. 如果token存在 校验令牌 如果校验失败 则返回错误提示
try{
JwtUtils.parseJWT(token);
} catch (Exception e){
log.info("令牌非法");
// 设置返回状态码 SC_UNAUTHORIZED 401 校验未通过
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// 6. 校验通过 放行
log.info("校验通过 放行");
filterChain.doFilter(servletRequest, servletResponse);
}
}
- 执行流程:

- 拦截路径
java
@WebFilter(urlPatterns = "/*")
|--------|--------------|---------------------|
| 拦截路径 | urlPatterns值 | 说明 |
| 拦截具体路径 | /login | 只有访问/login路径时,才会拦截 |
| 拦截目录 | /emps/* | 访问/emps下所有资源时,都会被拦截 |
| 拦截所有 | /* | 访问所有资源都会被拦截 |
- 过滤器链
一个web应用中,可以配置多个过滤器,这就形成了一个过滤器链(过滤器越多,性能就相对越低),多个过滤器时,按照类名依次执行
七. 拦截器 Interceptor
-
概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,主要用来动态拦截控制器方法的执行。
-
作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
-
快速入门
(1) 定义拦截器,实现HandlerInterceptor接口,并实现其所有方法
(2) 注册拦截器
java
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
@Component // 由拦截器是spring提供的技术 加@Component注解 交给ioc容器管理
public class webInterceptor implements HandlerInterceptor {
/*
* 目标资源方法执行前执行,返回 true则放行 返回false则不放行
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("拦截器preHandle 执行了");
return true;
}
/*
* 目标资源方法执行后执行
* */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("拦截器postHandle 执行了");
}
/*
* 视图渲染完毕后执行 最后执行 (目前大部分项目为前后端分离的项目 一般用不到)
* */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("拦截器afterCompletion 执行了");
}
}
java
import com.wyyzs.Interceptor.webInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/*
* 配置类
* 注册拦截器
* */
@Configuration // 代表这个类为配置类
public class webConfig implements WebMvcConfigurer {
@Autowired
private webInterceptor webInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(webInterceptor).addPathPatterns("/**"); // 拦截所有请求
}
}
- 登录令牌校验案例
java
import com.wyyzs.utils.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
/*
* 登录校验 目标资源执行前校验
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取请求路径
String url = request.getRequestURI();
// 2. 判断是否登录请求 是登录则放行
if (url.contains("/login")) {
log.info("登录操作--放行");
return true;
}
// 3.获取请求头中的token
String token = request.getHeader("token");
// 4. 判断Token是否存在,不存在 说明没登录
if (token == null || token.isBlank()) {
log.info("令牌token为空");
// 设置返回状态码 SC_UNAUTHORIZED 401 校验未通过
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 5. 如果token存在 校验令牌 如果校验失败 则返回错误提示
try{
JwtUtils.parseJWT(token);
} catch (Exception e){
log.info("令牌非法");
// 设置返回状态码 SC_UNAUTHORIZED 401 校验未通过
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 6. 校验通过 放行
log.info("校验通过 放行");
return true;
}
}
java
import com.wyyzs.Interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/*
* 配置类
* 注册拦截器
* */
@Configuration // 代表这个类为配置类
public class webConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**"); // 拦截所有请求
}
}
- 拦截器-拦截路径

如上述【登录令牌校验案例】中 配置上 .excludePathPatterns("/login") 则在 TokenInterceptor中不需要再执行【1、2】校验是否登录操作
|------------|---------------|-----------------------------------|
| 拦截路径 | 说明 | |
| /* | 一级路径 | 能匹配/deps /login 不能匹配 deps/1 |
| /** | 任意路径 | 能匹配/deps /login deps/1 |
| /deps/* | /depts 下的一级路径 | 能匹配 /deps/1 不能匹配 /deps /deps/1/2 |
| /deps/** | /depts 下的任意路径 | 能匹配 /deps /deps/1/2 不能匹配 /login/1 |
- 拦截器执行流程

Filter 与 Interceptor 的区别
-
接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
-
拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源