Web-Socket 鉴权示例

This commit is contained in:
click33 2022-02-11 02:57:11 +08:00
parent fbc139b430
commit 40a5cdc6dc
16 changed files with 700 additions and 0 deletions

View File

@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath

View File

@ -0,0 +1,69 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-websocket-spring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- WebScoket 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,32 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token 整合 WebSocket 鉴权示例
* @author kong
*
*/
@SpringBootApplication
public class SaTokenWebSocketSpringApplication {
/*
* 1访问登录接口拿到会话Token
* http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
*
* 2找一个WebSocket在线测试页面进行连接
* 例如
* https://www.bejson.com/httputil/websocket/
* 然后连接地址
* ws://localhost:8081/ws-connect?satoken=2e6db38f-1e78-40bc-aa8f-e8f1f77fbef5
*/
public static void main(String[] args) {
SpringApplication.run(SaTokenWebSocketSpringApplication.class, args);
System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig());
}
}

View File

@ -0,0 +1,48 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 登录测试
* @author kong
*
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 此处仅作模拟示例真实项目需要从数据库中查询数据进行比对
if("zhang".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功").set("token", StpUtil.getTokenValue());
}
return SaResult.error("登录失败");
}
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}

View File

@ -0,0 +1,83 @@
package com.pj.ws;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* 处理 WebSocket 连接
*
* @author kong
* @date: 2022-2-11
*/
public class MyWebSocketHandler extends TextWebSocketHandler {
/**
* 固定前缀
*/
private static final String USER_ID = "user_id_";
/**
* 存放Session集合方便推送消息
*/
private static ConcurrentHashMap<String, WebSocketSession> webSocketSessionMaps = new ConcurrentHashMap<>();
// 监听连接开启
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// put到集合方便后续操作
String userId = session.getAttributes().get("userId").toString();
webSocketSessionMaps.put(USER_ID + userId, session);
// 给个提示
String tips = "Web-Socket 连接成功sid=" + session.getId() + "userId=" + userId;
System.out.println(tips);
sendMessage(session, tips);
}
// 监听连接关闭
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 从集合移除
String userId = session.getAttributes().get("userId").toString();
webSocketSessionMaps.remove(USER_ID + userId);
// 给个提示
String tips = "Web-Socket 连接关闭sid=" + session.getId() + "userId=" + userId;
System.out.println(tips);
}
// 收到消息
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
System.out.println("sid为" + session.getId() + ",发来:" + message);
}
// -----------
// 向指定客户端推送消息
public static void sendMessage(WebSocketSession session, String message) {
try {
System.out.println("向sid为" + session.getId() + ",发送:" + message);
session.sendMessage(new TextMessage(message));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 向指定用户推送消息
public static void sendMessage(long userId, String message) {
WebSocketSession session = webSocketSessionMaps.get(USER_ID + userId);
if(session != null) {
sendMessage(session, message);
}
}
}

View File

@ -0,0 +1,30 @@
package com.pj.ws;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* WebSocket 相关配置
*
* @author kong
* @date: 2022-2-11
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
// 注册 WebSocket 处理器
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry
// WebSocket 连接处理器
.addHandler(new MyWebSocketHandler(), "/ws-connect")
// WebSocket 拦截器
.addInterceptors(new WebSocketInterceptor())
// 允许跨域
.setAllowedOrigins("*");
}
}

View File

@ -0,0 +1,45 @@
package com.pj.ws;
import java.util.Map;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import cn.dev33.satoken.stp.StpUtil;
/**
* WebSocket 握手的前置拦截器
*
* @author kong
* @date: 2022-2-11
*/
public class WebSocketInterceptor implements HandshakeInterceptor {
// 握手之前触发 (return true 才会握手成功 )
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler,
Map<String, Object> attr) {
System.out.println("---- 握手之前触发 " + StpUtil.getTokenValue());
// 未登录情况下拒绝握手
if(StpUtil.isLogin() == false) {
System.out.println("---- 未授权客户端,连接失败");
return false;
}
// 标记 userId握手成功
attr.put("userId", StpUtil.getLoginIdAsLong());
return true;
}
// 握手之后触发
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
System.out.println("---- 握手之后触发 ");
}
}

View File

@ -0,0 +1,49 @@
# 端口
server:
port: 8081
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

