Web中Token验证如何实现(go语言)

场景描述

给你一个场景,一个用户登录之后,进入到主页,但是HTTP是无连接的,所以服务器这个时候根本不知道进入到这个页面的是谁,那么你要怎么解决这个问题呢?

一般有两种方法,一种是 Cookie+Session 、另一种是 JWT+Token、甚至可以见到两者都用的情况。

Token是什么

Token = 登录成功后的"身份证"

在客户端登录成功后、服务端生成一个token 返回给客户端、客户端以后每次请求都带上它、然后服务端通过 token 判断:你是谁你有没有过期你有没有权限。

JWT+token

JWT 是一种 Token 的 **实现方式 / 规范 ,**是目前最常用的方式。

它规定了:Token 长什么样(Header.Payload.Signature)、怎么签名、怎么校验、里面可以放哪些字段(claims)

他包含三个部分:

Header(头部):说明签名算法,比如 HS256

Payload(负载):用户信息 / claims

Signature(签名):防篡改

其中负载里面的claims如下:

Go 复制代码
type Claims struct {
	UserID   int    `json:"user_id"`
	Username string `json:"username"`
	IsAdmin  int    `json:"is_admin"`
    // 上面三条都是你自己定义的,需要用Token来传的用户信息
	jwt.RegisteredClaims
}

处理流程+代码实现

1. 用户登录

校验用户名/密码

生成 Token(通常是 JWT)

2. 客户端保存 Token

放在**Authorization: Bearer <token>** 里(这是个标准的请求头)

3. 客户端访问受保护接口

请求头携带 Token (Authorization

3. 服务端验证 Token

是否存在

是否被篡改

是否过期

是否有权限

5. 通过 → 继续处理 失败 → 返回 401 / 403

准备工作

gIthub 拉取jwt

Go 复制代码
"github.com/golang-jwt/jwt/v5"

创建 jwt.go

Go 复制代码
package utils

import (
	"errors"
	"time"

	"github.com/golang-jwt/jwt/v5"
)
// 这是用来给 JWT Token 签名的密钥,用于验证 Token 的真实性和完整性,可以从生产环境获取,我这里直接设定了
var jwtKey = []byte("secret_key_123456") 

type Claims struct {
	UserID   int    `json:"user_id"`
	Username string `json:"username"`
	IsAdmin  int    `json:"is_admin"`
	jwt.RegisteredClaims
}

// 生成 token
func GenerateToken(userID int, username string, isAdmin int) (string, error) {
	claims := &Claims{
		UserID:   userID,
		Username: username,
		IsAdmin:  isAdmin, // 前三个字段代表token代表什么
		RegisteredClaims: jwt.RegisteredClaims{ // 标准字段
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 过期时间
			IssuedAt:  jwt.NewNumericDate(time.Now()),                     // 什么时候签发
			Issuer:    "go_web",                                           // 谁签发的
		},
	}
	// header+payload生成,jwtKey签名最终得到token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtKey)
}

// 解析 token
func ParseToken(tokenStr string) (*Claims, error) {
	token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return jwtKey, nil
	})
	if err != nil {
		return nil, err
	}
	if claims, ok := token.Claims.(*Claims); ok && token.Valid {
		return claims, nil
	}
	return nil, errors.New("token无效")
}

创建 中间件

Go 复制代码
package middleware

import (
	"context"
	"go_web/response"
	"go_web/utils"
	"net/http"
	"strings"
)

type contextKey string

const ClaimsKey = contextKey("claims")

type ClaimsContext struct {
	UserID   int
	Username string
	IsAdmin  int
}

func JwtMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		auth := r.Header.Get("Authorization")
		if auth == "" {
			response.WriteJson(w, 401, 1, "请先登录")
			return
		}
		token := strings.TrimSpace(auth)
		claims, err := utils.ParseToken(token)
		if err != nil {
			response.WriteJson(w, 401, 1, "token无效")
			return
		}
		ctx := context.WithValue(r.Context(), ClaimsKey, &ClaimsContext{
			UserID:   claims.UserID,
			Username: claims.Username,
			IsAdmin:  claims.IsAdmin,
		})
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

路由器使用中间件

Go 复制代码
apiMux := http.NewServeMux()
apiMux.Handle("/users", middleware.JwtMiddleware(http.HandlerFunc(userController.Users)))

前端登录 + 保存Token

javascript 复制代码
try {
    // 向服务器发送登录请求
   const res = await fetch("/api/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username, password })
   });
    // 得到服务端返回的信息
   const data = await res.json();
    // code = 0 登录成功, 存在data.data, 且data.token存在
   if (data.code === 0 && data.data && data.data.token) {
    // 保存 Token
    localStorage.setItem("token", data.data.token);
    location.href = "/index";
   } else {
    localStorage.removeItem("token");
    alert(data.msg || "登录失败");
   }
  } catch (e) {
   alert("登录失败,请重试");
  }

Token后端登录验证 + 返回Token

建议分层处理:

分层 职责 对应你的代码
控制器层(Controller) 接收前端请求、解析参数、返回响应、处理 HTTP 相关逻辑(如 Cookie) AuthController + LoginAPI 方法
服务层(Service) 封装核心业务逻辑(如用户校验、密码对比、权限判断) service.AuthService + Login 方法
数据访问层(DAO) 操作数据库(如查询用户、验证用户名密码) (隐藏在 AuthService 内部)

