Grafana页面嵌入自建Web应用

如需在自建Web应用中免登录查看Grafana大盘的页面,您可将Grafana大盘嵌入自建Web应用,以避免系统间的来回切换。

使用限制

当前仅Grafana共享版页面支持嵌入自建Web应用。

背景信息

Grafana大盘嵌入自建Web应用可以实现以下效果:

  • 可登录自有系统并浏览嵌入的Grafana大盘。

  • 可隐藏Grafana的顶部菜单栏和左侧导航栏。具体操作,请参见本文步骤五中的相关内容。

访问流程

外部系统访问Grafana的流程如下图所示。

image

步骤一:创建RAM用户并添加权限

使用阿里云账号创建RAM用户并为其添加调用STS服务扮演RAM角色的权限。

  1. 使用阿里云账号(主账号)或RAM管理员登录RAM控制台

  2. 在左侧导航栏,选择身份管理 > 用户

  3. 用户页面,单击创建用户

    image

  4. 创建用户页面的用户账号信息区域,输入登录名称显示名称,在访问方式区域,选中控制台访问,然后单击确定

    重要

    RAM会自动为RAM用户创建AccessKey(API访问密钥)。出于安全考虑,RAM控制台只提供一次查看或下载AccessKeySecret的机会,即创建AccessKey时,请务必将AccessKeySecret记录到安全的地方。

  5. 用户页面,单击目标RAM用户操作列的添加权限

    image

    您也可以选中多个RAM用户,单击用户列表下方的添加权限,为RAM用户批量授权。

  6. 新增授权面板的权限策略区域,添加AliyunSTSAssumeRoleAccess权限策略,然后单击确认新增授权

  7. 单击关闭

步骤二:创建RAM角色并添加权限

创建RAM角色并为其添加访问控制台的权限。RAM用户将会扮演该RAM角色访问控制台。

  1. 创建可信实体为阿里云账号的RAM角色

  2. 为角色添加ARMS的完整权限或只读权限。具体操作,请参见RAM角色授权

    • AliyunARMSFullAccess:ARMS的完整权限。

    • AliyunARMSReadOnlyAccess:ARMS的只读权限。

步骤三:获取临时AccessKeyToken

登录自建Web应用,然后在Web服务端调用STSAssumeRole接口获取临时AccessKeyToken,即临时身份。AssumeRole接口详情,请参见AssumeRole

您可以选择以下方式调用AssumeRole接口:

本文以通过Java SDK调用为例。

您在使用Java SDK时需要注意填写以下参数:

String accessKey = "<accessKeyId>"; //RAM用户的AccessKeyId。
String accessSecret = "<accessKeySecret>"; //RAM用户的AccessKeySecret。
String roleArn = "<roleArn>"; //RAM角色的标识ARN。

RAM用户的AccessKeyIdAccessKeySecret为创建RAM用户时获取。获取操作,请参见创建AccessKey

获取RAM角色的roleArn的操作步骤如下:

  1. RAM控制台身份管理 > 角色页面下方的RAM角色列表中,单击目标RAM角色名称。

  2. RAM角色详情页面的基本信息区域,复制ARN信息。

    Example ARN

步骤四:获取登录Token

通过STSAssumeRole接口获取临时AccessKeyToken后,调用登录服务接口获取登录Token。

重要

STS返回的安全Token中可能包含特殊字符,请对特殊字符进行URL编码后再输入。

请求样例:

https://signin.aliyun.com/federation?Action=GetSigninToken
    &AccessKeyId=<STS返回的临时AccessKeyId>
    &AccessKeySecret=<STS返回的临时AccessKeySecret>
    &SecurityToken=<STS返回的安全Token>
说明

如果为虚商接入,则还需添加&TicketType=mini参数。

步骤五:生成免登录链接

利用获取到的登录Token与待嵌入的Grafana大盘页面链接生成免登录访问链接,以最终实现在自建Web应用中免登录访问Grafana页面的目的。

说明

由于Token有效期为3小时,建议在自建Web应用中将链接设置为每次请求时生成新登录Token。

  1. Grafana获取待嵌入的大盘页面的URL链接。

    说明
    • URL链接的search部分添加kiosk=tv可以隐藏原有的左侧导航栏。

    • URL链接的search部分添加kiosk可以隐藏顶部菜单栏和左侧导航栏。

    • URL链接的search部分添加kiosk=tv2可以隐藏顶部菜单栏和左侧导航栏,但保留右上角的时间选择器。

  2. 利用登录TokenGrafana大盘链接生成免登录访问链接。

    http://signin.aliyun.com/federation?Action=Login
        &LoginUrl=<登录失效跳转的地址,一般配置为自建Web应用配置302跳转的URL>
        &Destination=<Grafana大盘链接>
        &SigninToken=<获取到的登录Token>
  3. 在浏览器访问该免登录访问链接。

展开查看完整的iframe url示例代码

