会话技术

Cookie技术有三个自动:

  • 服务器会 自动 的将 cookie 响应给浏览器。
  • 浏览器接收到响应回来的数据之后,会 自动 的将 cookie 存储在浏览器本地。
  • 在后续的请求当中,浏览器会 自动 的将 cookie 携带到服务器端。

Cookie的使用:

  1. 设置Cookie 服务端 ——> 浏览器

    关键: 形参包含 HttpServletResponse

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @RestController
    public class CookieController {

    // 1. 设置 Cookie
    @GetMapping("/setCookie")
    public String setCookie(HttpServletResponse response) {
    // 创建一个 Cookie 对象
    Cookie cookie = new Cookie("user_token", "abc123");

    // 可选:配置 Cookie 属性
    cookie.setPath("/"); // 作用路径(默认当前路径)
    cookie.setMaxAge(24 * 60 * 60); // 有效期 1 天(单位:秒)
    cookie.setHttpOnly(true); // 禁止 JS 访问(防 XSS)
    cookie.setSecure(false); // 非 HTTPS 时设为 false(本地测试用)

    // 将 Cookie 添加到响应中
    response.addCookie(cookie);

    return "Cookie 设置成功!";
    }
    }
  2. 读取/删除Cookie 浏览器——>服务端

    依旧使用 HttpServletRequest:

    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
    @GetMapping("/readCookie2")
    public String readCookie2(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
    for (Cookie cookie : cookies) {
    if ("user_token".equals(cookie.getName())) {
    return "获取到的 Cookie 值: " + cookie.getValue();
    }
    }
    }
    return "Cookie 不存在或已过期!";
    }


    // 3. 删除 Cookie
    @GetMapping("/deleteCookie")
    public String deleteCookie(HttpServletResponse response) {
    // 创建一个同名的空 Cookie,并设置 MaxAge=0
    Cookie cookie = new Cookie("user_token", "");
    cookie.setPath("/");
    cookie.setMaxAge(0); // 立即过期

    response.addCookie(cookie);
    return "Cookie 已删除!";
    }

Session

  1. 设置Session数据

    HttpServletRequest

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @RestController
    public class SessionController {

    @GetMapping("/setSession")
    public String setSession(HttpServletRequest request) {
    // 获取当前 Session(如果不存在则自动创建)
    HttpSession session = request.getSession();

    // 向 Session 中存储数据
    session.setAttribute("user", "Alice");
    session.setAttribute("role", "admin");

    return "Session 数据已设置!";
    }
    }
  2. 读取Session数据

    依旧HttpServletRequest,读写不同之处仅为 getAttribute 和 setAttribute

    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
    @GetMapping("/getSession")
    public String getSession(HttpServletRequest request) {
    HttpSession session = request.getSession(false); // 如果不存在,返回 null

    if (session == null) {
    return "Session 不存在或已过期!";
    }

    String user = (String) session.getAttribute("user");
    String role = (String) session.getAttribute("role");

    return String.format("Session 数据: user=%s, role=%s", user, role);
    }


    @GetMapping("/invalidateSession")
    public String invalidateSession(HttpServletRequest request) {
    HttpSession session = request.getSession(false);

    if (session != null) {
    session.invalidate(); // 立即销毁 Session
    return "Session 已销毁!";
    }
    return "Session 不存在!";
    }

令牌

三部分组成

  • 第一部分:Header(头), 记录令牌类型、签名算法等。 例如:{“alg”:”HS256”,”type”:”JWT”}
  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:{“id”:”1”,”username”:”Tom”}
  • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

自定义JWT获取/解析类

加密JWT的四个链式调用步骤:

  1. Jwts.builder().signWith(加密算法,人为给出加密字符串)
  2. ​ .addClaims(传入要加密的map集合)
  3. ​ .setExpiration(Date对象,规定过期时间)
  4. ​ .compact(); 最终打包

解密的四步链式调用:

  1. Jwts.parser()

  2. ​ .setSigningKey(加密时指定的加密字符串)

  3. ​ .parseClaimsJws(加密后的字符串)

  4. ​ .getBody()

    ——> 用map接收

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
public class JwtUtils {
private static String signKey = "SVRIRUlNQQ==";
private static Long expire = 43200000L;


//生成JWT令牌
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令牌
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
return claims;
}
}

各种会话技术的深度理解(核心)

Q:Cookie和Session本质上的区别和联系是什么

A:从形态上讲,Cookie可以理解为一个容器,Session方案是一种变种Cookie,

原来的Cookie是直接存放键值对数据,这导致数据会直接存在浏览器端,可能会产生不安全问题

而Session方案本质上也是使用Cookie,只是在Cookie中只存了一个SessionId,真正的信息需要到服务端凭借Id去取

Q:Cookie和Jwt方案的区别和联系是什么,换句话说,Cookie和Jwt所用到的核心内容载体“Token”有啥区别

A:Cookie和Token都是Http请求头中的一个字段,不同的是,

Cookie是一种已经被定义好的,不管是浏览器端还是服务端,都有对其相应的使用规范的一个字段

Token是一个自定义字段,Token只是约定俗成的名字,可以改成Auth,可以改成myCookie,只要前后端约定好就行

Token在Jwt中的角色作用,既可以是传递原来存在Cookie中的具体的值,也可以是模仿Session方案传递Id用来标识用户等,因为反正都会被加密

