首页  >  文章  >  后端开发  >  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