跳转到主要内容

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

Grafana 应用程序插件允许您捆绑面板和数据源。应用程序还允许您在 Grafana 中创建具有复杂功能的自定义页面。

选择身份验证方法

有两种方法可以向应用程序插件添加身份验证。配置您的应用程序插件以使用第三方 API 进行身份验证,有两种方法之一

案例使用
您是否需要使用基本身份验证或 API 密钥来对插件进行身份验证?使用数据源代理。
您的 API 是否使用数据源代理不支持的自定义身份验证方法?使用后端组件。
您的 API 是否使用除 HTTP 之外的协议进行通信?使用后端组件。

加密机密配置

应用程序插件有两种存储自定义配置的方法

  • jsonData
  • secureJsonFields
警告

不要使用 jsonData 存储敏感数据,例如密码、令牌和 API 密钥。如果您需要存储敏感信息,请使用 secureJsonData 代替。

secureJsonData 中存储配置

如果您需要存储敏感信息(机密),请使用secureJsonData而不是jsonData。每次用户保存应用配置时,secureJsonData中的机密将被发送到Grafana服务器,并在存储之前进行加密。

一旦您加密了安全配置,配置就无法从浏览器中访问。保存机密后访问它们的唯一方法是通过使用数据源代理或通过后端组件

将机密配置添加到您的应用插件中

您的启动应用插件应该有一个AppConfig组件,允许用户配置应用。该组件包含将apiKey存储在secureJsonData中的示例代码。您可以在plugin.meta部分的secureJsonFields中检查secureJsonData的属性,其中包含用户配置的键。

以下是一些代码亮点

  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)
}

处理Cookies

您的应用程序插件可以读取Grafana转发给应用程序的Cookies。

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 头部将用户头传递给插件。