【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版
这两个类共同实现了一个 基于 Token 的请求拦截验证机制,主要用于用户身份认证和权限控制。以下是详细解析:
1. AppInterceptor
(核心拦截器类)
类作用
• 实现 HandlerInterceptor
接口,对符合条件的请求进行拦截。
• 主要功能:验证请求中的 Token 是否有效(从 Header 或 Cookie 获取),无效则拒绝访问。
关键代码解析
(1) 成员变量
private static final String URL_ACCOUNT = "/account";
private static final String URL_FILE = "/file";
• 作用:定义不需要 Token 验证的白名单路径(如登录、文件公开访问)。
(2) preHandle()
方法(核心逻辑)
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 1. 检查 handler 是否有效
if (null == handler) {
return false; // 直接拒绝
}
// 2. 只处理 HandlerMethod 类型请求(如 @Controller 或 @RestController 定义的处理器方法,Spring 管理的控制器方法)
if (!(handler instanceof HandlerMethod)) {
return true; // 其他类型(如静态资源)直接放行
}
//Handler type: org.springframework.web.method.HandlerMethod → 控制器方法。
//Handler type: org.springframework.web.servlet.resource.ResourceHttpRequestHandler → 静态资源
// 3. 白名单路径放行
if (request.getRequestURI().contains(URL_ACCOUNT)) {
return true; // 如登录接口无需 Token
}
// 4. 获取 Token(优先从 Header,文件请求从 Cookie)
String token = request.getHeader(Constants.TOKEN_ADMIN);
if (request.getRequestURI().contains(URL_FILE)) {
token = getTokenFromCookie(request); // 文件请求特殊处理
}
// 5. Token 为空或无效时抛出异常
if (StringTools.isEmpty(token)) {
throw new BusinessException(ResponseCodeEnum.CODE_901); // Token 缺失
}
Object sessionObj = redisComponent.getTokenInfo4Admin(token);
if (null == sessionObj) {
throw new BusinessException(ResponseCodeEnum.CODE_901); // Token 无效
}
return true; // 验证通过
}
(3) getTokenFromCookie()
方法
private String getTokenFromCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null) return null;
for (Cookie cookie : cookies) {
if (cookie.getName().equals(Constants.TOKEN_ADMIN)) {
return cookie.getValue(); // 返回匹配的 Token
}
}
return null;
}
• 作用:从 Cookie 中提取 Token(用于文件请求等特殊场景)。
(4) 其他方法
• postHandle()
和 afterCompletion()
:空实现,用于拦截后处理(如日志记录),当前未使用。
2. WebAppConfigurer
(拦截器配置类)
类作用
• 实现 WebMvcConfigurer
接口,注册拦截器并配置拦截规则。
• 核心功能:将 AppInterceptor
应用到所有请求路径(/**
)。
关键代码解析
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
@Resource
private AppInterceptor appInterceptor; // 注入拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(appInterceptor)
.addPathPatterns("/**"); // 拦截所有路径
}
}
• addPathPatterns("/**")
:
表示拦截所有请求(可通过 excludePathPatterns()
排除特定路径,如 /api/public/**
)。
3. 协作流程
- 请求到达 Spring MVC
• 先经过WebAppConfigurer
注册的AppInterceptor
。 - 拦截器验证逻辑
• 白名单放行:如/account/login
直接通过。
• Token 检查:
◦ 普通请求从 Header 的TOKEN_ADMIN
获取 Token。
◦ 文件请求(/file/**
)从 Cookie 获取 Token。
• Redis 验证:检查 Token 是否有效(如是否过期或被踢出)。 - 验证结果处理
• 成功:继续执行后续拦截器或 Controller。
• 失败:抛出BusinessException
(最终会被全局异常处理器转换为错误响应)。
4. 设计亮点
- 灵活 Token 获取
• 支持 Header 和 Cookie 两种方式,适应不同场景(如 API 调用和浏览器文件下载)。 - 白名单机制
• 公开接口(如登录)无需 Token,避免循环验证。 - Redis 集中管理
• Token 状态(如有效期、强制失效)由 Redis 控制,实现分布式会话。
5. 典型应用场景
• 用户登录态校验:
访问 /admin/*
时要求携带有效 Token。
• 文件安全下载:
文件接口(/file/*
)通过 Cookie 传递 Token,确保只有登录用户可访问。
• 权限控制扩展:
可在 preHandle()
中进一步检查用户角色(如从 sessionObj
解析权限)。
总结
类名 | 职责 | 核心方法 |
---|---|---|
AppInterceptor |
拦截请求并验证 Token | preHandle() |
WebAppConfigurer |
注册拦截器到 Spring MVC | addInterceptors() |
通过这两个类,系统实现了 统一、可扩展的认证层,确保只有合法请求能访问受保护资源。
为什么文件请求和其他请求分开处理 Token 的获取方式?
1. 技术限制:浏览器对文件请求的 Header 处理
(1) 普通 API 请求(使用 Header 传递 Token)
场景:前后端分离架构中,前端(如 Vue/React)通过 AJAX/Fetch 调用 API。
Token 传递方式:
通过 HTTP 请求头(如Authorization: Bearer
或自定义头TOKEN_ADMIN
)传递。
优势
:
- 安全性高:Header 不会被浏览器自动缓存或记录在日志中。
- 适合 RESTful API:符合无状态认证的最佳实践。
(2) 文件请求(使用 Cookie 传递 Token)
- 场景:浏览器直接访问文件下载链接(如
[
]() 或 ``)。 - 问题:
浏览器在发起非 AJAX 的文件请求(如直接访问 URL、、
标签)时:- 无法自定义 Header:浏览器不会允许通过普通 HTML 标签或跳转设置自定义头。
- 自动携带 Cookie:浏览器会自动在请求中附加当前域的 Cookie(如果 Cookie 的
Path
和Domain
匹配)。
- 解决方案:
对于文件请求,改用 Cookie 传递 Token,因为:- 浏览器会自动管理 Cookie 的发送。
- 无需前端代码显式设置 Header。
4. 实际应用场景示例
(1) 普通 API 请求
javascript
复制
// 前端调用 API(可自定义 Header)
fetch("/api/data", {
headers: { "TOKEN_ADMIN": "xyz123" }
});
服务端:从 Header
提取 Token。
(2) 文件下载请求
html
运行
复制
<!-- 浏览器直接访问文件链接 -->
<a href="/file/123.pdf">下载PDF</a>
<!-- 或图片加载 -->
<img src="/file/456.jpg">
服务端:从 Cookie
提取 Token(因为浏览器不会为这些请求设置自定义 Header)。