分类 软件 下的文章
Odoo 集成 OAuth2 认证(Keycloak),实现统一登录
引用文字
准备工作
- OAuth2 认证基本知识;
- Odoo 14版本,单机部署,最好不使用 docker 环境;
- Odoo OAuth for Keycloak 扩展;
- Keycloak,配置好 Client 和 User;
工作原理
1. Odoo 插件安装
下载插件,https://github.com/OCA/server-auth/tree/14.0/auth_oidc,配置到 Odoo 外部扩展插件中,安装,具体步骤看插件 Readme 文件即可;
2. 配置 Odoo 支持 Keycloak 认证,按照下图例子参考配置 Odoo 即可;
3. Keycloak 配置,以便支持 Odoo;
Clients 配置基本功能即可,可参考:https://yiqisoft.cn/blogs/iot_platform/243.html,尤其需要配置一个 Mapper,匹配 email 地址用:
创建 Keycloak 内置用户即可;
4. Odoo 用户配置
已知内置用户可以配置成 OAuth2 用户,这样内置用户可以登录 Odoo,OAuth2 用户也可以登录;
内置用户可以不创建,第一次登录 OAuth2 服务器后会自动创建,此时新创建用户无法使用 Odoo 登录,需要发送认证email后创建密码;
成果演示
YiAPP 移动客户端集成 OAuth2 认证服务器,实现统一登录
移动客户端通过 OAuth2 登录,实现账号统一管理。
准备工作
- YiAPP 客户端;
- OAuth2 服务器;
- IoT 应用服务器;
工作原理
- 打开 YiAPP 客户端软件;
- 使用“第三方登录”,选择 YiCLOUD 或 Github;
- 登录 第三方账号;
- 登录成功后即可完成统一登录;
视频演示
ThingsBoard 集成 Keycloak 自定义 OAuth2 认证服务器
ThingsBoard 官方已经发布一些 OAuth2 支持的例子,https://thingsboard.io/docs/user-guide/oauth-2-support/,有 Google,Auth0,其他比如 GitHub 也比较简单。这里介绍的是 Keycloak 的配置。
1、前提条件
1.1、ThingsBoard Oauth2 支持
首先确保你的 ThingsBoard 服务器版本支持 OAuth2,比如 V3.3.*,以 sysadmin 登录即可。
1.2、Keycloak 服务器
安装
过程比较简单,可根据自身需求选择安装方式;测试的时候可以用 docker ,方便快捷。
阅读手册
找到你能读懂的文档,简单熟悉配置方法。
2、配置
2.1、Keycloak 配置
新建 realm
复制 secret
增加 user
设置 user 密码
2.2、ThingsBoard 配置
尤其注意 一系列 URI,其中 YiSERVER 换成你的 真实 realm 即可。
访问令牌URI:
http://localhost:8081/realms/YiSERVER/protocol/openid-connect/token
授权URI:
http://localhost:8081/realms/YiSERVER/protocol/openid-connect/auth
JSON Web Key URI:
http://localhost:8081/realms/YiSERVER/protocol/openid-connect/certs
用户信息URI:
http://localhost:8081/realms/YiSERVER/protocol/openid-connect/userinfo
3、验证
3.1、登录 ThingsBoard
3.2、跳转到 Keycloak
3.3、完成 OAuth2 登录集成
3.4、视频预览
ThingsBoard 二次开发:导出数据到 csv/excel
此二次开发主要是基于 ThingsBoard UI 界面上的 js 脚本开发,熟悉 js 开发的朋友应该很容易理解。
从 Dashboard 导出 csv 其实有好几种情况,1是 telemetry 数据导出,2是 attributes 数据导出。
准备工作
- Chrome 浏览器
- javascript 基础知识
- ThingsBoard UI 二次开发基础知识
- Dashbaord 开发经验
新建Dashboard
Telemetry 数据导出
导出csv文件内容:
timestamp,GasCon
2022-3-21 21:29:0,800
2022-3-21 21:44:0,800
2022-3-25 19:59:0,0
自定义 action,js脚本:
let $injector = widgetContext.$scope.$injector;
let attributeService = $injector.get(widgetContext
.servicesMap.get('attributeService'));
let deviceService = $injector.get(widgetContext.servicesMap
.get('deviceService'));
console.log(widgetContext);
let data = widgetContext.data;
let dataKeys = [];
// get all data keys to array
widgetContext.datasources[0].dataKeys.forEach(function(
item) {
dataKeys.push(item.name);
});
let fileTitle = "export-from-list-" + entityName;
function timeConverter(UNIX_timestamp) {
var a = new Date(UNIX_timestamp);
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
var year = a.getFullYear();
var month = a.getMonth()+1;
var date = a.getDate();
var hour = a.getHours();
var min = a.getMinutes();
var sec = a.getSeconds();
var time = year + '-' + month + '-' + date + ' ' +
hour + ':' + min + ':' + sec;
return time;
}
function exportCSV() {
// first line: header
var allLine = "";
dataKeys.unshift("timestamp");
allLine += dataKeys;
allLine += "\n";
var key_length = data.length; // 字段数目
var limit = data[0]["data"].length; // 数据行数
var line = new Array(limit + 1); // 包括首行
for (var l = 0; l < limit; l++)
line[l] = "";
data.forEach(
(item, idx) => {
var item_data = item["data"];
var dataKey = item["dataKey"];
for (var j = 0; j < limit; j++) {
if (idx == 0) {
line[j] += timeConverter(item_data[
j][0]);
line[j] += ",";
}
line[j] += item_data[j][1];
if (idx < key_length - 1)
line[j] += ",";
}
line[j] += "\n";
}
);
for (var k = 0; k < limit; k++) {
allLine += line[k];
allLine += "\n";
}
var exportedFilenmae = fileTitle + '.csv' ||
'export.csv';
var blob = new Blob([allLine], {
type: 'text/csv;charset=utf-8;'
});
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob,
exportedFilenmae);
} else {
var link = document.createElement("a");
if (link.download !==
undefined) { // feature detection
// Browsers that support HTML5 download attribute
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download",
exportedFilenmae);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
exportCSV();
Attributes 数据导出
导出csv文件内容:
timestamp,name,detector_tag,area,floor,location,department,equipment_name,gas,setup_date,sensor_date,cal_date,cal_result,ip,hub_port,sub_hub_port,maintenance
2022-3-28 21:25:9,CL96-06,,,,,,,,,,,,,,,
2022-3-28 21:25:9,F-CL96-SRD013-HF,,,,,,,,,,,,,,,
2022-3-28 21:25:9,F-CL96-SRD033-C-NH3,,,,,,,,,,,,,,,
2022-3-28 21:25:9,F-CL96-SRD033-D-NH3,,,,,,,,,,,,,,,
2022-3-28 21:25:9,F-CL96-SRD037-CLF3,,,,,,,,,,,,,,,
自定义 action,js 脚本:
let $injector = widgetContext.$scope.$injector;
let attributeService = $injector.get(widgetContext
.servicesMap.get('attributeService'));
let deviceService = $injector.get(widgetContext.servicesMap
.get('deviceService'));
let data = widgetContext.data;
console.log(data);
let dataKeys = [];
// get all data keys to array
widgetContext.datasources[0].dataKeys.forEach(function(
item) {
dataKeys.push(item.name);
});
let fileTitle = "export-device-list";
function timeConverter(UNIX_timestamp) {
var a = new Date(UNIX_timestamp);
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
var year = a.getFullYear();
var month = a.getMonth()+1;
var date = a.getDate();
var hour = a.getHours();
var min = a.getMinutes();
var sec = a.getSeconds();
var time = year + '-' + month + '-' + date + ' ' +
hour + ':' + min + ':' + sec;
return time;
}
function exportCSV() {
// first line: header
var allLine = "";
var key_length = dataKeys
.length; // csv字段数目, 不含timestamp
dataKeys.unshift("timestamp");
allLine += dataKeys;
// allLine += "\n";
var limit = data.length / key_length; // csv数据行数
var line = new Array(limit + 1); // csv总行数,包括首行
for (var l = 0; l < limit; l++)
line[l] = "";
//var j = 0;
data.forEach(
(item, idx) => {
var item_data = item["data"];
if (idx % key_length == 0) {
allLine += "\n"; // 换行
allLine += timeConverter(item_data[0][0]); // timestamp
allLine += ",";
}
allLine += item_data[0][1]; // value/值
if ((idx+1) % key_length != 0)
allLine += ",";
}
);
allLine += "\n";
// for (var k = 0; k < key_length; k++) {
// allLine += line[k];
// allLine += "\n";
// }
var exportedFilenmae = fileTitle + '.csv' ||
'export.csv';
var blob = new Blob([allLine], {
type: 'text/csv;charset=utf-8;'
});
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob,
exportedFilenmae);
} else {
var link = document.createElement("a");
if (link.download !==
undefined) { // feature detection
// Browsers that support HTML5 download attribute
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download",
exportedFilenmae);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
exportCSV();