这里是一个链式结构:

Go 复制代码
type AuthController struct {
	AuthService *service.AuthService
}
type AuthService struct {
	Repo *repository.Repo
}
type Repo struct{}

结构关系图:(上层依赖下层,下层不依赖上层)

HTTP Request

AuthController

AuthService

Repo

DB / Redis

这种设计目的:

职责清晰

  • Controller:接请求

  • Service:业务

  • Repo:数据

易维护

  • 改数据库 → 只动 Repo

  • 改业务规则 → 只动 Service

  • 改接口 → 只动 Controller

易测试

  • Service 可以 mock Repo

  • Controller 可以 mock Service

然后看登录处理:

Go 复制代码
func (c *AuthController) Login(w http.ResponseWriter, r *http.Request) {
	var loginUser model.AuthUser
	err := json.NewDecoder(r.Body).Decode(&loginUser)
	if err != nil {
		logger.Error("JSON 解码失败:%v | 原始请求体:%v", err, r.Body)
		response.WriteJson(w, 400, 1, "JSON格式错误")
		return
	}
	user, err := c.AuthService.Login(loginUser.Username, loginUser.Password)
	if err != nil {
		logger.Info("%s 登录失败:%v", loginUser.Username, err)
		response.WriteJson(w, 200, 1, err.Error())
		return
	}
	// 用数据库里的 is_admin 生成 JWT
	token, err := utils.GenerateToken(
		user.ID,
		user.Username,
		user.IsAdmin,
	)
	if err != nil {
		response.WriteJson(w, 500, 1, "生成 token 失败")
		return
	}
	logger.Info("%s 登录成功,is_admin=%d", user.Username, user.IsAdmin)
	response.WriteData(w, map[string]string{
		"token": token,
	})
}

客户端携带Token请求后端

Go 复制代码
async function authFetch(url, options = {}) {
  const token = localStorage.getItem("token");
  // 默认 headers
  const headers = {
   ...(options.headers || {}),
   "Authorization": token || "",
  };
  // 如果是 JSON body,自动补 Content-Type
  if (options.body && !(options.body instanceof FormData)) {
   headers["Content-Type"] = headers["Content-Type"] || "application/json";
  }
  let response;
  try {
   response = await fetch(url, {
    ...options,
    headers,
   });
  } catch (err) {
   alert("网络异常,请检查网络连接");
   throw err;
  }
  // token 失效 / 未登录
  if (response.status === 401) {
   localStorage.removeItem("token");
   alert("登录已过期,请重新登录");
   window.location.href = "/login";
   return;
  }
  // 非 JSON(例如 204)
  const contentType = response.headers.get("Content-Type") || "";
  if (!contentType.includes("application/json")) {
   return null;
  }
  const result = await response.json();
  return result;
 }
 /** ==== JWT + Token + 权限控制 ==== **/

 const token = localStorage.getItem('token');
 if (!token) {
  location.href = "/login";
 }
 // 解析 token
 function parseJwt(token) {
  const base64Url = token.split('.')[1];
  const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  const jsonStr = decodeURIComponent(
          atob(base64)
                  .split('')
                  .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
                  .join('')
  );
  return JSON.parse(jsonStr);
 }

后端验证

Go 复制代码
func (c *UserController) Users(w http.ResponseWriter, r *http.Request) {
	// 不是重复校验token,而是防止路由配置不经过中间件,一层保护
	claimsValue := r.Context().Value(middleware.ClaimsKey)
	claims, ok := claimsValue.(*middleware.ClaimsContext)
	if !ok {
		response.WriteJson(w, 401, 1, "未认证")
		return
	}

	switch r.Method {
	case http.MethodGet:
		c.Search(w, r)
	case http.MethodPost:
		if claims.IsAdmin != 1 {
			response.WriteJson(w, 403, 1, "没有权限")
			return
		}
		c.AddUser(w, r)
	case http.MethodPut:
		if claims.IsAdmin != 1 {
			response.WriteJson(w, 403, 1, "没有权限")
			return
		}
		c.UpdateUser(w, r, claims)
	case http.MethodDelete:
		if claims.IsAdmin != 1 {
			response.WriteJson(w, 403, 1, "没有权限")
			return
		}
		c.DeleteUser(w, r, claims)
	}
}
相关推荐
戌中横2 小时前
JavaScript——Web APIs DOM
前端·javascript·html
Beginner x_u2 小时前
如何解释JavaScript 中 this 的值?
开发语言·前端·javascript·this 指针
HWL56793 小时前
获取网页首屏加载时间
前端·javascript·vue.js
烟锁池塘柳03 小时前
【已解决】Google Chrome 浏览器报错 STATUS_ACCESS_VIOLATION 的解决方案
前端·chrome
速易达网络3 小时前
基于RuoYi-Vue 框架美妆系统
前端·javascript·vue.js
LYS_06183 小时前
RM赛事C型板九轴IMU解算(4)(卡尔曼滤波)
c语言·开发语言·前端·卡尔曼滤波
We་ct4 小时前
LeetCode 151. 反转字符串中的单词:两种解法深度剖析
前端·算法·leetcode·typescript
yinmaisoft5 小时前
JNPF 表单模板实操:高效复用表单设计指南
前端·javascript·html
37方寸5 小时前
前端基础知识(JavaScript)
开发语言·前端·javascript