对于第三方集成grafana,需要可以通过链接直接访问管理界面,跳过登录页面。
传统方法是使用代理认证,而最新的jwt方法可以更好地实现这个需求。
不过网络上相关教程文档较少,缺乏详细说明,故此记录。

一 控制台/面板-分享

grafana本身支持面板的内嵌分享,见 https://grafana.com/docs/grafana/latest/dashboards/share-dashboards-panels
注意修改以下配置,这里以环境变量的写法表示:
domain需要填写外部访问的host
port这里也修改成非默认端口,建议直接修改端口号而不是反向代理形式来隐藏默认端口
下面两个是允许内嵌和允许匿名访问,是关键。

GF_SERVER_DOMAIN="192.168.10.192"
GF_SERVER_HTTP_PORT=33303
GF_SECURITY_ALLOW_EMBEDDING=true
GF_AUTH_ANONYMOUS_ENABLED=true

二 免登录访问

对于第三方集成grafana,需要可以通过链接直接访问管理界面,跳过登录页面。传统方法是使用代理认证,而最新的jwt方法可以更好地实现这个需求。

1 认证代理方式资料

传统的方法是使用认证代理(Auth Proxy),需和nginx、apache等集成,如:

2 jwt方式资料

三 jwt免密访问步骤

1 获取jwks文件

访问 https://mkjwk.org/ ,参考如下值,keyId自行填写

将中间部分(含public、private的)保存为jwks.json文件,用于生成token。
右边的public key保存为jwks-public.json,用于对token进行认证。注意外围也要加上{"keys": [ ]},否则grafana会找不到keys。

2 启动grafana

这里以容器方式启动,注意,以下配置适用于9.3.2及以后版本
注意这里使用的是jwks-public.json

docker run \
        -p 33303:33303  \
        --user root   \
        --name my-grafana \
        --restart=always \
        --env GF_SERVER_DOMAIN="localhost" \
        --env GF_SERVER_HTTP_PORT=33303 \
        --env GF_SECURITY_ADMIN_PASSWORD="123456" \
        --env GF_SECURITY_ALLOW_EMBEDDING=true \
        --env GF_AUTH_ANONYMOUS_ENABLED=true \
        --env GF_USERS_DEFAULT_THEME=light \
        --env GF_AUTH_JWT_ENABLED=true \
        --env GF_AUTH_JWT_ENABLE_LOGIN_TOKEN=true \
        --env GF_AUTH_JWT_HEADER_NAME=GF-JWT \
        --env GF_AUTH_JWT_USERNAME_CLAIM=sub \
        --env GF_AUTH_JWT_EMAIL_CLAIM=sub \
        --env GF_AUTH_JWT_JWK_SET_FILE='/home/jwks.json' \
        --env GF_AUTH_JWT_CACHE_TTL=60m \
        --env GF_AUTH_JWT_ROLE_ATTRIBUTE_PATH=role \
        --env GF_AUTH_JWT_AUTO_SIGN_UP=true \
        --env GF_AUTH_JWT_URL_LOGIN=true \
        --env GF_AUTH_JWT_ALLOW_ASSIGN_GRAFANA_ADMIN=true \
        --env GF_LOG_LEVEL=info \
        -v /tmp/grafana:/var/lib/grafana \
        -v /tmp/jwks-public.json:/home/jwks.json \
        -d grafana/grafana

docker logs -f my-grafana

GF_AUTH_JWT_HEADER_NAME:token在header的名称,如果是在url中,则固定是auth_token
GF_AUTH_JWT_USERNAME_CLAIM:grafana将使用sub(即subject)字段作为用户名
GF_AUTH_JWT_ROLE_ATTRIBUTE_PATH:grafana将使用role字段作为用户角色
GF_AUTH_JWT_AUTO_SIGN_UP:新用户字段注册

3 生成token

如下,使用D:\tmp\jwks.json文件,生成用户名为user1,角色为Admin,有效期为1小时的token。

package jwk;

import cn.hutool.core.io.FileUtil;
import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.mint.DefaultJWSMinter;
import com.nimbusds.jwt.JWTClaimsSet;
import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Map;

public class JwtTest {

    @Test
    public void test() throws Exception {
        System.out.println(getJwt());
    }

    public String getJwt() throws Exception {
        JWKSet jwkSet = JWKSet.load(FileUtil.file("D:\\tmp\\jwks.json"));
        DefaultJWSMinter minter = new DefaultJWSMinter();
        minter.setJWKSource(new ImmutableJWKSet(jwkSet));

        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
                .type(JOSEObjectType.JWT)
                .build();
        // Create JWT
        Instant nowInstant = Instant.now();
        JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder()
                .subject("user2")
                .expirationTime(new Date(nowInstant.plus(1, ChronoUnit.HOURS).toEpochMilli()))
                .notBeforeTime(new Date(nowInstant.toEpochMilli()))
                .issueTime(new Date(nowInstant.toEpochMilli()))
                .build();
        Payload payload = jwtClaims.toPayload();
        Map<String, Object> payloadMap = payload.toJSONObject();
        payloadMap.put("role", "Admin");
        JWSObject jwsObject = minter.mint(header, new Payload(payloadMap), null);
        return jwsObject.serialize();
    }
    
}

生成的token可以在https://jwt.io/进行验证,如下

4 使用token进行访问

  1. url-token访问
    http://grafana:33303/datasources?auth_token=YOUR_TOKEN
  2. header访问
    headers携带GF-JWT的token,内容为:Bearer YOUR_TOKEN,注意中间有空格

Q.E.D.