flask-sqlalchemy 同时连接多个数据库,动态获得不同的session

目前正在用Flask做一个小项目,持久层用的是flask-sqlalchemy, 这个和直接使用sqlalchemy还略有些不同。
项目中有个需求,要从另外一个mssqlserver数据库中读取数据,查到的很多资料都是需要再次在代码定义数据库的表结构,通过定义不同的bind,然后使用关键字”bind_key“把表映射的不同的数据库中。这样略有些麻烦,要对接的mssqlserver是另外一个C/S系统的数据库,主要是读取报表功能,已经找到了一个方法,可以通过在界面上操作,直接获取到所有对数据库的操作。

1
2
3
4
5
6
7
SELECT st.text as sql_statement,
qs.creation_time as plan_last_compiled,
qs.last_execution_time as plan_last_executed,
qs.execution_count as plan_executed_count
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) st
order by plan_last_compiled desc

执行这条sql就可以知道这个客户端对sqlserver最终执行了什么,所以简单的操作就是直接把sql语句拿到我们的系统里直接执行就可以了,所以就需要一个方法传入一个变量,就直接拿到特殊的session,而这个session又是ORM框架管理,查了很多资料,包括flask-sqlalchemy的官网后都没有找到直接答案,自己翻了翻源码,发现这样实现是可以的。

配置部分:

db_setting.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from contextlib import contextmanager

from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

from greenwater_app import app


app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db/database1.db'
app.config['SQLALCHEMY_BINDS'] = {
'database2': 'sqlite:///db/database2.db'
}
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_ECHO'] = True

db = SQLAlchemy(app)

migrate = Migrate(app, db, compare_type=True)


@contextmanager
def session_scope(bind='default'):
"""Provide a transactional scope around a series of operations."""

if bind != 'default':
# 切换到指定的数据库,获取scope session
s = db.create_scoped_session(options={'bind': db.get_engine(db.get_app(), bind)})
else:
s = db.session

s.expire_on_commit = False
try:
yield s
s.commit()
except:
s.rollback()
raise
finally:
s.close()

正常的按照官网的逻辑是可以使用的。具体参考这里https://flask-sqlalchemy.palletsprojects.com/en/2.x/binds/

如果想直接拿到一个session去执行sql语句,可以这样:

1
2
3
4
5
6
7
8
sql = 'select * from user'
with session_scope(bind='database2') as s:
res = s.execute(sql).fetchall()
print([dict(r) for r in res])

with session_scope() as s:
res = s.execute(sql).fetchall()
print([dict(r) for r in res])

通过给session_scope传入不同的变量,就可以获得一个针对不同的数据库的session。

无花果竟然结果了

2021年3月22日在公司东面操场的空地上买了两只无花果的枝条种上了。
无花果刚种下的时候

每周去浇浇水,看看它,在最难熬的那段时间看着它每周都有成长,也算是一个不错的心里寄托。
无花果

2021年11月16日,发现虽然叶子全部都掉了,但是竟然结果了。
树叶全落的无花果
结了一个小无花果

应该好好总结过去的这半年,经历了一些,如果没有很好的收获,但是像这个无花果一样也有一个小小的结果。

Edge浏览器和搜狗输入法不兼容

Edge浏览器升级以后突然发现巨卡无比,尤其是新开标签页和搜索之前,浏览器整个陷入卡顿,无响应。
感觉不像是电脑整体卡住了,CPU的使用率也只是短暂的到达100%,最后实在找不到原因,也忍不了了,电脑也该重装了,就拿去IT那里给重装了。
结果重装完发现是好的,但是使用了半天发现问题又来了,排除了浏览器的所有插件,最后发现可能是搜狗输入法的问题,只要打开输入法,就用卡住。
但是Edge和搜狗都想用,尝试了半天修改搜狗的配置,最后发现是因为一个皮肤引起的,把搜狗的皮肤换成默认的,然后调整一下字体和颜色。
问题解决!

获取任意一个小程序的页面路径

学习了一下Vue和uni-app,发现做个小程序还是挺容易的,于是乎顺手搞了一个,现在已经发布了。
扫描这个码可以体验一下。
tancheng

