Grafana 前端嵌入与 JWT 鉴权实战

Grafana 前端嵌入与 JWT 鉴权实战指南

原理

在传统的 Web 架构中,Grafana 通常作为一个独立的应用运行,用户必须通过账号密码登录其界面才能查看数据。然而,当我们需要将 Grafana 的仪表盘无缝集成到自研的前端系统(如运维大屏、业务后台)时,直接使用账号密码不仅体验割裂,且存在极大的安全隐患。

为了解决这一问题,我们采用 JWT + JWK 的非对称加密鉴权机制。

后端生成 Token: 我们的业务后端充当"签发机构"。它持有绝密的私钥,根据当前用户的身份信息生成一张带有数字签名的"电子护照"(JWT Token)。

前端携带 Token: 前端应用在请求 Grafana 资源时,无需输入密码,只需在 URL 参数或 HTTP 头中出示这张"护照"。

Grafana 验证 JWK: Grafana不持有私钥,但持有一份公开的公钥集(JWK)。当收到请求时,Grafana 利用公钥验证 Token 上的数字签名是否由对应的私钥生成,且未被篡改。验证通过后,Grafana 根据 Token 中的声明自动识别用户身份并授予相应权限。

涉及关键角色及组件:

  • 身份提供者:业务后端
    职责: 负责生成 JWT Token。
    关键动作: 使用 RSA 算法生成一对密钥(公钥与私钥);妥善保管私钥;在代码中实现 Token 生成逻辑,确保 Payload 中包含 Grafana 能够识别的用户标识(如 sub 或 email)。
  • 验证者:Grafana Server
    职责: 负责验证 Token 的合法性并渲染仪表盘。
    关键动作: 读取 grafana.ini 配置文件;加载 JWK 公钥集;拦截带有 auth_token 的请求;解析 Token 并根据 auto_sign_up 策略处理用户登录。
  • 信任锚点:JWK 文件
    职责: 作为连接后端与 Grafana 的信任桥梁。
    格式: 这是一个标准的 JSON 文件(通常命名为 jwks.json),其中包含了公钥的模数、指数以及密钥 ID。
    位置: 部署在 Grafana 服务器可读取的路径下(如 /etc/grafana/jwks.json)。
  • 载体:JWT
    职责: 携带用户身份信息的载体。
    结构: 由头部、载荷和签名三部分组成。其中,头部声明算法(RS256)和密钥 ID;载荷包含用户信息;签名用于防篡改。

二、步骤

1. 配置grafana.ini

修改grafana配置文件并重启

我这里是在k8s平台部署,以configmap的形式挂载配置文件。

复制代码
[security]
allow_embedding = true
cookie_samesite = disabled

[auth]
whitelisted_domains = localhost

#################################### Auth JWT ##########################
[auth.jwt]
enabled = true
header_name = X-JWT-Assertion
enable_login_token = true
email_claim = sub
jwk_set_file = /etc/grafana/jwks.json # 步骤三生成jwks.json文件,填入正确的路径
key_id = grafana-embed-example
expect_claims = {}
role_attribute_path = role
role_attribute_strict = false
username_attribute_path = user.name
email_attribute_path = user.email
auto_sign_up = true
url_login = true
allow_assign_grafana_admin = false
skip_org_role_sync = false

[auth.anonymous]
enabled = false

[log.console]
level = info

2. 准备秘钥

shell 复制代码
# 私钥
openssl rand -base64 172 | tr -d '\n'

# 生成类似:

3.生成jwks.json配置文件

使用步骤2生成的私钥通过代码生成适配grafana格式的公钥配置文件

go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"os"

	"gopkg.in/square/go-jose.v2"
)

/**
openssl rand -base64 172 | tr -d '\n'
go run jwk/generator/main.go --secret-key=xxxxxxx --key-id=grafana-embed-example
*/

//var secretKey *string = flag.String("secret-key", "", "secret key")
//var keyID *string = flag.String("key-id", "", "key-id")

var (
	secretKey string
	keyID     string
)

