免密登录-python
要完成后台管理系统登录功能,通过查看登录页面,我们可以了解到,我们需要编写验证码图片获取接口和登录处理接口,然后在登录页面的HTML上编写AJAX。
在进行接口开发之前,还有一个重要的事情要处理,那就是对站点进行初始化,如果不进行初始化,那么独立文件编写的接口将会找不到,要将异常错误写入日志文件也会找不到路径,下面先上代码。
打开main.py文件,改为下面代码(大家可以比较一下和之前代码有什么不同)

1 #!/usr/bin/evn python
2 # coding=utf-8
3
4 import bottle
5 import sys
6 import os
7 import logging
8 import urllib.parse
9 from bottle import default_app, get, run, request, hook
10 from beaker.middleware import SessionMiddleware
11
12 # 导入工具函数包
13 from common import web_helper, log_helper
14 # 导入api代码模块(初始化api文件夹里的各个访问路由,这一句不能删除,删除后将无法访问api文件夹里的各个接口)
15 import api
16
17 #############################################
18 # 初始化bottle框架相关参数
19 #############################################
20 # 获取当前main.py文件所在服务器的绝对路径
21 program_path = os.path.split(os.path.realpath(__file__))[0]
22 # 将路径添加到python环境变量中
23 sys.path.append(program_path)
24 # 让提交数据最大改为2M(如果想上传更多的文件,可以在这里进行修改)
25 bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024 * 2
26
27 #############################################
28 # 初始化日志相关参数
29 #############################################
30 # 如果日志目录log文件夹不存在,则创建日志目录
31 if not os.path.exists('log'):
32 os.mkdir('log')
33 # 初始化日志目录路径
34 log_path = os.path.join(program_path, 'log')
35 # 定义日志输出格式与路径
36 logging.basicConfig(level=logging.INFO,
37 format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
38 filename="%s/info.log" % log_path,
39 filemode='a')
40
41 # 设置session参数
42 session_opts = {
43 'session.type': 'file',
44 'session.cookie_expires': 3600,
45 'session.data_dir': '/tmp/sessions/simple',
46 'session.auto': True
47 }
48
49
50 @hook('before_request')
51 def validate():
52 """使用勾子处理接口访问事件"""
53
54 # 获取当前访问的Url路径
55 path_info = request.environ.get("PATH_INFO")
56 # 过滤不用做任何操作的路由(即过滤不用进行判断是否登录和记录日志的url)
57 if path_info in ['/favicon.ico', '/', '/api/verify/']:
58 return
59 ### 记录客户端提交的参数 ###
60 # 获取当前访问url路径与ip
61 request_log = 'url:' + path_info + ' ip:' + web_helper.get_ip()
62 try:
63 # 添加json方式提交的参数
64 if request.json:
65 request_log = request_log + ' params(json):' + urllib.parse.unquote(str(request.json))
66 except:
67 pass
68 try:
69 # 添加GET方式提交的参数
70 if request.query_string:
71 request_log = request_log + ' params(get):' + urllib.parse.unquote(str(request.query_string))
72 # 添加POST方式提交的参数
73 if request.method == 'POST':
74 request_log = request_log + ' params(post):' + urllib.parse.unquote(str(request.params.__dict__))
75 # 存储到日志文件中
76 log_helper.info(request_log)
77 except:
78 pass
79
80 # 处理ajax提交的put、delete等请求转换为对应的请求路由(由于AJAX不支持RESTful风格提交,所以需要在这里处理一下,对提交方式进行转换)
81 if request.method == 'POST' and request.POST.get('_method'):
82 request.environ['REQUEST_METHOD'] = request.POST.get('_method', '')
83
84 # 过滤不用进行登录权限判断的路由(登录与退出登录不用检查是否已经登录)
85 url_list = ["/api/login/", "/api/logout/"]
86 if path_info in url_list:
87 pass
88 else:
89 # 已经登录成功的用户session肯定有值,没有值的就是未登录
90 session = web_helper.get_session()
91 # 获取用户id
92 manager_id = session.get('id', 0)
93 login_name = session.get('login_name', 0)
94 # 判断用户是否登录
95 if not manager_id or not login_name:
96 web_helper.return_raise(web_helper.return_msg(-404, "您的登录已失效,请重新登录"))
97
98
99
100 # 函数主入口
101 if __name__ == '__main__':
102 app_argv = SessionMiddleware(default_app(), session_opts)
103 run(app=app_argv, host='0.0.0.0', port=9090, debug=True, reloader=True)
104 else:
105 # 使用uwsgi方式处理python访问时,必须要添加这一句代码,不然无法访问
106 application = SessionMiddleware(default_app(), session_opts)