做这个小程序的时候,遇到一个小问题,有一个按钮需要直接跳转到携程的买票页面,可是在携程的页面上无法直接找到当前页面的path, 搜索了好久也没有发现靠谱的答案。
最后发现,在小程序的文章编辑页面,可以实现这个功能。先公众号管理后台,先新建一个文章,然后在正文中插入小程序,输入小程序的ID,(这个ID可以在小程序的详情中找到)。
getpath1

然后下一步中,选择“获取更多页面路径”,输入自己的微信号。

getpath2

打开自己的手机,点击小程序右上角的三个点,弹出的页面中就多出来一个新的复制路径的按钮。

getpath3

这个设计还是挺巧妙的,点个赞!

在python字典组成的数组中,按照其中的字典的一个key的值进行排序

对接海康的系统,取出客流信息后,发现返回的结果是乱序的, 并没有按照时间先后顺序排列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
[
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 28,
"flowOutNum": 17,
"holdValue": 0,
"createTime": 1622070960029,
"updateTime": 1622073660035,
"statTime": "2021-05-27T07:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 25,
"flowOutNum": 44,
"holdValue": 0,
"createTime": 1622103360028,
"updateTime": 1622106060028,
"statTime": "2021-05-27T16:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 40,
"flowOutNum": 33,
"holdValue": 0,
"createTime": 1622078160028,
"updateTime": 1622080920032,
"statTime": "2021-05-27T09:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 33,
"flowOutNum": 41,
"holdValue": 0,
"createTime": 1622081760029,
"updateTime": 1622084460028,
"statTime": "2021-05-27T10:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 28,
"flowOutNum": 30,
"holdValue": 0,
"createTime": 1622096160028,
"updateTime": 1622098860029,
"statTime": "2021-05-27T14:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 32,
"flowOutNum": 24,
"holdValue": 0,
"createTime": 1622092560213,
"updateTime": 1622095260028,
"statTime": "2021-05-27T13:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 0,
"holdValue": 0,
"createTime": 1622045760027,
"updateTime": 1622048460027,
"statTime": "2021-05-27T00:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 0,
"holdValue": 0,
"createTime": 1622056560027,
"updateTime": 1622059260043,
"statTime": "2021-05-27T03:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 0,
"holdValue": 0,
"createTime": 1622049360031,
"updateTime": 1622052060029,
"statTime": "2021-05-27T01:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 15,
"flowOutNum": 21,
"holdValue": 0,
"createTime": 1622063760027,
"updateTime": 1622066460027,
"statTime": "2021-05-27T05:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 29,
"flowOutNum": 19,
"holdValue": 0,
"createTime": 1622067360029,
"updateTime": 1622070060027,
"statTime": "2021-05-27T06:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 0,
"holdValue": 0,
"createTime": 1622052960029,
"updateTime": 1622055660027,
"statTime": "2021-05-27T02:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 2,
"holdValue": 0,
"createTime": 1622060160028,
"updateTime": 1622062861776,
"statTime": "2021-05-27T04:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 30,
"flowOutNum": 28,
"holdValue": 0,
"createTime": 1622074560035,
"updateTime": 1622077260037,
"statTime": "2021-05-27T08:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 19,
"flowOutNum": 51,
"holdValue": 0,
"createTime": 1622085360028,
"updateTime": 1622088060029,
"statTime": "2021-05-27T11:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 27,
"flowOutNum": 23,
"holdValue": 0,
"createTime": 1622088960033,
"updateTime": 1622091660029,
"statTime": "2021-05-27T12:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 16,
"flowOutNum": 21,
"holdValue": 0,
"createTime": 1622099760030,
"updateTime": 1622102460029,
"statTime": "2021-05-27T15:00:00.000+08:00"
}
]

Python的 sorted 内置函数提供了一个lambda 参数可以对它排序。

1
2
data = hik_http_request(url, body).json()['data']['list']
return sorted(data, key=lambda e: e.__getitem__('createTime'))