func main() {
	//flag.Parse()
	//if secretKey == nil || *secretKey == "" {
	//	log.Fatal("secret-key is required")
	//}
	//
	//if keyID == nil || *keyID == "" {
	//	log.Fatal("key-id is required")
	//}
	// 来自openssl rand -base64 172 | tr -d '\n'
	secretKey = "上边生成的key"
	keyID = "grafana-cmdb-embed-237-176" //自定义
	rawKey := []byte(secretKey)

	symKey := jose.JSONWebKey{
		Key:       rawKey,
		KeyID:     keyID,
		Algorithm: string(jose.HS512),
		Use:       "sig",
	}

	jwkJSON, err := json.MarshalIndent(jose.JSONWebKeySet{Keys: []jose.JSONWebKey{symKey}}, "", "  ")
	if err != nil {
		fmt.Printf("Failed to marshal JWK: %s\n", err)
		return
	}

	file, err := os.Create("jwks.json")
	if err != nil {
		fmt.Printf("Failed to create file: %s\n", err)
		return
	}
	defer file.Close()

	if _, err := file.Write(jwkJSON); err != nil {
		fmt.Printf("Failed to write JWK to file: %s\n", err)
		return
	}

	fmt.Println("JWK successfully written to jwks.json")
}

4.代码生成jwt token

go 复制代码
package main

import (
	"fmt"
	"log"
	"time"

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

var (
	secretKey    string
	keyID        string
	dashboardUrl string
)

func init() {
	dashboardUrl = "http://localhost:3000/d/adcm7q5/knative-service-view?orgId=1&kiosk&theme=light&from=now-15m&to=now&timezone=browser&var-knative_service_name=hqmtest&var-revision=hqmtest-00005&refresh=10s&auth_token="
}

func generateJWT() {
	secretKey = "刚才生成的key"
	keyID = "grafana-cmdb-embed-237-76" //自定义
	token := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
		"user": jwt.MapClaims{
			"email": "sub",//用户邮箱,可自定义
			"name":  "sub",//用户名,可自定义,跟配置文件grafanfa.ini中的username_claim对应
		},
		"sub":  "sub",
		"role": "Viewer",
		"iat":  time.Now().Unix(),
		"exp":  time.Now().Add(time.Hour * 1).Unix(),
	})

	token.Header["kid"] = keyID

	tokenString, err := token.SignedString([]byte(secretKey))
	if err != nil {
		//http.Error(w, err.Error(), http.StatusInternalServerError)
		log.Println("tokenstyring error", err)
		return
	}

	url := fmt.Sprintf("%s%s", dashboardUrl, tokenString)
	log.Println(url)
	//if _, err := w.Write([]byte(tokenString)); err != nil {
	//	log.Println(err)
	//}
}

func main() {
	generateJWT()
}


// 打印结果:
// http://localhost:3000/d/adcm7q5/knative-service-view?orgId=1&kiosk&theme=light&from=now-15m&to=now&timezone=browser&var-knative_service_name=hqmtest&var-revision=hqmtest-00005&refresh=10s&auth_token=xxxx

5.测试

将上一步打印的结果在无痕浏览器中打开,看是否可以直接看到dashboard

相关文章:

如何通过jwt授权登录grafana
基於 Golang 的 Grafana Dashboard 與 JWT 認證的前後端實作
github开源示例

相关推荐
小小小小宇1 小时前
前端 V8 引擎垃圾回收机制与内存问题排查
前端
前端老石人1 小时前
CSS 值定义语法
前端·css
sheeta19981 小时前
Vue 前端基础笔记
前端·vue.js·笔记
小小小小宇1 小时前
GitLab + GitLab Runner + Qiankun 微前端 + Nginx + Node 中间件 前端开发机从零搭建 CI/CD 全流程
前端
前端那点事1 小时前
别再写垃圾组件!Vue3 如何设计「真正可复用」的高质量通用组件
前端·vue.js
卷帘依旧1 小时前
JavaScript 中的 Symbol
前端·javascript
老王以为1 小时前
Claude Code 从 GUI 到 TUI:开发者界面的范式回归
前端·人工智能·全栈
JYeontu1 小时前
正方体翻滚Loading 2.0
前端·javascript·css
llq_3501 小时前
React 组件处理 Props
前端