在微服务体系里,"用户鉴权到底放在哪一层" ,几乎是每个后端团队都会反复讨论的问题:是每个服务各自校验 JWT?还是统一交给网关?服务网格又该扮演什么角色?这篇文章将通过一套完整可复现的实战流程 ,从零开始搭建 Kind + Linkerd + APISIX + Gin 的微服务环境,把 JWT 鉴权彻底前移到网关层,让后端服务只关心业务本身,最终实现一条清晰、稳定、可扩展的认证链路。
一、前提条件
1、创建kind(k8s in docker)
css
kind create cluster --config kind-config.yaml --name codee-jun
cs
kubectl get nodes
2、安装 Linkerd
bash
# 设置 LINKERD2_VERSION 环境变量,指定要安装的 Linkerd 版本
# 如果未设置,则会安装最新的 edge 版本
export LINKERD2_VERSION=edge-25.10.7
# 下载并运行 Linkerd 安装脚本
# 使用 TLSv1.2 协议确保安全下载
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install-edge | sh
# 将 Linkerd CLI 添加到 PATH 环境变量中
export PATH=$HOME/.linkerd2/bin:$PATH
# 检查 Linkerd 版本
# 验证 CLI 是否正确安装
linkerd version
# 安装 Gateway API 标准资源
# 使用 server-side 应用模式安装 v1.4.0 版本
kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml
# 运行 Linkerd 预检查
# 验证集群是否满足安装 Linkerd 的条件
linkerd check --pre
# 安装 Linkerd CRDs
# 先安装自定义资源定义
linkerd install --crds | kubectl apply -f -
# 安装 Linkerd 控制平面
# 安装核心组件
linkerd install | kubectl apply -f -
# 检查 Linkerd 部署状态
# 验证控制平面组件是否成功部署
kubectl -n linkerd get deploy
# 运行 Linkerd 健康检查
# 验证整个 Linkerd 安装是否健康
linkerd check
nginx
# 安装 Linkerd 可视化组件
# 安装 dashboard 等监控工具
linkerd viz install | kubectl apply -f -
# 启动 Linkerd 仪表盘
# 打开 Web 界面查看服务网格状态
linkerd viz dashboard
3、安装APISIX网关
bash
# 添加 APISIX Helm 仓库
helm repo add apisix https://charts.apiseven.com
# 更新 Helm 仓库
helm repo update
# 创建 apisix 命名空间
kubectl create ns apisix
# 检查 apisix 命名空间是否已正确创建
kubectl get ns apisix -o yaml | grep linkerd
helm install apisix apisix/apisix --create-namespace --namespace apisix
# 检查 APISIX 部署状态
# 验证 APISIX 组件是否成功部署
kubectl get pods -n apisix
# 安装 APISIX 路由
helm upgrade apisix apisix/apisix \
--namespace apisix \
--set ingress-controller.enabled=true \
--set ingress-controller.apisix.adminService.namespace=apisix
二、用户服务
- 安装comer工具
这里偷懒用之前写的快速生成RESTFul接口的工具来生成代码,之后再做修改。
nginx
go install github.com/imoowi/comer@latest
css
~ >> comer version coding-jun@bogon
Comer version v1.3.15
- 创建用户服务
bash
cd src/day_81
comer new user-service
cd user-service
swag init
go mod tidy

- 编译镜像
bash
docker build -t imoowi/golang_per_day:day81-user .
docker push imoowi/golang_per_day:day81-user
三、发布应用到linkerd
- app.yaml
makefile
# Kubernetes Deployment 配置
# 定义 user-service 应用的部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service # 部署名称
#namespace: apisix # 命名空间
spec:
replicas: 1 # 副本数
selector:
matchLabels:
app: user # 标签选择器,匹配 app=user 的 Pod
template:
metadata:
labels:
app: user # Pod 标签
annotations:
linkerd.io/inject: enabled # 启用 Linkerd 服务网格注入
spec:
containers:
- name: app # 容器名称
image: imoowi/golang_per_day:day81-user # 容器镜像
imagePullPolicy: IfNotPresent # 镜像拉取策略
ports:
- containerPort: 8000 # 容器暴露的端口
---
# Kubernetes Service 配置
# 为 user-service 提供网络访问
apiVersion: v1
kind: Service
metadata:
name: user-service # Service 名称
#namespace: apisix # 命名空间
spec:
selector:
app: user # 标签选择器,匹配 app=user 的 Pod
ports:
- port: 8000 # Service 暴露的端口
targetPort: 8000 # 目标 Pod 端口
- kubectl apply 部署app
powershell
kubectl apply -f app.configmap.yaml
kubectl apply -f app.yaml
四、配置 ApisixRoute
- 找到apisix的admin-key
cs
kubectl get cm apisix -n apisix -o yaml | grep key