main.py文件里有详细的注释说明,所以不进行细说,在这里讲一讲文件大体的思路。
因为我们编写的接口文件都放在api文件夹中,当web服务启动后需要将api里的接口文件自动装载进来,让我们可以通过url访问里面的接口,所以需要在main.py这个入口函数中,对api文件夹里的接口文件进行导入,前面讲解到我们api文件夹里有一个__init__.py文件,它会自动帮我们导入当前文件夹里的所有文件,所以我们只需要在main.py中添加import api这一行代码就可以了。
另外,我们需要告诉python服务当前程序所在的路径,所以需要将当前文件所在的绝对路径添加到python环境变量中(第21到23行)
我们要记录异常信息到日志,要记录客户端访问的url与提交的请求参数,方便出错时帮助我们进行排查错误,所以要初始化日志文件格式与存储路径(第30到39行)
bottle框架有两个好用的勾子处理函数(具体流程如下图),客户端访问接口时,首先会从bottle web服务绑定的入口进入,然后调用before_request这个勾子函数(第50到97行),执行完里面的代码后再进入对应的接口函数里,当接口函数运行完毕后,又会调用after_request这个勾子函数(我们使用了nginx处理前端访问服务不存在跨域问题,所以main.py就没有添加这个勾子函数),运行完里面的代码后才返回最终结果给客户端。所以我们有很多事情可以放在这两个勾子函数中进行处理。before_request中我们可以运行初始化操作、记录客户端访问的url与提交的请求参数操作、判断用户是否已经登录等操作(如果没有这个勾子函数,我们要判断用户是否登录,就必须在每个接口文件中处理,这样一方面代码会很冗余,出现大量重复的没有必要的代码,另一方面也很容易出错或遗漏掉,造成后端权限访问漏洞。而after_request这个函数通过是用来处理输出HTTP头信息等内容,比如跨域处理等。

第55到78行,会将客户端访问的url与各种方式提交的请求参数记录到日志。对于一些不想记录到日志的访问,可以添加到第57行。(如下图)

第90到96行,对登录用户访问进行处理,如果未登录的,则会返回-404状态,客户端的ajax接收到这个状态后,自行处理跳转到登录页面。
验证码接口
我们在api文件夹中创建verify.py文件

#!/usr/bin/python
#coding: utf-8 from io import BytesIO
from bottle import get, response
from common import verify_helper, log_helper, web_helper @get('/api/verify/')
def get_verify():
"""生成验证码图片"""
try:
# 获取生成验证码图片与验证码
code_img, verify_code = verify_helper.create_verify_code() # 将字符串转化成大写保存到session中
s = web_helper.get_session()
s['verify_code'] = verify_code.upper()
s.save() # 输出图片流
buffer = BytesIO()
code_img.save(buffer, "jpeg")
code_img.close()
response.set_header('Content-Type', 'image/jpg')
return buffer.getvalue()
except Exception as e:
log_helper.error(str(e.args))

code_img, verify_code = verify_helper.create_verify_code() :运行verify_helper.create_verify_code() ,会返回图片流和验证码,python语言执行函数后,可以直接返回字符串、数值、元组、字典、列表等各种类型的值,返回元组类型值时,就可以使用这样的方式进行接收。
log_helper.error(str(e.args)) 这是我们前面工具函数包时所讲到的错误记录函数,当生成验证码出现异常时,它会将异常信息记录到日志文件中,并将异常发送到我们指定的邮箱。
添加完这个文件后,我们就可以运行一下main.py,然后在浏览器中输入http://127.0.0.1:9090/api/verify/或http://127.0.0.1:81/api/verify/,就可以看到生成的验证码了(如果使用81端口无法访问)

登录接口
我们在api文件夹中创建login.py文件

1 #!/usr/bin/evn python
2 # coding=utf-8
3
4 from bottle import put
5 from common import web_helper, encrypt_helper, db_helper
6
7
8 @put('/api/login/')
9 def post_login():
10 """用户登陆验证"""
11 ##############################################################
12 # 获取并验证客户端提交的参数
13 ##############################################################
14 username = web_helper.get_form('username', '帐号')
15 password = web_helper.get_form('password', '密码')
16 verify = web_helper.get_form('verify', '验证码')
17 ip = web_helper.get_ip()
18
19 ##############################################################
20 # 从session中读取验证码信息
21 ##############################################################
22 s = web_helper.get_session()
23 verify_code = s.get('verify_code')
24 # 删除session中的验证码(验证码每提交一次就失效)
25 if 'verify_code' in s:
26 del s['verify_code']
27 s.save()
28 # 判断用户提交的验证码和存储在session中的验证码是否相同
29 if verify.upper() != verify_code:
30 return web_helper.return_msg(-1, '验证码错误')
31
32 ##############################################################
33 ### 获取登录用户记录,并进行登录验证 ###
34 ##############################################################
35 sql = """select * from manager where login_name='%s'""" % (username,)
36 # 从数据库中读取用户信息
37 manager_result = db_helper.read(sql)
38 # 判断用户记录是否存在
39 if not manager_result:
40 return web_helper.return_msg(-1, '账户不存在')
41
42 ##############################################################
43 ### 验证用户登录密码与状态 ###
44 ##############################################################
45 # 对客户端提交上来的验证进行md5加密将转为大写(为了密码的保密性,这里进行双重md5加密,加密时从第一次加密后的密串中提取一段字符串出来进行再次加密,提取的串大家可以自由设定)
46 # pwd = encrypt_helper.md5(encrypt_helper.md5(password)[1:30]).upper()
47 # 对客户端提交上来的验证进行md5加密将转为大写(只加密一次)
48 pwd = encrypt_helper.md5(password).upper()
49 # 检查登录密码输入是否正确
50 if pwd != manager_result[0].get('login_password', ''):
51 return web_helper.return_msg(-1, '密码错误')
52 # 检查该账号虽否禁用了
53 if manager_result[0].get('is_enable', 0) == 0:
54 return web_helper.return_msg(-1, '账号已被禁用')
55
56 ##############################################################
57 ### 把用户信息保存到session中 ###
58 ##############################################################
59 manager_id = manager_result[0].get('id', 0)
60 s['id'] = manager_id
61 s['login_name'] = username
62 s.save()
63
64 ##############################################################
65 ### 更新用户信息到数据库 ###
66 ##############################################################
67 # 更新当前管理员最后登录时间、Ip与登录次数(字段说明,请看数据字典)
68 sql = """update manager set last_login_time=%s, last_login_ip=%s, login_count=login_count+1 where id=%s"""
69 # 组合更新值
70 vars = ('now()', ip, manager_id,)
71 # 写入数据库
72 db_helper.write(sql, vars)
73
74 return web_helper.return_msg(0, '登录成功')

在编写登录接口前,我们首先要了解登录接口处理的流程是怎么样的

login.py后台登录处理接口代码可以看到,路由我们使用的是@put('/api/login/'),RESTful风格中,post是用于新增记录,put是用于修改或改变服务器数据,登录我理解它肯定不是新增,它是改变用户登录的状态,所以这里使用put方式接收
登录接口的代码有详细的注释,还有上面的流程图,所以就不再深入解说,大家自己看代码,如有不明白的,文章后面留言。
前端登录html页面(login.html)

1 <!DOCTYPE HTML>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <meta name="renderer" content="webkit|ie-comp|ie-stand">
6 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
7 <meta name="viewport"
8 content="width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
9 <meta http-equiv="Cache-Control" content="no-siteapp"/>
10 <!--[if lt IE 9]>
11 <script type="text/javascript" src="lib/html5shiv.js"></script>
12 <script type="text/javascript" src="lib/respond.min.js"></script>
13 <![endif]-->
14 <link href="static/h-ui/css/H-ui.min.css" rel="stylesheet" type="text/css"/>
15 <link href="static/h-ui.admin/css/H-ui.login.css" rel="stylesheet" type="text/css"/>
16 <link href="static/h-ui.admin/css/style.css" rel="stylesheet" type="text/css"/>
17 <link href="lib/Hui-iconfont/1.0.8/iconfont.css" rel="stylesheet" type="text/css"/>
18 <!--[if IE 6]>
19 <script type="text/javascript" src="lib/DD_belatedPNG_0.0.8a-min.js"></script>
20 <script>DD_belatedPNG.fix('*');</script>
21 <![endif]-->
22 <title>后台登录 - H-ui.admin v3.1</title>
23 <meta name="keywords" content="H-ui.admin v3.1,H-ui网站后台模版,后台模版下载,后台管理系统模版,HTML后台模版下载">
24 <meta name="description" content="H-ui.admin v3.1,是一款由国人开发的轻量级扁平化网站后台模板,完全免费开源的网站后台管理系统模版,适合中小型CMS后台系统。">
25 </head>
26 <body>
27 <input type="hidden" id="TenantId" name="TenantId" value=""/>
28 <div class="header"></div>
29 <div class="loginWraper">
30 <div id="loginform" class="loginBox">
31 <form class="form form-horizontal">
32 <div class="row cl">
33 <label class="form-label col-xs-3"><i class="Hui-iconfont"></i></label>
34 <div class="formControls col-xs-8">
35 <input id="username" name="username" type="text" placeholder="账号" class="input-text size-L">
36 </div>
37 </div>
38 <div class="row cl">
39 <label class="form-label col-xs-3"><i class="Hui-iconfont"></i></label>
40 <div class="formControls col-xs-8">
41 <input id="password" name="password" type="password" placeholder="密码" class="input-text size-L">
42 </div>
43 </div>
44 <div class="row cl">
45 <div class="formControls col-xs-8 col-xs-offset-3">
46 <input id="verify" name="verify" class="input-text size-L" type="text" value=""
47 style="width:150px;">
48 <img style="width: 100px;height: 40px;padding: 0px;vertical-align:middle" id="verifycode"
49 src="/api/verify/" onclick="get_verify()"> <a href="javascript:;" onclick="get_verify()">看不清,换一张</a></div>
50 </div>
51 <div class="row cl">
52 <div>
53 <h5 class="formControls col-xs-8 col-xs-offset-3"><span id="msg" style="color:#F00"></span></h5>
54 </div>
55 </div>
56 <div class="row cl">
57 <div class="col-xs-8 col-xs-offset-3">
58 <input type="button" class="btn btn-success size-L" onclick="submit1()"
59 value=" 登 录 ">
60 </div>
61 </div>
62 </form>
63 </div>
64 </div>
65 <div class="footer">Copyright 你的公司名称 by H-ui.admin v3.1</div>
66 <script type="text/javascript" src="lib/jquery/1.9.1/jquery.min.js"></script>
67 <script type="text/javascript" src="static/h-ui/js/H-ui.min.js"></script>
68 <script>
69 function submit1() {
70 if ($("#username").val().trim().length == '') {
71 $("#msg").html('').append('请输入用户名');
72 }
73 else if ($("#password").val().trim().length == '') {
74 $("#msg").html('').append('请输入登录密码');
75 }
76 else if ($("#verify").val().trim().length != 4) {
77 $("#msg").html('').append('请输入4位图形验证码');
78 } else {
79 username = $("#username").val();
80 password = $("#password").val();
81 verify = $("#verify").val();
82 $.ajax({
83 type: 'POST',
84 url: "/api/login/",
85 data: {'_method': 'put', 'username': username, 'password': password, 'verify': verify},
86 dataType: 'json',
87 success: function (data) {
88 if(data && data.state>-1){
89 $(location).prop('href', 'main.html');
90 }
91 else{
92 $("#msg").html('').append(data.msg);
93 get_verify();
94 }
95 },
96 error: function(data){
97 if (data){
98 alert(data.msg);
99 }
100 get_verify();
101 }
102 });
103 }
104 }
105
106 function get_verify() {
107 $("#verifycode").attr("src", "/api/verify/?" + 100 * Math.random());
108 }
109
110 </script>
111 </body>
112 </html>

对前面下载的login.html页面进行了微调,添加了请求的AJAX代码。
由于火狐和谷歌运行AJAX不支持PUT、DELETE等提交方式,所以AJAX提交时type类型还是POST方式,在提交参数项里面,需要增加 _method 这个参数,值为put。(由于本系列使用的是RESTful风格,所以虽然有点麻烦,但不影响我们的使用)
html和js我也不进行详细说明,大家自己看代码吧,如果大家都要求需要对js写注释的,我到时再添加注释进去。
相关页面功能都完成了,接下来就是进行运行调试
在浏览器中输入:http://127.0.0.1:81/login.html 然后输入账号:admin,密码:123456,还有验证码

点击登录,能正常跳转到http://127.0.0.1:81/main.html 页面,就表示登录接口能正常使用了。

大家想要熟悉登录接口代码的运行,最好使用debug运行跟踪一下,看看每一行代码是怎么运行的,就清楚了。当然如果想要加深理解,最佳方式是照着代码手打一次,每完成几行就debug运行一下,看看执行效果。
免密登录-python的更多相关文章
- Shell脚本实现SSH免密登录及批量配置管理
本节索引 场景分析 ssh免密登录 pssh工具批量管理 SHELL自动化脚本 本篇总结 场景分析 作为一个运维工程师,不是每个人工作的环境都想阿里.腾讯那样,动不动就上亿的PV量,上万台服务器.我们 ...
- selenium操作cookies实现免密登录,自动发微博
一直想用selenium实现个小功能,比如发微博之类的,但是有的网站在登录会有验证码,没想到太好的方法解决,于是想到利用cookies来登录网站 第一步:获取一个可用的cookies,获取的cooki ...
- 批量实现ssh免密登录
本节索引 场景分析 ssh免密登录 pssh工具批量管理 SHELL自动化脚本 本篇总结 场景分析 作为一个运维工程师,不是每个人工作的环境都想阿里.腾讯那样,动不动就上亿的PV量,上万台服务器.我们 ...
- ubuntu16.04服务器配置ssh免密登录
原版资料英文,链接在此 https://www.digitalocean.com/community/tutorials/how-to-set-up-ssh-keys--2 在客户端操作 ssh-k ...
- 基于vagrant工具在win7下免密登录linux
一.SSH加密方式 SSH采用的是"非对称密钥系统",即耳熟能详的公钥私钥加密系统,其安全验证又分为两种级别. 1. 基于口令的安全验证 这种方式使用用户名密码进行联机登录,一般情 ...
- openstack私有云布署实践【11.3 计算nova - compute节点-nova用户免密登录(用于云主机冷迁移+扩展云主机大小)】
云主机迁移+扩展云主机大小 ,官方说它依赖nova用户之间的免密登录.确保每个resion区域的compute节点服务器他们可以相互SSH免密 compute1-7 他们相互SSH免密 k ...
- 关于Ubuntu的ssh免密登录
1.打开"终端窗口",输入"sudo apt-get update"-->回车-->"输入当前登录用户的管理员密码"--> ...
- linux(十)配置ssh免密登录实现
知道ssh的朋友应该知道它是用来干什么的,如果你不知道什么是ssh远程登录的话,可以去看一下我的上一篇博客,关于linux的网络基础的知识.备注:ssh是用于远端登入.执行ssh指令开启终端机阶段作业 ...
- 【Linux】ssh免密登录
一.ssh免密配置 ssh 无密码登录要使用公钥与私钥.linux下可以用用ssh-keygen生成公钥/私钥对,下面我以CentOS为例.有机器A(192.168.1.155),B(192.168. ...
随机推荐
- 寻找春天 九宫格日记-2014.04.26
写九宫格日记 总会在听到某一首歌的时候泪流满面:总会在看到某个似曾相识的背影的时候惆怅莫名,总会在嗅到某种香味的时候默默发呆,总会在经过某个地方的时候频频回首.生命有限,不要把它浪费在重复别人的生活上 ...
- Jumpstart for Oracle Service Bus Development
http://www.oracle.com/technetwork/articles/jumpstart-for-osb-development-page--097357.html Tutorial ...
- Linux内核通用队列的使用笔记(读linux内核设计与实现)
Linux内核通用队列实现 Kfifo位置:kernel/kififo.c 使用需要包含头文件#include <kernel/kififo> 1.创建队列(动态创建)int kfifo_ ...
- java集合类中的迭代器模式
不说模式的问题,看一个<<设计模式之禅>>里面的例子. 老板要看到公司了各个项目的情况.(我知道我这个概述很让人头大,看代码吧) 示例程序 v1 package Iterato ...
- Android虚拟设备访问WebSocket问题
Android虚拟设备访问WebSocket问题 最近写erlang的WebSocket网站,需要运行在RHEL6上,用Android设备访问. 可惜AVD无法访问主机 Win7上的虚拟机(RHEL6 ...
- 高仿qq健康
概述 学习别人的代码,在此基础上 优化代码结构 增加动画 要点记录 通过mRatio参数,让宽高始终是一个比例 贝塞尔曲线手动画矩形圆角 画虚线 根据基准点绘制文字 属性动画的使用 画笔宽度的自适应 ...
- javascript操作select元素一例
熟悉一下js对select元素的操作,html页面中建立一个form,其中包含一个select元素和submit按钮. 当选择select中某一项时改变其文字,当select中所有项的文字都改变后,重 ...
- shim & polyfill
在JavaScript中,经常提到shim和polyfill,polyfill是shim的一种.shim 是将不同 api 封装成一种,比如 jQuery 的 $.ajax 封装了 XMLHttpRe ...
- css3中的布局相关样式
web页面中的布局是指在页面中如何对标题.导航栏.主要内容.脚注.表单等各种构成要素进行合理编辑.在css3之前,主要使用float属性或position属性进行页面中的简单布局,但是也存在一些缺点, ...
- ORACLE分页SQL语句(转载)
1.根据ROWID来分select * from t_xiaoxi where rowid in(select rid from (select rownum rn,rid from(select r ...