脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Golang - 用Go模拟实现单点登录Token生成和验证解析

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

2024-01-15 14:33二进制空间安全 Golang

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

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, 而无需再次提供用户名和密码。

原理图如下:

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

2.JWT原理

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

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

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

JWT原理图如下:

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

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服务端, 返回结果如下:

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

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

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

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

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

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

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

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



原文地址:https://mp.weixin.qq.com/s/vbYKCANRYSym98kVKNGXKg

延伸 · 阅读

精彩推荐
  • GolangGoland支持泛型了(上机实操)

    Goland支持泛型了(上机实操)

    Go的泛型不是还在设计草图吗?最乐观估计也要2021年8月份。你说Go语言现在都没开发好泛型,你支持这个特性有什么用呢?感兴趣的朋友跟随小编一起看看...

    zhangshen02326052021-02-23
  • GolangGo中sync 包Cond使用场景分析

    Go中sync 包Cond使用场景分析

    Cond 是和某个条件相关,在条件还没有满足的时候,所有等待这个条件的协程都会被阻塞住,只有这个条件满足的时候,等待的协程才可能继续进行下去,...

    水淹萌龙7792023-03-05
  • GolangGo语言实现冒泡排序、选择排序、快速排序及插入排序的方法

    Go语言实现冒泡排序、选择排序、快速排序及插入排序的方法

    这篇文章主要介绍了Go语言实现冒泡排序、选择排序、快速排序及插入排序的方法,以实例形式详细分析了几种常见的排序技巧与实现方法,非常具有实用价值...

    脚本之家3462020-04-13
  • Golanggo语言中int和byte转换方式

    go语言中int和byte转换方式

    这篇文章主要介绍了go语言中int和byte转换方式,需要的朋友可以参考下 ...

    wuzhc7892020-05-21
  • GolangGo语言中slice的用法实例分析

    Go语言中slice的用法实例分析

    这篇文章主要介绍了Go语言中slice的用法,实例分析了slice的功能及使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    不是JS1902020-04-17
  • GolangVsCode搭建Go语言开发环境的配置教程

    VsCode搭建Go语言开发环境的配置教程

    这篇文章主要介绍了在VsCode中搭建Go开发环境的配置教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考...

    闹闹吃鱼6712020-07-05
  • Golanggolang通过反射设置结构体变量的值

    golang通过反射设置结构体变量的值

    这篇文章主要介绍了golang通过反射设置结构体变量的值操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    butterfly52113147102021-05-28
  • Golang在goland中读取tpl文件的图文操作

    在goland中读取tpl文件的图文操作

    这篇文章主要介绍了在goland中读取tpl文件的图文操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    刘光伟11432021-02-24