冲突点:切面配置的扫描范围(Pointcut)太广了,例如 AuthAspectexecution(* com.xxx.controller....(...))。
发生了什么:WebSocket 类()放在了 包下。Spring AOP 扫描到了它,并试图给它创建一个 DocChatWebSocketControllercontrollerCGLIB 代理(Proxy)来实现拦截。
为何报错:'@ServerEnd 是 Java EE 标准(JSR 356),Spring 的 @ServerEndpointServerEndpointExporter 在注册端点时,需要直接操作。当它发现这个 Bean 变成了一个 原生类Spring AOP 代理对象时,它无法读取原生类上的 注解元数据,从而抛出 。@ServerEndpointIllegalStateException
简单来说:WebSocket 端点类不能被 AOP 代理,必须保持"纯净"。
✅ 解决方案
有两个方案,推荐 (最快)或 (架构更合理)。方案一方案二
dart
<!-- Spring Boot AOP starter(核心依赖,包含AspectJ相关组件) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<!-- 如果你的项目已经指定了spring-boot-starter-parent的版本,这里无需写version -->
<!-- 若未指定,需添加版本,比如:<version>2.7.15</version>(根据你的Spring Boot版本调整) -->
</dependency>
方案一:修改 AOP 切面,排除 WebSocket 类(推荐)
在切点AuthAspect@ServerEndpoint 注解的类。
修改 :AuthAspect.java
java
package com.yige.security;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
// 引入 WebSocket 注解
import javax.websocket.server.ServerEndpoint;
@Aspect
@Component
public class AuthAspect {
// 原来的切点可能长这样:
// @Pointcut("execution(public * com.iflytek.knowledge.controller..*.*(..))")
// 👇 修改后的切点:增加 !@within(...)
@Pointcut("execution(public * com.iflytek.knowledge.controller..*.*(..)) " +
"&& !execution(public * com.iflytek.knowledge.controller..*.login(..))" +
"&& !@within(javax.websocket.server.ServerEndpoint)") // 关键:排除带有 @ServerEndpoint 注解的类
public void authPointCut() {}
@Around("authPointCut()")
public Object interceptor(ProceedingJoinPoint point) throws Throwable {
// ... 你的原有逻辑 ...
return point.proceed();
}
}
原理:'!@within(javax.websocket.server.ServerE 告诉 Spring AOP:"只要这个类头上戴了 !@within(javax.websocket.server.ServerEndpoint)@ServerEndpoint 的帽子,你就别去碰它,别生成代理。"
方案二:调整包结构(物理隔离)
既然 WebSocket 不是 HTTP Controller,从架构整洁度来说,不应该放在 包下。controller
新建包:com.iflytek.knowledge.websocket
移动文件:把 移到这个新包里。DocChatWebSocketController
效果:因为你的 AOP 只扫 包,自然就扫不到 包了,问题解决。controllerwebsocket
⚠️ 重要的后续问题:WebSocket 的鉴权怎么办?
一旦你排除了 AOP,,变成了裸奔状态。WebSocket 接口就失去了"自动拦截验证"的保护
WebSocket 的握手和 HTTP 不同,且 'A 里的 AuthAspectHttpServletRequest request = attributes.getRequest(); 在 WebSocket 的 方法里通常是拿不到东西的,或者逻辑不通。@OnMessage
你需要在 WebSocket 的 方法里手动调用 进行验证。@OnOpenSessionManager
WebSocket 鉴权最佳实践:
前端连接时,把 Token 放在 URL 参数里:ws://localhost:8080/ws/chat?token=YOUR_TOKEN
后端代码调整 ():DocChatWebSocketController
Java
@ServerEndpoint(value = "/ws/chat")
@Component
public class DocChatWebSocketController {
// 注意:WebSocket 是多例的,这里注入 Bean 需要特殊处理,或者用 static 上下文获取
// 这里假设你已经解决了 WebSocket 注入 Spring Bean 的问题(通常用 setApplicationContext)
private static SessionManager sessionManager;
@Autowired
public void setSessionManager(SessionManager manager) {
DocChatWebSocketController.sessionManager = manager;
}
@OnOpen
public void onOpen(Session session) throws IOException {
// 1. 从 URL 查询参数中获取 Token
String queryString = session.getQueryString(); // 获取 "token=xxxx"
String token = getTokenFromQuery(queryString);
try {
// 2. 手动调用鉴权逻辑!
if (token == null) {
throw new Exception("无 Token");
}
// 这里调用我们之前写的 SessionManager
sessionManager.validate(token);
System.out.println("WebSocket 连接成功,鉴权通过");
} catch (Exception e) {
// 3. 鉴权失败,直接关闭连接
System.err.println("WebSocket 鉴权失败: " + e.getMessage());
session.close(); // 拒绝连接
}
}
// 简单的解析工具方法
private String getTokenFromQuery(String queryString) {
if (queryString != null && queryString.contains("token=")) {
String[] params = queryString.split("&");
for (String param : params) {
if (param.startsWith("token=")) {
return param.split("=")[1];
}
}
}
return null;
}
}
总结
立即解决启动报错:在 AuthAspect 的 @Pointcut 中加上 && !@within(javax.websocket.server.ServerEndpoint)。
补全安全漏洞:在 WebSocket 的 @OnOpen 方法中,手动调用 SessionManager.validate(token),因为 AOP 不再保护它了。