1. 概述
1.1 版本
报表服务器版本 |
---|
10.0 |
1.2 问题描述
用户在使用自定义登录页时,需要将输入的明文密码发送到数据决策系统中进行登录验证。在传输过程中,明文密码的安全性较低,用户希望能够对密码进行加密,以密文形式传输来保障信息的安全性。
1.3 实现思路
使用加密算法对用户在登录页输入的密码进行加密,将加密后的密文传输至数据决策系统进行登录验证。
数据库中存储加密后的密文,并通过 同步用户 的方式将数据库中的信息存储至数据决策系统。当传输的密文与数据决策系统中所存储的密文一致时,登录验证通过,用户成功登录数据决策系统。如下图所示:
实现步骤:
1)准备加密 JS 文件,用于加密用户在登录页面输入的明文密码。
2)新建 HTML 文件,自定义登录页面。实现以下效果:
引用步骤 1 准备的加密 JS 文件,调用加密方法将用户输入的明文转化成密文。
调用单点登录接口,将密文传输至数据决策系统进行登录验证。
登录验证成功后跳转到用户的数据决策系统页面。
3)将 HTML 文件与加密 JS 文件放到指定位置。
4)创建服务器数据集,存储用户的登录信息,包括用户名、登录密码等,其中登录密码为加密后的密文。
注:同步用户后,系统会对服务器数据集中该密文进行一次 SHA256 加密,得到的密文写入 FineDB 库。
5)同步用户信息至数据决策系统,用于登录验证。
2. 示例
本文示例:自定义一个数据决策系统登录页,对用户输入的密码进行 md5(16位大写)加密,使用加密后的密文进行登录验证,验证通过后跳转到用户的数据决策系统页面。
2.1 准备加密 JS 文件
准备加密 JS 文件,用于加密用户在登录页面输入的明文密码。
新建 JS 文件,命名为 md5.js,代码如下所示:
点击下载并解压获得加密 JS 文件:加密.zip
注:本节还提供了 sha256.js 加密文件,用户可按需选用,使用步骤参考第二节示例即可。
var hexcase = 1;
var b64pad = "";
var chrsz = 8;
var mode = 16; //模式选择 (16为16位的加密 32 为32位的加密)
function preprocess(form)
{
var str = "";
str += form.verifycode.value;
str = str.toUpperCase();
form.p.value = md5(md5_3(form.p.value)+str);
return true;
}
function md5_3(s)
{
var tmp = new Array;
tmp = core_md5(str2binl(s), s.length * chrsz);
tmp = core_md5(tmp, 16 * chrsz);
tmp = core_md5(tmp, 16 * chrsz);
return binl2hex(tmp);
}
function md5(s)
{
return hex_md5(s);
}
function hex_md5(s)
{
return binl2hex(core_md5(str2binl(s), s.length * chrsz));
}
function b64_md5(s)
{
return binl2b64(core_md5(str2binl(s), s.length * chrsz));
}
function str_md5(s)
{
return binl2str(core_md5(str2binl(s), s.length * chrsz));
}
function hex_hmac_md5(key, data)
{
return binl2hex(core_hmac_md5(key, data));
}
function b64_hmac_md5(key, data)
{
return binl2b64(core_hmac_md5(key, data));
}
function str_hmac_md5(key, data)
{
return binl2str(core_hmac_md5(key, data));
}
function md5_vm_test()
{
return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}
function core_md5(x, len)
{
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = - 271733879;
var c = - 1732584194;
var d = 271733878;
for (var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i + 0], 7, - 680876936);
d = md5_ff(d, a, b, c, x[i + 1], 12, - 389564586);
c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i + 3], 22, - 1044525330);
a = md5_ff(a, b, c, d, x[i + 4], 7, - 176418897);
d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i + 6], 17, - 1473231341);
b = md5_ff(b, c, d, a, x[i + 7], 22, - 45705983);
a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
d = md5_ff(d, a, b, c, x[i + 9], 12, - 1958414417);
c = md5_ff(c, d, a, b, x[i + 10], 17, - 42063);
b = md5_ff(b, c, d, a, x[i + 11], 22, - 1990404162);
a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5_ff(d, a, b, c, x[i + 13], 12, - 40341101);
c = md5_ff(c, d, a, b, x[i + 14], 17, - 1502002290);
b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i + 1], 5, - 165796510);
d = md5_gg(d, a, b, c, x[i + 6], 9, - 1069501632);
c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i + 0], 20, - 373897302);
a = md5_gg(a, b, c, d, x[i + 5], 5, - 701558691);
d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5_gg(c, d, a, b, x[i + 15], 14, - 660478335);
b = md5_gg(b, c, d, a, x[i + 4], 20, - 405537848);
a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5_gg(d, a, b, c, x[i + 14], 9, - 1019803690);
c = md5_gg(c, d, a, b, x[i + 3], 14, - 187363961);
b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i + 13], 5, - 1444681467);
d = md5_gg(d, a, b, c, x[i + 2], 9, - 51403784);
c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i + 12], 20, - 1926607734);
a = md5_hh(a, b, c, d, x[i + 5], 4, - 378558);
d = md5_hh(d, a, b, c, x[i + 8], 11, - 2022574463);
c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i + 14], 23, - 35309556);
a = md5_hh(a, b, c, d, x[i + 1], 4, - 1530992060);
d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i + 7], 16, - 155497632);
b = md5_hh(b, c, d, a, x[i + 10], 23, - 1094730640);
a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5_hh(d, a, b, c, x[i + 0], 11, - 358537222);
c = md5_hh(c, d, a, b, x[i + 3], 16, - 722521979);
b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i + 9], 4, - 640364487);
d = md5_hh(d, a, b, c, x[i + 12], 11, - 421815835);
c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i + 2], 23, - 995338651);
a = md5_ii(a, b, c, d, x[i + 0], 6, - 198630844);
d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i + 14], 15, - 1416354905);
b = md5_ii(b, c, d, a, x[i + 5], 21, - 57434055);
a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5_ii(d, a, b, c, x[i + 3], 10, - 1894986606);
c = md5_ii(c, d, a, b, x[i + 10], 15, - 1051523);
b = md5_ii(b, c, d, a, x[i + 1], 21, - 2054922799);
a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5_ii(d, a, b, c, x[i + 15], 10, - 30611744);
c = md5_ii(c, d, a, b, x[i + 6], 15, - 1560198380);
b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i + 4], 6, - 145523070);
d = md5_ii(d, a, b, c, x[i + 11], 10, - 1120210379);
c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i + 9], 21, - 343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
if (mode == 16)
{
return Array(b, c);
}
else
{
return Array(a, b, c, d);
}
}
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
function core_hmac_md5(key, data)
{
var bkey = str2binl(key);
if (bkey.length > 16)
bkey = core_md5(bkey, key.length * chrsz);
var ipad = Array(16), opad = Array(16);
for (var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = core_md5(ipad.concat(str2binl(data)), 512+data.length * chrsz);
return core_md5(opad.concat(hash), 512+128);
}
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32-cnt));
}
function str2binl(str)
{
var bin = Array();
var mask = (1 << chrsz) - 1;
for (var i = 0; i < str.length * chrsz; i += chrsz)
bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32);
return bin;
}
function binl2str(bin)
{
var str = "";
var mask = (1 << chrsz) - 1;
for (var i = 0; i < bin.length * 32; i += chrsz)
str += String.fromCharCode((bin[i >> 5] >>> (i % 32)) & mask);
return str;
}
function binl2hex(binarray)
{
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for (var i = 0; i < binarray.length * 4; i++)
{
str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8+4)) & 0xF) +
hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF);
}
return str;
}
function binl2b64(binarray)
{
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for (var i = 0; i < binarray.length * 4; i += 3)
{
var triplet = (((binarray[i >> 2] >> 8 * (i % 4)) & 0xFF) << 16) | ((
(binarray[i + 1 >> 2] >> 8 * ((i + 1) % 4)) & 0xFF) << 8) | ((binarray[i
+ 2 >> 2] >> 8 * ((i + 2) % 4)) & 0xFF);
for (var j = 0; j < 4; j++)
{
if (i * 8+j * 6 > binarray.length * 32)
str += b64pad;
else
str += tab.charAt((triplet >> 6 * (3-j)) & 0x3F);
}
}
return str;
}
在 2.2 节自定义登录页 login.html 中引入 md5.js 文件后,可以在 doSubmit() 函数中直接调用 md5 加密方法对用户输入的明文密码进行加密。
使用方法如下:
注:md5.js 中提供了 6 种加密方法,包括:hex_md5(value)、b64_md5(value)、str_md5(value)、hex_hmac_md5(key, data)、b64_hmac_md5(key, data)、str_hmac_md5(key, data),用户可按需选用。
<script type="text/javascript" src="./md5.js"></script> //引入 md5.js
var password = md5(document.getElementById("password").value.trim()); // 使用 md5 加密方法对用户输入密码进行加密
2.2 新建登录页面
新建 HTML 文件自定义决策平台登录页面,命名为login.html。完整代码如下所示:
注:需根据实际情况修改代码中的访问路径或端口号。
点击下载并解压获得 HTML 文件:login.zip
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>自定义登录页加密Demo</title>
<!-- 自定义样式,根据实际需求使用 -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="./md5.js"></script>
<style>
.container {
display: flex;
justify-content: center;
}
.login-box {
width: 300px;
margin-top: 100px;
}
.login-box h2 {
font-size: 26px;
text-align: center;
margin-bottom: 25px;
}
.login-item {
margin-bottom: 20px;
}
</style>
<body>
<div class="container">
<form class="login-box" action="" method="post" onsubmit="return false;">
<h2>数据决策系统</h2>
<div class="login-item">
<label for="inputUsername" class="sr-only">用户名</label>
<input type="text" id="inputUsername" class="form-control" placeholder="用户名" required="" autofocus="">
</div>
<div class="login-item">
<label for="inputPassword" class="sr-only">密码</label>
<!-- autocomplete="off"属性,阻止浏览器从cookies获取数据填充登录表单 -->
<input type="text" id="inputPassword" οnfοcus="this.type='password'" class="form-control" placeholder="密码" required="" autocomplete="off">
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" id="submitBtn">登录</button>
</form>
</div>
<script>
document.getElementById("submitBtn").addEventListener("click", function () {
doSubmit();
});
function doSubmit() {
var username = document.getElementById("inputUsername").value.trim();
var password = md5(document.getElementById("inputPassword").value.trim()); //对用户输入密码进行加密
if (username === "") {
window.alert("请输入用户名");
return false;
}
if (password === "") {
window.alert("请输入密码");
return false;
}
var url = "http://localhost:8075/webroot/decision/login/cross/domain" + "?fine_username=" + username + "&fine_password=" + password + "&validity=" + -1;
jQuery.ajax({
url: url,//单点登录的管理平台报表服务器
timeout: 5000,//超时时间(单位:毫秒)
dataType:"jsonp",//跨域采用jsonp方式
jsonp:"callback",
success: function (res) {
console.log(res);
if (res.errorCode) {
window.alert(res.errorMsg);
}else {
// 保存token并跳转到对应链接
window.location.href = "http://localhost:8075/webroot/decision";
}
},
error: function () {
alert("超时或服务器其他错误");// 登录失败(超时或服务器其他错误)
}
});
}
</script>
</body>
</html>
2.3 将文件放到指定位置
将 login.html 和 md5.js 文件保存至%FR_HOME%/webapps/webroot文件夹下,如下图所示:
2.4 同步数据库信息至数据决策系统
2.4.1 数据准备
准备一张用户信息表,其中 password 数据列中的密码为 md5( 16位大写)加密后的密文。表结构如下图所示:
示例:若用户 a 在登录页面实际输入的明文密码为:123456,则 password 列中存放的密码为 md5(16位大写)加密后的密文:49BA59ABBE56E057。
点击下载用户信息表:同步用户.xlsx
2.4.2 新增服务器数据集
使用 Navicat 等第三方工具,将上表导入数据库,并建立系统与该数据库的 数据连接 。下文将以 FRDemo 数据库为例。
1)管理员登录数据决策系统,点击「管理系统>数据连接>服务器数据集」,创建「SQL数据集」。如下图所示:
2)设置数据集名称为「同步用户」,数据连接选择「FRDemo」,SQL 语句为:
select * from 同步用户
2.4.3 同步用户
1)管理员登录数据决策系统,点击「管理系统>用户管理>所有用户」,点击「同步用户」。
跳出提示框「是否保留现有非同步数据,包括导入/添加的用户、部门职务、角色」,如下图所示:
注1:本章是针对「之前从未进行过同步用户」、「同步用户未开启状态下执行首次同步操作」的数据更新规则。
若之前同步过用户,在同步用户开启状态下执行非首次同步操作将不会出现此节提示弹窗,也不会按照此节中更新规则进行同步。
注2:同步的用户与「手动添加/导入的用户」可并存。
不同选择对应的更新逻辑如下:
选择 | 定义 |
---|---|
保留 | 如果现有用户不在同步的服务器数据集中,该用户信息和权限将被保留,不修改 如果现有用户在服务器数据集中(用户名相同):
|
清空 | 平台现有「手动添加/导入的用户」的用户名、姓名、密码、手机、邮箱、部门、职务、角色、权限均被删除,重新同步用户 |
注:根据选择的更新逻辑,首次同步后有部分用户信息被更新。
之后能被自动更新的只有已变为同步类型的用户。
之后的同步,数据集不能再覆盖更新内置数据,否则将冲突报错。
2)配置同步用户。如下图所示:
用户来源选择为:2.4.2 中准备的服务器数据集「同步用户」
密码选择为:password
加密方式选择为:内置SHA加密
点击「确定」,同步用户成功。如下图所示:
2.5 效果预览
用户 a 在浏览器中访问链接:http://localhost:8075/webroot/login.html
在登录页面输入明文密码:123456,点击「登录」,成功登录数据决策系统。如下图所示:
注:示例动图为了展示加密登录效果,在 2.2 节提供的代码基础上添加了alert(url); 等来查看页面信息,用户可自行添加查看加密登录效果。
用户点击「登录」按钮后,前台会对用户输入的登录信息进行以下处理:
1)对输入的密码进行 md5 加密,将最终得到的密文作为接口入参。
示例:输入密码为:123456,md5 加密后得到密文:49BA59ABBE56E057。
2)调用前台单点登录接口,将用户信息传输至数据决策系统进行登录验证。
若传输的密码与数据决策系统中所存储的密码一致,即可成功访问数据决策系统。如下图所示: