springboot集成socket.io通过jwt-token身份认证鉴权

背景:

目前手上正在开发一个xxx中心的项目,服务端需要和客户端进行即时通讯以确定客户端是否在线。认证这块通过用户登录方式使用http通道颁发token令牌,socket通道则使用这个token令牌进行建立通信链接。需求是如果令牌是非法的则不允许建立链接。

开始集成:

服务端

1、引入socket.io坐标:

   <dependency>
            <groupId>com.corundumstudio.socketio</groupId>
            <artifactId>netty-socketio</artifactId>
            <version>1.7.7</version>
   </dependency>

2、在配置文件中增加socket配置项

socketio:
  #生产环境注意把127.0.0.1给换了
  host: 127.0.0.1
  port: 8188
  maxFramePayloadLength: 1048576
  maxHttpContentLength: 1048576
  bossCount: 1
  workCount: 100
  allowCustomRequests: true
  upgradeTimeout: 1000000
  pingTimeout: 6000000
  pingInterval: 25000

3、创建socket启动配置类,注意:jwt鉴权服务类你要换成自己的,在文章中我就不写了,代码太多了。

package com.back.web.core.config;

import com.back.framework.web.service.TokenService;
import com.back.system.service.ISysUserService;
import com.corundumstudio.socketio.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SocketIOConfig {

  /*
  //这里换成你的签名验签服务
  @Autowired
    private  TokenService tokenService;*/

    @Value("${socketio.host}")
    private String host;

    @Value("${socketio.port}")
    private Integer port;

    @Value("${socketio.bossCount}")
    private int bossCount;

    @Value("${socketio.workCount}")
    private int workCount;

    @Value("${socketio.allowCustomRequests}")
    private boolean allowCustomRequests;

    @Value("${socketio.upgradeTimeout}")
    private int upgradeTimeout;

    @Value("${socketio.pingTimeout}")
    private int pingTimeout;

    @Value("${socketio.pingInterval}")
    private int pingInterval;

    @Bean
    public SocketIOServer socketIOServer() {
        SocketConfig socketConfig = new SocketConfig();
        socketConfig.setTcpNoDelay(true);
        socketConfig.setSoLinger(0);
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
        config.setSocketConfig(socketConfig);
        config.setHostname(host);
        config.setPort(port);
        config.setBossThreads(bossCount);
        config.setWorkerThreads(workCount);
        config.setAllowCustomRequests(allowCustomRequests);
        config.setUpgradeTimeout(upgradeTimeout);
        config.setPingTimeout(pingTimeout);
        config.setPingInterval(pingInterval);
        //下面屏蔽的是链接鉴权方法,由于我使用的jwt相关方法和类很多,没法往文章里写
      /*  config.setAuthorizationListener(new AuthorizationListener() {
            @Override
            public boolean isAuthorized(HandshakeData handshakeData) {
                //获取socket链接发来的token参数
                String tonken=handshakeData.getSingleUrlParam("token");
                String userId=handshakeData.getSingleUrlParam("userId");

                //这个isPass是我的验签token方法,如果验签通过就是true,否则false,false的话就不允许建立socket链接
                return  tokenService.isPass(tonken);
            }
        });*/
        return new SocketIOServer(config);

    }

}

备注:这里让大家看一下我的isPass方法,因为自定义程度较高,嵌套调用了很多我自己封装方法,并不通用这里只供参考,所以不需要复制到你的代码中。

public boolean isPass(  String token)
{
    // 获取请求携带的令牌

    System.out.println("每次请求获得的token:"+token);
    if (StringUtils.isNotEmpty(token))
    {
        try
        {
            Claims claims = parseToken(token);
            // 解析对应的权限以及用户信息
            String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
            String userKey = getTokenKey(uuid);
            LoginUser user = redisCache.getCacheObject(userKey);
            if(user!=null){
                return true;
            }else {
                return false;
            }
        }
        catch (Exception e)
        {
        }
    }
    return false;
}

