💃

Goで学ぶWebSocket:[JWT認証 & 負荷分散設計]

2025/03/11に公開

はじめに

前回の記事では、WebSocketを活用した通知システム、Redisを使ったWebSocketサーバーの構築方法を解説しました。
今回はさらに発展させて、JWT認証を組み込んだWebSocket実装と 負荷分散を考慮した設計について解説します。

対象読者

  • WebSocketに認証を組み込みたい方
  • 負荷の高いWebSocketアプリを効率的にスケールアウトしたい方
  • セキュアなリアルタイム通信を実現したい方

目次

  1. JWT認証を組み込んだセキュアなWebSocketの実装
    • JWT認証とは?
    • GoでのJWT認証の実装
    • WebSocketへのJWT認証の適用
  2. 負荷分散を考慮したWebSocket設計
    • 負荷分散の仕組み
    • WebSocketをスケールアウトする方法
    • Redis & Nginxを使った負荷分散

1. JWT認証を組み込んだセキュアなWebSocketの実装

1.1 そもそもJWT認証とは?

JWT(JSON Web Token) は、Webアプリケーションで広く使われる認証方式です。

JWTの仕組み:

  1. ユーザーがログイン → サーバーが秘密鍵でJWTトークンを発行
  2. クライアントがJWTをリクエストヘッダーに追加
  3. サーバーが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つの方式を採用します。

  1. Nginxを使ったWebSocket負荷分散
  2. 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