使用 spring boot 开发获取 qlik ticket 代理服务

一、介绍


本文档主要介绍了如果使用 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&amp&sheet=ca034abd-8ae8-4380-a8e4-79a18cd49aa7&amp&opt=currsel&amp&select=clearall&amp&qlikTicket=SjKmCtF4HJftS3we

You may also like...

发表评论

电子邮件地址不会被公开。