在上一篇文章 Golang实现高并发WebSocket服务端开发最后提到“ WebSocket升级跨域处理中的CheckOrigin都默认true,也就是说只要地址暴露,任何人不需要任何允准就可以随意连接,这免不了非法链接和恶行攻击的风险”,所以这篇文章就这个问题提出一种解决思路,大家如果有需要可以参考借鉴。

项目目录结构

该项目目录结构和上一篇文章 Golang实现高并发WebSocket服务端开发的项目目录结构一致,而跨域认证处理只需在 serverBase.go 进行。该项目目录结构具体如下所示:

websocket项目结构示意图

处理过程

serverBase.go 原有结构

package WebSocketHandler

import (
	"github.com/gin-gonic/gin"
	"golang.org/websocket"
	"net/http"
)

// websocket 升级并跨域
var (
	upgrade = &websocket.Upgrader{
		// 允许跨域
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
)

// WebSocketBase TODO:服务基本函数
func WebSocketBase(c *gin.Context) {
	var (
		err error
		conn *websocket.Conn
		ws *wsConn
	)
	if conn, err = upgrade.Upgrade(c.Writer,c.Request,nil); err != nil{
		return
	}
	if ws, err = InitWebSocket(conn); err != nil{
		return
	}
	// 使得inChan和outChan耦合起来
	for {
		var data []byte
		if data, err = ws.InChanRead(); err != nil{
			goto ERR
		}
		if err = ws.OutChanWrite(data); err != nil{
			goto ERR
		}
	}
ERR:
	ws.CloseConn()
}

大家仔细观察定义变量中的 upgrade 中的 CheckOrigin 后面是一个函数,具体的思路就是将那个函数拿出来处理,这样就可以实现我们想要的效果。

CheckOrigin的函数处理

// crossDomainAuth TODO:跨域认证
/* 
假如只需认证secretID和apiKey这两个值,
*/
func crossDomainAuth(r *http.Request) bool{
	if secret := r.URL.Query().Get("secretID"); secret != ""{
		if secret != "123456789"{
			return false
		}else{
			if key := r.URL.Query().Get("apiKey"); key != ""{
				if key != "987654321"{
					return false
				}else{
					return true
				}
			}else{
				return false
			}
		}
	}else{
		return false
	}
}

serverBase.go 更改后的结构

package WebSocketHandler

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"golang.org/websocket"
	"net/http"
)

// websocket 升级并跨域
var (
	upgrade = &websocket.Upgrader{
		// 允许跨域
		CheckOrigin: crossDomainAuth,
		Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
			fmt.Println(status)
			fmt.Println(reason.Error())
		},
	}
)

// crossDomainAuth TODO:跨域认证
/* 
假如只需认证secretID和apiKey这两个值,
*/
func crossDomainAuth(r *http.Request) bool{
	if secret := r.URL.Query().Get("secretID"); secret != ""{
		if secret != "123456789"{
			return false
		}else{
			if key := r.URL.Query().Get("apiKey"); key != ""{
				if key != "987654321"{
					return false
				}else{
					return true
				}
			}else{
				return false
			}
		}
	}else{
		return false
	}
}

// WebSocketBase TODO:服务基本函数
func WebSocketBase(c *gin.Context) {
	var (
		err error
		conn *websocket.Conn
		ws *wsConn
	)
	if conn, err = upgrade.Upgrade(c.Writer,c.Request,nil); err != nil{
		return
	}
	if ws, err = InitWebSocket(conn); err != nil{
		return
	}
	// 使得inChan和outChan耦合起来
	for {
		var data []byte
		if data, err = ws.InChanRead(); err != nil{
			goto ERR
		}
		if err = ws.OutChanWrite(data); err != nil{
			goto ERR
		}
	}
ERR:
	ws.CloseConn()
}

客户端连接变更

  • 原来的 URL
ws://localhost/ws
  • 变更后的 URL
ws://localhost/ws?secretID=123456789&apiKey=987654321

额外说明

由上面处理可得知,虽然加了两个参数用来检验连接的合法性,但还是存在一个问题需要注意,如果 sercretID apiKey 泄露了,这种情况对于服务端运营方来说虽然已经一定程度杜绝非法连接和恶意攻击的风险,但对于客户来讲安全性还不够高,这对客户来说不够友好——显然这种做法不可取。所以要如何改善这种情况也是需要考虑的,如果大家有兴趣了解,可以关注,后面我会在此基础上再次优化。