- 添加route
2.1 curl模式
bash
kubectl run curl --image=curlimages/curl --restart=Never -it --rm -- sh
shell
$ curl -s -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" http://apisix-admin:9180/apisix/admin/routes
cpp
$ curl -s -X POST -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -H \
"Content-Type: application/json" \
http://apisix-admin:9180/apisix/admin/routes -d \
'{
"uri": "/api/user/*",
"upstream": {
"type": "roundrobin",
"nodes": {
"user-service.default.svc.cluster.local:8000": 1
}
}
}'
2.2 ui模式
apache
kubectl port-forward svc/apisix-dashboard 9080:80
浏览器访问 http://127.0.0.1:9080/,添加路由,如图



- 修改 nodePort: 30080 ,保存退出,k8s自动重启服务
nginx
kubectl edit svc apisix-gateway
- 访问浏览器
http://localhost/api/user/common/captcha

看见上面的输出,说明apisix到linkerd的路径打通了。
五、修改user-service jwt的验证方式
- 修改中间件
js
JWTAuthMiddleware()
👇
ApisixMiddleware()
go
//user-service/internal/router/auth.router.go
func AuthRouters(e *gin.Engine) {
api := e.Group(`/api/user`)
{
api.POST(`/auth-login`, middlewares.VcodeMiddleware(), controllers.AuthLogin)
api.GET(`/auth-logout`, middlewares.ApisixMiddleware(), controllers.AuthLogout)
api.GET(`/auth-info`, middlewares.ApisixMiddleware(), controllers.AuthInfo)
}
}
- 中间件绝对信任上游的uid
go
/*
generated by comer,https://github.com/imoowi/comer
Copyright © 2023 jun<simpleyuan@gmail.com>
*/
package middlewares
import (
"net/http"
"user-service/internal/services"
"github.com/gin-gonic/gin"
"github.com/imoowi/comer/utils/response"
"github.com/spf13/cast"
)
func ApisixMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
authHeader := c.Request.Header.Get("Authorization")
if authHeader == "" {
response.Error(c, "请求头中auth为空", http.StatusUnauthorized)
return
}
//判断用户是否主动注销过
isLogouted := services.User.IsLogouted(c, authHeader)
if isLogouted {
response.Error(c, "token 失效", http.StatusUnauthorized)
return
}
//检测apisix过来的用户id有没有
userId := c.GetHeader("X-User-Id")
if userId == "" {
response.Error(c, "X-User-Id is empty", http.StatusUnauthorized)
return
}
//绝对信任上游传递的用户id
c.Set("uid", cast.ToUint(userId))
c.Next()
}
}
六、配置Apisix的jwt插件
所有操作都在Postman上执行
- 环境变量

- 添加consumer

- 添加路由

请求body是
swift
{
"id": "api-user-auth",
"uri": "/api/user*",
"plugins": {
"jwt-auth": {
"key": "user-service-admin",
"secret": "a-string-secret-at-least-256-bits-long",
"algorithm": "HS256",
"ignore": {
"uris": [
"/api/user/login",
"/api/user/common/captcha"
]
},
"payload": [
"user_id"
]
},
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function(conf, ctx)\n local jwt = require('resty.jwt')\n local auth = ngx.req.get_headers()['Authorization']\n if not auth then return end\n local token = string.gsub(auth, 'Bearer ', '')\n local obj = jwt:load_jwt(token)\n if not obj.valid then return end\n if obj.payload.user_id then ngx.req.set_header('X-User-Id', obj.payload.user_id) end\nend"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"user-service:8000": 1
}
}
}
七、测试一下
- 获取验证码

显示验证码的脚步填写在Post-response里,这样每次请求的验证码就会显示到图片上
xml
const res = pm.response.json();
// console.log('res=',res)
pm.visualizer.set(`
<div>
<h3>验证码</h3>
<img src="{{img}}" />
<p>captcha_id: {{id}}</p>
</div>
`, {
img: res.captcha_code,
id: res.captcha_id
});
- 登录

登录之后需要把token设置到环境变量里
javascript
const res = pm.response.json();
console.log('res=',res)
pm.environment.set("token", res.token);
- 获取用户信息

取数据之前,会把token放在header里

这样整个流程就跑通了。
源码地址*
评论区回复666给
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!