首頁  >  文章  >  後端開發  >  golang實作sso登錄

golang實作sso登錄

PHPz
PHPz原創
2023-05-10 11:33:361222瀏覽

隨著網路的發展,越來越多的網站和應用程式需要實現單一登入(SSO)功能,以提供更好的使用者體驗和更高的安全性。在本文中,我們將介紹如何使用Go語言實作SSO登入。

一、什麼是SSO?

單一登入(SSO)是一種身份驗證協議,它允許使用者使用一個使用者名稱和密碼來存取多個應用程序,而不是針對每個應用程式進行單獨認證。在SSO系統中,使用者只需在第一次登入時提供憑證,然後在後續的登入中自動登入。

SSO的核心原理是在不同的領域之間共享憑證。在這種情況下,使用者的憑證(如使用者名稱和密碼)只需要在一個網域中進行驗證,然後可以用來存取其他網域中的應用程式。這個過程可以簡化使用者的登入流程,減少使用者的負擔,提高安全性。

二、如何實作SSO?

為了實現SSO登錄,我們需要定義一個公共的認證中心(Authentication Center),它與各個應用程式之間通訊。認證中心負責驗證使用者的憑證,然後向每個應用程式提供令牌(Token)來允許使用者存取該應用程式。在後續的存取中,使用者可以使用令牌進行認證,而無需再次提供憑證。

在Go語言中,我們可以使用輕量級的web框架gin來實作SSO登入。下面是一個基本的SSO架構:

SSO Architecture

在上圖中,我們定義了一個認證中心和兩個應用程式(App1和App2),它們之間通過認證中心實現SSO登入。我們使用JWT(JSON Web Token)來表示令牌,因為它是一種輕量級的標準,可以使用JSON來描述和傳輸訊息。

三、如何實現?

  1. 安裝GIN

首先,我們需要安裝Gin框架。在終端機上輸入以下命令以安裝gin:

go get -u github.com/gin-gonic/gin
  1. 編寫認證中心

#接下來,我們需要編寫認證中心的程式碼。在這個例子中,我們將使用Gin框架來建立一個HTTP伺服器。我們需要在伺服器上讓使用者登錄,並建立一個JWT令牌來表示使用者。令牌將在認證後返回給用戶,並在後續的請求中進行驗證。

package main

import (
  "net/http"
  "time"

  "github.com/gin-gonic/gin"
  "github.com/golang-jwt/jwt"
)

var jwtKey = []byte("my_secret_key")

type Credentials struct {
  Username string `json:"username"`
  Password string `json:"password"`
}

type Claims struct {
  Username string `json:"username"`
  jwt.StandardClaims
}

func main() {
  router := gin.Default()

  router.POST("/login", loginHandler)

  router.GET("/validate", validateHandler)

  router.Run(":8080")
}

func loginHandler(c *gin.Context) {
  var creds Credentials
  if err := c.BindJSON(&creds); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
    return
  }

  if creds.Username != "user" || creds.Password != "password" {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
    return
  }

  expirationTime := time.Now().Add(5 * time.Minute)
  claims := &Claims{
    Username: creds.Username,
    StandardClaims: jwt.StandardClaims{
      ExpiresAt: expirationTime.Unix(),
    },
  }

  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  tokenString, err := token.SignedString(jwtKey)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not generate token"})
    return
  }

  c.JSON(http.StatusOK, gin.H{"token": tokenString})
}

func validateHandler(c *gin.Context) {
  tokenString := c.Request.Header.Get("Authorization")
  if tokenString == "" {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
    return
  }

  token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
    if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
      return nil, jwt.ErrSignatureInvalid
    }
    return jwtKey, nil
  })

  if err != nil {
    c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
    return
  }

  if !token.Valid {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
    return
  }

  claims, ok := token.Claims.(*Claims)
  if !ok {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "Failed to parse claims"})
    return
  }

  c.JSON(http.StatusOK, gin.H{"username": claims.Username})
}

在認證中心中,我們定義了兩個路由「/login」和「/validate」。

在「/login」路由上,我們讀取憑證並進行驗證(這裡只是一個簡單的範例),如果驗證通過,我們建立一個JWT令牌,並將其發送回客戶端。

在「/validate」路由上,我們讀取JWT令牌,並使用相同的秘鑰對其進行驗證。如果令牌有效,我們將提取其中的聲明,並將其發送回客戶端。

  1. 寫應用程式

接下來,我們需要寫應用程式的程式碼。在這個範例中,我們將使用Gin框架來建立HTTP伺服器。我們需要訪問認證中心來驗證使用者的憑證,並在驗證後取得JWT令牌。令牌將儲存在Cookie中,並在後續的請求中進行驗證。

package main

import (
  "net/http"

  "github.com/gin-gonic/gin"
  "github.com/golang-jwt/jwt"
)

var jwtKey = []byte("my_secret_key")

type Claims struct {
  Username string `json:"username"`
  jwt.StandardClaims
}

func main() {
  router := gin.Default()

  router.POST("/login", loginHandler)

  router.GET("/private", authMiddleware(), privateHandler)

  router.Run(":8081")
}

func loginHandler(c *gin.Context) {
  username := c.PostForm("username")
  password := c.PostForm("password")

  authURL := "http://localhost:8080/validate"
  client := &http.Client{}
  req, err := http.NewRequest("GET", authURL, nil)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
    return
  }

  req.Header.Set("Authorization", c.GetHeader("Authorization"))
  res, err := client.Do(req)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to validate token"})
    return
  }

  if res.StatusCode != http.StatusOK {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
    return
  }

  claims := &Claims{
    Username: username,
    StandardClaims: jwt.StandardClaims{},
  }

  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  tokenString, err := token.SignedString(jwtKey)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not generate token"})
    return
  }

  c.SetCookie("token", tokenString, 0, "", "", false, true)
  c.JSON(http.StatusOK, gin.H{"message": "Login success"})
}

func privateHandler(c *gin.Context) {
  claims := c.MustGet("claims").(*Claims)
  c.JSON(http.StatusOK, gin.H{"message": "You are logged in as " + claims.Username})
}

func authMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    tokenString, err := c.Cookie("token")
    if err != nil {
      c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
      c.Abort()
      return
    }

    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
      if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
        return nil, jwt.ErrSignatureInvalid
      }
      return jwtKey, nil
    })

    if err != nil {
      c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
      c.Abort()
      return
    }

    if !token.Valid {
      c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
      c.Abort()
      return
    }

    claims, ok := token.Claims.(*Claims)
    if !ok {
      c.JSON(http.StatusUnauthorized, gin.H{"error": "Failed to parse claims"})
      c.Abort()
      return
    }

    c.Set("claims", claims)
    c.Next()
  }
}

在應用程式中,我們定義了兩個路由「/login」和「/private」。

在「/login」路由上,我們向認證中心發出GET請求,並在請求頭中新增JWT令牌以進行驗證。如果驗證通過,我們將建立一個JWT令牌,並將其儲存在Cookie中。

在「/private」路由上,我們使用中間件來驗證請求中是否包含JWT令牌。如果令牌有效,我們將提取其中的聲明,並進行相應的處理。

四、總結

在本文中,我們介紹如何使用Go語言實現單一登入(SSO)功能。我們使用了Gin框架來建立HTTP伺服器,並使用JWT令牌來表示使用者。我們定義了一個認證中心,並將其用於驗證請求和頒發令牌。我們也編寫了應用程式的程式碼,並使用中間件來進行身份驗證。這個例子只是一個簡單的範例,可以擴展以滿足特定的應用程式需求。

以上是golang實作sso登錄的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn