自主系统通过iframe 嵌入 grafana 仪表板(dashboard)
使用iframe方式嵌入dashboard主要会遇到以下几个问题:
1、iframe方式嵌入dashboard跳转拒绝访问;
2、浏览器控制台报错:Refused to display 'http://{ip:port}/' in a frame because it set 'X-Frame-Options' to 'deny';
3、跳转之后依然需要登录;
......
以上有些问题,可以通过修改配置文件相关配置来解决(修改配置文件之后,记得重启grafana服务)
# 允许浏览器嵌入
allow_embedding = true
# 开启匿名访问,及以下组织和角色相关注释
enabled = true
但是针对跳转后依然需要登录的问题,如果只是用匿名访问的方式,这样就太被动了。对dashboard的维护以及对应操作控制都不够友好,针对以上问题有以下几种处理方式:
1、授权登录
grafana提供标准协议oauth2的接入方式,可以根据相关接入文档进行配置使用,但不可避免的需要做很多额外的学习以及操作。
2、自定义图表
grafana提供 api keys,可以通过调用相关接口进行自定义图表开发展示。
3、白名单配置
开启匿名登录,但是需要做好安全性配置,例如白名单,但同样存在局限性。
4、接口登录转发(同域条件下)
这是本文介绍的方式,核心流程就是提供前端访问后台接口,后台对接口进行权限校验(校验主系统登录权限),校验通过后访问grafana登录接口,成功后将得到的cookie添加至响应信息并重定向至grafna页面
5、更多…
官方接口文档:https://grafana.com/docs/grafana/latest/developers/http_api/
一、具体实现
1、grafana配置信息
配置nginx代理grafana实现域名访问参考《配置nginx代理grafana实现域名访问》。
以上3000端口为grafana服务的默认端口,根据占用情况决定是否修改,domain的ip地址为当前服务器的ip地址,即部署grafana服务器,root_url中的上下文/grafana根据实际情况自行修改,但是需要和nginx的代理配置保持一致。nginx的配置具体如下:
location /grafana {
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4M;
proxy_busy_buffers_size 4M;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://10.130.9.30:3000;
}
加入以上配置之后记得重启nginx,配置之后的效果就是在业务系统域名之后跟/grafana就可以访问到grafana服务了,但是直接使用ip加端口的方式就无法访问了。比如说主系统的访问地址为:xxx.xxx.com,那grafana服务的访问地址就是xxx.xxx.com/grafana。
2、yaml文件中配置grafana相关信息
grafana:
baseUrl: http://127.0.0.1:3000
adminUser: admin
adminPwd: admin
viewUser: viewer
viewPwd: viewer
auth: glsa_wSWxeJgOo0WUKtP1mwONiwk9GakvhW7P_bd7a45d3
kiosk: kiosk=full
2、GrafanaController.java
@RestController
@RequestMapping(value = "/grafana")
@Api(tags = {"Grafana"})
public class GrafanaController {
@Autowired
private GrafanaService grafanaService;
@GetMapping(value = "/dashboard/redirect")
@ApiOperation(value = "跳转仪表板", httpMethod = "GET")
@ApiImplicitParams({
@ApiImplicitParam(name = "dashboardUrl", value = "仪表盘地址", dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "action", value = "行为(edit/view)", dataType = "String", paramType = "query")
})
public void redirect(@RequestParam(name = "dashboardUrl") String dashboardUrl,
@RequestParam(name = "action") String action) {
grafanaService.redirect(dashboardUrl, action);
}
}
3、GrafanaService.java
public interface GrafanaService {
/**
* 跳转grafana仪表盘
*
* @param dashboardUrl 仪表盘地址
* @param action 行为(edit/view)
*/
void redirect(String dashboardUrl, String action);
}
4、GrafanaServiceImpl.java
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.zdww.cloud.monitor.web.config.GrafanaProperties;
import com.zdww.cloud.monitor.web.constant.GrafanaConstants;
import com.zdww.cloud.monitor.web.service.GrafanaService;
import com.zdww.cloud.monitor.web.utils.RestTemplateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
@Service
@Slf4j
public class GrafanaServiceImpl implements GrafanaService {
@Autowired
private GrafanaProperties grafanaProperties;
@Autowired
private RestTemplateUtil restTemplateUtil;
@Resource
private HttpServletResponse response;
@Override
public void redirect(String dashboardUrl, String action) {
if (this.check(dashboardUrl)) {
HttpCookie httpCookie = this.getLoginCookie(action);
Cookie cookie = this.getRedirectCookie(httpCookie);
response.addCookie(cookie);
}
try {
response.sendRedirect(dashboardUrl);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获取当前组织的id
*
*/
private String getCurrentOrgId() {
String orgId = null;
String result = restTemplateUtil.getStrData(grafanaProperties.getBaseUrl() + GrafanaConstants.GETCURRENTORG);
if (null != JSON.parseObject(result).get("id")) {
orgId = JSON.parseObject(result).get("id").toString();
}
return orgId;
}
/**
* 获取重定向的Cookie信息
*
* @param httpCookie 登录用户的Cookie信息
*/
private Cookie getRedirectCookie(HttpCookie httpCookie) {
Cookie cookie = null;
if (null != httpCookie) {
cookie = new Cookie(httpCookie.getName(), httpCookie.getValue());
// cookie.setDomain(httpCookie.getDomain());
cookie.setPath(httpCookie.getPath());
cookie.setVersion(httpCookie.getVersion());
cookie.setComment(httpCookie.getComment());
cookie.setHttpOnly(httpCookie.isHttpOnly());
cookie.setMaxAge((int) httpCookie.getMaxAge());
cookie.setSecure(httpCookie.getSecure());
}
return cookie;
}
/**
* 获取不同权限登录用户的Cookie信息
*
* @param action 行为(查看/编辑)
*/
private HttpCookie getLoginCookie(String action) {
JSONObject params = new JSONObject();
if (action.equals(GrafanaConstants.EDIT)) {
params.put("user", grafanaProperties.getAdminUser());
params.put("password", grafanaProperties.getAdminPwd());
} else if (action.equals(GrafanaConstants.VIEW)) {
params.put("user", grafanaProperties.getViewUser());
params.put("password", grafanaProperties.getViewPwd());
}
ResponseEntity<String> result = restTemplateUtil.postForEntity(grafanaProperties.getBaseUrl() + GrafanaConstants.LOGIN, params.toJSONString());
List<String> cookies = result.getHeaders().get(GrafanaConstants.COOKIE);
if (CollUtil.isNotEmpty(cookies)) {
for (String cookie : cookies) {
List<HttpCookie> httpCookieList = HttpCookie.parse(cookie);
if (CollUtil.isNotEmpty(httpCookieList)) {
for (HttpCookie httpCookie : httpCookieList) {
if (GrafanaConstants.GRAFANA_SESSION.equals(httpCookie.getName())) {
return httpCookie;
}
}
} else {
log.error("登录Cookie为空!");
}
}
} else {
log.error("登录信息为空!");
}
return null;
}
/**
* 检查dashboard地址中的组织id对应的组织是否存在
*
* @param dashboardUrl dashboard地址
*/
private Boolean check(String dashboardUrl) {
String orgId = this.getOrgId(dashboardUrl);
JSONObject result = restTemplateUtil.getJsonData(grafanaProperties.getBaseUrl() + GrafanaConstants.GETORGBYID + orgId);
if (null == result.get("name")) {
log.error("该组织为无效组织!");
return Boolean.FALSE;
}
return Boolean.TRUE;
}
/**
* 获取dashboard地址中的组织id
*
* @param dashboardUrl dashboard地址
*/
private String getOrgId(String dashboardUrl) {
String orgId = null;
// 使用URIBuilder来构建URI
URI uri;
try {
// 构建的URI,特殊字符不被转义
uri = new URI(dashboardUrl);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
String query = uri.getQuery();
String[] params = query.split("&");
for (String param : params) {
if (param.contains("=")) {
String key = param.substring(0, param.indexOf("="));
if ("orgId".equals(key)) {
orgId = param.substring(param.indexOf("=") + 1);
}
}
}
return orgId;
}
}
以上代码中用的配置类及工具类代码
1)GrafanaProperties.java
@Data
@Configuration
@ConfigurationProperties("grafana")
public class GrafanaProperties {
/**
* 访问地址
*/
private String baseUrl;
/**
* 管理员账号
*/
private String adminUser;
/**
* 管理员密码
*/
private String adminPwd;
/**
* 查看人员账号
*/
private String viewUser;
/**
* 查看人员密码
*/
private String viewPwd;
/**
* Token
*/
private String auth;
/**
* 设置dashboard隐藏左侧边栏和顶部菜单栏(kiosk=tv,隐藏左侧边栏;kiosk=full,隐藏左侧边栏且不会隐藏下拉框)
*/
private String kiosk;
}
其中的auth和kiosk属性这里再说明一下,auth是调用grafana的api接口是需要的参数,具体的可以参考官方接口文档获取,kiosk是用来优化dashboard展示效果的。
2)RestTemplateUtil.java
@Component
public class RestTemplateUtil {
private static final String Authorization = "Authorization";
@Autowired
private RestTemplate restTemplate;
@Autowired
private GrafanaProperties grafanaProperties;
public JSONObject getJsonData(String url) {
HttpHeaders headers = new HttpHeaders();
String auth = grafanaProperties.getAdminUser() + ":" + grafanaProperties.getAdminPwd();
byte[] encodedAuth = Base64Utils.encode(auth.getBytes());
headers.set(Authorization, "Basic " + new String(encodedAuth));
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
return restTemplate.exchange(url, HttpMethod.GET, requestEntity, JSONObject.class).getBody();
}
public String getStrData(String url) {
HttpHeaders headers = new HttpHeaders();
headers.add(Authorization, "Bearer " + grafanaProperties.getAuth());
HttpEntity<JSONObject> requestEntity = new HttpEntity<>(headers);
return restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class).getBody();
}
public ResponseEntity<String> postForEntity(String url, String params) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> requestEntity = new HttpEntity<>(params, headers);
return restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
}
}
3)GrafanaConstants.java
public class GrafanaConstants {
/**
* cookie
*/
public static final String COOKIE = "Set-Cookie";
/**
* session
*/
public static final String GRAFANA_SESSION = "grafana_session";
/**
* 编辑
*/
public static final String EDIT = "edit";
/**
* 查看
*/
public static final String VIEW = "view";
/**
* 登录(因为我在nginx中配置了/grafana上下文的域名代理,这里多个/grafana,正常应该是/login)
*/
public static final String LOGIN = "/grafana/login";
/**
* 根据 ID 获取组织
*/
public static final String GETORGBYID = "/api/orgs/";
/**
* 获取当前组织
*/
public static final String GETCURRENTORG = "/api/org/";
}
dashboard跳转之后,为了提高用户体验,隐藏左侧菜单栏和顶部菜单栏,点击如下图所示的“小电脑”图标。
1、点击第一次,隐藏左侧边栏,相当于URL后面加上参数“&kiosk=tv”;
2、点击第二次,隐藏左侧边栏和顶部菜单栏(会隐藏下拉框),相当于URL后面加上参数“&kiosk”;
3、隐藏左侧边栏且不会隐藏下拉框,在URL后面加上参数“&kiosk=full”。
更多推荐
所有评论(0)