目录
[1.1 请求和响应数据](#1.1 请求和响应数据)
[1.2 代码实现](#1.2 代码实现)
[2.1 会话技术](#2.1 会话技术)
[2.2 会话跟踪技术------Cookie](#2.2 会话跟踪技术——Cookie)
[2.3 会话跟踪技术------Session](#2.3 会话跟踪技术——Session)
[2.4 会话跟踪技术------令牌](#2.4 会话跟踪技术——令牌)
[3.1 JWT的核心结构](#3.1 JWT的核心结构)
[4.1 Filter(过滤器)令牌校验](#4.1 Filter(过滤器)令牌校验)
[4.1.1 场景实现](#4.1.1 场景实现)
[4.2 Interceptor(拦截器)令牌校验](#4.2 Interceptor(拦截器)令牌校验)
[4.2.1 拦截器的三个核心工作时机](#4.2.1 拦截器的三个核心工作时机)
[4.2.2 流程](#4.2.2 流程)
一、登录功能的实现

1.1 请求和响应数据
请求数据:

响应数据:

1.2 代码实现
Controller层:
对于登录功能映射到了/login地址,因此我们新创建一个LoginController类
Controller层的逻辑:
将前端的请求数据发给Service层,Service层返回info结果,如果该用户存在则登录成功,如果用户不存在那么Service层返回的info为空,那么登录失败。
java
@Slf4j
@RestController
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp){
log.info("登录员工: {}", emp);
LoginInfo info = empService.login(emp);
if(info != null){
return Result.success(info);
}else
return Result.error("用户名或密码错误!");
}
}
Service层:
java
@Override
public LoginInfo login(Emp emp) {
Emp e = empMapper.selectByUsernameAndPassword(emp);
if(e != null){
return new LoginInfo(e.getId(), e.getUsername(), e.getName(), "");
}
return null;
}
Mapper层:
Mapper层主要是进行数据库的操作,因此起名时要见名知意。
java
@Select("select id, username, name from tilas.emp where username = #{username} and password = #{password}")
Emp selectByUsernameAndPassword(Emp emp);
实现上述代码后,我们发现将登录成功后的网址复制到一个新的页面上,无需登录就可以进行数据表的修改,我们只在前端页面上实现了该登录逻辑,但是并没有真正的实现登录的校验功能。
二、登录校验

登录校验的思路:
在用户登录时,存入一个登录标记,在执行各种操作时判断这个用户的登录标记是否存在;但是每个接口都要判断一次登录标记,在工程上是很复杂的,因此引入一个统一拦截器,浏览器发送请求后由统一拦截器拦截,取出登录标记进行判断。如果成功取出,那么就将这个请求放行,去访问对应的接口。
登录标记:用户登录成功之后,在后续的每一次请求中,都可以获取到该标记。[会话技术】
统一拦截:过滤器Filter、拦截器Interceptor
2.1 会话技术
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
会话跟踪方案:
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
- 令牌技术
2.2 会话跟踪技术------Cookie
Cookie 是最早出现的会话跟踪技术,它的核心思想是"把凭证发给客户端,让客户端每次请求都带着"。
工作原理:
客户端发起第一次请求(比如登录)。
服务器响应时,在 HTTP 响应头中添加
Set-Cookie字段,把一些数据(如用户标识)发送给浏览器。浏览器接收到 Cookie 后,会自动将其保存在本地。
之后,浏览器每次向同一个域名发送请求时,都会自动在 HTTP 请求头中带上
Cookie字段,服务器通过解析 Cookie 就能认出你是谁。优点:
实现简单,浏览器自动管理。
服务器不需要存储状态数据,减轻了服务器内存压力。
缺点:
不安全: 数据明文保存在客户端,容易被截获或篡改(容易遭受 XSS 和 CSRF 攻击)。
大小受限: 单个 Cookie 的大小通常被限制在 4KB 左右。
性能损耗: 每次请求都会携带所在域名下的所有 Cookie,增加网络带宽消耗。
2.3 会话跟踪技术------Session
为了解决 Cookie 不安全的问题,Session 技术诞生了。它的核心思想是"数据存在服务器,只给客户端发一把钥匙"。
工作原理:
用户登录成功后,服务器会在内存中创建一个 Session 对象,用来存储该用户的敏感数据,并生成一个唯一的
Session ID。服务器将这个
Session ID通过 Cookie(通常叫JSESSIONID或PHPSESSID)发送给浏览器。浏览器下次请求时,带着包含
Session ID的 Cookie 过来。服务器拿到
Session ID后,去内存里找到对应的 Session 对象,从而获取用户状态。优点:
安全性高: 敏感数据存储在服务器端,客户端只保存一个无意义的 ID。
没有大小限制: 可以存储复杂的对象。
缺点:
服务器压力大: 如果用户量非常大,服务器内存会被大量的 Session 对象占满。
分布式扩展困难: 在多台服务器集群的环境下,如果用户的第一次请求打在 A 机器(Session 在 A 上),第二次请求打在 B 机器,B 机器上没有该用户的 Session,用户就会被迫重新登录。解决这个问题需要引入"Session 共享"(如使用 Redis),增加了系统架构的复杂度。
2.4 会话跟踪技术------令牌
随着移动端 App 的兴起和微服务/分布式架构的普及,Session 的局限性暴露无遗(App 不支持原生 Cookie,分布式 Session 共享复杂)。于是,Token(最常见的是 JWT - JSON Web Token)成为了主流。
它的核心思想是"服务器不保存状态,全靠密码学签名防篡改"。
工作原理:
用户登录成功后,服务器根据用户信息(如 UserID、角色等)生成一个 JSON 字符串,并使用服务器侧的"密钥"对其进行加密签名,最终生成一串长字符串(这就是 Token)。
服务器把 Token 发给客户端(不再强制要求存在 Cookie 里,客户端可以存放在 LocalStorage 甚至内存里)。
客户端后续发请求时,通常将 Token 放在 HTTP 请求头的
Authorization字段中。服务器收到请求后,不需要去内存里查数据,只需要用自己的"密钥"对 Token 的签名进行验证。如果签名合法且没过期,就信任该 Token 携带的用户信息。
优点:
彻底无状态: 服务器不需要存储任何 Session,极大地节省了服务器资源,天生支持分布式集群。
跨端支持好: 不依赖 Cookie,可以完美支持手机 App、小程序等跨平台应用。
避免 CSRF 攻击: 因为不需要浏览器自动发送 Cookie,防御了跨站请求伪造攻击。
缺点:
一旦签发,极难废弃: Token 在过期前始终有效,如果用户中途修改密码或被封号,很难立刻让旧 Token 失效(除非服务器引入黑名单机制,但这又变回了有状态)。
体积较大: 包含了用户信息和较长的签名,每次请求携带会消耗一定带宽。
三、JWT令牌技术
JSON Web Token (JWT) 是一种目前非常流行、用于跨域和无状态身份验证的开放标准

3.1 JWT的核心结构
如果你在浏览器控制台或网络请求里抓过包,你看到的 JWT 往往是一长串毫无规律的乱码,例如: xxxxxx.yyyyyy.zzzzzz
仔细看,它由两个英文句号 . 分隔成了三个部分:
-
第一部分:Header(头部) 记录了令牌的类型(通常是
JWT)以及签名使用的加密算法(如HS256或RS256)。这部分会被 Base64Url 编码。 -
第二部分:Payload(负载/载荷) 这是 JWT 的核心,用来存放实际需要传递的数据(称为 Claims 声明)。比如用户的 ID、角色、签发时间(iat)、过期时间(exp)等。这部分同样会被 Base64Url 编码。 (注意:Payload 是明文编码,可以被任何人解码,因此千万不要在里面存放密码等敏感信息!)
-
第三部分:Signature(签名) 这是防止令牌被篡改的关键。服务器会将编码后的 Header、编码后的 Payload,加上一个只有服务器自己知道的密钥(Secret),使用 Header 中指定的算法进行加密运算,生成签名。
加密代码实现:
java
public void testGenerateJwt(){
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", 1);
dataMap.put("username", "admin");
String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "c3BybmVmdA==")//指定加密算法,密钥
.addClaims(dataMap)//添加自定义信息
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24))//设置过期时间
.compact();//生成令牌
System.out.println(jwt);
}
得到的jwt令牌:
java
eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImV4cCI6MTc3OTAwMTg1M30.abcewY65PS145Y_naxlNkadh-fNyNoENsyfClUWlprM


解密代码实现:
java
public void testParseJwt(){
String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImV4cCI6MTc3OTAwMTg1M30.abcewY65PS145Y_naxlNkadh-fNyNoENsyfClUWlprM";
Claims claims = Jwts.parser()
.setSigningKey("c3BybmVmdA==")//指定密钥
.parseClaimsJws(jwt)//解析JWT
.getBody();//获取自定义信息
System.out.println(claims);
}
解密后得到的body数据:

四、令牌校验
在上一章节中,我们实现了如何生成用于验证身份的jwt令牌,接下来我们要实现拦截令牌并验证令牌的有效性。
4.1 Filter(过滤器)令牌校验
Filter 是 Java Web(Servlet 规范)提供的一种技术。
当客户端发起 HTTP 请求到达服务器,但在真正到达你的 Controller(业务代码)之前,会先经过 Filter。同样的,当 Controller 处理完准备返回响应给客户端时,也会再次经过 Filter。
利用这个特性,我们可以把校验令牌(Token)的逻辑写在 Filter 里,实现统一拦截。
快速入门程序:
1.定义Filter:定义一个类,实现 Filter 接口,并实现其所有方法。
2.配置Filter:Filter类上加@WebFilter注解,配置拦截路径。引导类上加 @ServletComponentScan开启Servlet组件支持。
实现代码:
java
@Slf4j
@WebFilter(urlPatterns = "/*") // 拦截所有请求
public class DemoFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("init 初始化方法");
}
//拦截到请求执行,会执行多次
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("doFilter 拦截到请求执行");
}
@Override
public void destroy() {
log.info("destroy 销毁方法");
}
}
引导类注解:

每一次请求都会被Filter拦截:

拦截后要进行放行操作:

4.1.1 场景实现
java
@WebFilter(urlPatterns = "/*")
@Slf4j
public class JwtFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1.获取请求路径,uri
String uri = request.getRequestURI();
//2.判断请求路径是否需要处理,如果路径包含/login,则直接放行
if(uri.contains("/login")){
log.info("登录请求,放行");
chain.doFilter(request, response);
return;
}
//3.获取请求头中的令牌
String token = request.getHeader("token");
//4.判断令牌是否存在,不存在说明用户没有登录,返回401状态码
if(token == null){
log.info("令牌不存在,返回401状态码");
response.setStatus(401);
return;
}
//5.判断令牌是否有效,如果无效,则返回401状态码
try {
JwtUtils.parseJwt(token);
}catch (Exception e){
log.info("令牌无效,返回401状态码");
response.setStatus(401);
return;
}
log.info("令牌有效,放行");
chain.doFilter(request, response);
}
}
4.2 Interceptor(拦截器)令牌校验
Interceptor 是 Spring MVC 框架 自带的技术。它专门用来拦截发送到 Controller(控制器) 的请求。
4.2.1 拦截器的三个核心工作时机
实现一个拦截器,通常需要实现 HandlerInterceptor 接口。这个接口里面有三个非常重要的方法:
-
preHandle(前置处理): 最常用,在请求到达 Controller 之前执行。-
作用: 校验 JWT 令牌、检查权限。
-
返回值: 返回
true表示放行;返回false表示拦截,请求直接打回,不会走到 Controller。
-
-
postHandle(后置处理): 在 Controller 执行完毕,但在视图渲染之前执行。(在目前前后端分离的 JSON 接口开发中,较少使用)。 -
afterCompletion(完成之后): 整个请求处理完毕(连数据都返回给前端了)之后执行。- 作用: 清理资源、记录极其详细的操作日志、处理异常。
4.2.2 流程
1.定义拦截器,实现HandlerInterceptor接口,并实现其所有方法。
java
@Slf4j
@Component
public class DemoInterceptor implements HandlerInterceptor {
@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");
}
}
2.注册拦截器
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private DemoInterceptor demoInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(demoInterceptor).addPathPatterns("/**");
}
}
五、Filter与Interceptor对比
| 维度 | Filter(过滤器) | Interceptor(拦截器) |
|---|---|---|
| 技术出身 | Servlet 规范(Java EE 标准,Tomcat 提供支持) | Spring 框架(Spring MVC 特有组件) |
| 拦截范围 | 大。 拦截所有的进入 Tomcat 的请求(包括静态资源 html、css、图片等)。 | 小。 只能拦截发往 Spring Controller 的请求。 |
| 上下文感知 | 弱。 它只知道 HTTP 请求和响应,不知道 Spring 内部的事。 | 强(核心优势)。 它的 preHandle 方法里有个参数叫 Object handler,这个对象其实就是你要访问的 Controller 方法。你可以通过反射拿到这个方法上的自定义注解 (比如 @RequireAdmin)。 |
| 触发时机 | 在请求进入 Servlet 前触发。 | 在请求进入 Servlet 后,到达 Controller 前触发。 |



