使用 spring boot 开发获取 qlik ticket 代理服务
二、操作环境
三、导出证书
1 qlik qmc 导出证书
四、项目准备
1 创建 java spring boot 项目
2 项目配置
3 转换认证文件
五、编写代码
1 QlikTicketUtils.java
2 QlikController.java
六、运行程序
1 本地运行
2 部署运行
七、校验程序
1 测试 9091 端口
2 get_ticket 接口调用
3 访问报表
一、介绍
本文档主要介绍了如果使用 spring-boot 搭建一个用于获取 qlik sense ticket 的代理服务。
其中,下方的 qlik 社区中的一位大哥说的非常形象且生动,有需要了解一下的可以看看,地址如下:
https://community.qlik.com/blogs/qlikviewdesignblog/2014/12/08/tickets-in-qlik-sense
qlik sense 集成方案有很多种,此文档采用通过 iframe 集成的方法,集成方案文档地址如下:
http://help.qlik.com/en-US/sense-developer/September2017/Subsystems/Platform/Content/Integration/embed-qlik-sense.htm
二、操作环境
- windows 10
- qlik sense 3.2_x64
- jdk 1.8.0_141
- maven 3.5
- eclipse 4.7 oxygen
三、导出证书
1 qlik qmc 导出证书
(1) 登录 qlik qmc 管理后台
http://yourip[/proxy]/qmc
使用管理员用户及密码登录 -> 点击 "Certificates"
在如下界面中点击 "Add machine name" 按钮
在如下界面中填入 主机信息,password,format 然后点击 "Export certificates",然后在下方会给出在 qlik sense server 上的导出的证书在文件系统中的位置。
注:
- 第一个文本框中填写主机名,该处填写的内容是随意的,但一般使用分配证书的对象的主机名来标识(对应了生成的证书的目录名)
- password 处是证书的密码,此处的密码在使用证书认证或者证书转换时都需要用到(此处使用 "passwd" 作为密码,可以自行设置)
- format 选择 Windows format ,导出的是 .pfx 的证书文件
导出成功
证书文件:
注:
- 可以看到,test 目录下生成了三个文件
- 由于我们使用 java jks 来认证,因此在后面会对 client.pfx 和 root.cer 文件进行转换,将其转换为 .jks 的文件
四、项目准备
1 创建 java spring boot 项目
maven 项目的创建在此不做过多介绍,详细创建步骤可参考另一篇文章,[eclipse 创建 maven 动态 web 工程]。
在此创建一个 qlik-tick 的 maven项目
创建完成后的项目目录:
2 项目配置
(1) pom.xml 配置
pom.xml 文件的配置示例如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd";>
<modelVersion>4.0.0</modelVersion>
<groupId>com.qlik</groupId>
<artifactId>qlik-tick</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>qlik-tick Maven Webapp</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.3.7</version>
</dependency>
</dependencies>
<build>
<finalName>qlik-tick</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(2) spring-boot 配置文件
spring-boot 的配置文件 application.properties 如下示例:
################################################################################
#DOC_URL: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#common-application-properties-security
#GITHub : https://github.com/spring-projects/spring-boot/tree/1.2.x/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure
################################################################################
##server config
spring.main.web_environment:true
spring.main.show_banner:true
##mvc config
spring.mvc.date-format:yyyy-MM-dd
spring.mvc.favicon.enabled:false
# HTTP encoding (HttpEncodingProperties)
spring.http.encoding.charset:UTF-8
spring.http.encoding.force:true
##view config
spring.view.prefix: /templates/
spring.view.suffix: .html
# SPRING RESOURCES HANDLING (ResourceProperties)
# 60 * 60 * 24 * 30
spring.resources.cache-period:2592000
# MULTIPART (MultipartProperties)
multipart.file-size-threshold:0
multipart.location:${java.io.tmpdir}
multipart.max-file-size:1Mb
multipart.max-request-size:10Mb
##log config
logging.file: ./logs/qlik-tick.log
#测试
server.port:9091
#线上
#server.port:9090
注:
- logging.file 配置日志文件目录位置
- server.port 指定 spring-boot 运行时的端口
3 转换认证文件
(1) 复制 pfx 文件到项目 classpath
- 右键 /src/main/resources ->New -> Folder -> certificates
(2) 将从 qlik sense qmc 后台导出到 qlik server 上的认证文件 client.pfx 和 root.cer 复制到项目的 /src/main/resources/certificates 目录下
(3) 进入项目的 certificates 文件系统目录
(4) 在当前目录的地址栏输入 cmd 命令按 enter 进入命令行模式
(5) 在打开的命令行中执行如下示例的命令将 pfx 文件转换为 jks 文件
D:\eclipse-workspace\qlik-tick\src\main\resources\certificates>keytool -importkeystore -srckeystore "client.pfx" -srcstoretype pkcs12 -destkeystore "client.jks" -deststoretype JKS -srcstorepass passwd -deststorepass passwd -noprompt
D:\>keytool -import -alias QlikCA -keystore "root.jks" -file "root.cer" -storepass secret -noprompt
将从 qlik qmc 导出的 pfx 证书文件转换为 jks 证书文件:
转换成功后刷新项目目录,可以看到 jks 的文件已经生成
注:
- 在进行导出前请确保本机已经安装有 JDK 环境,且 JDK 已经配置到环境变量中(验证方式:java -version)
- "srcstorepass passwd" 源密码,这儿需要与导出 pfx 证书时填写的密码一致
- "deststorepass passwd" 此处是目标密码,即 jks 证书文件的,此密码至少需要六位,可以与 pfx 设置的不同,此处选择 passwd 为密码与 pfx 的密码相同。(这儿设置的密码在代码连接中也需要用到)
- "storepass passwd" 此处的 passwd 与导出证书时的密码一致即可
五、编写代码
为了方便代码归集与展示,将所有的代码全部写在了一个类 QlikTicketUtils 中,可以自行对类进行拆分。
1 QlikTicketUtils.java
package com.qlik.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
importjava.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
importjavax.net.ssl.HostnameVerifier;
importjavax.net.ssl.HttpsURLConnection;
importjavax.net.ssl.KeyManagerFactory;
importjavax.net.ssl.SSLContext;
importjavax.net.ssl.SSLSession;
importjavax.net.ssl.SSLSocketFactory;
importjavax.net.ssl.TrustManager;
importjavax.net.ssl.TrustManagerFactory;
importjavax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class QlikTicketUtils {
private static Logger logger = LoggerFactory.getLogger(QlikTicketUtils.class);
static {
disableSslVerification();
}
/**
* 此方法禁用了 HTTPS 校驗功能,解決了如下錯誤:
* java.security.cert.CertificateException: No subject alternative names present
*/
public static void disableSslVerification() {
try {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
public static String getTicket(String user, String userDir)
{
String xrfkey = "Lk3HFOa8f9fdaJdt"; //Xrfkey to prevent cross-site issues
// String host = "QlikSenseServerHostIP/DoaminName"; //Enter the Qlik Sense Server hostname here
String host = "192.168.56.130";
// String vproxy = "VirtualProxyPrefix"; //Enter the prefix for the virtual proxy configured in Qlik Sense Steps Step 1
String vproxy = "";
try
{
/************** BEGIN Certificate Acquisition **************/
String proxyCertPass="passwd"; //This is the password to access the Java Key Store information
String rootCertPass = "passwd"; //This is the password to access the Java Key Store information
/************** END Certificate Acquisition **************/
/************** BEGIN Certificate configuration for use in connection **************/
KeyStore ks = KeyStore.getInstance("JKS");
InputStream inputStream = ClassLoader.getSystemResourceAsStream("certificates/client.jks");
ks.load(inputStream, proxyCertPass.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, proxyCertPass.toCharArray());
SSLContext context = SSLContext.getInstance("SSL");
KeyStore ksTrust = KeyStore.getInstance("JKS");
InputStream isRootCert = ClassLoader.getSystemResourceAsStream("certificates/root.jks");
ksTrust.load(isRootCert, rootCertPass.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ksTrust);
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLSocketFactory sslSocketFactory = context.getSocketFactory();
/************** END Certificate configuration for use in connection **************/
/************** BEGIN HTTPS Connection **************/
logger.info("Browsing to: " + "https://"; + host + ":4243/" + vproxy + "qps/ticket?xrfkey=" + xrfkey);
URL url = new URL("https://"; + host + ":4243/" + vproxy + "qps/ticket?xrfkey=" + xrfkey);
HttpsURLConnection connection = (HttpsURLConnection ) url.openConnection();
connection.setSSLSocketFactory(sslSocketFactory);
connection.setRequestProperty("x-qlik-xrfkey", xrfkey);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestMethod("POST");
/************** BEGIN JSON Message to Qlik Sense Proxy API **************/
String body = "{ 'UserId':'" + user + "','UserDirectory':'" + userDir + "',";
body += "'Attributes': [],";
body+= "}";
System.out.println("Payload: " + body);
logger.info("Payload: " + body);
/************** END JSON Message to Qlik Sense Proxy API **************/
System.setProperty("javax.net.ssl.trustStore","certificates");
OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream());
wr.write(body);
wr.flush(); // Get the response from the QPS BufferedReader
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder builder = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null)
{
builder.append(inputLine);
}
in.close();
String data = builder.toString();
logger.info("The response from the server is: {}", data);
return data;
/************** END HTTPS Connection **************/
}
catch (KeyStoreException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }
catch (CertificateException e) { e.printStackTrace(); }
catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
catch (UnrecoverableKeyException e) { e.printStackTrace(); }
catch (KeyManagementException e) { e.printStackTrace(); }
return null;
}
public static void main(String[] args) {
String ticket = QlikTicketUtils.getTicket("appadmin","SWTBCCAHJYB");
logger.info("The qlik ticket data is ===> " + ticket);
}
}
注:
- xrfkey 是一个 16 位的随机字符串,可以自行随意设置
- host 是你的 qlik sense server 的域名或者 IP
- vproxy 是你的 qlik sense 的虚拟代理的前缀,没有则置空
- proxyCertPass 及 rootCertPass 就是转换 jks 文件时设置的目标密码
- ClassLoader.getSystemResourceAsStream("certificates/client.jks"); ClassLoader.getSystemResourceAsStream("certificates/root.jks"); 表示从 classpath (src/main/resources) 下的 certificates 目录下分别读取 jks 文件的内容,如果你的文件存放目录不一样,请自行修改
- main 函数中传递的 appadmin 是 qlik sense 的 admin 用户,SWTBCCAHJYB 则是 appadmin 要访问的空间
- System.setProperty("javax.net.ssl.trustStore","certificates");表示设置证书目录位置
运行 main 函数,测试运行结果:
运行成功
2 QlikController.java
package com.qlik.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.qlik.util.QlikTicketUtils;
@SpringBootApplication
@EnableAutoConfiguration
@RestController
public class QlikController {
@RequestMapping("/")
public String greeting() {
return "Hello Spring Boot!";
}
@RequestMapping(value = "/get_ticket")
@ResponseBody
public String getTicket(String user, String userDir) {
String ticket = QlikTicketUtils.getTicket(user, userDir);
return ticket;
}
public static void main(String[] args) {
SpringApplication.run(QlikController.class, args);
}
}
六、运行程序
1 本地运行
右键项目 qlik-tick -> Run As -> Maven Build…
控制台出现类似如下信息表示运行成功:
注:
- 运行 spring-boot 程序时,只能有一个 main 函数,因此需要把 QlikTicketUtils 类中的 main 注释掉
- 本地运行要保证本地能够连接到 qlik sense server
2 部署运行
右键项目 qlik-tick -> Run As -> Maven Install
出现类似如下信息,表示打包成功
在 target 目录下能找到打好的 jar 包:
进入项目文件系统目录,找到 target 下的 jar 包,将其拷贝到需要部署程序的服务器上,命令行执行如下命令部署:
java -jar qlik-tick.jar
部署成功:
注:
- 部署服务器上需要安装 JDK
- 如果有防火墙,需要开通防火墙端口
- 部署服务器必须与 qlik sense 服务器能够正常通信
七、校验程序
1 测试 9091 端口
2 get_ticket 接口调用
如下所示,可以看到已经正常返回 ticket
到这里,获取 ticket 的程序就完成了,剩下的就是把你从 qlik hub dev 中获取的报表对象的 url 后拼接上 ticket 就可以访问了
3 访问报表
使用上面获取的 ticket 访问其中一张报表,可以看到已经可以成功访问
http://ip/single/?appid=7e901454-2f27-4734-8bff-ff82cb521b9b&&sheet=ca034abd-8ae8-4380-a8e4-79a18cd49aa7&&opt=currsel&&select=clearall&&qlikTicket=SjKmCtF4HJftS3we
近期评论