为应用程序插件添加身份验证
Grafana 应用程序插件允许您捆绑面板和数据源。应用程序还允许您在 Grafana 中创建具有复杂功能的自定义页面。
选择一种身份验证方法
有两种方法为应用程序插件添加身份验证。通过以下两种方式之一将您的应用程序插件配置为针对第三方 API 进行身份验证:
情况 | 使用 |
---|---|
您需要使用基本认证(Basic Auth)或 API 密钥来验证您的插件吗? | 使用数据源代理。 |
您的 API 使用了数据源代理不支持的自定义身份验证方法吗? | 使用后端组件。 |
您的 API 是否使用 HTTP 以外的协议进行通信? | 使用后端组件。 |
加密秘密配置
应用程序插件有两种存储自定义配置的方式
jsonData
secureJsonFields
请勿将 jsonData
用于存储敏感数据,例如密码、令牌和 API 密钥。如果您需要存储敏感信息,请改用 secureJsonData
。
在 secureJsonData
中存储配置
如果您需要存储敏感信息(秘密),请改用 secureJsonData
而非 jsonData
。每当用户保存应用程序配置时,secureJsonData
中的秘密都会发送到 Grafana 服务器,并在存储前进行加密。
一旦您加密了安全配置,就无法再从浏览器访问该配置。保存秘密后,唯一访问它们的方式是使用数据源代理或通过后端组件。
向您的应用程序插件添加秘密配置
您初始化的应用程序插件应该有一个 AppConfig
组件,允许用户配置应用程序。此组件包含将 apiKey
存储在 secureJsonData
中的示例代码。您可以在 plugin.meta
中的 secureJsonFields
中检查 secureJsonData
的属性。secureJsonFields
对象包含用户已配置的密钥。
以下是一些代码要点:
-
无论用户是否已配置
secureJsonData
,它都不会带有填充的值。相反,您可以通过检查secureJsonFields
中对应的键是否为true
来确定属性是否已配置。例如:const { jsonData, secureJsonFields } = plugin.meta;
const [state, setState] = useState<State>({
apiUrl: jsonData?.apiUrl || '',
apiKey: '',
// check if the key is true or false to determine if it has been configured
isApiKeySet: Boolean(secureJsonFields?.apiKey),
}); -
您可以通过向
/api/plugins/<pluginId>/settings
端点发送 POST 请求来更新secureJsonData
。如果您在
secureJsonData
中设置密钥,则只应发送用户修改过值的密钥。发送任何值(包括空字符串)都会覆盖现有配置。const secureJsonData = apiKey.length > 0 ? { apiKey } : undefined;
await getBackendSrv().fetch({
url: `/api/plugins/${pluginId}/settings`,
method: 'POST',
data: {
secureJsonData,
},
});
使用数据源代理进行身份验证
一旦用户保存了您的应用程序配置,秘密配置在浏览器中将不可用。加密的秘密只能在服务器上访问。那么如何将它们添加到您的请求中呢?
Grafana 服务器带有一个代理,允许您为请求定义模板:代理路由。Grafana 将代理路由发送到服务器,解密秘密以及其他配置,并在发送请求之前将其添加到请求中。
请确保不要将数据代理与认证代理(auth proxy)混淆。数据代理用于验证插件请求,而认证代理用于登录 Grafana 本身。
为您的插件添加代理路由
要通过数据代理转发请求,您需要配置一个或多个代理路由。代理路由是由代理处理的任何传出请求的模板。您可以在plugin.json 文件中配置代理路由。
-
将路由添加到
plugin.json
src/plugin.json"routes": [
{
"path": "myRoutePath",
"url": "https://api.example.com"
}
]注意每次更改
plugin.json
文件时,您都需要构建插件并重启 Grafana 服务器。 -
在您的应用程序插件中,使用来自
@grafana/runtime
包的getBackendSrv
函数从代理路由获取数据import { getBackendSrv } from '@grafana/runtime';
import { lastValueFrom } from 'rxjs';
async function getDataFromApi() {
const dataProxyUrl = `api/plugin-proxy/${PLUGIN_ID}/myRoutePath`;
const response = getBackendSrv().fetch({
url: dataProxyUrl,
});
return await lastValueFrom(response);
}
为您的插件添加动态代理路由
Grafana 将数据代理请求发送到服务器后,数据源代理会解密敏感数据。然后,数据源代理会在发出请求之前,使用解密的数据填充模板变量。
要为您的路由添加用户自定义配置:
-
对于存储在
jsonData
中的配置,使用.JsonData
。例如,其中projectId
是jsonData
对象中属性的名称src/plugin.json"routes": [
{
"path": "example",
"url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
}
] -
对于存储在
secureJsonData
中的敏感数据,使用.SecureJsonData
。例如,使用.SecureJsonData
,其中password
是secureJsonData
对象中属性的名称src/plugin.json"routes": [
{
"path": "example",
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
}
]
除了将 URL 添加到代理路由外,您还可以添加请求头(headers)、URL 参数(URL parameters)和请求体(request body)。
为代理路由添加 HTTP 请求头
以下是添加 name
和 content
作为 HTTP 请求头的示例:
"routes": [
{
"path": "example",
"url": "https://api.example.com",
"headers": [
{
"name": "Authorization",
"content": "Bearer {{ .SecureJsonData.apiToken }}"
}
]
}
]
为代理路由添加请求体
以下是添加 username
和 password
到请求体的示例:
"routes": [
{
"path": "example",
"url": "http://api.example.com",
"body": {
"username": "{{ .JsonData.username }}",
"password": "{{ .SecureJsonData.password }}"
}
}
]
应用程序插件中数据代理的限制
- 应用程序插件不支持
urlParams
配置。 - 应用程序插件不支持
tokenAuth
配置(针对 OAuth 2.0)。
使用后端组件进行身份验证
虽然数据代理支持 HTTP API 最常见的身份验证方法,但使用代理路由存在一些限制:
- 代理路由仅支持 HTTP 或 HTTPS。
- 代理路由不支持自定义令牌身份验证。
- 应用程序的代理路由不支持
urlParams
。 - 应用程序的代理路由不支持
tokenAuth
。
如果您的插件存在任何这些限制,您需要为插件添加一个后端组件。由于后端组件在服务器上运行,它们可以访问解密的秘密,从而更容易实现自定义身份验证方法。
在后端组件中访问秘密
解密的秘密可以从应用程序实例设置中的 DecryptedSecureJSONData
字段获取。
func (a *App) registerRoutes(mux *http.ServeMux) {
// ... other routes
mux.HandleFunc("/test", a.handleMyRequest)
}
func (a *App) handleMyRequest(w http.ResponseWriter, req *http.Request) {
pluginConfig := backend.PluginConfigFromContext(req.Context())
secureJsonData := pluginConfig.AppInstanceSettings.DecryptedSecureJSONData
// Use the decrypted data
w.Header().Add("Content-Type", "application/json")
if _, err := w.Write([]byte(`{"message": "ok}`)); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
使用 Cookie
您的应用程序插件可以读取 Grafana 转发给应用程序的 Cookie。
func (a *App) handleMyRequest(w http.ResponseWriter, req *http.Request) {
cookies := req.Cookies()
// loop through cookies as an example
for _, cookie := range cookies {
log.Printf("cookie: %+v", cookie)
}
// Use the cookies
w.Header().Add("Content-Type", "application/json")
if _, err := w.Write([]byte(`{"message": "ok}`)); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
转发已登录用户的用户请求头
当send_user_header
启用时,Grafana 会使用 X-Grafana-User
请求头将用户请求头传递给插件。