💃
Goで学ぶWebSocket:[JWT認証 & 負荷分散設計]
はじめに
前回の記事では、WebSocketを活用した通知システム、Redisを使ったWebSocketサーバーの構築方法を解説しました。
今回はさらに発展させて、JWT認証を組み込んだWebSocket実装と 負荷分散を考慮した設計について解説します。
対象読者
- WebSocketに認証を組み込みたい方
- 負荷の高いWebSocketアプリを効率的にスケールアウトしたい方
- セキュアなリアルタイム通信を実現したい方
目次
- JWT認証を組み込んだセキュアなWebSocketの実装
- JWT認証とは?
- GoでのJWT認証の実装
- WebSocketへのJWT認証の適用
- 負荷分散を考慮したWebSocket設計
- 負荷分散の仕組み
- WebSocketをスケールアウトする方法
- Redis & Nginxを使った負荷分散
1. JWT認証を組み込んだセキュアなWebSocketの実装
1.1 そもそもJWT認証とは?
JWT(JSON Web Token) は、Webアプリケーションで広く使われる認証方式です。
JWTの仕組み:
- ユーザーがログイン → サーバーが秘密鍵でJWTトークンを発行
- クライアントがJWTをリクエストヘッダーに追加
- サーバーがJWTを検証し、認証を通過した場合のみ接続を許可
JWT の例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImV4cCI6MTY3NTAwMDAwMH0.mFkjcYJwGm7SUk8BzL7L5bJxz5XL8yYpXFA0eH0H-lw
1.2 GoでのJWT認証の実装
まず、JWTライブラリをインストールします。
go get github.com/golang-jwt/jwt/v4
jwt.go
package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v4"
)
var secretKey = []byte("supersecretkey")
type Claims struct {
UserID int `json:"userId"`
jwt.RegisteredClaims
}
func GenerateJWT(userID int) (string, error) {
claims := Claims{
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secretKey)
}
func ValidateJWT(tokenStr string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, err
}
1.3 WebSocketへのJWT認証の適用
server.go
package main
import (
"fmt"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
tokenStr := r.URL.Query().Get("token")
claims, err := ValidateJWT(tokenStr)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
fmt.Println("Authenticated user:", claims.UserID)
conn, _ := upgrader.Upgrade(w, r, nil)
defer conn.Close()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
break
}
fmt.Printf("Received message: %s\n", msg)
}
}
- WebSocketのリクエストにJWTを含めることで認証されたユーザーのみ接続を許可する
-
ValidateJWT()
でJWTの検証を行い、不正なトークンの場合は401 Unauthorized
を返す
2. 負荷分散を考慮したWebSocket設計
2.1 負荷分散の仕組み
WebSocketの負荷分散では、以下の2つの方式を採用します。
- Nginxを使ったWebSocket負荷分散
- Redisを使ったWebSocketのスケールアウト
2.2 WebSocketをスケールアウトする方法(Nginx + Redis)
NginxをリバースプロキシとしてWebSocket負荷分散を実装する
nginx.conf
http {
upstream websocket_backend {
server ws-server1:8080;
server ws-server2:8080;
}
server {
listen 80;
location /ws {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
}
2.3 Redisを使ったWebSocketセッション共有
複数のWebSocketサーバーがある場合、Redis Pub/Subを活用してメッセージを共有。
server_redis.go
rdb.Publish(ctx, "websocket_messages", message)
各WebSocketサーバーはRedisのメッセージを受信して全クライアントにブロードキャストされる。
まとめ
項目 | 説明 |
---|---|
JWT認証 | WebSocketで認証を行い、セキュアな通信を確立 |
Nginx負荷分散 | WebSocketのトラフィックを複数のサーバーに分散 |
Redisセッション共有 | 複数サーバー間でメッセージを共有し、スケールアウト可能に |
WebSocketは汎用性も高い仕組みで色々な用途があります!
参考になれば嬉しいです!
Discussion