View File

@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath

View File

@ -0,0 +1,69 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.29.0</sa-token-version>
</properties>
<dependencies>
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- WebScoket 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,32 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token 整合 WebSocket 鉴权示例
* @author kong
*
*/
@SpringBootApplication
public class SaTokenWebSocketApplication {
/*
* 1访问登录接口拿到会话Token
* http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
*
* 2找一个WebSocket在线测试页面进行连接
* 例如
* https://www.bejson.com/httputil/websocket/
* 然后连接地址
* ws://localhost:8081/ws-connect/2e6db38f-1e78-40bc-aa8f-e8f1f77fbef5
*/
public static void main(String[] args) {
SpringApplication.run(SaTokenWebSocketApplication.class, args);
System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig());
}
}

View File

@ -0,0 +1,48 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 登录测试
* @author kong
*
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 此处仅作模拟示例真实项目需要从数据库中查询数据进行比对
if("zhang".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功").set("token", StpUtil.getTokenValue());
}
return SaResult.error("登录失败");
}
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}

View File

@ -0,0 +1,18 @@
package com.pj.ws;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启WebSocket支持
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

View File

@ -0,0 +1,102 @@
package com.pj.ws;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* WebSocket 连接测试
*/
@Component
@ServerEndpoint("/ws-connect/{satoken}")
public class WebSocketConnect {
/**
* 固定前缀
*/
private static final String USER_ID = "user_id_";
/**
* 存放Session集合方便推送消息 javax.websocket.Session
*/
private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
// 监听连接成功
@OnOpen
public void onOpen(Session session, @PathParam("satoken") String satoken) throws IOException {
// 根据 token 获取对应的 userId
Object loginId = StpUtil.getLoginIdByToken(satoken);
if(loginId == null) {
session.close();
throw new SaTokenException("连接失败无效Token" + satoken);
}
// put到集合方便后续操作
long userId = SaFoxUtil.getValueByType(loginId, long.class);
sessionMap.put(USER_ID + userId, session);
// 给个提示
String tips = "Web-Socket 连接成功sid=" + session.getId() + "userId=" + userId;
System.out.println(tips);
sendMessage(session, tips);
}
// 监听: 连接关闭
@OnClose
public void onClose(Session session) {
System.out.println("连接关闭sid=" + session.getId());
for (String key : sessionMap.keySet()) {
if(sessionMap.get(key).getId().equals(session.getId())) {
sessionMap.remove(key);
}
}
}
// 监听收到客户端发送的消息
@OnMessage
public void onMessage(Session session, String message) {
System.out.println("sid为" + session.getId() + ",发来:" + message);
}
// 监听发生异常
@OnError
public void onError(Session session, Throwable error) {
System.out.println("sid为" + session.getId() + ",发生错误");
error.printStackTrace();
}
// ---------
// 向指定客户端推送消息
public static void sendMessage(Session session, String message) {
try {
System.out.println("向sid为" + session.getId() + ",发送:" + message);
session.getBasicRemote().sendText(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 向指定用户推送消息
public static void sendMessage(long userId, String message) {
Session session = sessionMap.get(USER_ID + userId);
if(session != null) {
sendMessage(session, message);
}
}
}

View File

@ -0,0 +1,49 @@
# 端口
server:
port: 8081
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

View File

@ -120,6 +120,8 @@ implementation 'cn.dev33:sa-token-core:${sa.top.version}'
├── sa-token-demo-sso-client-h5 // [示例] Sa-Token 集成 SSO单点登录-client应用端 (前后端分离)
├── sa-token-demo-oauth2-server // [示例] Sa-Token 集成 OAuth2.0 (服务端)
├── sa-token-demo-oauth2-client // [示例] Sa-Token 集成 OAuth2.0 (客户端)
├── sa-token-demo-websocket // [示例] Sa-Token 集成 Web-Socket 鉴权示例
├── sa-token-demo-websocket-spring // [示例] Sa-Token 集成 Web-SocketSpring封装版 鉴权示例
├── sa-token-test // [测试] Sa-Token 单元测试合集
├── sa-token-core-test // [测试] Sa-Token Core核心包单元测试
├── sa-token-springboot-test // [测试] Sa-Token SpringBoot 整合测试