Grafana 前端嵌入与 JWT 鉴权实战指南
- 原理
- 二、步骤
-
- [1. 配置grafana.ini](#1. 配置grafana.ini)
- [2. 准备秘钥](#2. 准备秘钥)
- 3.生成jwks.json配置文件
- [4.代码生成jwt token](#4.代码生成jwt token)
- 5.测试
- 相关文章:
原理
在传统的 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开源示例