feat(box): 添加调试环境下的 F12 开发者工具快捷键

- 在 BrowserWindow 和 BrowserWindowJDialog 中添加了键盘事件处理,检测 F12 键- 按下 F12 键时,会立即创建并显示开发者工具
- 仅在调试环境下启用此功能,以避免在生产环境中暴露开发者工具
This commit is contained in:
tzdwindows 7
2025-08-18 19:20:24 +08:00
parent a30c306cf1
commit 5df14e353a
11 changed files with 265 additions and 128 deletions

View File

@@ -1 +0,0 @@
org.gradle.java.home=E:\\Softwares\\Java\\jdk-20.0.2

View File

@@ -95,14 +95,16 @@ public class AxisInnovatorsBox {
organizingCrashReports(throwable instanceof Exception ?
(Exception) throwable : new Exception(throwable));
});
// 初始化,这里为了能够在登录窗口使用主题,特意将初始化放在构造函数中
initLog4j2();
setTopic();
}
/**
* 弹出登录窗口
*/
private void popupLogin(){
// 加载登录信息,如果没有,弹出登录弹窗,后续可以删掉默认弹出
// TODO: login window should not be show when AxisInnovatorsBox initialize,
// it should be show when user click login button.
try {
StateManager stateManager = new StateManager();
String excryptedKey = "loginToken";
try {
excryptedKey = Base64CryptoUtil.base64Encode(excryptedKey);
@@ -151,7 +153,6 @@ public class AxisInnovatorsBox {
} catch (Exception e) {
logger.error("Exception: Failed to load login information", e);
} // 添加了更详细的异常处理
}
/**
@@ -1007,7 +1008,10 @@ public class AxisInnovatorsBox {
main = new AxisInnovatorsBox(args,isDebug);
try {
main.initLog4j2();
main.setTopic();
main.popupLogin();
List<Map<String, String>> validFiles = ArgsParser.parseArgs(args);
for (Map<String, String> fileInfo : validFiles) {
String extension = fileInfo.get("extension");

View File

@@ -26,7 +26,7 @@ public class Main {
private static FileLock lock = null;
private static RandomAccessFile lockFile = null;
private static FileChannel lockChannel = null;
private final static boolean releaseEnvironments = false;
public static void main(String[] args) {
// 清理日志文件最大日志为10
FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10);
@@ -41,7 +41,7 @@ public class Main {
// 检查是否包含调试控制台参数
boolean debugWindowEnabled = false;
for (int i = 0; i < args.length; i++) {
if ("-debugControlWindow-on".equals(args[i])) {
if (!releaseEnvironments && "-debugControlWindow-on".equals(args[i])) {
debugWindowEnabled = true;
// 移除此参数避免干扰后续处理
String[] newArgs = new String[args.length - 1];
@@ -79,7 +79,7 @@ public class Main {
if (!acquireLock()) {
return;
}
AxisInnovatorsBox.run(args, debugWindowEnabled);
AxisInnovatorsBox.run(args, true);
}
/**

View File

@@ -1,5 +1,6 @@
package com.axis.innovators.box.browser;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.events.BrowserCreationCallback;
import org.cef.CefApp;
import org.cef.CefClient;
@@ -289,6 +290,20 @@ public class BrowserWindow extends JFrame {
}
});
if (AxisInnovatorsBox.getMain() != null && AxisInnovatorsBox.getMain().isDebugEnvironment()) {
client.addKeyboardHandler(new CefKeyboardHandlerAdapter() {
@Override
public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) {
// 检测 F12
if (event.windows_key_code == 123) {
browser.getDevTools().createImmediately();
return true;
}
return false;
}
});
}
client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
@Override
public boolean onBeforePopup(CefBrowser browser, CefFrame frame,

View File

@@ -1,5 +1,6 @@
package com.axis.innovators.box.browser;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.events.BrowserCreationCallback;
import org.cef.CefApp;
import org.cef.CefClient;
@@ -294,6 +295,20 @@ public class BrowserWindowJDialog extends JDialog {
}
});
if (AxisInnovatorsBox.getMain() != null && AxisInnovatorsBox.getMain().isDebugEnvironment()) {
client.addKeyboardHandler(new CefKeyboardHandlerAdapter() {
@Override
public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) {
// 检测 F12
if (event.windows_key_code == 123) {
browser.getDevTools().createImmediately();
return true;
}
return false;
}
});
}
client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
@Override
public boolean onBeforePopup(CefBrowser browser, CefFrame frame,

View File

@@ -81,6 +81,9 @@ public class CefAppManager {
String processType,
CefCommandLine commandLine
) {
//commandLine.appendSwitch("disable-dev-tools");
//commandLine.appendSwitch("disable-view-source");
LanguageManager.loadSavedLanguage();
LanguageManager.Language currentLang = LanguageManager.getLoadedLanguages();
if (currentLang != null){

View File

@@ -41,7 +41,7 @@ public class MainApplication {
WindowRegistry.getInstance().createNewWindow("main", builder ->
window.set(builder.title("Axis Innovators Box AI 工具箱")
.size(1280, 720)
.htmlUrl("https://chat.deepseek.com/")
.htmlUrl("https://www.bilibili.com/")
.openLinksInBrowser(true)
.operationHandler(createOperationHandler())
.build())

View File

@@ -3,6 +3,7 @@ package com.axis.innovators.box.tools.Crypto;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.security.SecureRandom;
import java.util.Base64;
@@ -33,7 +34,7 @@ public class AESCryptoUtil {
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8"));
byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
}
@@ -45,6 +46,6 @@ public class AESCryptoUtil {
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decoded = Base64.getDecoder().decode(encrypted);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, "UTF-8");
return new String(decrypted, StandardCharsets.UTF_8);
}
}

View File

@@ -1,5 +1,6 @@
package com.axis.innovators.box.window;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.browser.CefAppManager;
import com.axis.innovators.box.tools.LoadResource;
import com.axis.innovators.box.verification.CasdoorServer;
@@ -12,39 +13,36 @@ import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.awt.Desktop;
import java.awt.FlowLayout;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.casbin.casdoor.config.Config;
import org.casbin.casdoor.entity.User;
import org.casbin.casdoor.service.AuthService;
import javax.swing.*;
import config.CasdoorConfig;
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.CefContextMenuHandlerAdapter;
import org.cef.handler.CefKeyboardHandlerAdapter;
import org.cef.handler.CefLifeSpanHandlerAdapter;
import org.cef.handler.CefLoadHandlerAdapter;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefContextMenuParams;
public class CasdoorLoginWindow {
private final Logger logger = LogManager.getLogger(CasdoorLoginWindow.class);
private final CasdoorServer casdoorServer;
private CefBrowser browser; // 便于复用
private HttpServer server; // 后端处理登陆成功后的跳转
private CefBrowser browser;
private HttpServer server;
private JDialog dialog;
private LoginResult loginResult = null;
private boolean windowVisible = true;
private boolean isModal = true;
@@ -52,7 +50,6 @@ public class CasdoorLoginWindow {
casdoorServer = new CasdoorServer();
}
// 启动本地 HTTP 服务监听 Casdoor 回调
private void startLocalCallbackServer() {
try {
server = HttpServer.create(new java.net.InetSocketAddress(CasdoorConfig.CASDOOR_WEB_SERVER_PORT), 0);
@@ -64,7 +61,6 @@ public class CasdoorLoginWindow {
}
}
// 处理回调请求,自动填充 code 和 state
private void handleCallback(HttpExchange exchange) throws IOException {
String query = exchange.getRequestURI().getQuery();
AtomicReference<String> code = new AtomicReference<>("");
@@ -84,7 +80,6 @@ public class CasdoorLoginWindow {
try (OutputStream os = exchange.getResponseBody()) {
os.write(response.getBytes());
}
}
}
}
@@ -93,76 +88,205 @@ public class CasdoorLoginWindow {
private void initUI() {
if (browser == null) {
try {
// 初始化浏览器
CefApp cefApp = CefAppManager.getInstance();
CefClient client = cefApp.createClient();
browser = client.createBrowser(
casdoorServer.getSigninUrl(),
false,
false);
browser = client.createBrowser(casdoorServer.getSigninUrl(), false, false);
if (AxisInnovatorsBox.getMain().isDebugEnvironment()) {
client.addKeyboardHandler(new CefKeyboardHandlerAdapter() {
@Override
public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) {
// 检测 F12
if (event.windows_key_code == 123) {
browser.getDevTools().createImmediately();
return true;
}
return false;
}
});
}
// 🔹 页面加载完成后注入 JS / CSS
client.addLoadHandler(new org.cef.handler.CefLoadHandlerAdapter() {
@Override
public void onLoadEnd(CefBrowser cefBrowser, org.cef.browser.CefFrame frame, int httpStatusCode) {
if (frame != null && !frame.isMain()) return;
String url = cefBrowser.getURL();
try {
String js =
"(function(){"
+ "if(window._axis_toolbar_injected) return; window._axis_toolbar_injected = true;"
+ "var style = document.createElement('style'); style.innerHTML = '"
+ ".axis-toolbar{position:fixed;right:16px;display:flex;gap:8px;z-index:2147483647;transition:transform .36s cubic-bezier(.2,.9,.2,1),opacity .28s;transform:translateY(20px) scale(.98);opacity:0;} "
+ ".axis-toolbar.show{transform:translateY(0) scale(1);opacity:1;} "
+ ".axis-btn{width:40px;height:40px;border-radius:10px;border:none;padding:6px;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(6px);background:rgba(255,255,255,0.08);box-shadow:0 6px 18px rgba(0,0,0,0.18);cursor:pointer;font-family:Segoe UI,SegoeUI,Arial,sans-serif;} "
+ ".axis-btn:hover{transform:translateY(-2px) scale(1.03);} "
+ ".axis-btn svg{width:20px;height:20px;display:block;} "
+ "@keyframes axisPageOut{0%{transform:scale(1);opacity:1}100%{transform:scale(0.96);opacity:0}}"
+ "@keyframes axisPageIn{0%{transform:scale(1.04);opacity:0}100%{transform:scale(1);opacity:1}}"
+ ".axis-page-out{animation:axisPageOut .3s ease-out forwards;pointer-events:none;}"
+ ".axis-page-in{animation:axisPageIn .4s ease-out forwards;}"
+ "';"
+ "document.head.appendChild(style);"
+ "var toolbar = document.createElement('div'); toolbar.className = 'axis-toolbar';"
+ "function makeBtn(title, svg, onclick){ var b = document.createElement('button'); b.className='axis-btn'; b.title=title; b.innerHTML = svg; b.addEventListener('click', function(e){ e.preventDefault(); try{ onclick(); }catch(ex){} }); return b; }"
+ "var svgBack = '<svg viewBox=\\'0 0 24 24\\' fill=\\'none\\' xmlns=\\'http://www.w3.org/2000/svg\\'><path d=\\'M15 6L9 12l6 6\\' stroke=\\'currentColor\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'/></svg>';"
+ "var svgFwd = '<svg viewBox=\\'0 0 24 24\\' fill=\\'none\\' xmlns=\\'http://www.w3.org/2000/svg\\'><path d=\\'M9 18l6-6-6-6\\' stroke=\\'currentColor\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'/></svg>';"
+ "var svgReload = '<svg viewBox=\\'0 0 24 24\\' fill=\\'none\\' xmlns=\\'http://www.w3.org/2000/svg\\'><path d=\\'M21 12a9 9 0 10-3 6.7\\' stroke=\\'currentColor\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'/><path d=\\'M21 3v6h-6\\' stroke=\\'currentColor\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'/></svg>';"
+ "var bBack = makeBtn('后退', svgBack, function(){ try{ history.back(); }catch(e){} });"
+ "var bFwd = makeBtn('前进', svgFwd, function(){ try{ history.forward(); }catch(e){} });"
+ "var bReload = makeBtn('重新加载', svgReload, function(){ try{ location.reload(); }catch(e){} });"
+ "toolbar.appendChild(bBack); toolbar.appendChild(bFwd); toolbar.appendChild(bReload); document.body.appendChild(toolbar);"
+ "setTimeout(function(){ try{ var footer = document.querySelector('footer'); var bottom = 24; if(footer && footer.offsetHeight) bottom = footer.offsetHeight + 16; toolbar.style.bottom = bottom + 'px'; }catch(e){} toolbar.classList.add('show'); }, 60);"
+ "var isAnimating = false;"
+ "document.addEventListener('click', function(e){"
+ " try{"
+ " if(isAnimating) return;"
+ " var a = e.target.closest && e.target.closest('a');"
+ " if(!a) return;"
+ " if(a.target=='_blank') return;"
+ " var href = a.href;"
+ " if(!href) return;"
+ " var same = (new URL(href, location.href)).origin === location.origin;"
+ " if(!same) return;"
+ " e.preventDefault();"
+ " isAnimating = true;"
+ " document.documentElement.classList.add('axis-page-out');"
+ " setTimeout(function(){"
+ " location.href = href;"
+ " isAnimating = false;"
+ " }, 300);"
+ " }catch(ex){}"
+ "}, true);"
+ "function runPageInAnimation() {"
+ " document.documentElement.classList.remove('axis-page-out');"
+ " document.documentElement.classList.add('axis-page-in');"
+ " setTimeout(function(){"
+ " document.documentElement.classList.remove('axis-page-in');"
+ " }, 400);"
+ "}"
+ "window.addEventListener('pageshow', function(event){"
+ " if (event.persisted) runPageInAnimation();"
+ "});"
+ "window.addEventListener('DOMContentLoaded', runPageInAnimation);"
+ "})();";
cefBrowser.executeJavaScript(js, url, 0);
} catch (Throwable ex) {
ex.printStackTrace();
}
}
});
client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
@Override
public boolean onBeforePopup(CefBrowser browser, CefFrame frame,
String targetUrl, String targetFrameName) {
try {
Desktop.getDesktop().browse(new URI(targetUrl));
} catch (Exception e) {
System.out.println("Failed to open external browser: " + e.getMessage());
}
return true;
}
});
// 🔹 自定义右键菜单
client.addContextMenuHandler(new org.cef.handler.CefContextMenuHandlerAdapter() {
private final int CMD_COPY = 100;
private final int CMD_BACK = 101;
private final int CMD_FORWARD = 102;
private final int CMD_RELOAD = 103;
@Override
public void onBeforeContextMenu(org.cef.browser.CefBrowser browser, org.cef.browser.CefFrame frame,
org.cef.callback.CefContextMenuParams params, org.cef.callback.CefMenuModel model) {
model.clear(); // 清空默认菜单
boolean hasSelection = false;
try {
java.lang.reflect.Method m = params.getClass().getMethod("getSelectionText");
Object sel = m.invoke(params);
if (sel != null && !sel.toString().trim().isEmpty()) {
hasSelection = true;
}
} catch (Throwable ignored) {}
if (hasSelection) {
model.addItem(CMD_COPY, "复制");
}
// 可后退/前进时显示
try {
if (browser.canGoBack()) {
model.addItem(CMD_BACK, "后退");
}
if (browser.canGoForward()) {
model.addItem(CMD_FORWARD, "前进");
}
} catch (Throwable ignored) {}
model.addItem(CMD_RELOAD, "刷新");
}
@Override
public boolean onContextMenuCommand(org.cef.browser.CefBrowser browser, org.cef.browser.CefFrame frame,
org.cef.callback.CefContextMenuParams params, int commandId, int eventFlags) {
switch (commandId) {
case CMD_COPY:
// 通过 JS 将选中内容复制到剪贴板(包含降级方案)
String copyJs =
"(function(){"
+ "var s = window.getSelection ? window.getSelection().toString() : (document.selection ? document.selection.createRange().text : '');"
+ "if(!s) return;"
+ "try{ navigator.clipboard.writeText(s); }"
+ "catch(e){ var ta=document.createElement('textarea'); ta.value=s; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta);} "
+ "})();";
browser.executeJavaScript(copyJs, browser.getURL(), 0);
return true;
case CMD_BACK:
try { if (browser.canGoBack()) browser.goBack(); } catch (Throwable ignored) {}
return true;
case CMD_FORWARD:
try { if (browser.canGoForward()) browser.goForward(); } catch (Throwable ignored) {}
return true;
case CMD_RELOAD:
try { browser.reload(); } catch (Throwable ignored) {}
return true;
default:
return false;
}
}
});
} catch (Throwable e) {
logger.error("Failed to initialize CefBrowser", e);
// 浏览器初始化失败,弹出默认浏览器替代,并提示用户
// 如果浏览器初始化失败,改为在默认浏览器打开登录页面
openCasdoorLoginPageInDefaultBrowser();
// JOptionPane.showMessageDialog(dialog,
// "浏览器初始化失败,已在默认浏览器打开登录页面,请手动完成登录。",
// "内嵌浏览器初始化失败",
// JOptionPane.WARNING_MESSAGE
// );
String message = "浏览器初始化失败,已在默认浏览器打开登录页面,请手动完成登录。\n或者手动复制下面链接在浏览器打开进行登录\n"
+ casdoorServer.getSigninUrl();
+ casdoorServer.getSigninUrl();
JTextArea textArea = new JTextArea(message);
textArea.setEditable(false);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setBackground(null);
textArea.setSize(600, 400);
textArea.setSize(504,835);
JOptionPane.showMessageDialog(dialog, new JScrollPane(textArea), "内嵌浏览器初始化失败", JOptionPane.WARNING_MESSAGE);
}
}
dialog = new JDialog();
dialog.setTitle("AXIS 认证");
dialog.setSize(500, 750);
dialog.setLocationRelativeTo(null); // 居中显示
dialog.setSize(504,835);
dialog.setLocationRelativeTo(null);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
if (browser != null) {
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
JButton homeButton = new JButton("");
homeButton.setIcon(LoadIcon.loadIcon("material-symbols-home.png", 24));
homeButton.addActionListener(e -> browser.loadURL(casdoorServer.getSigninUrl()));
homeButton.setFocusPainted(false);
homeButton.setBorderPainted(false);
JButton backButton = new JButton("");
backButton.setIcon(LoadIcon.loadIcon("material-symbols-back.png", 24));
backButton.addActionListener(e -> browser.goBack());
backButton.setFocusPainted(false);
backButton.setBorderPainted(false);
JButton forwardButton = new JButton("");
forwardButton.setIcon(LoadIcon.loadIcon("material-symbols-forward.png", 24));
forwardButton.addActionListener(e -> browser.goForward());
forwardButton.setFocusPainted(false);
forwardButton.setBorderPainted(false);
JButton refreshButton = new JButton("");
refreshButton.setIcon(LoadIcon.loadIcon("material-symbols-refresh.png", 24));
refreshButton.addActionListener(e -> browser.reload());
refreshButton.setFocusPainted(false);
refreshButton.setBorderPainted(false);
// 将按钮面板添加到主panel顶部
buttonPanel.add(homeButton);
buttonPanel.add(backButton);
buttonPanel.add(forwardButton);
buttonPanel.add(refreshButton);
panel.add(buttonPanel, 0);
panel.add(browser.getUIComponent());
}
dialog.add(panel);
@@ -186,9 +310,6 @@ public class CasdoorLoginWindow {
}
}
/**
* 在默认浏览器打开 Casdoor 登录页面
*/
private void openCasdoorLoginPageInDefaultBrowser() {
String loginUrl = casdoorServer.getSigninUrl();
try {
@@ -216,13 +337,9 @@ public class CasdoorLoginWindow {
loginResult = null;
}
/**
* 以阻塞形式显示除非setVisible为false窗口同时在退出时获取返回值
* @return 返回登录结果
*/
public LoginResult exec() {
resetResult();
initUI(); // 每次显示窗口都先调用一次
initUI();
openLoginAndListen();
dialog.setModal(true);
isModal = true;
@@ -230,12 +347,9 @@ public class CasdoorLoginWindow {
return getLoginResult();
}
/**
* 非模态形式显示窗口与setVisible无关
*/
public CompletableFuture<LoginResult> show() {
resetResult();
initUI(); // 每次显示窗口都先调用一次
initUI();
CompletableFuture<LoginResult> future = new CompletableFuture<>();
dialog.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
@@ -251,28 +365,15 @@ public class CasdoorLoginWindow {
return future;
}
/**
* 窗口是否模态窗口
* @return true or false
*/
public boolean isModal() {
return isModal;
}
/**
* 设置窗口是否可见
* @param b 是否可见
*/
public void setVisible(boolean b) {
this.windowVisible = b;
dialog.setVisible(b);
}
/**
* 获取登录结果
* @return 登录结果
*/
public LoginResult getLoginResult() {
return loginResult;
}
@@ -286,7 +387,6 @@ public class CasdoorLoginWindow {
return result.get();
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

View File

@@ -3,8 +3,8 @@ package config;
public class CasdoorConfig {
// Casdoor Config Contructor
public static final String CASDOOR_API_URL = "https://casdoor.lingqi.vip";
public static final String CASDOOR_CLIENT_ID = "efb6af7c9517f19340da";
public static final String CASDOOR_CLIENT_SECRET = "7396f7e4bcda83756641179108b89356bb4d6d3b";
public static final String CASDOOR_CLIENT_ID = "65dfd46ebfa91f358f40";
public static final String CASDOOR_CLIENT_SECRET = "ebc76a637a20358aefeea70bc5dee7101454589f";
public static final String CASDOOR_CERTIFICATE_FILE = "";
public static final String CASDOOR_ORGANIZATION_NAME = "灵启";
public static final String CASDOOR_APPLICATION_NAME = "lingqi_box";

View File

@@ -1,29 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE3TCCAsWgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMCgxDjAMBgNVBAoTBWFk
bWluMRYwFAYDVQQDEw1jZXJ0LWJ1aWx0LWluMB4XDTI1MDgxNTEzNDgwMVoXDTQ1
MDgxNTEzNDgwMVowKDEOMAwGA1UEChMFYWRtaW4xFjAUBgNVBAMTDWNlcnQtYnVp
bHQtaW4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCm56dxzpBRDyG7
CNt3v51+ikP3dgAtgky5yUSeiQJW8eHaSJayNfIT42GCH3Nk6ZI3xR3nAZ1kaFyg
/ZiVw9QHzju3859osXoK+NMiv75G3Z0xEe2wg2gRJh0FvTDw6TKSv179C5BrxK5y
6O8caP5NAA5Kqn2OiQkIXRBxYU7i0fHrABeJpO396KtZ9oPoe9/ZWGRDTsilbyRB
8gz7JaVq9ECsgPDErmtvUhXLtNPKSfXN2UVVSpE+EET0DJv7dVlSowoaF4tNtA5e
QVTyAR559aPBnXZKD/9arTRCtU1YB2Uv3NuFELeJzchAmf/l+IPbBUfvGdaLE/kg
TB0T9hfiyRpIvjfdEpQTeaOSDVbnO165Z1AWhpxVHEHfaEDD/48aSSfAKty29CaV
wmmBA1aw90AtkyJR3BfrHExAMK4+s89P07ihe+YVUCgrrZk8J7xAQ+g3P4+FF4MI
NrhS/OgHss2e8pxoiv6rvlMXyZqFlocKuaNKCvTy0vvspj1wn6XdX8jZZnpZrKLy
Qbc1LN/HNmMe4RGjIBdlBdBHI0RIH7afLhghor+fZ/5lAa1Fzwv6TbD+QIx1wEq9
zY6lRamN4iS/AxkIEovcKJq+W0Vcn+0bovcjjmojN2MnkHWH+XCBeyEixh+fuzSi
Zjt3ydhPzU2Vb1vPsVqFQjHHR+FYbwIDAQABoxAwDjAMBgNVHRMBAf8EAjAAMA0G
CSqGSIb3DQEBCwUAA4ICAQBTFfNo5BMqwwlSimxfT2uj1xfXZ/ek0suaY9xZIVVo
XlrbIuE1j4KS1MjOCs1IbkYZW5+MO4c4OcjHh/XusQ2t2PKP7Q0atdFjYeiw987a
oE9r5am2IPzfh3DI3jC7rfz6Jrq64oK23JAXisa21xoB4CflzlpgOFX3h6UfcNhB
bTSQjdyeTZYwCeSmO53iAW9oNlffnzaYtQXqiq7pCGcC7sGZBZJnM0nNvQRYqzcU
8E8elfEwdWqc9ZJ/ah8byobZQHvraRcYDEZ3oqfjMsoycbFfJztaZV1pfJOZNRzG
lypDUCjZeK1z91K6UFws6qOGc6j06pLIXxVmpuw62yCnG3KM55O3GcUc9q3oAyTb
Iq2g3CjIiaFu8AowVyFswvOIK2pvRBBA+54eP079SLqqznHV+AE91nvVLOMn7xVu
vfV2XbxzAk3to39WilEn3KpGavpQnyQnD/nVjpEzzInAjPO6vCkWXs1ZS2sqNciz
T+r9Q3cZUMWusgNNoGfWnjchLOaOPe1zGINwAsSDXjv1YD18pfp5hEPlwSTGrulI
7fN+cGQ5Spv5VhFcOyodgmIvMnM2EcgeH4CPkQ5aiE3qAGzRzfQiPeght/Wkdd4J
+SMykEE2VmLmVD8VQf2j/EVbGQ32TTllYfQEgS/S5pfUPr5RAEljCHlCUiNcHF9c
+g==
bWluMRYwFAYDVQQDEw1jZXJ0LWJ1aWx0LWluMB4XDTI1MDgxODAyMTE0NVoXDTQ1
MDgxODAyMTE0NVowKDEOMAwGA1UEChMFYWRtaW4xFjAUBgNVBAMTDWNlcnQtYnVp
bHQtaW4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC32DEkN4pQskEt
QBpfBTAK6yMjW0z9qVpUY34DR4nOfoLRdRxh3lA/Pc1+50ubMEP06zG1stDdW7o0
R6sQjAWzP0edgVyi5nx86+fcBOZGUlA96WYF1Dos326+o0ar3DqWN09CIjQrKCXP
LdmXG14NCpQ0RlyTdPnYoKcQ+74Yq6iSsf13tHIJjz9v2hPp31pPqENjB4MAXuIe
JBy8A8vdGTvYsuI9YDi6Dr7X0ohv7b97NS7TtS5o+tL5X9sSSLaQmu3A+pqDCUXZ
J15PAkiR83Lrb9gAb0RcM4iQ598EZs991jGEmumxeQFm/HT/9Ba4g9nkk9e6euh4
uH74DwL++ouegaSmK5Wul3RbV/L1KoiKtlyZTdLy1c77vd/DJq6FvQ/RYqWImXc9
HyvhPSCl8Pm6BEEFwEsa6OfsgtmIxuz902vfAEe2iCcdDnuwfe1NC+zFNTn123US
OLfmNuU1o8LyyVr5zQDctICHv+iSei/iJakbT8XSSAUFC839YIYWIuhqqORyLDSk
sXUX3fG7bzakAJlwKvZrm7z9vFTXMlDfuvAGHcHIkINL9QFYvyAot7sWPrB6JqtX
Tr/ddDs9w4Wpu1116y0TnJk/zdCkqzxYD0ZKQfLPxdirwEeawV+TPk58ZPhevDMC
D3XDUNXk+LMfLCnMBPy2344w9RsdFwIDAQABoxAwDjAMBgNVHRMBAf8EAjAAMA0G
CSqGSIb3DQEBCwUAA4ICAQBXnX7ZrjK7ra+ckfhPp1ZoeF+uRVIsXJQVessr7usg
FYZoS0aZX6uOCMsplSCmyasQDfny051ItKdzi6l6HKFzd8Vg8v9+n8htoJHYsgjn
ZH/V5UJ3Ji0y8+plLz572iR02kvaV+zozhtIuBzOtoKcO6JXBU5Yos2XZZBzz66L
AXL71SrAzP9oehM98mSlmz+ahpMVpC8hWnorCWnoLUY6AtqVWo0vf3YKKKC1R02Y
idaBCZEFE8RKPDmQUNW34kpUwvK2X8hJOAU3S0auWTXrjy/pZxEcY/FH0E0gDOWG
8dS31UGbUXcjricP+CSzvwu/hwPh3OrM5CarJ1nllROrscNV0Jrnp+DLFav297rs
YBb3iUVrNA3Nebuz7CnNApS10+wW0KPUeMV1sN+5LE5yY6uBnUqFa7omWjdoGub4
RcSzRxat4vB1AYS0iu7FhSWcBiaB1cqZiW29hTZDI1XF2yO9v+8/qHftjZEUEEYX
xh1rFJn4jCorN4xfkKYvk3gk0sxJ0tCaIZIr5CIVWEYoiy48kiQcO0kQEIpUtTEB
4r0X/WLqwTL+u79br1a4wuZFZs48Czxq5A4A93l4+c27+60Hnt3XuvN29/gojcam
JhvmOEyxmSUD7pUrrJeNUAzLC0HUJj2CQQr0v1v+7i1jAD04qCQEXOaB8MtLmoiZ
Ww==
-----END CERTIFICATE-----