排序后的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
[
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 0,
"holdValue": 0,
"createTime": 1622045760027,
"updateTime": 1622048460027,
"statTime": "2021-05-27T00:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 0,
"holdValue": 0,
"createTime": 1622049360031,
"updateTime": 1622052060029,
"statTime": "2021-05-27T01:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 0,
"holdValue": 0,
"createTime": 1622052960029,
"updateTime": 1622055660027,
"statTime": "2021-05-27T02:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 0,
"holdValue": 0,
"createTime": 1622056560027,
"updateTime": 1622059260043,
"statTime": "2021-05-27T03:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 0,
"flowOutNum": 2,
"holdValue": 0,
"createTime": 1622060160028,
"updateTime": 1622062861776,
"statTime": "2021-05-27T04:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 15,
"flowOutNum": 21,
"holdValue": 0,
"createTime": 1622063760027,
"updateTime": 1622066460027,
"statTime": "2021-05-27T05:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 29,
"flowOutNum": 19,
"holdValue": 0,
"createTime": 1622067360029,
"updateTime": 1622070060027,
"statTime": "2021-05-27T06:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 28,
"flowOutNum": 17,
"holdValue": 0,
"createTime": 1622070960029,
"updateTime": 1622073660035,
"statTime": "2021-05-27T07:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 30,
"flowOutNum": 28,
"holdValue": 0,
"createTime": 1622074560035,
"updateTime": 1622077260037,
"statTime": "2021-05-27T08:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 40,
"flowOutNum": 33,
"holdValue": 0,
"createTime": 1622078160028,
"updateTime": 1622080920032,
"statTime": "2021-05-27T09:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 33,
"flowOutNum": 41,
"holdValue": 0,
"createTime": 1622081760029,
"updateTime": 1622084460028,
"statTime": "2021-05-27T10:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 19,
"flowOutNum": 51,
"holdValue": 0,
"createTime": 1622085360028,
"updateTime": 1622088060029,
"statTime": "2021-05-27T11:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 27,
"flowOutNum": 23,
"holdValue": 0,
"createTime": 1622088960033,
"updateTime": 1622091660029,
"statTime": "2021-05-27T12:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 32,
"flowOutNum": 24,
"holdValue": 0,
"createTime": 1622092560213,
"updateTime": 1622095260028,
"statTime": "2021-05-27T13:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 28,
"flowOutNum": 30,
"holdValue": 0,
"createTime": 1622096160028,
"updateTime": 1622098860029,
"statTime": "2021-05-27T14:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 16,
"flowOutNum": 21,
"holdValue": 0,
"createTime": 1622099760030,
"updateTime": 1622102460029,
"statTime": "2021-05-27T15:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 25,
"flowOutNum": 44,
"holdValue": 0,
"createTime": 1622103360028,
"updateTime": 1622106060028,
"statTime": "2021-05-27T16:00:00.000+08:00"
},
{
"groupId": "b5900064-b435-4886-8061-2ba39148c7c6",
"groupName": "主入口",
"flowInNum": 7,
"flowOutNum": 8,
"holdValue": 0,
"createTime": 1622106960027,
"updateTime": 1622106960027,
"statTime": "2021-05-27T17:00:00.000+08:00"
}
]

使用layui时,由jinja2动态生成的checkbox,如何在赋值的时候选中

官网的例子 https://www.layui.com/demo/form.html

1
2
3
4
5
6
7
8
9
10
11
<div class="layui-input-block">
<input type="checkbox" name="like[write]" title="写作">
<input type="checkbox" name="like[read]" title="阅读" checked="">
<input type="checkbox" name="like[game]" title="游戏">
</div>

layui.$('#LAY-component-form-setval').on('click', function(){
form.val('example', {
"like[write]": true //复选框选中状态
});
});

如果此时input是动态生成的,比如这样:

1
2
3
4
5
<div class="layui-input-inline" style="width:260px">
{% for role in roles %}
<input type="checkbox" name="role[{{ role.role_name }}]" value="{{ role.role_name }}" title="{{ role.role_name }}">
{% endfor %}
</div>

如果用这样的写法, 发现不好用,在浏览器里debug后发现,key被直接当错字符串传下去了,并没有翻译成真正的值,真是坑。

