单点登录(Single Sign-On,简称SSO)是一种身份验证和授权机制,允许用户在访问多个相关独立的系统或应用程序时只需一次登录, 而不需要为每个系统都提供单独的身份验证凭证。SSO的目的是简化用户体验、提高安全性, 并减少用户因频繁登录而可能面临密码疲劳问题。
SSO的工作原理涉及以下关键概念:
原理图如下:
图片
JWT 是一种基于 JSON 格式的轻量级令牌,其主要原理是通过在服务端生成一个包含用户信息的 JSON 对象,然后使用密钥对该对象进行签名,生成一个令牌。这个令牌可以被发送到客户端,客户端可以在之后的请求中携带该令牌,服务端使用密钥验证令牌的签名,并解析其中的信息, 从而完成身份验证。
JWT 由三部分组成:Header(头部)、Payload(负载)和 Signature(签名)。
JWT原理图如下:
图片
为了模拟单点登录(SSO), 将创建两个简单的Golang服务: 一个用于认证用户(认证中心), 另一个用于资源提供。用户如果要获取资源,必须先登录认证中心获取令牌, 然后再通过令牌访问资源服务器, 下面是认证中心的服务端实现代码:
package mainimport ( "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 mainimport ( "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, 返回该用户的资源信息。
将上面两段代码分别编译成两个独立Server端,并开启两个窗口分别运行。首先请求SSO服务端, 返回结果如下:
图片
从上图可知,SSO服务端成功返回了一个Token令牌, 下面先不用Token访问一下资源服务器试试:
图片
从上图可以看到,没有令牌无法正常请求到所需资源, 下面使用Apifox新建一个请求, 在里面加上Token, 如图:
图片
保存之后, 带着Token请求一下资源服务端, 如图:
图片
可以看到, 成功返回了资源, 整个流程测试成功。
本文链接:http://www.28at.com/showinfo-26-60955-0.html用Go模拟实现单点登录Token生成和验证解析
声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。