1. 问题现象与初步排查
登录系统后,页面图片和头像无法正常显示,但控制台并未报错。通过Chrome开发者工具检查网络请求发现,图片资源的请求返回状态码为401(Unauthorized)。这显然是一个权限验证失败的问题,但奇怪的是登录流程本身是正常的,token也能正确存储在本地缓存中。
关键排查点:当遇到资源加载问题时,首先应该检查网络请求的状态码和响应内容,而不是直接怀疑前端渲染问题。
在Vue.js前端代码中,图片加载使用的是标准的img标签,src属性指向后端接口(例如/files/avatar/123.jpg)。理论上这种静态资源请求应该不需要携带token,但实际情况却是请求被拦截了。
2. 后端拦截机制分析
系统采用JWT(JSON Web Token)作为鉴权方案,通过拦截器对所有API请求进行验证。查看Spring Boot的后端代码,发现JWT拦截器的配置如下:
java复制@Configuration
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
if (StringUtils.isEmpty(token)) {
throw new UnauthorizedException("Token不能为空"); // 问题出在这一行
}
// ...其他验证逻辑
return true;
}
}
同时,在WebMvcConfigurer的配置中:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/login", "/user/register");
}
}
3. 问题定位过程
3.1 接口测试验证
使用Postman直接请求图片接口/files/avatar/123.jpg,发现返回401错误。这证实了我们的猜想——文件资源请求被JWT拦截器错误地拦截了。
测试技巧:当遇到前端资源加载问题时,先用API测试工具直接请求后端接口,可以快速定位是前端还是后端的问题。
3.2 拦截器调试
在IDEA中对JWT拦截器进行调试,发现当请求图片资源时:
- 请求头中没有携带Authorization字段
- 拦截器直接抛出"Token不能为空"异常
- 由于是静态资源请求,浏览器无法处理这种JSON格式的错误响应
3.3 配置问题发现
检查WebConfig配置类,发现虽然排除了登录和注册接口,但没有排除文件相关的接口:
java复制.excludePathPatterns("/user/login", "/user/register")
// 缺少对/files路径的排除
4. 解决方案与实现
4.1 修正拦截配置
修改WebConfig类,添加文件接口的排除:
java复制@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(
"/user/login",
"/user/register",
"/files/**" // 新增这一行
);
}
4.2 文件控制器安全考虑
虽然放行了文件接口,但需要考虑安全问题。建议在FilesController中实现以下措施:
java复制@RestController
@RequestMapping("/files")
public class FilesController {
@GetMapping("/{type}/{filename}")
public ResponseEntity<Resource> getFile(
@PathVariable String type,
@PathVariable String filename) {
// 1. 验证文件类型是否合法(如只允许avatar, image等)
if (!Arrays.asList("avatar", "image").contains(type)) {
throw new ForbiddenException("非法文件类型");
}
// 2. 验证文件名格式(防止路径遍历攻击)
if (!filename.matches("[a-zA-Z0-9-_.]+")) {
throw new ForbiddenException("非法文件名");
}
// ...文件加载逻辑
}
}
5. 经验总结与避坑指南
5.1 JWT拦截配置最佳实践
-
必须排除的路径:
- 登录/注册接口
- 静态资源路径
- 公开API文档路径(如/swagger-ui/**)
- 健康检查接口(如/actuator/health)
-
推荐配置方式:
java复制.excludePathPatterns(
"/auth/**",
"/public/**",
"/files/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/actuator/**"
)
5.2 前端处理技巧
即使后端正确配置了拦截排除,前端也应当:
- 对图片加载错误进行处理:
vue复制<template>
<img :src="avatarUrl" @error="handleImageError" />
</template>
<script>
export default {
methods: {
handleImageError(e) {
e.target.src = '/default-avatar.png'; // 提供默认图片
console.error('图片加载失败', e);
}
}
}
</script>
- 对于敏感资源,可以主动添加token:
javascript复制// 在axios拦截器中为特定请求添加token
axios.interceptors.request.use(config => {
if (config.url.startsWith('/private-files/')) {
config.headers.Authorization = `Bearer ${store.getters.token}`;
}
return config;
});
5.3 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 登录后图片不显示 | 1. JWT拦截了静态资源请求 2. 文件服务器配置错误 |
1. 检查拦截器排除配置 2. 直接访问文件URL测试 |
| 图片有时显示有时不显示 | 浏览器缓存了401响应 | 1. 清除浏览器缓存 2. 确保服务器返回正确缓存头 |
| 控制台报CORS错误 | 文件服务器未配置跨域 | 在文件服务器添加CORS配置 |
6. 扩展思考:权限与安全的平衡
这个问题本质上反映了权限控制的粒度问题。在实际项目中,我们需要在安全性和便利性之间找到平衡:
-
完全开放模式:
- 优点:简单直接,性能好
- 缺点:无法控制敏感文件访问
- 适用场景:完全公开的静态资源
-
完全保护模式:
- 优点:所有资源都受保护
- 缺点:增加系统复杂度,影响性能
- 适用场景:企业内网等高安全要求系统
-
混合模式(推荐):
- 公开目录:/public/*(无需认证)
- 私有目录:/private/*(需要认证)
- 特殊处理:根据业务需求定制(如/avatars/*可公开但限制上传)
在实现上,可以通过Spring Security进行更精细的控制:
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/private/**").authenticated()
.antMatchers(HttpMethod.GET, "/avatars/**").permitAll()
.antMatchers(HttpMethod.POST, "/avatars/**").authenticated();
}
这个案例给我的启示是:权限系统设计时一定要考虑各种边缘情况,特别是静态资源的访问控制。一个好的做法是在项目初期就建立完整的接口清单,明确哪些需要认证,哪些不需要,而不是后期修修补补。