Spring拦截器实现接口访问鉴权(cookie、session、token三者区别)
大家好,小编今天分享一下Spring拦截器鉴权,在讲解鉴权拦截之前,我们先来了解一下cookie、session、token(JWT)相关知识,帮助我们对接口鉴权型形成一个整体的知识框架。文章中存在错误之处,可以进行留言,博主会及时纠正。如果文章对您的学习有所帮助,不要忘记点赞哟!🤞🤞🤞🤞🤞🤞
目录
一、Cookie
1、了解Cookie
HTTP协议本身不具备记录用户状态的能力,每次请求与响应都独立存在(无状态特性)。为了解决服务器无法识别连续请求来源的问题,Cookie技术应运而生,成为维持会话状态的桥梁。
其核心机制表现为:
(1)客户端存储机制
服务器通过响应头向浏览器下发微型数据片段(即Cookie),浏览器会将其持久化存储在本地。当用户后续访问同一服务器时,浏览器会自动在请求头中附加该域名下的所有有效Cookie,使服务器能通过读取这些数据识别用户身份或历史行为。
(2)域名隔离原则
Cookie通过域名绑定机制实现安全隔离,例如为"example.com"设置的Cookie默认不会被发送至"anothersite.com"。该限制通过两个维度实现:
-
作用域控制:可显式定义生效域名(如允许子域共享时设置为".example.com")
- 访问限制:浏览器严格遵循同源策略,禁止跨域名访问Cookie
注:同一域名下的不同端口默认共享Cookie,但现代浏览器逐渐加强端口隔离的安全性限制
2、特点
- 存储于客户端,默认通过 HTTP 请求头自动传递。
- 支持设置过期时间(持久化 Cookie)或会话级失效(临时 Cookie)。
- 安全性依赖 HTTPS 和属性设置(如
HttpOnly、Secure)
3、适用场景
- 存储非敏感信息(如用户偏好、语言设置)。
- 结合 Session 传递 Session ID(传统认证方案) 。
二、Session
1、了解Session
-
session 是另一种记录服务器和客户端会话状态的机制
-
session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中
2、Session认证流程
(1) 会话初始化阶段
当客户端首次发起请求时,服务端通过以下步骤建立有状态会话:
-
会话存储创建:验证用户凭证后,在服务端内存或分布式存储中生成会话数据容器(Session),内含用户身份、权限状态等敏感信息
-
会话密钥同步:将全局唯一的会话标识符(SessionID)通过HTTP响应头
Set-Cookie传递至客户端,例如:
Set-Cookie: session_id=5f4dcc3b5aa765d61d8327deb882cf99; Domain=.example.com; Path=/; HttpOnly
(2)客户端持久化存储
浏览器接收到SessionID后执行:
-
Cookie容器写入:按照RFC 6265规范,将SessionID持久化存储至与域名绑定的Cookie存储区
-
安全策略附加:根据服务端设置的Cookie属性(Secure/HttpOnly/SameSite等),限制JavaScript访问与传输条件
(3)会话维持阶段
客户端发起后续请求时触发自动凭证传递:
-
请求头自动装配:浏览器依据同源策略,自动在HTTP请求头中附加匹配当前域名的Cookie数据:
Cookie: session_id=5f4dcc3b5aa765d61d8327deb882cf99
-
服务端会话验证:服务端执行链式验证:
① 从Cookie提取SessionID → ② 查询会话存储 → ③ 会话有效性判定(超时/主动销毁等) -
状态感知响应:根据验证结果执行分支逻辑:
-
有效会话:读取会话数据,执行授权操作
-
无效会话:返回401 Unauthorized或重定向至认证入口
-
3、适用场景
-
需要服务端控制会话状态的场景(如电商购物车、权限管理)。
-
传统单体架构的登录认证 。
三、Token(令牌)
1、了解Token
为了解决 Session 的扩展性问题,Token(如 JWT)采用无状态设计,将用户数据编码到 Token 中并签名,客户端自行存储 Token,服务端仅需验证签名。
2、特点及结构
(1)特点
-
无状态:服务端无需存储 Token,通过签名验证合法性。
-
自包含:Token 中直接编码用户信息(Payload)。
-
可扩展性:支持分布式系统、跨域认证(如 OAuth、单点登录) 。
(2)结构
-
Header:算法类型(如 HS256)。
-
Payload:承载用户数据(如用户 ID、角色)。
-
Signature:基于密钥对前两部分的签名,防篡改。
3、Token认证流程(例如登陆过程)
-
用户在浏览器使用账号和密码进行登录,发送post请求
-
服务端收到请求,去验证用户名与密码
-
验证成功后,服务端会生成一个 token 并把这个 token 发送给客户端
-
客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
-
客户端每次向服务端请求资源的时候需要带着服务端发送的token
-
服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据。
4、适用场景
- 移动端 App 认证、微服务间鉴权、第三方登录(如微信登录)
四、Cookie、Session和Token区别总结
| 特性 | Cookie | Session | Token(JWT) |
|---|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务端(内存、数据库) | 客户端(LocalStorage、Cookie) |
| 安全性 | 较低(易被 XSS/CSRF 攻击) | 较高(仅暴露 Session ID) | 高(签名防篡改,需配合 HTTPS) |
| 扩展性 | 受限于跨域策略 | 需解决分布式 Session 共享 | 天然支持分布式系统 |
| 典型场景 | 用户偏好设置、临时会话标识 | 传统 Web 应用的登录状态管理 | 移动端、API 鉴权、单点登录 |
五、Spring拦截器实现鉴权
1、创建JWT工具类,生成Token令牌
package com.springboot.until;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
public class GenerateTokenUtil {
/**
* 生成token
* @param username 用户名
* @param password 密码
* @return
*/
public static String generateToken(String username, String password) {
//token有效期12小时,12小时后过期
Long expireTime = System.currentTimeMillis() + 1000 * 60 * 60 * 12;
String token = JWT.create()
.withAudience(username) //设置token的签发者
.withSubject("token") //设置token的主题
.withExpiresAt(new Date(expireTime)) //设置token的过期时间
.sign(Algorithm.HMAC256(password)); //设置token的加密算法和密码
return token;
}
/**
* 解析token获取用户名
* @param token
* @return
*/
public static String parseToken(String token) {
return JWT.decode(token).getAudience().get(0);
}
/**
* 如果token过期,则返回false,否则返回true
* @param token
* @param password
* @return
*/
public static boolean verifyToken(String token,String password) {
try {
Date expireTime = JWT.decode(token).getExpiresAt();
if (expireTime.before(new Date())) {//token已过期
return false;
}else {
return true;
}
}catch (Exception e){
return false;
}
}
}
2、拦截器鉴权实现
(1)拦截器介绍
拦截器的主要是基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在Service或者一个方法前调用一个方法,或者在方法后调用一个方法,甚至在抛出异常的时候做业务逻辑的操作。它的作用就是拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。
(2)构造拦截器实现鉴权(示例代码)
定义拦截器:
package com.springboot.config;
import com.alibaba.druid.util.StringUtils;
import com.springboot.pojo.Staff;
import com.springboot.service.Crud;
import com.springboot.service.CrudStaff;
import com.springboot.until.GenerateTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
@Component
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
@Autowired
private CrudStaff crudStaffService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在请求处理之前进行调用(Controller方法调用之前)
// 返回true才会继续向下执行,返回false取消当前请求
// 这个方法也是需要当前用户登录以后,才能进行操作。
try {
String token = request.getHeader("token");
//设置白名单(不需要拦截的请求)
if (request.getRequestURI().contains("/user/login") ||
request.getRequestURI().contains("/user/register")
) {
return true;
}
if (!StringUtils.isEmpty(token)) {
//校验用户信息是否存在
String userName = GenerateTokenUtil.parseToken(token);
Staff staff = crudStaffService.getByUserName(userName);
if (Objects.isNull(staff)) {
log.info("用户信息不存在");
return false;
}
//校验token是否过期
if (GenerateTokenUtil.verifyToken(token)) {
log.info("token未过期");
}else {
log.info("token过期");
return false;
}
} else {
log.info("token为空");
return false;
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}catch (Exception e){
log.info("拦截器异常");
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求结束之后被调用,也就是在DispatcherServlet渲染了对应的视图之后执行(主要是用于进行资源清理工作)
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
注意:
- preHandle方法:在请求处理之前进行调用(Controller方法调用之前),返回true才会继续向下执行,返回false取消当前请求
- postHandle方法:请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
- afterCompletion方法:在整个请求结束之后被调用,也就是在DispatcherServlet渲染了对应的视图之后执行(主要是用于进行资源清理工作)
注册配置拦截器
package com.springboot.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class MyWebConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//定义拦截对象
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**") //拦截所有请求
.excludePathPatterns("/user/login","/user/register"); //排除白名单(不拦截该请求路径)
}
}
至此,我们完成了Spring的拦截器的配置,之后遇到不需要拦截的请求路径,可以直接加入到白名单中。
更多推荐


所有评论(0)