用Go模拟实现单点登录Token生成和验证解析

2024年 1月 13日 100.7k 0

1.单点登录(SSO)原理

单点登录(Single Sign-On,简称SSO)是一种身份验证和授权机制,允许用户在访问多个相关独立的系统或应用程序时只需一次登录, 而不需要为每个系统都提供单独的身份验证凭证。SSO的目的是简化用户体验、提高安全性, 并减少用户因频繁登录而可能面临密码疲劳问题。

SSO的工作原理涉及以下关键概念:

  • 身份提供者(Identity Provider, IdP): 负责验证用户的身份并生成令牌(Token)。IdP通常是一个中心化的认证系统, 负责向其他相关系统提供认证服务。
  • 服务提供者(Service Provider, SP): 各个系统或应用程序, 它们依赖于IdP来验证用户身份。SP接收到IdP颁发的令牌后, 可以通过验证令牌的有效性来信任用户身份。
  • 令牌(Token): 由IdP颁发, 包含有关用户身份的信息, 以及可能的授权信息。
  • 单一登录会话(Single Sign-On Session): 用户只需一次登录到IdP,然后就可以访问所有与IdP集成的SP, 而无需再次提供用户名和密码。

原理图如下:

图片图片

2.JWT原理

JWT 是一种基于 JSON 格式的轻量级令牌,其主要原理是通过在服务端生成一个包含用户信息的 JSON 对象,然后使用密钥对该对象进行签名,生成一个令牌。这个令牌可以被发送到客户端,客户端可以在之后的请求中携带该令牌,服务端使用密钥验证令牌的签名,并解析其中的信息, 从而完成身份验证。

JWT 由三部分组成:Header(头部)、Payload(负载)和 Signature(签名)。

  • Header(头部):包含了两部分信息,token 的类型(JWT)和使用的签名算法,通常是 Base64 编码的 JSON 字符串。
  • Payload(负载):包含了一些声明(Claim),其中包括标准声明、私有声明等。这部分也是 Base64 编码的 JSON 字符串,用于携带一些关键的信息。
  • Signature(签名):由前两部分使用指定的算法签名而成,用于验证消息的完整性。

JWT原理图如下:

图片图片

3.使用Golang模拟实现过程

为了模拟单点登录(SSO), 将创建两个简单的Golang服务: 一个用于认证用户(认证中心), 另一个用于资源提供。用户如果要获取资源,必须先登录认证中心获取令牌, 然后再通过令牌访问资源服务器, 下面是认证中心的服务端实现代码:

package main

import (
  "fmt"
  "net/http"
  "time"

  "github.com/dgrijalva/jwt-go"
)

var secretKey = []byte("btk.gqv7jtu7VZD1dar")

func main() {
  http.HandleFunc("/login", handleLogin)
  http.ListenAndServe(":8080", nil)
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
  // 在实际应用中,这里应该有用户认证的逻辑,为了简化,这里直接使用一个固定用户
  userID := "9527"
  tokenString, err := createToken(userID)
  if err != nil {
    http.Error(w, "创建令牌失败", http.StatusInternalServerError)
    return
  }

  // 将 JWT 令牌附加到响应中
  w.Header().Set("Authorization", "Bearer "+tokenString)
  w.WriteHeader(http.StatusOK)
  fmt.Fprintf(w, "登录成功. Token: %s", tokenString)
}

func createToken(userID string) (string, error) {
  // 创建负载
  payload := jwt.MapClaims{
    "user": userID,
    "exptime": time.Now().Add(time.Minute * 15).Unix(), // 令牌过期时间为15分钟
  }

  // 创建 Token
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)

  // 签名并获取完整的 Token 字符串
  tokenString, err := token.SignedString(secretKey)
  if err != nil {
    return "", err
  }

  return tokenString, nil
}

在上面的代码中, 认证中心服务端在本地监听8080端口, 用来模拟处理用户的登录请求, 在收到用户请求之后, 服务端根据用户ID调用JWT的方法生成Token, 并将Token设置到HTTP头的Authorization字段中返回给客户端。

接下来实现资源提供的服务端,参考代码如下:

package main

import (
  "fmt"
  "net/http"

  "github.com/dgrijalva/jwt-go"
)

var secretKey = []byte("btk.gqv7jtu7VZD1dar")

func main() {
  http.HandleFunc("/resource", handleResource)
  http.ListenAndServe(":8081", nil)
}

func handleResource(w http.ResponseWriter, r *http.Request) {
  // 从请求中获取 Authorization 头
  authHeader := r.Header.Get("Authorization")
  if authHeader == "" {
    http.Error(w, "未找到Authorization字段", http.StatusUnauthorized)
    return
  }

  // 解析JWT令牌
  tokenString := authHeader[len("Bearer "):]
  token, err := parseToken(tokenString)
  if err != nil || !token.Valid {
    http.Error(w, "令牌不合法", http.StatusUnauthorized)
    return
  }

  // 获取负载信息
  claims, ok := token.Claims.(jwt.MapClaims)
  if !ok {
    http.Error(w, "令牌不合法", http.StatusUnauthorized)
    return
  }

  userID, ok := claims["user"].(string)
  if !ok {
    http.Error(w, "访问令牌中的用户不存在", http.StatusUnauthorized)
    return
  }

  // 在实际应用中,这里可以根据 userID 获取用户信息或提供资源
  // 这里只是一个简单的示例
  response := fmt.Sprintf("用户ID%s获取资源成功!", userID)
  w.WriteHeader(http.StatusOK)
  fmt.Fprintf(w, response)
}

func parseToken(tokenString string) (*jwt.Token, error) {
  // 解析 Token 字符串
  token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    return secretKey, nil
  })

  if err != nil {
    return nil, err
  }

  return token, nil
}

在上面的代码中, 资源服务端监听本地的8081端口, 当接收到用户请求之后, 首先从请求头中的Authorization字段获取Token令牌, 并对令牌进行解析, 如果正确解析出用户ID, 返回该用户的资源信息。

4.验证实现结果

将上面两段代码分别编译成两个独立Server端,并开启两个窗口分别运行。首先请求SSO服务端, 返回结果如下:

图片图片

从上图可知,SSO服务端成功返回了一个Token令牌, 下面先不用Token访问一下资源服务器试试:

图片图片

从上图可以看到,没有令牌无法正常请求到所需资源, 下面使用Apifox新建一个请求, 在里面加上Token, 如图:

图片图片

保存之后, 带着Token请求一下资源服务端, 如图:

图片图片

可以看到, 成功返回了资源, 整个流程测试成功。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论