Go-DDDマーケットプレイスアプリケーション Echo JWT認証の詳細解説¶
1. 概要¶
この文書では、Go-DDDマーケットプレイスアプリケーションにおけるJWT(JSON Web Token)を使用した認証機能の実装について詳しく解説します。JWTは、クライアントとサーバー間で安全に情報を交換するための、コンパクトで自己完結型のトークン形式です。
1.1 JWT認証の役割¶
JWT認証は以下の役割を担います:
- ユーザー認証: ユーザーのログイン情報を検証し、有効な認証トークンを発行します。
- アクセス制御: 保護されたリソースへのアクセスを制限し、認証されたユーザーのみがアクセスできるようにします。
- ステートレス認証: サーバー側でセッション状態を保持せず、トークン自体に必要な情報を含めることでステートレスな認証を実現します。
- クロスドメイン認証: 異なるドメイン間でも認証情報を安全に共有できます。
1.2 アーキテクチャにおける位置づけ¶
以下の図は、JWT認証のアーキテクチャにおける位置づけを示しています:
2. JWT認証の実装¶
以下はJWT認証実装の概要を表すフロー図です:
2.1 JWT設定¶
2.1.1 JWT設定の定義¶
JWT認証に必要な設定を定義します。
package config
import (
"time"
)
// JWTConfig はJWT認証の設定
type JWTConfig struct {
SecretKey string
TokenExpiry time.Duration
SigningMethod string
}
// NewJWTConfig はJWT設定を作成する
func NewJWTConfig() *JWTConfig {
return &JWTConfig{
SecretKey: "your-secret-key", // 本番環境では環境変数から取得するなど安全に管理する
TokenExpiry: 24 * time.Hour, // トークンの有効期限(24時間)
SigningMethod: "HS256", // 署名アルゴリズム
}
}
2.1.2 認証ミドルウェアの設定¶
Echoフレームワークに認証ミドルウェアを設定します。
package rest
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/sklinkert/go-ddd/internal/config"
)
// SetupAuthMiddleware はJWT認証ミドルウェアを設定する
func SetupAuthMiddleware(e *echo.Echo, jwtConfig *config.JWTConfig) {
// JWT設定
config := middleware.JWTConfig{
SigningKey: []byte(jwtConfig.SecretKey),
TokenLookup: "header:Authorization",
AuthScheme: "Bearer",
}
// 認証が必要なルートグループ
auth := e.Group("/api/v1/auth")
auth.Use(middleware.JWTWithConfig(config))
// 認証が必要なエンドポイントを追加
auth.GET("/profile", getProfile)
auth.GET("/orders", getOrders)
// その他の保護されたエンドポイント
}
2.2 ユーザー認証¶
2.2.1 ログインハンドラの実装¶
ユーザーのログイン処理を行うハンドラを実装します。
package rest
import (
"github.com/dgrijalva/jwt-go"
"github.com/labstack/echo/v4"
"github.com/sklinkert/go-ddd/internal/application/services"
"github.com/sklinkert/go-ddd/internal/config"
"net/http"
"time"
)
type AuthController struct {
userService *services.UserService
jwtConfig *config.JWTConfig
}
func NewAuthController(e *echo.Echo, userService *services.UserService, jwtConfig *config.JWTConfig) {
controller := &AuthController{
userService: userService,
jwtConfig: jwtConfig,
}
// 認証エンドポイント
e.POST("/api/v1/login", controller.Login)
}
// Login はユーザーログイン処理を行う
func (c *AuthController) Login(ctx echo.Context) error {
// リクエストからユーザー情報を取得
var req struct {
Email string `json:"email"`
Password string `json:"password"`
}
if err := ctx.Bind(&req); err != nil {
return ctx.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request"})
}
// ユーザー認証
user, err := c.userService.Authenticate(req.Email, req.Password)
if err != nil {
return ctx.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid credentials"})
}
// JWTトークンの生成
token, err := c.generateToken(user.ID, user.Email)
if err != nil {
return ctx.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to generate token"})
}
// レスポンス
return ctx.JSON(http.StatusOK, map[string]string{
"token": token,
"type": "Bearer",
})
}
// generateToken はJWTトークンを生成する
func (c *AuthController) generateToken(userID, email string) (string, error) {
// トークンの有効期限
expiry := time.Now().Add(c.jwtConfig.TokenExpiry)
// クレームの作成
claims := jwt.MapClaims{
"id": userID,
"email": email,
"exp": expiry.Unix(),
}
// トークンの作成
token := jwt.NewWithClaims(jwt.GetSigningMethod(c.jwtConfig.SigningMethod), claims)
// トークンの署名
return token.SignedString([]byte(c.jwtConfig.SecretKey))
}
2.2.2 ユーザー検証の実装¶
ユーザーサービスでの認証処理を実装します。
package services
import (
"errors"
"github.com/sklinkert/go-ddd/internal/domain/entities"
"github.com/sklinkert/go-ddd/internal/domain/repositories"
"golang.org/x/crypto/bcrypt"
)
type UserService struct {
userRepository repositories.UserRepository
}
func NewUserService(userRepository repositories.UserRepository) *UserService {
return &UserService{
userRepository: userRepository,
}
}
// Authenticate はユーザーの認証を行う
func (s *UserService) Authenticate(email, password string) (*entities.User, error) {
// メールアドレスでユーザーを検索
user, err := s.userRepository.FindByEmail(email)
if err != nil {
return nil, errors.New("user not found")
}
// パスワードの検証
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
if err != nil {
return nil, errors.New("invalid password")
}
return user, nil
}
2.3 保護されたリソースへのアクセス¶
2.3.1 認証済みユーザーのプロファイル取得¶
認証が必要なエンドポイントの実装例です。
// getProfile は認証済みユーザーのプロファイル情報を返す
func getProfile(c echo.Context) error {
// JWTトークンからユーザー情報を取得
user := c.Get("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
userID := claims["id"].(string)
email := claims["email"].(string)
// ユーザープロファイルを返す
return c.JSON(http.StatusOK, map[string]interface{}{
"id": userID,
"email": email,
"message": "認証済みユーザー",
})
}
// getOrders は認証済みユーザーの注文履歴を返す
func getOrders(c echo.Context) error {
// JWTトークンからユーザーIDを取得
user := c.Get("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
userID := claims["id"].(string)
// ユーザーの注文履歴を取得(実際の実装ではサービスを呼び出す)
orders := []map[string]interface{}{
{"id": "order1", "amount": 1000, "date": "2023-01-01"},
{"id": "order2", "amount": 2000, "date": "2023-01-15"},
}
return c.JSON(http.StatusOK, map[string]interface{}{
"user_id": userID,
"orders": orders,
})
}
3. セキュリティ考慮事項¶
3.1 トークンの安全性¶
JWT認証を安全に実装するための考慮事項:
- シークレットキーの保護: シークレットキーは環境変数や安全な設定管理システムで管理し、ソースコードに直接記述しない。
- トークンの有効期限: 短い有効期限を設定し、長期間のアクセスにはリフレッシュトークンを使用する。
- 適切な署名アルゴリズム: HS256やRS256などの安全な署名アルゴリズムを使用する。
- センシティブ情報の保護: パスワードなどのセンシティブな情報はJWTに含めない。
3.2 CSRF対策¶
クロスサイトリクエストフォージェリ(CSRF)攻撃からの保護:
// CSRFミドルウェアの設定
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "header:X-CSRF-Token",
CookieName: "csrf",
CookiePath: "/",
Skipper: func(c echo.Context) bool {
// APIエンドポイントはCSRF保護をスキップ
return strings.HasPrefix(c.Path(), "/api/")
},
}))
3.3 リフレッシュトークンの実装¶
長期間のセッション維持のためのリフレッシュトークン実装:
```go // リフレッシュトークンの生成 func (c *AuthController) generateRefreshToken(userID string) (string, error) { // リフレッシュトークンの有効期限(30日) expiry := time.Now().Add(30 * 24 * time.Hour)
claims := jwt.MapClaims{
"id": userID,
"exp": expiry.Unix(),
"type": "refresh",
}