1
2
3
4
5
6
7
8
var arr = data['roles'].split('#');
$("input").prop("checked",false);
$.each( arr, function( index, value ) {
key = "role["+value+"]";
form.val('example', {
key: true //复选框选中状态
});
});

所以只能改成这样的写法,用选择器点击上,但是样式没有发生变化,检查页面发现,checkbox的按钮实际上已经显示checked了。

1
2
3
4
5
6
var arr = data['roles'].split('#');
$("input").prop("checked",false);
$.each( arr, function( index, value ) {
key = "role["+value+"]";
$("input[name='"+key+"']").prop("checked",true);
});

查了文档后发现,只要重新渲染一下就可以了。

https://www.layui.com/doc/base/faq.html#form

1
form.render('checkbox');

飞天信诚软件锁 rockey arm 在web环境下开发记录

需求:

开发了一套web的系统,服务器端用的Python实现,现在需要客户用浏览器登录的时候检查客户电脑上是否插上了软件锁加密狗,如果有则继续登录,如果没有果断拒绝登录。

硬件采购:

最后选择了飞天信诚的rockey ram,淘宝评价看起来不错。
https://www.ftsafe.com.cn/products/rockey/ROCKEY-ARM
从售前获取到的开发包是Rockey1Smart–V1.0007-20180201。

工作原理

加密狗支持一系列的加密算法,当前的加密狗版本是内置RSA、ECC、DES、SHA1和国密算法(SM2、SM3、SM4)等高数据加密算法,我们这里直接选中RSA。
加密狗提供管理软件和接口,允许我们写入需要的对应各种算法的密钥,同时又提供接口供我们调用,传入明文,就会得到密文。同时加密狗又能保证我们写入的密钥是完全不可以读出来的,这样就保证了私钥的安全。
在需要验证加密狗的时候,比如登录的时候,我们先从服务器端申请一个10位的随机字符串,然后在浏览器调用JS API获取到加密狗计算的密文,然后把密文和账号密码一同发往服务器,服务用公钥和存在session里的随机字符串去验证这段密文是否有效,如果是就正常返回逻辑。
另外如果服务器也需要保护的话,服务器上也插上软件锁加密狗,但是加密狗里存的是公钥,服务器端验证的时候直接调用加密狗的算法去验证,而不是自己算,这样就保证了公钥的安全。

开发工作:

准备部分

  1. 客户端OS装上一个EXE的插件,插件在开发包里的位置sdk\Extends\Samples\Multi-Browser\JavaScript\JSRockeyArmWebSocketSetup_x64.exe, 或者JSRockeyArmWebSocketSetup.exe。
  2. 据观察这个插件安装完成后会启动一个websocket服务,运行在浏览器里的js代码会发消息给这个websocket服务,然后由这个websocket服务去跟软件锁加密狗沟通,所以这个插件按照设计应该是可以跨浏览器的。
  3. 使用sdk\Tools\RyARMTool.exe配置软件锁加密狗,进行唯一化操作,牢记管理员密码!!!如果丢了,只能返厂了。
  4. 选择“文件管理”,“文件类型”选“RAS私钥文件”,然后“生成”秘钥对, dog.Rsapri和dog.Rsapub。
  5. 点“创建”按钮,填入文件ID“0002”,创建, 这个对应后面js文件里RsaPriFileID。
  6. 点刚刚创建的文件,然后“写私钥”,选择刚刚创建的“dog.Rsapri”。
  7. 利用sdk\Tools\Rsa2PemTool.exe将刚刚生成的秘钥对转成可读的pem文件,注意选择“RSA到PEM”,得到dog.pri.pem和dog.pub.pem。注意后面要用到这个dog.pub.pem。
  8. 如果后期发现这个文件的顶部不是“—–BEGIN PUBLIC KEY—–”,而是多了一个RSA,需要在这个网站转成der,然后再把der转成pem,就可以了。https://www.ssleye.com/der_pem.html

