跳到主要内容

为应用程序插件添加身份验证

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 对象包含用户已配置的密钥。

以下是一些代码重点

  1. 无论用户是否配置了 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),
    });
  2. 您可以通过向 /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 文件中配置代理路由。

  1. 将路由添加到 plugin.json

    src/plugin.json
    "routes": [
    {
    "path": "myRoutePath",
    "url": "https://api.example.com"
    }
    ]
    注意

    每次更改 plugin.json 文件时,您都需要构建插件并重启 Grafana 服务器。

  2. 在您的应用程序插件中,使用 @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。例如,其中 projectIdjsonData 对象中属性的名称

    src/plugin.json
    "routes": [
    {
    "path": "example",
    "url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
    }
    ]
  • 对于存储在 secureJsonData 中的敏感数据,请使用 .SecureJsonData。例如,在 passwordsecureJsonData 对象中属性的名称时,使用 .SecureJsonData

    src/plugin.json
    "routes": [
    {
    "path": "example",
    "url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
    }
    ]

除了向代理路由添加 URL 之外,您还可以添加标头、URL 参数和请求正文。

向代理路由添加 HTTP 标头

以下是添加 namecontent 作为 HTTP 标头的示例

src/plugin.json
"routes": [
{
"path": "example",
"url": "https://api.example.com",
"headers": [
{
"name": "Authorization",
"content": "Bearer {{ .SecureJsonData.apiToken }}"
}
]
}
]

向代理路由添加请求正文

以下是向请求正文添加 usernamepassword 的示例

src/plugin.json
"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 标头将用户标头传递给插件。