跳转到主要内容

为数据源插件添加身份验证

Grafana 插件可以使用数据源代理或通过自定义的 后端插件 对第三方 API 进行身份验证请求。

选择一种身份验证方法

配置您的数据源插件以通过第三方 API 进行身份验证,有以下两种方式之一

案例使用
您需要使用基本身份验证或 API 密钥来验证您的插件吗?使用数据源代理。
您的 API 是否支持使用客户端凭据的 OAuth 2.0?使用数据源代理。
您的 API 是否使用数据源代理不支持的自定义身份验证方法?使用后端插件。
您的 API 是否通过除 HTTP 以外的协议进行通信?使用后端插件。
您的插件是否需要警报支持?使用后端插件。

加密数据源配置

数据源插件有两种存储自定义配置的方式: jsonDatasecureJsonData

具有查看器角色的用户可以访问数据源配置,例如以明文形式访问jsonData的内容。如果您已启用匿名访问,任何可以访问浏览器中Grafana的人都可以看到jsonData的内容。

Grafana企业版的用户可以限制对数据源的访问,仅限于特定的用户和团队。更多信息,请参阅数据源权限

您可以通过在浏览器开发者控制台中输入window.grafanaBootData来查看当前用户可以访问的设置。

警告

不要使用jsonData与敏感数据(如密码、令牌和API密钥)一起使用。如果您需要存储敏感信息,请使用secureJsonData

secureJsonData中存储配置

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

一旦加密了安全配置,就无法从浏览器中访问。在保存之后访问这些机密的唯一方法是通过使用数据源代理

将密钥配置添加到您的数据源插件

要将密钥添加到数据源插件,您可以为配置API密钥添加支持。

  1. types.ts中创建一个新的接口来保存API密钥

    export interface MySecureJsonData {
    apiKey?: string;
    }
  2. 通过更新ConfigEditor的props以接受该接口作为第二个类型参数,将类型信息添加到您的secureJsonData对象中。从ConfigEditor内部的optionsprop中访问密钥的值

    interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions, MySecureJsonData> {}
    const { secureJsonData, secureJsonFields } = options;
    const { apiKey } = secureJsonData;
    注意

    您可以在用户保存配置之前这样做;当用户保存配置后,Grafana将清除值。之后,您可以使用secureJsonFields来确定是否已配置该属性。

  3. 要安全地更新插件配置编辑器中的机密,请使用onOptionsChangeprop更新secureJsonData对象

    const onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => {
    onOptionsChange({
    ...options,
    secureJsonData: {
    apiKey: event.target.value,
    },
    });
    };
  4. 定义一个可以接受用户输入的组件

    <Input
    type="password"
    placeholder={secureJsonFields?.apiKey ? 'configured' : ''}
    value={secureJsonData.apiKey ?? ''}
    onChange={onAPIKeyChange}
    />
  5. 可选:如果您希望用户能够重置API密钥,则需要在secureJsonFields对象中将属性设置为false

    const onResetAPIKey = () => {
    onOptionsChange({
    ...options,
    secureJsonFields: {
    ...options.secureJsonFields,
    apiKey: false,
    },
    secureJsonData: {
    ...options.secureJsonData,
    apiKey: '',
    },
    });
    };

一旦用户可以配置密钥,下一步就是了解我们如何将它们添加到我们的请求中。

使用数据源代理进行身份验证

一旦用户保存了数据源的配置,加密的数据源配置将不再在浏览器中可用。加密的秘密只能在服务器上访问。那么您如何在请求中添加它们呢?

Grafana服务器自带一个代理,允许您定义请求模板:代理路由。Grafana将代理路由发送到服务器,解密秘密以及其他配置,然后在发送请求之前将其添加到请求中。

注意

请确保不要混淆数据源代理与认证代理。数据源代理用于验证数据源,而认证代理用于登录Grafana本身。

将代理路由添加到您的插件

要转发通过Grafana代理的请求,您需要配置一个或多个代理路由。代理路由是代理处理的任何传出请求的模板。您可以在plugin.json文件中配置代理路由。

  1. 将路由添加到plugin.json

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

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

  2. DataSource中,将代理URL从instanceSettings提取到一个名为url的类属性中

    export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
    url?: string;

    constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
    super(instanceSettings);

    this.url = instanceSettings.url;
    }

    // ...
    }
  3. query方法中,使用BackendSrv发送请求。URL路径的第一部分需要与代理路由的path匹配。数据源代理将this.url + routePath替换为路由的url。根据我们的示例,请求的URL将是https://api.example.com/v1/users

    import { getBackendSrv } from '@grafana/runtime';
    const routePath = '/example';

    getBackendSrv().datasourceRequest({
    url: this.url + routePath + '/v1/users',
    method: 'GET',
    });

将动态代理路由添加到您的插件

Grafana将代理路由发送到服务器,数据源代理解密任何敏感数据,并在发送请求之前用解密的数据替换模板变量。

要将用户定义的配置添加到您的路由中

  • 使用.JsonData配置存储在jsonData中的配置。例如,如果projectIdjsonData对象中属性的名称

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

    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 }}"
}
]
}
]

将URL参数添加到代理路由

以下是将namecontent作为URL参数添加的示例

src/plugin.json
"routes": [
{
"path": "example",
"url": "http://api.example.com",
"urlParams": [
{
"name": "apiKey",
"content": "{{ .SecureJsonData.apiKey }}"
}
]
}
]
注意

请注意,urlParams 配置仅在数据源插件中受支持。它不支持在 应用插件 中。

向代理路由添加请求体

以下是将 usernamepassword 添加到请求体的示例

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

将 OAuth 2.0 代理路由添加到您的插件中

由于您对每个路由的请求都通过 OAuth 2.0 身份验证在服务器端进行,因此仅支持机器到机器的请求。换句话说,如果您需要使用除客户端凭证之外的其他授权方式,您需要自行实现。

要使用 OAuth 2.0 进行身份验证,请向代理路由定义中添加一个 tokenAuth 对象。如果需要,Grafana 将在向代理路由中的 URL 发送请求之前,向 tokenAuth 中定义的 URL 发送请求以获取令牌。Grafana 会自动在令牌过期时更新令牌。

tokenAuth.params 中定义的任何参数都将编码为 application/x-www-form-urlencoded 并发送到令牌 URL。

src/plugin.json
{
"routes": [
{
"path": "api",
"url": "https://api.example.com/v1",
"tokenAuth": {
"url": "https://api.example.com/v1/oauth/token",
"params": {
"grant_type": "client_credentials",
"client_id": "{{ .SecureJsonData.clientId }}",
"client_secret": "{{ .SecureJsonData.clientSecret }}"
}
}
}
]
}
注意

请注意,tokenAuth 配置仅在数据源插件中受支持。它不支持在 应用插件 中。

使用后端插件进行身份验证

虽然数据源代理支持 HTTP API 的大部分常用身份验证方法,但使用代理路由有一些限制

  • 代理路由仅支持 HTTP 或 HTTPS。
  • 代理路由不支持自定义令牌身份验证。

如果这些限制中的任何一项适用于您的插件,您需要添加一个 后端插件。因为后端插件在服务器上运行,它们可以访问解密密钥,这使得实现自定义身份验证方法更加容易。

解密密钥可以从实例设置的 DecryptedSecureJSONData 字段中获取。

func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
instanceSettings := req.PluginContext.DataSourceInstanceSettings

if apiKey, exists := instanceSettings.DecryptedSecureJSONData["apiKey"]; exists {
// Use the decrypted API key.
}

// ...
}

转发已登录用户的 OAuth 身份

如果您的数据源与 Grafana 本身使用相同的 OAuth 提供商,例如,使用 通用 OAuth 身份验证,则您的数据源插件可以重用已登录 Grafana 用户的访问令牌。

要允许 Grafana 将访问令牌传递给插件,请更新数据源配置并将 jsonData.oauthPassThru 属性设置为 trueDataSourceHttpSettings 设置提供了一个切换按钮,即 转发 OAuth 身份 选项。您也可以在数据源配置页面 UI 中构建适当的切换按钮来设置 jsonData.oauthPassThru

配置完成后,Grafana 可以将授权 HTTP 标头(如 AuthorizationX-ID-Token)转发到后端数据源。这些信息在 QueryDataCallResourceCheckHealth 请求中可用。

要使 Grafana 转发标头,请使用 Grafana 插件 SDK for Go 创建一个 HTTP 客户端并将 ForwardHTTPHeaders 选项设置为 true(默认设置为 false)。此包公开了请求信息,这些信息可以随后向下转发或直接在插件中使用,或两者兼而有之。

func NewDatasource(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
opts, err := settings.HTTPClientOptions(ctx)
if err != nil {
return nil, fmt.Errorf("http client options: %w", err)
}

// Important: Reuse the same client for each query to avoid using all available connections on a host.

opts.ForwardHTTPHeaders = true

cl, err := httpclient.New(opts)
if err != nil {
return nil, fmt.Errorf("httpclient new: %w", err)
}
return &Datasource{
httpClient: cl,
}, nil
}

func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
// Necessary to keep the Context, since the injected middleware is configured there
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://some-url", nil)
if err != nil {
return nil, fmt.Errorf("new request with context: %w", err)
}
// Authorization header is automatically injected if oauthPassThru is configured
resp, err := ds.httpClient.Do(req)
// ...
}

您可以在以下位置查看一个完整的插件示例: datasource-http-backend

从 HTTP 请求中提取头信息

如果您需要直接访问 HTTP 头信息,您也可以从请求中提取该信息

func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
token := strings.Fields(req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName))
var (
tokenType = token[0]
accessToken = token[1]
)
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName) // present if user's token includes an ID token

// ...
return &backend.CheckHealthResult{Status: backend.HealthStatusOk}, nil
}

func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
token := strings.Fields(req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName))
var (
tokenType = token[0]
accessToken = token[1]
)
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName)

for _, q := range req.Queries {
// ...
}
}

func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
token := req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName)
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName)

// ...
}

处理 cookies

转发登录用户的 cookies

您的数据源插件可以将登录的 Grafana 用户的 cookies 转发到数据源。在数据源的配置页面上使用 DataSourceHttpSettings 组件。它提供了 允许的 cookies 选项,您可以在其中指定 cookie 名称。

配置完成后,与授权头信息一样,如果您使用 SDK HTTP 客户端,这些 cookies 将自动注入。

提取登录用户的 cookies

如果需要,您还可以在 QueryDataCallResourceCheckHealth 请求中提取 cookies。

QueryData

func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)

// ...
}

CallResource

func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)

// ...
}

CheckHealth

func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)

// ...
}

转发登录用户的用户头信息

send_user_header启用时,Grafana 使用X-Grafana-User头将用户头传递给插件。您可以转发此头信息,以及授权头信息配置的 cookies

QueryData

像这样转发 QueryData 头信息

func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
u := req.GetHTTPHeader("X-Grafana-User")

// ...
}

CallResource

像这样转发 CallResource 头信息

func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
u := req.GetHTTPHeader("X-Grafana-User")

// ...
}

CheckHealth

像这样转发 CheckHealth 头信息

func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
u := req.GetHTTPHeader("X-Grafana-User")

// ...
}