前端代码

  1. 引入3个js文件
    1. sdk\Extends\Samples\Multi-Browser\JavaScript\base64.js
    2. sdk\Extends\Samples\Multi-Browser\JavaScript\test.js
    3. sdk\Extends\Samples\Multi-Browser\JavaScript\websocket.js
  2. 登录页面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <div class="login-content">
    <div class="form">
    <img src="../static/images/login/avatar.svg">
    <h2 class="title"> </h2>
    <div style="min-height:50px;vertical-align:top;text-align;color:orange"><h5 id="feedback"></h5></div>
    <div class="input-div one">
    <div class="i">
    <i class="fa fa-user"></i>
    </div>
    <div class="div">
    <h5>用户名</h5>
    <input id="login-user" type="text" class="input">
    </div>
    </div>
    <div class="input-div pass">
    <div class="i">
    <i class="fa fa-lock"></i>
    </div>
    <div class="div">
    <h5>密码</h5>
    <input id= "login-pass" type="password" class="input">
    </div>
    </div>
    <input id="login-button" type="submit" class="btn" value="登录">
    </div>
    </div>
  3. JS逻辑, 页面打开的时候就去检查加密狗状态,登录按钮触发后再去调用加密接口。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    var ctrl = null;
    var websock = true;
    var b = new Base64();
    var ArmHandle; //加密锁句柄


    function message(status, shake = false, id = "") {
    if (shake) {
    $("#" + id).effect("shake", {
    direction: "right",
    times: 2,
    distance: 8
    }, 250);
    }
    document.getElementById("feedback").innerHTML = status;
    $("#feedback").show().delay(3000).fadeOut();
    }

    //从服务器端获取安全码
    var get_safe_code = function () {
    $.post({
    type: "GET",
    url: "/api/auth/login",
    error(response){
    message('错误:无法连接服务器,请稍后重试。',true, "signup-box");
    },
    success(response) {
    //调用加密算法
    Arm_RsaPri(response['safe_code'])
    }
    });

    };

    var login = function(ras_code){
    $.post({
    type: "POST",
    url: "/api/auth/login",
    data: {
    "username": $("#login-user").val(),
    "password": $("#login-pass").val(),
    "rsa_code": ras_code
    },
    error(response){
    message('错误:用户名或者密码错',true, "signup-box");
    },
    success(response) {
    window.location.href = '/home'
    }
    });
    }

    function checkSafeDog(){
    try{
    ctrl = new AtlCtrlForRockeyArm("{33020048-3E6B-40BE-A1D4-35577F57BF14}");
    }catch (e){
    ctrl = null;
    websock = false;
    }
    ctrl.ready(function(){
    ctrl.Arm_Enum(function(result, response){
    if (!result){
    alert("Arm_Enum error. " + response);
    }else{
    rtn = response;
    // alert("找到锁的个数为:" + rtn);
    }
    });
    Index = 0;
    ctrl.Arm_Open(function(result, response){
    if (!result){
    alert("Arm_Open error. " + response);
    }else{
    ArmHandle = response;
    console.log("加密锁句柄为:" + ArmHandle);
    if (parseInt(ArmHandle) < 0){
    message('错误:加密狗连接失败,请插入后刷新。',true, "signup-box");
    }else{
    message('加密狗连接成功')
    }

    }
    });
    });
    }



    //Rsa私钥运算
    function Arm_RsaPri(code){
    Handle = ArmHandle;
    RsaPriFileID = 0002;
    RsaPriFileSize = 1024; //文件ID为RsaPriFileID的位数,取值为1024或2048
    RsaPri_Flag = 0;
    RsaPriInData = b.encode(code);

    ctrl.Arm_RsaPri(function(result,response){
    if (!result){
    alert("Arm_RsaPri error. " + response);
    return null;
    }else{
    //私钥加密后的数据经过了base64编码传出
    RsaPriData = response;
    console.log("密文:" + RsaPriData);
    if (parseInt(RsaPriData) < 0 ){
    message('错误:加密狗连接失败,请插入后刷新。',true, "signup-box");
    }else{
    //调用登录
    login(RsaPriData)
    }
    }
    });

    }

    $(document).ready(function () {
    // 添加登录时间给登录按钮
    $(document).on("click", "#login-button", get_safe_code);
    $(document).keypress(function (e) {
    if (e.which === 13) {
    get_safe_code();
    }
    });
    //页面启动的时候检查加密狗状态
    checkSafeDog();
    });