4、创建服务层接口类

package com.back.system.service;
/**
 * 即时通讯 服务层
 *
 * @author liujian
 */
public interface ISocketIOService {
    /**
     * 启动服务
     */
    void start();

    /**
     * 停止服务
     */
    void stop();

    /**
     * 推送信息给指定客户端
     *
     * @param userId:     客户端唯一标识
     * @param msgContent: 消息内容
     */
    void pushMessageToUser(String userId, String msgContent);
}

5、创建服务层实现类

package com.back.system.service.impl;

import com.back.common.utils.DateUtils;
import com.back.common.utils.http.HttpUtils;
import com.back.system.service.ISocketIOService;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


@Service(value = "socketIOService")
public class SocketIOServiceImpl implements ISocketIOService {
    private static final Logger log = LoggerFactory.getLogger(SocketIOServiceImpl.class);

    /**
     * 存放已连接的客户端
     */
    private static Map<String, SocketIOClient> clientMap = new ConcurrentHashMap<>();

    /**
     * 自定义事件`push_data_event`,用于服务端与客户端通信
     */
    private static final String PUSH_DATA_EVENT = "push_data_event";

    @Autowired
    private SocketIOServer socketIOServer;

    /**
     * Spring IoC容器创建之后,在加载SocketIOServiceImpl Bean之后启动
     */
    @PostConstruct
    private void autoStartup() {
        start();
    }

    /**
     * Spring IoC容器在销毁SocketIOServiceImpl Bean之前关闭,避免重启项目服务端口占用问题
     */
    @PreDestroy
    private void autoStop() {
        stop();
    }

    @Override
    public void start() {
        // 监听客户端连接
        socketIOServer.addConnectListener(client -> {
            log.debug("客户端: " + getIpByClient(client) + " 已连接 ");
            // 自定义事件`connected` -> 与客户端通信  (也可以使用内置事件,如:Socket.EVENT_CONNECT)
            client.sendEvent("connected", "连接成功");
            String userId = getParamsByClient(client);
            if (userId != null) {
                clientMap.put(userId, client);
            }
        });

        // 监听客户端断开连接
        socketIOServer.addDisconnectListener(client -> {
            String clientIp = getIpByClient(client);
            log.debug(clientIp + "客户端已断开连接");
            String userId = getParamsByClient(client);
            if (userId != null) {
                clientMap.remove(userId);
                client.disconnect();
            }
        });

        // 自定义事件`client_info_event` -> 监听客户端消息
        socketIOServer.addEventListener(PUSH_DATA_EVENT, String.class, (client, data, ackSender) -> {
            // 客户端推送`client_info_event`事件时,onData接受数据,这里是string类型的json数据,还可以为Byte[],object其他类型
            String clientIp = getIpByClient(client);
            log.debug(clientIp + "客户端:" + data);
        });

        // 启动服务
        socketIOServer.start();

        // broadcast: 默认是向所有的socket连接进行广播,但是不包括发送者自身,如果自己也打算接收消息的话,需要给自己单独发送。
  /*      new Thread(() -> {
            int i = 0;
            while (true) {
                try {
                    // 每5秒发送一次广播消息
                    Thread.sleep(5000);
                    socketIOServer.getBroadcastOperations().sendEvent("myBroadcast", "广播消息 " + DateUtils.dateTimeNow());
                     log.debug("服务端:"+"发送了第"+(i+1)+"条广播!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();*/
    }

    @Override
    public void stop() {
        if (socketIOServer != null) {
            socketIOServer.stop();
            socketIOServer = null;
        }
    }

    @Override
    public void pushMessageToUser(String userId, String msgContent) {
        SocketIOClient client = clientMap.get(userId);
        if (client != null) {
            client.sendEvent(PUSH_DATA_EVENT, msgContent);
        }
    }

    /**
     * 获取客户端url中的userId参数(这里根据个人需求和客户端对应修改即可)
     *
     * @param client: 客户端
     * @return: java.lang.String
     */
    private String getParamsByClient(SocketIOClient client) {
        // 获取客户端url参数(这里的userId是唯一标识)
        Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
        List<String> userIdList = params.get("userId");
        if (!CollectionUtils.isEmpty(userIdList)) {
            return userIdList.get(0);
        }
        return null;
    }

    /**
     * 获取连接的客户端ip地址
     *
     * @param client: 客户端
     * @return: java.lang.String
     */
    private String getIpByClient(SocketIOClient client) {
        String sa = client.getRemoteAddress().toString();
        String clientIp = sa.substring(1, sa.indexOf(":"));
        return clientIp;
    }

}

客户端

首先你的使用ide创建一个maven项目,在这个项目里进行下列操作:

1、引入socket.io客户端坐标以及一个工具类的坐标

	<dependency>
			<groupId>io.socket</groupId>
			<artifactId>socket.io-client</artifactId>
			<version>1.0.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>

2、客户端代码,主要是main函数里面的

	public static void main(String[] args) {
		//SpringApplication.run(SocketClientApplication.class, args);
		// 服务端socket.io连接通信地址
		String url = "http://127.0.0.1:8188";
		try {
			IO.Options options = new IO.Options();
			options.transports = new String[]{"websocket"};

			options.reconnectionAttempts = 2;
			// 失败重连的时间间隔
			options.reconnectionDelay = 1000;
			// 连接超时时间(ms)
			options.timeout = 500;
			// userId: 唯一标识 传给服务端存储
			final Socket socket = IO.socket(url + "?userId=1&token=eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImIxNmY3MDFhLTI0YTEtNDRkZi05Y2Y5LTE5ZTM2ZDAzOThmYSJ9.005IuyLNA5cxtMNK33zrWXi9ulGNnvy-t7px98BD2Y3s_-iXCnkqtfE5rYWW9AX4806lhwTZ4hrOPKZtOckAdA", options);

			socket.on(Socket.EVENT_CONNECT, args1 -> socket.send("hello..."));

			// 自定义事件`connected` -> 接收服务端成功连接消息
			socket.on("connected", objects -> System.out.println("服务端:" + objects[0].toString()));

			// 自定义事件`push_data_event` -> 接收服务端消息
			socket.on("push_data_event", objects -> System.out.println("服务端:" + objects[0].toString()));

			// 自定义事件`myBroadcast` -> 接收服务端广播消息
			socket.on("myBroadcast", objects -> System.out.println("服务端:" + objects[0].toString()));

			socket.connect();

			while (true) {
				Thread.sleep(5000);

				// 自定义事件`push_data_event` -> 向服务端发送消息
				    socket.emit("push_data_event", "发送数据 " + DateUtils.dateTimeNow());
				// 自定义事件`push_data_event` -> 接收服务端消息
				socket.on("push_data_event", new Emitter.Listener() {
					@Override
					public void call(Object... objects) {
						if (objects.length > 1) {
							System.out.println("服务端:" + objects[0].toString());

						}
					}
				});

				// 自定义事件`push_data_event` -> 接收服务端消息
				//               socket.on("push_data_event", objects -> System.out.println("服务端:" + objects[0].toString()));

				// 自定义事件`myBroadcast` -> 接收服务端广播消息
				   socket.on("myBroadcast", objects  -> System.out.println("服务端:" + objects[0].toString()));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

3、工具类,这个工具类用到了开头的那个long3的坐标。

package socket.io.client.socketClient;

import org.apache.commons.lang3.time.DateFormatUtils;

import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 时间工具类
 * 
 * @author liujian
 */
public class DateUtils extends org.apache.commons.lang3.time.DateUtils
{
    public static String YYYY = "yyyy";

    public static String YYYY_MM = "yyyy-MM";

    public static String YYYY_MM_DD = "yyyy-MM-dd";

    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";

    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    
    private static String[] parsePatterns = {
            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", 
            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};

    /**
     * 获取当前Date型日期
     * 
     * @return Date() 当前日期
     */
    public static Date getNowDate()
    {
        return new Date();
    }

    /**
     * 获取当前日期, 默认格式为yyyy-MM-dd
     * 
     * @return String
     */
    public static String getDate()
    {
        return dateTimeNow(YYYY_MM_DD);
    }

    public static final String getTime()
    {
        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
    }

    public static final String dateTimeNow()
    {
        return dateTimeNow(YYYYMMDDHHMMSS);
    }

    public static final String dateTimeNow(final String format)
    {
        return parseDateToStr(format, new Date());
    }

    public static final String dateTime(final Date date)
    {
        return parseDateToStr(YYYY_MM_DD, date);
    }

    public static final String parseDateToStr(final String format, final Date date)
    {
        return new SimpleDateFormat(format).format(date);
    }

    public static final Date dateTime(final String format, final String ts)
    {
        try
        {
            return new SimpleDateFormat(format).parse(ts);
        }
        catch (ParseException e)
        {
            throw new RuntimeException(e);
        }
    }

    /**
     * 日期路径 即年/月/日 如2018/08/08
     */
    public static final String datePath()
    {
        Date now = new Date();
        return DateFormatUtils.format(now, "yyyy/MM/dd");
    }

    /**
     * 日期路径 即年/月/日 如20180808
     */
    public static final String dateTime()
    {
        Date now = new Date();
        return DateFormatUtils.format(now, "yyyyMMdd");
    }

    /**
     * 日期型字符串转化为日期 格式
     */
    public static Date parseDate(Object str)
    {
        if (str == null)
        {
            return null;
        }
        try
        {
            return parseDate(str.toString(), parsePatterns);
        }
        catch (ParseException e)
        {
            return null;
        }
    }
    
    /**
     * 获取服务器启动时间
     */
    public static Date getServerStartDate()
    {
        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
        return new Date(time);
    }

    /**
     * 计算两个时间差
     */
    public static String getDatePoor(Date endDate, Date nowDate)
    {
        long nd = 1000 * 24 * 60 * 60;
        long nh = 1000 * 60 * 60;
        long nm = 1000 * 60;
        // long ns = 1000;
        // 获得两个时间的毫秒时间差异
        long diff = endDate.getTime() - nowDate.getTime();
        // 计算差多少天
        long day = diff / nd;
        // 计算差多少小时
        long hour = diff % nd / nh;
        // 计算差多少分钟
        long min = diff % nd % nh / nm;
        // 计算差多少秒//输出结果
        // long sec = diff % nd % nh % nm / ns;
        return day + "天" + hour + "小时" + min + "分钟";
    }
}

最后:一个socket通信服务,并且使用token鉴权完成了。提示一下,我遇到了鉴权失败的问题,因为客户端token传错了。注意下图

如果不注意上图,加了单引号,那么在这里获取token的时候,那个token值如下图解释。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要在Spring Boot中使用jwt.io库来解析JWT,您需要按照以下步骤进行操作: 1. 在pom.xml中引入JWT库的依赖: ```xml <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> ``` 引用 2. 创建一个工具类来处理JWT的解析: ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; public class JwtUtils { public static Claims parseJwt(String jwtToken, String secretKey) { return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwtToken) .getBody(); } } ``` 引用 3. 在您的Spring Boot应用程序中调用JwtUtils类来解析JWT: ```java String jwtToken = "your_jwt_token_here"; String secretKey = "your_secret_key_here"; Claims claims = JwtUtils.parseJwt(jwtToken, secretKey); ``` 通过这些步骤,您就可以在Spring Boot中使用jwt.io库来解析JWT了。请确保将"your_jwt_token_here"和"your_secret_key_here"替换为实际的JWT令牌和秘钥。引用<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Springboot + JWT-Token 生成与解析](https://blog.csdn.net/gaogzhen/article/details/113106736)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [springboot集成socket.io通过jwt-token身份认证](https://blog.csdn.net/qq_16334741/article/details/122949127)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值