为应用程序插件添加身份验证
Grafana 应用程序插件允许您捆绑面板和数据源。应用程序还允许您在 Grafana 中创建具有复杂功能的自定义页面。
选择身份验证方法
有两种方法可以为应用程序插件添加身份验证。配置您的应用程序插件,以通过以下两种方式之一针对第三方 API 进行身份验证
案例 | 用途 |
---|---|
您是否需要使用基本身份验证或 API 密钥对插件进行身份验证? | 使用数据源代理。 |
您的 API 是否使用数据源代理不支持的自定义身份验证方法? | 使用后端组件。 |
您的 API 是否通过 HTTP 以外的协议进行通信? | 使用后端组件。 |
加密密钥配置
应用程序插件有两种存储自定义配置的方式
jsonData
secureJsonFields
请勿将 jsonData
与敏感数据(如密码、令牌和 API 密钥)一起使用。如果您需要存储敏感信息,请改用 secureJsonData
。
将配置存储在 secureJsonData
中
如果您需要存储敏感信息(密钥),请使用 secureJsonData
而不是 jsonData
。每当用户保存应用程序配置时,secureJsonData
中的密钥都会发送到 Grafana 服务器,并在存储之前进行加密。
一旦您加密了安全配置,就无法再从浏览器访问该配置。在保存密钥后访问密钥的唯一方法是通过使用 数据源代理 或通过 后端组件。
向您的应用程序插件添加密钥配置
您引导的应用程序插件应具有一个 AppConfig
组件,允许用户配置应用程序。此组件包含示例代码,用于在 secureJsonData
中存储 apiKey
。您可以在 secureJsonFields
中检查 secureJsonData
的属性,它是 plugin.meta
的一部分。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 将代理路由发送到服务器,解密密钥以及其他配置,并将它们添加到请求中,然后再发送请求。
请务必不要将数据代理与 身份验证代理 混淆。数据代理用于验证插件请求,而身份验证代理用于登录 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
。例如,在password
是secureJsonData
对象中属性的名称时,使用.SecureJsonData
src/plugin.json"routes": [
{
"path": "example",
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
}
]
除了向代理路由添加 URL 之外,您还可以添加标头、URL 参数和请求正文。
向代理路由添加 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
标头将用户标头传递给插件。