Q:Reddis方案相比于其他方案有什么区别和联系

A:Redis方案,是把数据存放到Redis中,在用户登录校验的场景中,存放的可以是整个用户对象(包含用户的id),

使用Redis方案也要用到自定义的Token,用来传递用户标识,然后拿来当作key去redis里面取

A2:Redis方案和Jwt方案还解决了分布式多Tomcat服务器下,Session方案由于自带SessionId只能在同一个tomcat服务器流通而产生的一个问题:

用redis实现登录和用户信息的传递,和使用jwt实现登录以及用户信息传递,都是本质上通过自定义token来进行用户的唯一标识,解决了基于session时,使用自带的sessionid作为唯一标识导致多tomcat服务器时的不兼容问题

过滤器

Filter表示过滤器,是 JavaWeb三大组件(Servlet、Filter、Listener)之一,也是现在web开发中唯一有一个还在用的

Filter的使用:

  • 第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。
  • 第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持
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
@WebFilter(urlPatterns = "/*")  //2.1配置拦截路径
public class DemoFilter implements Filter { //1.定义类,实现接口并重写方法


//初始化方法, web服务器启动, 创建Filter实例时调用, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init ...");
}

//拦截到请求时,调用该方法,可以调用多次
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
System.out.println("拦截到了请求...");
}

//销毁方法, web服务器关闭时调用, 只调用一次
public void destroy() {
System.out.println("destroy ... ");
}
}

//。。。。。。。遥远的SpringBoot启动类中。。。:
@ServletComponentScan //2.2开启对Servlet组件的支持
@SpringBootApplication
public class TliasManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasManagementApplication.class, args);
}
}
image-20250913154225970

具体实现:

核心有三点:

  1. 把参数中的 ServletRequest 和 ServletRequest 强转成HttpServletRequest 和 HttpServletResponse,从request中获取路径信息,token信息
  2. 放行调用chain.doFilter方法,参数是request和response,这俩本来就是要传的,所以相当于放行了
  3. chain.deFilter放行以后,会等相关所有方法都执行完,在回到他的下一条语句进行执行
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
@Slf4j
@WebFilter(urlPatterns = "/*") //规定拦截路径
public class TokenFilter implements Filter {

@Override
public void doFilter(ServletRequest req, ServletRequest resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req; //依旧HttpServletRequest
HttpServletResponse response = (HttpServletResponse) resp;
//1. 获取请求url。
String url = request.getRequestURL().toString();

//2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){ //登录请求
log.info("登录请求 , 直接放行");
chain.doFilter(request, response); //放行
log.info("登陆后的语句");//这个也会执行完
return;
}

//3. 获取请求头中的令牌(token)。
String jwt = request.getHeader("token");

//4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){ //jwt为空
log.info("获取到jwt令牌为空, 返回错误结果");
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
return;
}

//5. 解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
log.info("解析令牌失败, 返回错误结果");
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
return;
}

//6. 放行。
log.info("令牌合法, 放行");
chain.doFilter(request , response);
}

}
拦截路径 urlPatterns值 含义
拦截具体路径 /login 只有访问 /login 路径时,才会被拦截
目录拦截 /emps/* 访问/emps下的所有资源,都会被拦截
拦截所有 /* 访问所有资源,都会被拦截

过滤器链

过滤器链上过滤器的执行顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序。 比如:

AbcFilter DemoFilter

这两个过滤器来说,AbcFilter 会先执行,DemoFilter会后执行,访问完web资源之后,还会回到过滤器当中来执行过滤器放行后的逻辑,而在执行放行后的逻辑的时候,顺序是反着的

拦截器

拦截器是SpringBoot提供的

使用拦截器

  1. 定义拦截器 ——> 实现HandlerInterceptor接口,并重写其所有方法

  2. 使用拦截器 ——> 创建一个配置类实现 WebMvcConfigurer 接口,并重写 addInterceptors 方法

    在方法中,将拦截器对象通过 addInterceptor 方法 传入 registry ,链式调用 addPathPatterns 方法指定拦截路径

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
//自定义拦截器
@Component
public class DemoInterceptor implements HandlerInterceptor {
//目标资源方法执行前执行。 返回true:放行 返回false:不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 获取请求url。
String url = request.getRequestURL().toString();

//2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){ //登录请求
log.info("登录请求 , 直接放行");
return true;
}

//3. 获取请求头中的令牌(token)。
String jwt = request.getHeader("token");

//4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){ //jwt为空
log.info("获取到jwt令牌为空, 返回错误结果");
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
return false;
}

//5. 解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
log.info("解析令牌失败, 返回错误结果");
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
return false;
}

//6. 放行。
log.info("令牌合法, 放行");
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration  //加注解
public class WebConfig implements WebMvcConfigurer {

//自定义的拦截器对象
@Autowired
private DemoInterceptor demoInterceptor;


@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(demoInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
}
}

拦截路径自定义:通过 addPathPatterns 和 excludePathPatterns 方法

image-20250913160713870

拦截器拦截路径:

拦截路径 含义 举例
/* 一级路径 能匹配/depts,/emps,/login,不能匹配 /depts/1
/** 任意级路径 能匹配/depts,/depts/1,/depts/1/2
/depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/** /depts下的任意级路径 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1

image-20250913160828177

image-20250913161009177