前言:在 Web 开发中,安全性是系统的重中之重。本文将带你从零实现一个企业级的登录认证系统,涵盖从基础的登录接口开发到进阶的 JWT 令牌技术,以及如何利用 SpringBoot 的过滤器和拦截器实现统一权限校验,确保系统资源不被非法访问。
⚡ 快速参考
- 适用场景:需要实现用户登录、权限校验、移动端适配及集群环境下的会话共享。
- 核心结论:JWT 令牌是当前前后端分离架构下的主流会话跟踪方案,优于传统的 Cookie-Session。
- 最简步骤:1. 实现登录接口生成 JWT;2. 前端存储并携带 JWT;3. 后端拦截器统一校验。
- 必备代码 :
Jwts.builder().signWith(...).compact()生成令牌;HandlerInterceptor实现拦截。 - 高危避坑:JWT 秘钥泄露、过期时间设置过长、拦截器路径配置错误导致登录接口被拦截。
📚 学习目标
- 掌握基于 JWT 令牌的登录认证流程及代码实现。
- 理解会话跟踪技术(Cookie, Session, Token)的区别与优缺点。
- 能够熟练运用 Filter 和 Interceptor 实现 SpringBoot 的统一请求拦截。
一、基础概念
1.1 登录功能本质
登录功能的本质是查询:根据用户提供的用户名和密码,在数据库中匹配对应的记录。如果匹配成功,则下发身份凭证。
1.2 会话技术
- 会话:浏览器与服务器之间的一次连接(从打开浏览器访问到关闭)。
- 会话跟踪:服务器识别多次请求是否来自同一浏览器的过程。
| 技术方案 | 存储位置 | 跨域支持 | 集群支持 | 适用场景 |
|---|---|---|---|---|
| Cookie | 客户端 | 不支持 | 支持 | 简单 Web 应用 |
| Session | 服务端 | 不支持 | 不支持(默认) | 传统单体应用 |
| Token (JWT) | 客户端 | 支持 | 支持 | 移动端/前后端分离/微服务 |
二、原理详解
2.1 为什么选择 JWT?
HTTP 协议是无状态的,服务器无法自动识别用户身份。
- Cookie-Session 痛点:Session 存储在服务端,集群环境下无法共享;Cookie 不支持跨域且移动端支持差。
- JWT 优势:自包含(载荷可存数据)、无状态(服务器不存数据)、安全可靠(签名防篡改)、天然支持跨域。
2.2 JWT 结构
JWT 字符串由三部分组成(. 分隔):
- Header (头部):签名算法和令牌类型。
- Payload (有效载荷):自定义数据(如用户 ID、姓名等)。
- Signature (签名):防止篡改的核心,由 Header + Payload + 秘钥通过算法生成。
三、完整实战代码
3.1 登录接口实现
1. 结果封装类 LoginInfo
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginInfo {
private Integer id;
private String username;
private String name;
private String token; // 登录成功后下发的令牌
}
2. Controller 层
java
@Slf4j
@RestController
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp){
log.info("员工登录: {}", emp);
LoginInfo loginInfo = empService.login(emp);
return loginInfo != null ? Result.success(loginInfo) : Result.error("用户名或密码错误~");
}
}
3. Service 实现与 JWT 生成
java
@Override
public LoginInfo login(Emp emp) {
Emp empLogin = empMapper.getUsernameAndPassword(emp);
if(empLogin != null){
// 生成 JWT 令牌
Map<String, Object> claims = new HashMap<>();
claims.put("id", empLogin.getId());
claims.put("username", empLogin.getUsername());
String jwt = JwtUtils.generateJwt(claims);
return new LoginInfo(empLogin.getId(), empLogin.getUsername(), empLogin.getName(), jwt);
}
return null;
}
3.2 JWT 工具类
java
public class JwtUtils {
private static String signKey = "SVRIRUlNQQ=="; // 秘钥
private static Long expire = 43200000L; // 有效期 12 小时
public static String generateJwt(Map<String, Object> claims){
return Jwts.builder()
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, signKey)
.setExpiration(new Date(System.currentTimeMillis() + expire))
.compact();
}
public static Claims parseJWT(String jwt){
return Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody();
}
}
3.3 统一拦截校验(Interceptor 方案)
1. 定义拦截器
java
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURL().toString();
// 1. 放行登录请求
if(url.contains("login")){ return true; }
// 2. 获取并校验令牌
String jwt = request.getHeader("token");
if(!StringUtils.hasLength(jwt)){
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
return true;
}
}
2. 注册拦截器
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
四、场景应用
场景1:前后端分离管理系统
- 需求:前端 Vue/React,后端 SpringBoot,部署在不同域名下。
- 方案 :使用 JWT 存储在 LocalStorage 中,Axios 拦截器统一在 Header 携带
token。 - 收益:解决跨域 Cookie 无法使用的难题,实现灵活的权限控制。
场景2:移动端 APP 登录
- 需求:Android/iOS 客户端登录。
- 方案:移动端不具备浏览器 Cookie 环境,通过请求头传递 JWT。
- 收益:一套认证逻辑完美适配 PC、移动端及平板。
五、开发避坑总结
- 问题 :JWT 令牌过期后解析报错。
原因 :Jwts.parser()在解析过期令牌时会抛出ExpiredJwtException。
解决 :在拦截器中使用try-catch捕获异常,并统一返回 401 状态码。 - 问题 :拦截器配置后,登录接口也无法访问。
原因 :addPathPatterns("/**")拦截了所有请求。
解决 :使用excludePathPatterns("/login")排除登录接口。 - 问题 :JWT Payload 存放了敏感信息。
原因 :JWT 的 Header 和 Payload 仅是 Base64 编码,可被轻易解码。
解决:禁止在 JWT 中存放密码、手机号等敏感信息。
六、面试考点
-
Q1:JWT 令牌和 Session 认证的区别是什么?
- A:Session 是有状态的,数据存在服务端,不支持分布式集群;JWT 是无状态的,数据存在客户端,适合分布式和微服务架构。
-
Q2:Filter 和 Interceptor 有什么区别?
- A:Filter 是 Servlet 规范定义的,拦截范围广;Interceptor 是 Spring 提供的,能访问 Spring 上下文。
-
Q3:如何防止 JWT 被篡改?
- A:依靠 Signature(签名)部分。签名是由头部、载荷加秘钥生成的,攻击者没有秘钥无法生成有效的签名。
-
追问1:JWT 秘钥泄露了怎么办?(需立即更换秘钥,所有旧令牌失效)
-
追问2:如何主动让某个 JWT 令牌失效?(JWT 是无状态的,通常需配合 Redis 黑名单实现)
七、总结
本文从基础的登录业务出发,深入剖析了会话跟踪技术的演进,并通过代码实战演示了如何在 SpringBoot 中集成 JWT 令牌。掌握了 Filter 和 Interceptor 的使用,你就掌握了 Web 系统安全防护的第一道防线。下一步,建议学习 Spring Security 或 Shiro 等专业安全框架,以应对更复杂的权限需求。
附录:
本文为MY_TRUCK原创实战学习笔记,持续更新Java后端与AI应用领域干货,问题欢迎评论区交流。