服务端

  1. Python API。这个地方比较坑的是,python的RSA加密库有好几个版本,尝试了所有,发现都无法正常用公钥验证,怀疑和证书的版本有关系,最后没办法选择使用PHP来搞定。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    @ns.route('/login')
    class Userlogin(Resource):
    def post(self):
    form = user_form.UserForm(request.form)
    if form.validate():
    username = request.form['username'].lower()
    password = request.form['password']
    rsa_code = request.form['rsa_code']
    if rsa_code:
    result = check_rsa_code(rsa_code)
    if not (result and 'safe_code' in result and result['safe_code'] == session['safe_code']):
    return json_failed('加密狗密文校验失败')
    else:
    return json_failed('需要加密狗密文字段')
    flag, token = user_dao.credentials_valid(username, password)
    if flag:
    return json_success(token=token)
    else:
    return json_failed('用户名或者密码错')
    else:
    return json_failed()

    # 返回安全码
    def get(self):
    safe_code = ''.join(str(random.choice(range(10))) for _ in range(10))
    session['safe_code'] = safe_code #放入session用于验证
    return json_success(safe_code=safe_code)


    def check_rsa_code(rsa_code):
    check_url = "http://<php_server_ip_port>/decode.php?encrypted='%s'" % rsa_code
    act = getattr(requests, 'post')
    res = act(check_url).json()
    return res

    def json_success(msg='', **kwargs, ):
    if "code" not in kwargs:
    code = 200
    else:
    code = kwargs['code']
    return dict({'status': 'success', 'msg': msg}, **kwargs), code


    def json_failed(msg='', **kwargs):
    if "code" not in kwargs:
    code = 500
    else:
    code = kwargs['code']
    return dict({'status': 'failed', 'msg': msg}, **kwargs), code
  2. PHP代码,第4步中的check_url指向的服务器,建议直接使用phpstudy的一键部署PHP环境。
    1
    yum install -y wget && wget -O install.sh https://notdocker.xp.cn/install.sh && sh install.sh
    把下面的代码放入/www/admin/localhost_80/wwwroot/decode.php, 其中的$public_key来自dog.pub.pem。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?php

    $public_key = '-----BEGIN PUBLIC KEY-----
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxx
    -----END PUBLIC KEY-----';

    $pu_key = openssl_pkey_get_public($public_key);

    $encrypted=rawurldecode(urlencode(urldecode($_GET['encrypted'])));

    $decrypted = "";

    openssl_public_decrypt(base64_decode($encrypted),$decrypted,$pu_key);

    header('Content-Type:text/json;charset=utf-8');
    $str = array
    (
    'safe_code'=>$decrypted
    );

    $jsonencode = json_encode($str);
    echo $jsonencode;
    ?>

后期工作

现在只是在登录界面进行了验证,理论上在Token等授权有效期内,如果用户不访问登录界面,加密狗就不会被验证,所以加密狗是可以被拔下来在其他的电脑上登录的。
后面可以添加如下的功能去预防这种情况。

  1. 每次API访问后,服务端在after_request拦截器里都更新一下session里的安全码,并把安全码放入到reponse的header里。
  2. 浏览器收到response后,把安全码放入到cookie中。
  3. 再次发起请求时,把cookie里的安全码进行加密并放到request的header里发送出去。
  4. 服务端在before_request拦截器里取出header里的密文进行验证。

这样基本上就能实现时刻验证加密狗的存在了,但是应该会损失一下效率,在客户端不多的情况下,完全可以使用。

安全提取字符串中的数字

去除输入中的字母和其他字符,只留下数字

1
2
3
4
5
data = filter(lambda ch: ch in '0123456789.', str(data))
if len(list(data)) > 0:
return float(str(''.join(list(data))))
else:
return 0.0

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2021 Tiaobug All Rights Reserved.

本站总访问量