/*
Copyright 2022 Alibaba Cloud.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.aliyun.arms.unifydemo.unifydemo;

import com.alibaba.fastjson.JSON;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.net.URLEncoder;

/**
 * @author XX
 * @version V1.0.0
 * @dept XX-XX
 * @date 2025/7/2 16:10
 * @description
 */
public class GrafanaIframeUrlGenerator {
    private static final String signInHost = "https://signin.aliyun.com";
    private static final String loginUrl = "https://www.aliyun.com";

    private static final String loginUri = "/federation?Action=Login&LoginUrl=%s&Destination=%s&SigninToken=%s";

    private static final String signInTokenUri = "/federation?Action=GetSigninToken&AccessKeyId=%s&AccessKeySecret=%s&SecurityToken=%s&TicketType=%s";

    public static void main(String[] args) {
        try {
            //请根据自己账号角色情况自行设置,Grafana地址虚商需要加4service
            String destination = "https://gnew4service.console.aliyun.com/d/1098370038******-53945-422/ack-pro-apiserver?orgId=9&refresh=60s";
            String regionId = "cn-hangzhou";//请自行设置
            String accessKey = "";  //请根据自己账号角色情况自行设置,账号需要有STS权限 AliyunSTSAssumeRoleAccess
            String secretKey = "";//请根据自己账号角色情况自行设置
            //注意:请根据自己账号角色情况自行设置,角色需要有读权限 AliyunARMSReadOnlyAccess 角色权限不足会无法访问
            String role="acs:ram::109837003******:role/armsreadonlyforgrafanaiframe";

            /*
             * 第一步
             * */
            //设置参数,指定角色ARN,并设置Policy以进一步限制STS Token获取的权 // acs:ram::$accountID:role/$roleName
            //构建AssumeRole请求
            AssumeRoleResponse.Credentials key = getCredentials(regionId, accessKey, secretKey, role, "role-" + System.currentTimeMillis());

            /*
             * 第二步
             * 获取登录Token
             * */
            String token = getLoginToken(key, destination);

            /*
             * 第三步
             * 获取免登地址
             * */
            String url = getUrl(token, destination);

            /*
             * 第四步
             * 跳转地址
             * */
            System.out.println(url);
        } catch (Error e) {
            e.printStackTrace();
        }
    }


    private static AssumeRoleResponse.Credentials getCredentials(String regionId, String accessKey, String secretKey, String roleArn, String roleSessionName) {

        DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKey, secretKey);


        IAcsClient client = new DefaultAcsClient(profile);

        AssumeRoleRequest request = new AssumeRoleRequest();
        request.setSysMethod(MethodType.POST);
        request.setRoleArn(roleArn);
        request.setRoleSessionName(roleSessionName);
        request.setDurationSeconds(3600L);
        try {
            AssumeRoleResponse response = client.getAcsResponse(request);
            return response.getCredentials();
        } catch (Exception e) {
            throw new RuntimeException("AssumeRoleService load ErrCode:" + e.getMessage());
        }
    }


    public static String getLoginToken(AssumeRoleResponse.Credentials key, String destination) {
        String token = "";
        if (key == null) {
            return "";
        }
        String ticketType = "normal";
        if (destination == null || destination.trim().length() == 0 || destination.contains("4service")) {
            ticketType = "mini";
        }
        String signInTokenUrl = "";
        try {
            signInTokenUrl = signInHost + String.format(signInTokenUri,
                    URLEncoder.encode(key.getAccessKeyId(), "utf-8"),
                    URLEncoder.encode(key.getAccessKeySecret(), "utf-8"),
                    URLEncoder.encode(key.getSecurityToken(), "utf-8"),
                    URLEncoder.encode(ticketType, "utf-8"));
        } catch (Exception e) {
            throw new RuntimeException("SigninTokenService build signInTokenUrl error:" + e.getMessage());
        }
        final CloseableHttpClient httpClient = HttpClients.createDefault();
        try {
            HttpGet signInGet = new HttpGet(signInTokenUrl);
            HttpResponse httpResponse = httpClient.execute(signInGet);
            String signInToken = "";
            if (httpResponse.getStatusLine().getStatusCode() != 200) {
                throw new RuntimeException("SigninTokenService failed to retrieve signInToken!");
            }
            String signInRes = EntityUtils.toString(httpResponse.getEntity());
            signInToken = JSON.parseObject(signInRes).getString("SigninToken");
            if (signInToken == null) {
                throw new RuntimeException("SigninTokenService signInToken is empty while signInRes is:" + signInRes);
            }
            return signInToken;
        } catch (Exception e) {
            throw new RuntimeException("SigninTokenService get signInToken error:" + e.getMessage());
        }
    }

    public static String getUrl(String token, String destination) {
        String url = "";
        try {
            url = signInHost + String.format(loginUri,
                    URLEncoder.encode(loginUrl, "utf-8"),
                    URLEncoder.encode(destination, "utf-8"),
                    URLEncoder.encode(token, "utf-8"));
        } catch (Exception e) {
            throw new RuntimeException("SigninUrlService build getUrl error:" + e.getMessage());
        }
        return url;
    }
}