精华内容
下载资源
问答
  • Flask框架

    千次阅读 2019-10-10 18:03:29
    框架、轻量级框架不比Django框架那种重量级框架体型臃肿,内容封装的比较全使用的时候好多可以直接调用,需要代码量较少, Flask框架属于轻型框架,使用时所需代码量较多,用起来稍微麻烦一点 Flask框架pip下载就...

    微框架、轻量级框架不比Django框架那种重量级框架体型臃肿,内容封装的比较全使用的时候好多可以直接调用,需要代码量较少,
    Flask框架属于轻型框架,使用时所需代码量较多,用起来稍微麻烦一点
    Flask框架pip下载就ok了

    1. 最简单的flask程序

    from flask import Flask
    app = Flask(__name__)
    
    @app.route('/') #装饰器
    def hello_world():
        return 'Hello World!'
    
    if __name__ == '__main__':
       app.run()
    

    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述找不到路径的问题
    在这里插入图片描述
    更改代码 debug 后,不需要重新运行就可以跳转页面:
    在这里插入图片描述
    在这里插入图片描述
    flask访问服务器的端口号是 5000 ,flask框架的服务server是内置的

    2. 调试模式

    app.run(debug=True)
    app.debug = True
    

    3. 外部访问

    host = '0.0.0.0'
    

    4. 路由

    @app.route( ' / ' )
    def index():
        return 'index Page'
    
    @app.route('/hello')
    def hello():
        return 'Hello World'
    

    5. 变量规则

    要给URL添加变量部分,可以把特殊字段标记为<variable_name>,这部分将会作为命令参数传递到函数。规则可以用converter:variable_name指定一个可选的转换器

    @app.route('/user/<username>')
    def show_user_profile(username):
        return 'User %s' % username
        
    @app.route('/post/<int:post_id>')
    def show_post(post_id)
    return 'Post %d' post_id
    

    在这里插入图片描述
    在这里插入图片描述

    @app.route("/user/<username>")
    def show_user_profile(username):
        return "User %s" % username
    
    @app.route("/post/<int:post_id>")
    def show_post(post_id):
        return "Post %d" % post_id
    

    在这里插入图片描述
    可以转类型,三个类型 int 、 float 、path

    6. 唯一URL/重定向行为:

    带斜线 ( /…/ )重定向,不带斜线 ( /…) 是唯一
    Flask的URL规则都基于Werkzeug的路由模块。这个模块背后的思想是基于Apache以及更早的HTTP服务器主张的先例,保证优雅且唯一的URL

    @app.route(' /projects/ ')
    def projects():
        return  'The project page'
    
    @app.route('/about')
    def about():
        return  'The about page'
    

    7. 构造URL

    flask能匹配URL, Flask也可以生成它们,可以用 url_for() 来给指定的函数构造URL。它接受函数名作为第一参数,也接受对应URL规则的变量部分的命名参数。

    from flask import Flask,url_for
    app = Flask(__name__)
    
    @app.route("/")
    def index(): pass
    
    @app.route("/user/<username>")
    def profile(username):pass
    
    with app.test_request_context():
    print url_for('index')
    print url_for('login')
    print url_for('login',next='/')
    print url_for('profile',username='John Doe')
    

    8. HTTP方法

    @app.route('/login', methods=['GET','POST'])
    def login():
        if request.method == 'POST':
             do_the_login()
        else:
             show_the_login_form()
    

    1/ get方式
    传值的时候会暴露传输参数
    参数大小有限制

    2/ post方式
    传值的时候不会暴露传输参数
    参数大小没有限制

    上面两种方式从不同的角度看,对比一下可以看出区别。

    数据角度来看,两者一样;
    安全角度来看,可以加密

    9. 静态文件

    10. 模板渲染

    from flask import render_template
    
    @app.route('/hello/')
    @app.route('/hello/<name>')
    def hello(name=None):
        return render_template('hello.html', name=name)
    

    11. 文件上传

    index.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body >
    <form action="/login" method="post">
        用户名:<input type="text" name="username" value="">
        密码:<input type="password" name="password">
        <input type="submit" value="提交">
    
    </form>
    <p style="color: red">{{error}}</p>
    

    在这里插入图片描述
    success.html :

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/jquery-1.11.3.js"></script>
    <script src="/static/jquery.form.js"></script>
    <script>
        function upload() {
            var options = {
                type:"post",
                dataType:"text",
                success:function (data) {
                    $('#img').attr("src",data);
                }
    
            }
    
    
            $('#jvForm').ajaxSubmit(options);
        }
    
    
    </script>
    </head>
    <body>
     <h1>登录成功,欢迎{{username}}</h1>
         <form action="/upload/" method="post" enctype="multipart/form-data" id="jvForm">
             <input type="file" name="file" onchange="upload()">
             <!--<input type="submit" value="上传">-->
         </form>
    <img src="" id="img">
    </body>
    </html>
    

    在这里插入图片描述

    page_not_found.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    这是一个错误页面   404
    </body>
    </html>
    

    在这里插入图片描述

    12. Cookies

    通过cookies属性来访问Cookies, 用影响对象的 set_cookie方法 来设置Cookies 。请求对象的 cookies属性 是一个内容为 客户端提交的所有Cookies的字典。

    读取cookies:

    from flask import request
    
    @app.route('/')
    def index():
        username = request.cookies.get('username')
    

    存储cookies:

    from flask import make_response
    
    @app.route('/')
    def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp      
    

    13. 重定向(redirect)和错误

    可以用 redirect( )函数 把用户重定向到其它地方。放弃请求并返回错误代码,用 abort()函数 。这里是一个它们如何使用的例子:

    from flask import abort, redirect, url_for
    
    @app.route('/')
    def index():
        return redirect(url_for('login'))
    
    @app.route('/login')
    def login():
        abort(401)
        this_is_never_executed()   # 不可执行
    

    这是一个相当无意义的例子,因为用户会从主页面重定向到一个不能访问的页面(401意味着禁止访问),但是它展示了重定向是如何工作的。

    默认情况下,错误代码会显示一个黑白的错误页面。如果你要定制错误页面,可以使用 errorhandler()装饰器

    from flask import render_template
    
    @app.errorhandler(404)
    def page_not_found(error):
        return render_template('page_not_found.html'),404
    

    注意render_template()调用之后的404,这告诉Flask,该页的错误代码是404,即没有找到。默认为200,也就是一切正常。
    在这里插入图片描述

    14. 会话 session

    除请求对象外,还有一个 session对象。它允许你在不同请求间存储信息。它是在 Cookies的基础上实现的,并且对Cookies进行密钥签名。以查看你Cookie的内容,但却不能修改它,除非用户知道签名的密钥。

    要使用会话,你需要设置一个密钥。这里介绍会话如何工作:

    from flask import Flask, session, redirect, url_for, escape, request
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        if 'username' in session:        # 如果username在session中
            return 'Logged in as %s' % escape(session['username'])     # 如果在就返回一个字符串
        return 'You are not logged in' 
    
    @app.route('/login',methods=['GET','POST'])
    def login():
        if request.method == ‘POST’:     # 判断
              session['username']=request.form['username']
              return redirect(url_for('index'))
           return ....
    
    @app.route('/logout')
    def logout():
        session.pop('username',None)
        return redirect(url_for('index'))
    
    app.secret_key = '  A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
    

    flask_demo.py:

    from flask import Flask,url_for,render_template,request,make_response,redirect,abort
    
    app = Flask(__name__)
    
    @app.route("/")
    def hello():
        return redirect("/asd")
    
    @app.route("/asd")
    def hello1():
        abort(404)
    
    @app.errorhandler(404)
    def page_not_found(error):
        return render_template('page_not_found.html'), 404
        
    #跳转到index.html  登陆页面
    
    @app.route("/index/")
    def hello_word():
    username = request.cookies.get("username")
    if username==None:
        username=""
    return render_template("index.html",username=username)
    
    #接收参数  验证登录
    @app.route("/login/",methods=["GET","POST"])
    def login():
    username = request.args.get("username")
    password = request.args.get("password")
    if username=="admin" and password=="123" :
        resp = make_response(render_template("success.html",username=username))
        resp.set_cookie('username', "admin")
        return resp
    
    return render_template("index.html",error="*用户名或密码错误!!!")
    
    #文件上传
    @app.route("/upload/",methods=['GET','POST'])
    def upload():
    f = request.files["file"]
    
    f.save("static/HeadAttack0.gif")
    return "/static/HeadAttack0.gif"
    
    if __name__=='__main__':
    with app.test_request_context():
        print(url_for('hello_word',name = "asd"))
    
    app.debug = True
    app.run(host='0.0.0.0')
    

    login.py:

    from flask import           
    Flask,url_for,render_template,request,make_response,redirect,abort,session,escape,flash
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
    if 'username' in session:
        return session['username']
        flash('You were successfully logged in')
        return 'You are not logged in'
    
    @app.route("/index1")
    def index1():
        return render_template("index.html")
    
    @app.route("/login" ,methods=['GET','POST'])
    def login():
        username = request.form["username"]
        password = request.form["password"]
    if username=="admin" and password=="123":
    
        session["username"] = username
    return redirect("/")
    
    app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
    app.run()
    

    ‘’’

    二、Flask连接MySQL数据库:

    Flask 用 Flask-SQLAlchemy 连接 MySQL

    安装: pip install Flask-SQLAlchemy
    

    测试环境目录结构
    在这里插入图片描述
    上图是Flask做工程的基本文档结构,最上面 “ttt” 是工程名,其次下面的“ttt”是文件名,
    Python3.0版本以后,

    __init__.py 可以不写了;
    
    creat_tables.py 是创建表的;
    
    models.py 是模型,这里面写的东西,是和创建的表里面写的东西是一一对应关系;
    
    外层还有一个  manage.py  是做管理的,一定要注意这个文件是和文件名“ttt”是同级的!
    
    还有一个 settings.py 是做设置的
    

    settings.py :

    DIALECT = 'mysql'
    DRIVER = 'pymysql'
    USERNAME = 'root'
    PASSWORD = '808069'
    HOST = '127.0.0.1'
    PORT = '3306'
    DATABASE = 'cms'
    
    SQLALCHEMY_DATABASE_URI = '{}+{}://{}:{}@{}:{}/{}?charset=utf8'.format(
        DIALECT,DRIVER,USERNAME,PASSWORD,HOST,PORT,DATABASE
    )
    SQLALCHEMY_COMMIT_ON_TEARDOWN = True
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    
    SQLALCHEMY_POOL_SIZE = 10
    SQLALCHEMY_MAX_OVERFLOW = 5
    

    __init__.py:   (文件“ttt”导包的时候会自动运行这个)
    
    
    from flask_sqlalchemy import SQLAlchemy
    from flask import Flask   # 导入Flask后,下面创建app
    
    db = SQLAlchemy()
    
    def create_app():    # 创建app对象
       app = Flask(__name__)
       app.config.from_object('settings')   # 这是从settings加载配置文件,settings里的设置是连接数据库用的,所以读到app去
    
    db = SQLAlchemy()   # 创建了一个db对象
    db.init_app(app)    # 用app里读到的一些参数,来初始化
    
    return app
    

    manage.py :

    from ttt import create_app
    
    app = create_app()
    
    if __name__ == '__main__':
      app.run()   # 跑起来
    

    models.py :

    from manage import db
    
    class User(db.Model):  # 类
          __tablename__ = 'user'   # 用户
          
          # 下面有三列:id username password
          
          id = db.Column(db.INTEGER,primary_key=True)    
          username = db.Column(db.String(80),unique=True)
          password = db.Column(db.String(80),nullable=False)
    
    class CodeCountRecord(db.Model):
          __tablename = 'codecountrecord'
          
          # 下面4列
          
          id = db.Column(db.INTEGER,primary_key=True)
          count = db.Column(db.INTEGER)
          data = db.Column(db.DATE)
          user = db.Column(db.ForeignKey('user.id'))
    

    create_tables.py :

    from ttt import db, create_app
    from ttt.models import *    # 导入models里所有的类
    
    app = create_app()
    app_ctx = app.app_context()  # app_ctx = app/g   加载连接数据库
    with app_ctx:               # __enter__,通过LocalStack放入Local中
          db.create_all()       # 调用LocalStack放入Local中获取app,再去app中获取配置
    

    这种方式即为 离线脚本(不用启动项目)的方式创建数据库

    直接右键运行 models.py 即可创建表
    在这里插入图片描述

    操作:

    增:

    from cms.models import User
    from manage import db
    
    def create_user():
          # 创建一个新用户对象
         user = User()
         user.username = 'fuyong'
         user.password = '123'
    
          # 将新创建的用户添加到数据库会话中
          db.session.add(user)
          # 将数据库会话中的变动提交到数据库中, 记住, 如果不 commit, 数据库中是没有变化的.
          db.session.commit()
          
          create_user()
    

    删:

    def delete_user():
        # 获取用户对象
        user = User.query.filter_by(id=1).first()
    
        # 删除用户
        db.session.delete(user)   # 这个user里只包含id就可以了
    
        #提交数据库会话
        db.session.commit()
    
    delete_user()
    

    改:

    def update_user():
         # 获取用户对象
         user = User.query.filter_by(id=2).first()
    
         # 修改用户
         user.password = '123567'
    
         # 提交数据库会话
         db.session.commit()
    
    update_user()
    

    查:

    def select_user():
         # 查询所有用户
         users_list = User.query.all()
    
         # 查询用户名称为 fuyong 的第一个用户, 并返回用户实例, 因为之前定义数据库的时候定义用户名称唯一, 所以数据库中用户名称为 test 的应该只有一个.
         user = User.query.filter_by(username='fuyong').first()
         
         # or
         user = User.query.filter(User.username == 'fuyong').first()
    
         # 模糊查询, 查找用户名以abc 结尾的所有用户
         users_list = User.query.filter(User.username.endsWith('g')).all()
    
         # 查询用户名不是 fuyong 的第一个用户
         user = User.query.filter(User.username != 'fuyong').first()
    

    循环导入的问题:

    如果上面的例子继续写下去的时候,我们或许会在 视图views 中 引入models文件 以操作数据,
    在models文件中引入manage文件中的db以定义类和字段,
    然后在manage文件中引入views文件以注册蓝图(register_blueprint),
    这样就出现了 a引入b,b引入c,c引入a的问题,就会报错,

    解决办法就是另外创建一个 ext.py文件,专门用来创建db,代码如下:

    from flask_sqlalchemy import SQLAlchemy
    
    db = SQLAlchemy()
    

    注意:此时先不讲app传入

    然后在 manage.py 文件中,导入db,然后初始化,将app传进去:

    db.init_app(app)
    

    这样,在视图中需要用db的之后直接从ext导入,而不再从manage里导入

    三、Flask点餐系统

    demo:

    1.可研分析;2.需求分析;3.系统设计(概要设计 详细设计);4.编码;5.测试;6.实施运维;7.项目结束
    在这里插入图片描述

    用户表:

    id   姓名   证件类型  证件号码   电话  性别  照片 
    住址  出生日期  职业  是否黑名单  密码  级别  状态  备用1 ... 
    

    商家:

    id  名称  法人  电话  法人证件类型  证件号码  营业执照号码  附件(可以存多个照片) 地址
    

    餐品管理:

    id  名称  价格  推荐   热卖  上架/下架   状态   新品
    

    订单:

    订单id   商家id   用户id   菜品ids   单价  个数  总价    状态
    

    配送:

    id 订单id  配送员id  商家id  用户id  配送地址  电话  时间  预计到达时间  配送状态(配送完成/配送中)
    

    配送员:

    id  出生日期  姓名   性别  证件号码   电话  级别   负责区域    状态(美团这边删配送员不是真删,就好像淘宝删三鹿奶粉不是真删,只是下架了)
    

    评论:

    id    商家id   用户id   内容   时间   状态
    

    在这里插入图片描述

    demo:

    __init__.py:
    
    from flask_sqlalchemy import SQLAlchemy
    from flask import Flask
    
    db = SQLAlchemy()
    
    
    def create_app():
        app = Flask(__name__)
        app.config.from_object('settings')
        db = SQLAlchemy()
        db.init_app(app)
    
        return app
    

    creat_tables.py:

    from demo import db, create_app
    from demo.models import *
    
    app = create_app()
    app_ctx = app.app_context()  # app_ctx = app/g
    with app_ctx:  # __enter__,通过LocalStack放入Local中
         db.create_all()  # 调用LocalStack放入Local中获取app,再去app中获取配置
    

    models.py:

    from demo import db
    
    class Business(db.Model):
    __tablename__ = 'business'
    
    id = db.Column(db.INTEGER, primary_key=True)
    bname = db.Column(db.String(80), unique=True ,nullable=False)
    corporate = db.Column(db.String(80), nullable=False)
    tel = db.Column(db.String(80), nullable=False)
    idtype = db.Column(db.String(80), nullable=False)
    idnum = db.Column(db.String(80), nullable=False)
    licensenum = db.Column(db.String(80), nullable=False)
    imgs = db.Column(db.String(80), nullable=False)
    address = db.Column(db.String(80), nullable=False)
    state = db.Column(db.String(80), nullable=False)
    

    manage.py

    from demo import create_app , db
    from demo.models import User
    
    app = create_app()
    
    @app.route("/")
    def index():
        user = User()
        user.username = "苏大强"
        user.password = "123"
        db.session.add(user)
        db.session.commit()
    
    
    if __name__ == '__main__':
        app.run()
    

    settings.py:

    DIALECT = 'mysql'
    DRIVER = 'pymysql'
    USERNAME = 'root'
    PASSWORD = 'root'
    HOST = 'localhost'
    PORT = '3306'
    DATABASE = 'py1901'
    
    SQLALCHEMY_DATABASE_URI = '{}+{}://{}:{}@{}:{}/{}?charset=utf8'.format(
    DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE
    )
    
    SQLALCHEMY_COMMIT_ON_TEARDOWN = True
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    
    SQLALCHEMY_POOL_SIZE = 10
    SQLALCHEMY_MAX_OVERFLOW = 5
    

    welcome.html:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <title>江苏省卫生监督业务系统</title>
    <link href="/static/css/main.css" rel="stylesheet" type="text/css" media="all" />
    <script src="/static/js/jquery-1.4.2.min.js" type="text/javascript"></script>
    </head>
    
    <body class="welcome">  
    
    </body>
    </html>
    

    index.html:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <title>点餐系统</title>
    <style>
    html,body { overflow-x:hidden;}
    </style>
    <link href="/static/css/main.css" rel="stylesheet" type="text/css" media="all" />
    <script src="/static/js/jquery-1.4.2.min.js" type="text/javascript"></script>
    <script src="/static/js/jquery.onlyforindex.js" type="text/javascript"></script>
    </head>
    
    <body>
    <div id="header-wrap">
    <iframe allowtransparency="true" frameborder="0" id="header-box" scrolling="no" src="inc-header"></iframe>
    </div> 
    <div id="main-wrap">
    <div id="main-nav">
    	<iframe frameborder="0" id="siderbar-box" scrolling="no" src="inc-nav"></iframe>
    </div>
    <div id="main-content">
        <table border="0" cellpadding="0" cellspacing="0" id="main-content-box">
            <tr>
                <td class="toggle"></td>
                <td class="content-wrap"><iframe frameborder="0" id="content-box" src="welcome" scrolling="auto"></iframe></td>
            </tr>
        </table>
    </div>
    </div>
    </body>
    </html>
    

    inc-nav.html:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <title>点餐系统</title>
    <link href="/static/css/main.css" rel="stylesheet" type="text/css" />
    <script src="/static/js/jquery-1.4.2.min.js" type="text/javascript"></script>
    <script src="/static/js/jquery.treeview.js" type="text/javascript"></script>
    <link href="/static/css/jquery.treeview.css" rel="stylesheet" type="text/css" />
    
    <script language="javascript">
    $().ready(function(){
    
    //树状菜单生成 JQuery Treeview
    $("#browser").treeview({
    	//animated菜单展开关闭时动画效果
    	animated : "slow",
        //collapsed菜单载入时关闭还是展开
    	collapsed: false
        //unique同一层次是否只允许展开一个
    	//unique: true
    });
    //设置树状菜单外框DIV纵向滚动条属性为自动
    
    $("#nav-box").css("overflowY","auto");
    
    
    	//自动添加a标签title为a标签中的内容
    	for(var i=0; i<$("span.file a").length; i++ ){
    		$("span.file a").eq(i).attr("title", $("span.file a").eq(i).text());
    	}
    
    });
    
    //链接转入index.html页面ID为content-box的iframe显示
    function urlTarget(urls) {
    	$("#content-box",parent.document.body).attr("src",urls);
    }
    </script>
    
    </head>
    
    <body class="inc-nav-body">
    <div id="nav-box">
    
    <ul id="browser" class="filetree">
    	<li><span class="folder">商家管理</span>
    		<ul>
            	<li><span class="file"><a onclick="urlTarget('selectBusinesses');">商家管理</a></span></li>
    		</ul>
    	</li>
    
    	{#<li><span class="folder">人员管理</span>
    		<ul>
            	<li><span class="file"><a onclick="urlTarget('content/member-list.html');">人员列表</a></span></li>
            	<li><span class="file"><a onclick="urlTarget('content/staff-entrust.html');">人员授权</a></span></li>
            	<li><span class="file"><a onclick="urlTarget('content/staff-entrust2.html');">被授权人确认</a></span></li>
            	<!--
            	<li><span class="file"><a onclick="urlTarget('content/training-list.html');">培训考核列表</a></span></li>
            	-->
            	<li><span class="file"><a onclick="urlTarget('content/leadership-list.html');">所级领导名录管理</a></span></li>
            	<li><span class="file"><a onclick="urlTarget('content/leadership-tatisticsReport.html');">所级领导名录统计表</a></span></li>
            	<li><span class="file"><a onclick="urlTarget('content/staff-composition.html');">人员构成情况表</a></span></li>
            	<li><span class="file"><a onclick="urlTarget('content/member-supervisor.html');">卫生监督员管理信息汇总</a></span></li>
            	<li><span class="file"><a onclick="urlTarget('content/member-technicist.html');">专业技术人员基本情况表</a></span></li>
            	<li><span class="file"><a onclick="urlTarget('content/member-administrators.html');">行政管理人员基本情况表</a></span></li>
    		</ul>
    	</li>
    	<li><span class="folder">登录号管理</span>
    		<ul>
            	<li><span class="file"><a onclick="urlTarget('content/userid-list.html');">登录号列表</a></span></li>
    		</ul>
    	</li>#}
    </ul>
    </div>
    </body>
    </html>
    

    inc-header.html:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <title>江苏省卫生监督业务系统</title>
    <link href="/static/css/main.css" rel="stylesheet" type="text/css" media="all" />
    <script language="javascript" src="/static/js/jquery-1.4.2.min.js" type="application/javascript"></script>
    </head>
    
    <body>
    <div id="header">
    <div class="logo-title">
       	<h1>江苏省卫生监督业务系统</h1>
    </div>
    <div class="logout user-icon">
      	欢迎登录,<span class="user-text">管理员</span> [<span class="signout-text"><a href="javascript:void(0);" onclick="window.opener=null; window.parent.close();" title="退出系统">退出系统</a></span>]
    </div>
    </div>
    </body>
    </html>
    

    business-view.html:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <title>点餐系统</title>
    <link href="/static/css/main.css" rel="stylesheet" type="text/css" media="all" />
    <script src="/static/js/jquery-1.4.2.min.js" type="text/javascript"></script>
    </head>
    
    <body class="content-pages-body">
    <div class="content-pages-wrap">
    <div class="commonTitle">
    <h2>&gt;&gt; <a href="#">商家管理</a> - 商家信息</h2></div>
    <table border="0" cellspacing="1" cellpadding="0" class="commonTable">
        <form id="institutionCreat" name="institutionCreat" action="" method="post">
      <tr>
            <td width="16%" align="right" class="title"><span class="required">*</span>商家编码:</td>
            <td width="17%" align="left">{{ business.id }}</td>
             <td width="16%" align="right" class="title"><span class="required">*</span>商家名称:</td>
            <td width="17%" align="left">{{ business.bname}}</td>
            <td width="16%" class="title" align="right"><span class="required">*</span>法人:</td>
            <td width="17%" align="left">{{ business.corporate}}</td>
        </tr>
        <tr>
           <td width="15%" align="right" class="title"><span class="required">*</span>电话:</td>
            <td width="19%" align="left">{{ business.tel}}</td>
            <td align="right" class="title">证件类型:</td>
            <td align="left" colspan="3">{{ business.idtype}}</td>
        </tr>
        <tr>
            <td align="right" class="title">证件号码:</td>
            <td align="left" colspan="3">{{ business.idnum}}</td>
            <td align="right" class="title">营业执照号码:</td>
            <td align="left">{{ business.licensenum}}</td>
        </tr>
        <tr>
            <td align="right" class="title">地址:</td>
            <td align="left">{{ business.address}}</td>
            <td align="right" class="title">图片:</td>
            <td align="left">{{ business.imgs}}</td>
    
        </tr>
    
        </form>
    </table>
    <!--//commonTable-->
    
    </div>
    <!--//content pages wrap-->
    </body>
    </html>
    

    business-list.html:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <title>点餐系统</title>
    <link href="/static/css/main.css" rel="stylesheet" type="text/css" media="all" />
    <script src="/static/js/jquery-1.4.2.min.js" type="text/javascript"></script>
    </head>
    
    <body class="content-pages-body">
    <div class="content-pages-wrap">
    <div class="commonTitle">
      <h2>&gt;&gt;条件查询</h2>
    </div>
    <table width="100%" border="0" cellspacing="0" cellpadding="0" class="commonTableSearch">
       	<form id="form-search" name="form-search" action="" method="post">
        <tr>
          <th align="left">商家编号:</th>
         <td align="left"><input name="textfield6" type="text" class="inputTextNormal" id="textfield6" /></td>
          <td align="left"><div align="left">商家名称:</div></td>
          <td align="left"><input name="textfield6" type="text" class="inputTextNormal" id="textfield6" /></td>
          <td align="left">法人:</td>
          <td align="left"><input name="textfield62" type="text" class="inputTextNormal" id="textfield62" /></td>
          <td align="right">&nbsp;</td><td align="right"><button>检索</button></td>
        </tr>
    
       	</form>
    </table>
    <!--//commonTableSearch-->
    <div class="btnBar">
    	<ul class="clearfix">
        	<li><a href="toAddBusiness" title="商家信息录入" class="btnNormal">新增</a></li>
        </ul>
    </div>
    <table border="0" cellspacing="1" cellpadding="0" class="commonTable">
        <tr>
            <th >商家名称</th>
            <th>法人</th>
            <th >电话</th>
            <th>证件类型</th>
            <th >证件号码</th>
            <th >营业执照号码</th>
            <th >地址</th>
            <th class="editColM">操作</th>
        </tr>
        {% for b in blist %}
        <tr>
            <td align="center">{{ b.bname }}</td>
            <td align="left">{{ b.corporate }}</td>
            <td align="center">{{ b.tel }}</td>
            <td align="left"> {{ b.idtype }}</td>
            <td align="center">{{ b.idnum }}</td>
            <td align="center">{{ b.licensenum }}</td>
            <td align="center">{{ b.address }}</td>
            <td align="center">
            	<a href="showBusiness/{{ b.id }}" class="btnIconView" title="查看详情"></a>
                <a href="toUpdateBusiness/{{ b.id }}" class="btnIconEdit" title="更新"></a>
                <a href="deleteBusiness/{{ b.id }}" onclick="if(!confirm('确定删除吗?')) {return false}" class="btnIconDel" title="删除"></a>
            </td>
        </tr>
        {% endfor %}
    
    
    
    
    </table>
    <!--//commonTable-->
    <div id="pagelist">
    	<ul class="clearfix">
        	<li><a href="#">首页</a></li>
            <li><a href="#">上页</a></li>
            <li><a href="#">1</a></li>
            <li><a href="#">2</a></li>
            <li class="current">3</li>
            <li><a href="#">4</a></li>
            <li><a href="#">5</a></li>
            <li><a href="#">下页</a></li>
            <li><a href="#">尾页</a></li>
            <li class="pageinfo">第3页</li>
            <li class="pageinfo">共8页</li>
        </ul>
    </div>
    </div>
    <!--//content pages wrap-->
    </body>
    </html>
    

    business-edit.html:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <title>点餐系统</title>
    <link href="/static/css/main.css" rel="stylesheet" type="text/css" media="all" />
    <script src="/static/js/jquery-1.4.2.min.js" type="text/javascript"></script>
    <script>
        function submitForm() {
            $('#jvForm').submit();
        }
    
    </script>
    </head>
    
    <body class="content-pages-body">
    <div class="content-pages-wrap">
    <div class="commonTitle"><h2>&gt;&gt; <a href="#">商家管理</a> - 商家信息修改</h2></div>
    <table border="0" cellspacing="1" cellpadding="0" class="commonTable">
        <form id="jvForm" name="institutionCreat" action="/updateBusiness" method="post">
        <tr>
            <td align="right"><span class="required">*</span>营业执照编号:</td>
            <td align="left"><input name="licensenum" type="text" class="inputTextMiddle" id="textfield" value="{{ business.licensenum }}"/></td>
            <td align="right"><span class="required">*</span>商家名称:</td>
            <td align="left" colspan="3"><input name="bname" type="text" class="inputTextMiddle" id="textfield" value="{{ business.bname }}"/></td>
        </tr>
        <tr>
            <td align="right"><span class="required">*</span>法人:</td>
            <td align="left"><input name="corporate" type="text" class="inputTextMiddle" id="textfield2" value="{{ business.corporate }}"/></td>
            <td align="right"><span class="required">*</span>证件类型:</td>
            <td align="left" colspan="4">
            	<input type="radio" name="idtype" id="radio5" value="身份证" {% if business.idtype=='身份证'  %}checked{% endif %}/>身份证
                <input type="radio" name="idtype" id="radio6" value="军官证" {% if business.idtype=='军官证'  %}checked{% endif %}/>军官证
                <input type="radio" name="idtype" id="radio7" value="学生证" {% if business.idtype=='学生证'  %}checked{% endif %}/>学生证
            </td>
        </tr>
        <tr>
            <td align="right">证件号码:</td>
            <td colspan="3" align="left"><input name="idnum" type="text" class="inputTextLong" id="textfield5" value="{{ business.idnum }}"/></td>
            <td align="right">电话:</td>
            <td align="left"><input name="tel" type="text" class="inputTextNormal" id="textfield4" value="{{ business.tel }}"/></td>
        </tr>
        <tr>
       	     <td align="right"><span class="required">*</span>地址:</td>
            <td align="left"><input name="address" type="text" class="inputTextNormal" id="textfield6" value="{{ business.address }}" /></td>
    
            <td align="right">附件:</td>
            <td align="left"><input name="files" type="file" /></td>
        </tr>
        <input type="hidden" name="id" value="{{ business.id }}"/>
        <input type="hidden" name="state" value="{{ business.state }}"/>
        </form>
    </table>
    <!--//commonTable-->
    <div id="formPageButton">
    	<ul>
        	<li><a href="javascript:submitForm()" title="保存" class="btnShort">保存</a></li>
        	<li><a href="javascript:window.history.go(-1)" title="返回" class="btnShort">返回</a></li>
        </ul>
    </div>
    <!--//commonToolBar-->
    
    </div>
    <!--//content pages wrap-->
    </body>
    </html>
    

    business-create.html:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <title>点餐系统</title>
    <link href="/static/css/main.css" rel="stylesheet" type="text/css" media="all" />
    <script src="/static/js/jquery-1.4.2.min.js" type="text/javascript"></script>
    <script>
        function submitForm() {
            $('#jvForm').submit();
        }
    
    </script>
    </head>
    
    <body class="content-pages-body">
    <div class="content-pages-wrap">
    <div class="commonTitle"><h2>&gt;&gt; <a href="#">商家管理</a> - 商家信息录入</h2></div>
    <table border="0" cellspacing="1" cellpadding="0" class="commonTable">
        <form id="jvForm" name="institutionCreat" action="addBusiness" method="post">
        <tr>
            <td align="right"><span class="required">*</span>营业执照编号:</td>
            <td align="left"><input name="licensenum" type="text" class="inputTextMiddle" id="textfield" /></td>
            <td align="right"><span class="required">*</span>商家名称:</td>
            <td align="left" colspan="3"><input name="bname" type="text" class="inputTextMiddle" id="textfield" /></td>
        </tr>
        <tr>
            <td align="right"><span class="required">*</span>法人:</td>
            <td align="left"><input name="corporate" type="text" class="inputTextMiddle" id="textfield2" /></td>
            <td align="right"><span class="required">*</span>证件类型:</td>
            <td align="left" colspan="4">
            	<input type="radio" name="idtype" id="radio5" value="身份证" checked/>身份证
                <input type="radio" name="idtype" id="radio6" value="军官证" />军官证
                <input type="radio" name="idtype" id="radio7" value="学生证" />学生证
            </td>
        </tr>
        <tr>
            <td align="right">证件号码:</td>
            <td colspan="3" align="left"><input name="idnum" type="text" class="inputTextLong" id="textfield5" /></td>
            <td align="right">电话:</td>
            <td align="left"><input name="tel" type="text" class="inputTextNormal" id="textfield4" /></td>
        </tr>
        <tr>
       	     <td align="right"><span class="required">*</span>地址:</td>
            <td align="left"><input name="address" type="text" class="inputTextNormal" id="textfield6" /></td>
    
            <td align="right">附件:</td>
            <td align="left"><input name="files" type="file" /></td>
        </tr>
    
        </form>
    </table>
    <!--//commonTable-->
    <div id="formPageButton">
    	<ul>
        	<li><a href="javascript:submitForm()" title="保存" class="btnShort">保存</a></li>
        	<li><a href="javascript:window.history.go(-1)" title="返回" class="btnShort">返回</a></li>
        </ul>
    </div>
    <!--//commonToolBar-->
    
    </div>
    <!--//content pages wrap-->
    </body>
    </html>
    

    四、Flask分页

    展开全文
  • web框架安全概览--你所使用的web框架是否安全?

    多人点赞 热门讨论 2020-05-12 09:02:29
    文章目录web框架安全MVC框架实现安全方案模板引擎与XSS防御web框架与CSRF防御HTTP Headers管理数据持久层与SQL注入web框架自身安全struts2命令执行漏洞spring MVC命令执行Django命令执行漏洞shiro反序化漏洞...

    web框架安全

    不安全–>安全框架–>框架安全–>不安全

    MVC框架实现安全方案

    mvc框架将web应用分为三层即

    view层----负责用户试图、页面暂时等
    controller层----负责应用的逻辑是西安,接受view层传入的用户请求转发给model层处理。
    model层----负责实现模型,完成数据处理

    根据这个模型,然乎秉着在正确的地方,做正确的事情的原则,去实施一个优秀的安全方案

    例如,在mvc架构里,如何去防御SQL注入,很多不对的做法是在view层做安全防护的,导致黑客可以各种绕过进而实现SQL注入,但是SQL注入是在model层解决的事情。

    一些主要的web安全威胁,如XSS、CSRF、SQL注入、访问控制、URL跳转等不涉及业务逻辑的安全问题都可以集中放在MVC框架中解决。

    模板引擎与XSS防御

    针对XSS防御,因为XSS攻击是在浏览器上执行的,其形成过程是在服务器端也米娜渲染时,注入的恶意HTML代码导致的。所以主要防御方法就是"输入检查"和"输出检查",输入检查就是各种过滤,而输出检查就是输出编码,针对不同的情况使用不同的编码函数。

    在MVC框架中并不符合这样的设计,是如何防御的呢?

    正确的地点----也就是view层,常用的技术就是使用模板引擎对页面进行渲染。

    在模板引擎中可以实现自定义的编码函数,应用于不同场景。通过自定义的方法时得XSS防御的功能得到完善。

    web框架与CSRF防御

    CSRF攻击的目标,一般都会产生"写数据"操作的URL,而读数据操作并不是CSRF的目标。对于防御机制,一是HTTP POST进行防御,但实际上POST本身并不足以对抗CSRF,因为POST也是可以自动提交的。但是POST的使用,对于保护token有着几级的意义,而security token的私密性(不可预测性原则),是防御CSRF攻击的基础。

    对于web框架来说,可以自动的在所有涉及POST的代码中添加token,这些地方包括所有的form表单、所有的Ajax POST请求等。

    在spring MVC以及一些其他的流行web框架中,并没有提供针对CSRF的保护,因此这些功能需要自己实现。

    HTTP Headers管理

    1. 想必CRLF注入都很熟悉,如果不加以控制会导致任意URL跳转,从而可能跳转到攻击者构造的页面当中。

      因此对于web框架来说管理好跳转目的地址是很有必要的,比如设置白名单。

    2. 以及对抗点击劫持的响应消息头,X-Frame-Options:SAMEORIGIN web框架可以封装此功能并提供页面配置

    3. 还有一个很重要的----cookie信息,提到cookie又会想到HttpOnly,而用web框架实现的好处就是不用担心在某个网页上遗漏。

    数据持久层与SQL注入

    都知道SQL注入最佳防御除了深层防御,最佳防御方式就是使用"预编译绑定变量",但实际上还有一个难点就是应用复杂之后代码数量庞大,难以把可能存在SQL注入的地方不遗漏的找出来。

    而ORM框架可以解决这个问题,当前ORM框架主要有五种:Hibernate(Nhibernate),iBATIS,mybatis,EclipseLink,JFinal。

    ORM技术特点:
    1.提高了开发效率。由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,所以我们实际可能已经不需要一个专用的、庞大的数据访问层。
    2.ORM提供了对数据库的映射,不用sql直接编码,能够像操作对象一样从数据库获取数据。

    web框架自身安全

    凡是有利有弊,即使使用web框架会简单的实现很多可靠的安全方案,但是自身也会含有很多漏洞

    struts2命令执行漏洞

    s2-001
    s2-057

    spring MVC命令执行

    CVE-2018-1273
    CVE-2018-1270

    Django命令执行漏洞

    ----CVE-2019-14234
    ----CVE-2020-9402

    shiro反序列化漏洞

    ----CVE-2016-4437

    thinkphp命令执行

    5-rce
    5.0.23-rce

    参考《《白帽子讲安全》》,部分漏洞复现见左上角头像。

    展开全文
  • bro框架-- 输入框架

    2018-10-11 20:11:03
    原文地址: ... 输入框架(Input Framework) Bro提供了输入框架,让用户可以导入数据到Bro中去。数据可以读到Bro表中,或者转换为脚本可以处理的事件。...默认情况下,输入框架读取的数据和由日志框架在Bro中写的...

    原文地址:

    https://www.bro.org/sphinx/frameworks/input.html

    输入框架(Input Framework

    Bro提供了输入框架,让用户可以导入数据到Bro中去。数据可以读到Bro表中,或者转换为脚本可以处理的事件。

     

    读取数据到表中

    输入框架的最有趣的用例是读数据到一个Bro表中。默认情况下,输入框架读取的数据和由日志框架在Bro中写的数据是同样的格式(由tab分隔的ASCII文件)。

    我们将用一个简单的例子说明将文件读入Bro的集中方法。我们假设我们想从一个包含服务器IP地址、时间戳以及块(block)的reason的黑名单(blacklist)最近中导入数据。

    一个示例的输入文件如下所示(注意所有的区域都是用tab分隔的):

    #fields ip timestamp reason

    192.168.17.1 1333252748 Malware host

    192.168.27.2 1330235733 Botnet server

    192.168.250.3 1333145108 Virus detected

     

    为了将一个文件读到Bro表中,需要定义两种record类型。一个包含构成表键的列的类型和名称,另一个包含构成表值的列的类型和名称。

    在我们的例子中,我们想查找IP。因此,我们的key record仅包含服务器IP,所有其他的元素都作为表内容来存储。

    两个record定义如下:

    type Idx: record {

    ip: addr;

    };

     

    type Val: record {

    timestamp: time;

    reason: string;

    };

    注意上面record定义中的域的名称必须和日志文件中‘#fields’行中定义的域相同,在本例中就是ip,timestamp,reason。另外注意到列的顺序是无关紧要的,因为每一个列都是由它的名称来标识的。

    可以通过简单地调用Input::add_table方法来将日志文件读入表中:

    global blacklist: table[addr] of Val = table();
    
    event bro_init() {
        Input::add_table([$source="blacklist.file", $name="blacklist",
                          $idx=Idx, $val=Val, $destination=blacklist]);
        Input::remove("blacklist");
    }
    
    

    这里我们先创建一个空的表(用于存放blacklist中的数据)并用输入框架打开一个名为blacklist的输入流将数据读入到表中。最后又将输入流移除了,这是因为我们在数据读完之后不再需要它了。

    因为有一些数据文件可能会非常大,所以输入框架是异步地工作的。为每个输入流创建一个新的线程,这个线程打开输入数据文件,将数据转化为Bro格式并将它发回给Bro主线程。

    因此,数据不是立即可得的。在所有数据展示在表中之前可能要花费几毫秒到几秒不等的时间来将数据从数据源中取出,具体时间依赖于数据源的大小。也就是说当Bro在没有输入源或者是工作在非常小的抓取文件上的时候,它可能在数据展示在表中之前就已经终止了(因为Bro在导入线程结束之前就已经处理完所有的包了)。

    之后顺序到来的对输入源的调用会进入队列直到前面的工作都已经完成。比如,调用了add_table和remove,在相继的两行中,remove在第一个read完成之前一直呆在队列中。

    一旦输入框架结束从一个数据源中读取数据,它将触发Input::end_of_data事件。一旦这个时间已经获取了表中可获取的所有输入文件中的数据的时候。。

     

    event Input::end_of_data(name: string, source: string) {
            # now all data is in the table
            print blacklist;
    }

    只要这个数据还在被读,这个表就继续使用。这可能在事件触发之前没能包含输入文件中的所有行。填好之后就可以像这样。。

     

    if ( 192.168.18.12 in blacklist )
            # take action

    重读和流数据(Re-reading and streaming data)

    对于很多数据源,比如很多blacklists,源数据是不断变化的。对这些情况,Bro输入框架支持处理变化的数据文件(changing data files)。

    首先,最基础的方法就是刷新输入流。当输入流打开的时候(也就是说还没有通过调用Input::remove来去除它的时候),可以调用函数Input::force_update。这会触发对表的一次完整的刷新,任何的文件中变化了的元素都会被更新。在更新完成的时候,会触发Input::end_of_data事件。

    在我们的例子中,是这样调用的:

     

    Input::force_update("blacklist");

     

    输入框架可以在它探测到输入文件变化的时候自动刷新表的内容。为了使用这个特性,你需要通过设置Input::add_table调用的mode选项指定一个non-default read模式。有效的值有:Input::MANUAL(默认的),Input::REREAD和Input::STREAM。就可以这样:

     

    Input::add_table([$source="blacklist.file", $name="blacklist",
                      $idx=Idx, $val=Val, $destination=blacklist,
                      $mode=Input::REREAD]);

    当使用reread模式的时候(如 $mode=Input::REREAD),Bro会持续地检查输入文件是否已经发生变化。如果这个文件已经发生变化,为了反映当前状态,会重读这个文件并将Bro表中的数据更新。每一次探测到变化并且新数据被读到表中的时候,会触发end_of_data事件。

    当使用streaming模式的时候(如 $mode=Input::STREAM),Bro假设源数据是仅能在末尾追加的文件(append-only file),新数据将在其末尾追加。Bro不断地在文件末尾检查新数据,然后把新数据添加到表中。如果文件中的新的行(newer lines)和前面的行有相同的索引(index),它们将覆盖输出表中的值。因为流读取的特性(数据不断地往表中追加),所以当使用streaming模式的时候永远都不会触发end_of_data事件。

    接收变化事件(receiving change events)

    当重读(re-reading)文件的时候,能够知道源文件中的哪些行发生了变化该多好啊。
    出于这个原因,当有一个新的数据项在表中添加、移除或者更改的时候,输入框架会触发一个事件。

    事件就像这样(注意你可以在自己的Bro脚本中改变事件的名称):

     

    event entry(description: Input::TableDescription, tpe: Input::Event,
                left: Idx, right: Val) {
            # do something here...
            print fmt("%s = %s", left, right);
    }

    事件必须在add_table调用的$ev中指明:

    Input::add_table([$source="blacklist.file", $name="blacklist",
                      $idx=Idx, $val=Val, $destination=blacklist,
                      $mode=Input::REREAD, $ev=entry]);

    这里事件的description参数包含那些一开始应用给add_table调用的参数。因此,流的名字可以这样获取(description$name)。事件的tpe参数是一个包含发生了的变化的类型的一个enum型数据。

    如果一个未曾出现在表中的一行被添加到了表中,tpe的值会变成Input::EVENT_NEW。在这个例子中left包含了已添加的表项的索引,right包含了已添加的表项的值。

    如果在对一个文件进行重读或者流读取的时候,表中的一个已经存在的表项被修改,tpe的值会变成Input::EVENT_CHANGED。在这个情况下,left包含被修改的表项的索引,right包含表项被修改之前的值。原因是,当事件触发的时候,表已经被修改了。表中的当前值可以通过查询表值来弄清楚。因此,把表的新值和旧值进行比较也是可能的。

    如果通过一次重读,表中的一个元素(本来存在的)不复存在了,tpe的值将变为Input::EVENT_REMOVED。在这个情况下,left包含索引并且right包含被移除元素的值。

     

    在导入过程中过滤数据(Filtering data during import)

    输入框架也允许用户在导入的过程中过滤数据。使用了断定函数。断定函数在新的元素被添加/改变/移除的时候调用。断定函数可以通过对要接受的变化返回true值或对要拒绝的变化返回false值来接受抑或是否决一个变化。更多的是,它可以在数据写入表之前就修改数据。

    下面的例子会拒绝在表中添加项(那种在一个月之前就生成的项)。但它会接受对当前已经在表中的项的值的所有改变或者是移除。

    Input::add_table([$source="blacklist.file", $name="blacklist",
                      $idx=Idx, $val=Val, $destination=blacklist,
                      $mode=Input::REREAD,
                      $pred(typ: Input::Event, left: Idx, right: Val) = {
                        if ( typ != Input::EVENT_NEW ) {
                            return T;
                        }
                        return (current_time() - right$timestamp) < 30day;
                      }]);

    为了在元素导入的时候修改它们,断定函数(predicate function)可以操纵left和right。注意一下,断定函数在变化提交给表之前就调用了。因此,当一个表元素变化的时候(typ为Input::EVENT_CHANGED),left和right包含了旧的值。这允许断定函数在判断这些变化是否应该被允许之前先检验一下旧版本和新版本之间的变化。

     

    不同的读者(different readers)

    输入框架对不同种类的源数据文件智齿不同种类的读者。在这个时候,默认的读者读Bro日志文件格式的ASCII文件(有#fields头部行且用tab分隔的值)。但也有其他的读者。

    raw读者读由指定记录分隔符(默认是newline)分隔的文件。内容以一行一行的形式返回,它可以用于读配置文件或者类似的文件,可能仅在事件模式且不是往表中读数据的时候才行。

    二进制读者(binary reader)在读文件分析输入流的时候使用的,在处理文件分析输入流(file analysis input streams)的时候默认就是用的二进制读者。

    参照读者(benchmark reader)被使用用来优化输入框架的速度。它可以生成任意数量的所有bro数据类型的且输入框架支持的半随机数据。

     

    读取数据到事件中(reading data to events)

    输入框架支持的第二种模式是从读数据到Bro事件中,而不是读到表中。

    事件流和表流的工作机制在很多已经讨论过的地方非常相似。为了将前面例子中blacklist读入一个事件流,我们使用了Input::add_event函数。

     

    type Val: record {
            ip: addr;
            timestamp: time;
            reason: string;
    };
    
    event blacklistentry(description: Input::EventDescription,
                         t: Input::Event, data: Val) {
            # do something here...
            print "data:", data;
    }
    
    event bro_init() {
            Input::add_event([$source="blacklist.file", $name="blacklist",
                              $fields=Val, $ev=blacklistentry]);
    }
    事件流的声明中,主要不同的地方是:事件流不需要单独的index和value声明,所有的数据类型都在record定义中提供了。
    

    此外,事件流工作和表流极其相似且提供了绝大多数表流支持的选项。

    展开全文
  • Bro可以通过使用网络控制框架和网络设备连接...网络控制框架对活动的响应(active response)提供了一个灵活、统一的接口,并将复杂的异构网络设备(heterogeneous network equipment)隐藏到一个面向任务的API(一...

    原文地址:https://www.bro.org/sphinx/frameworks/netcontrol.html

    Bro可以通过使用网络控制框架和网络设备连接(比如交换机,软、硬件防火墙)。网络控制框架对活动的响应(active response)提供了一个灵活、统一的接口,并将复杂的异构网络设备(heterogeneous network equipment)隐藏到一个面向任务的API(一个可以通过bro脚本轻松调用的API)中。本文对如何在不同的场景下使用网络控制框架给出了一个概要性描述。倘若您想更深入地了解它是如何在实际中运用的,那么请看看下面的单元测试(unit tests),应该会很有帮助。

    网络控制体系结构(NetControl Architecture)

     上图便是网络控制框架的基本体系结构了。从概念上来理解,网络控制框架坐落于用户提供的脚本(使用了bro的事件机制)和网络设备(既可以是硬件设备也可以是软件设备)之间,它(网络控制框架)可以用来执行命令。
    网络控制框架支持很多高级调用,比如NetControl::drop_address函数,或者是低层次的语法规则。在给网络控制框架添加一条规则之后,网络控制框架会给它的一个或者多个后端(backends)发送规则。每一个后端都会对一个硬件或者软件设备负责。网络控制框架会在它的整个声明周期中跟踪这些规则并且汇报状态(比如success、failure和timeouts)给用户脚本。
    这些后端是通过使用一个基于插件的API实现的,这是一个典型的例子base/frameworks/netcontrol/plugins/broker.bro。如何写插件NetControl Plugin

    网络控制API(NetControl API)
    高级网络控制API(High-level NetControl API)
    这部分我们将介绍高级网络控制API。网络控制使用后端和外部设备通信(这些外部设备可以实现规则--rules)。在你使用网络控制的时候,至少要有一个活动后端。在我们的例子中,我们将使用一个调试插件来创建一个后端。这个插件会将标准输出的所有动作输出。
    后端应该在NetControl::init事件中初始化,并在插件实例初始化之后调用NetControl::activate函数。调试插件可以进行如下的初始化:

    event NetControl::init()
      {
      local debug_plugin = NetControl::create_debug(T);
      NetControl::activate(debug_plugin, 0);
      }

    在给网络控制框架添加至少一个后端之后,框架就会投入使用并向后端发送新增的规则。
    网络控制框架包含了一些可以让用户断开某些地址和网络的连接,避开网络流量,等等。下表描述了当前可用的高级函数。

    函数 描述
    NetControl::drop_address 调用此函数让网络控制框架阻止包含某个IP的数据包的转发。
    NetControl::drop_connection 调用此函数阻止某个特定连接(由它的五元组标识的)上的所有数据包的转发。
    NetControl::drop_address_catch_release 调用此函数导致某一个特定的源IP的所有报文都被阻塞,这个函数具备捕捉和释放(catch-and-release)功能并且为了保有规则在网络硬件中的空间,这个IP很快就会被丢弃。当在网络流量中再次发现这个IP的之后,立刻丢弃。想要知道更多信息,看Catch and Release
    NetControl::shunt_flow 调用此函数让网络控制框架停止转发单向流量到Bro中。这允许Bro通过将有益的流量分流的途径来保存资源。
    NetControl::redirect_flow 调用此函数让网络控制框架重定向一个单向流到网络设备的另一个部分上去。
    NetControl::quarantine_host 调用这个函数允许Bro通过从一个特殊的DNS服务器上发送DNS流量的方法来隔离一个主机,这个特殊的DNS服务器会将所有的请求指向该主机自身。这个隔离的主机仅允许在特殊的服务器之间存在,它将给用户产生一个详细描述下一步的警告信息。
    NetControl::whitelist_address 调用这个函数让网络控制框架给网络硬件推送一个IP地址组成的白名单入口。
    NetControl::whitelist_subnet 调用这个函数让网络控制框架给网络硬件推送一个子网的白名单入口。

     在添加后端之后,所有这些函数都可以立即使用并开始发送规则到新增的后端。下面是一个非常简单的例子,这个脚本会简单粗暴地阻塞所有将要建立的连接的流量。

    netcontrol-1-drop-with-debug.bro
    
    event NetControl::init()
    	{
    	local debug_plugin = NetControl::create_debug(T);
    	NetControl::activate(debug_plugin, 0);
    	}
    
    event connection_established(c: connection)
    	{
    	NetControl::drop_connection(c$id, 20 secs);
    	}

    运行这个脚本,加载的文件中有一个连接。这将导致调试插件打印一行到标准输入中,包含所添加的规则的信息。它还会产生一个叫作netcontrol.log的日志文件,这个日志文件包含了网络控制框架的所有活动的信息。

    # bro -C -r tls/ecdhe.pcap netcontrol-1-drop-with-debug.bro
    netcontrol debug (Debug-All): init
    netcontrol debug (Debug-All): add_rule: [ty=NetControl::DROP, target=NetControl::FORWARD, entity=[ty=NetControl::CONNECTION, conn=[orig_h=192.168.18.50, orig_p=56981/tcp, resp_h=74.125.239.97, resp_p=443/tcp], flow=<uninitialized>, ip=<uninitialized>, mac=<uninitialized>], expire=20.0 secs, priority=0, location=, out_port=<uninitialized>, mod=<uninitialized>, id=2, cid=2, _plugin_ids={\x0a\x0a}, _active_plugin_ids={\x0a\x0a}, _no_expire_plugins={\x0a\x0a}, _added=F]
    # cat netcontrol.log
    #separator \x09
    #set_separator    ,
    #empty_field      (empty)
    #unset_field      -
    #path     netcontrol
    #open     2018-11-22-14-05-14
    #fields   ts      rule_id category        cmd     state   action  target  entity_type     entity  mod     msg     priority        expire  location        plugin
    #types    time    string  enum    string  enum    string  enum    string  string  string  string  int     interval        string  string
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       activating plugin with priority 0       -       -       -       Debug-All
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       activation finished     -       -       -       Debug-All
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       plugin initialization done      -       -       -       -
    1398529018.678276 2       NetControl::RULE        ADD     NetControl::REQUESTED   NetControl::DROP        NetControl::FORWARD     NetControl::CONNECTION  192.168.18.50/56981<->74.125.239.97/443 -       -       0       20.000000       -       Debug-All
    1398529018.678276 2       NetControl::RULE        ADD     NetControl::SUCCEEDED   NetControl::DROP        NetControl::FORWARD     NetControl::CONNECTION  192.168.18.50/56981<->74.125.239.97/443 -       -       0       20.000000       -       Debug-All
    #close    2018-11-22-14-05-14

     上例中,netcontrol.log包含了几条NetControl::MESSAGE入口,表示调试插件已经初始化并添加。此后,有两条NetControl::RULE入口,第一个表示请求添加一条规则(状态是NetControl::REQUESTED)。下一行表示规则已经成功添加(状态时NetControl::SUCCEEDED)。日志行的提醒给出了新增规则的更多信息,在我们例子中是一个五元组。
    除了netcontrol.log,drop命令也创建了一个叫作netcontrol_drop.log的日志文件。这个日志文件就简洁得多了,它仅包含网络控制框架制定的丢弃信息:

    # cat netcontrol_drop.log
    #separator \x09
    #set_separator    ,
    #empty_field      (empty)
    #unset_field      -
    #path     netcontrol_drop
    #open     2018-11-22-14-05-14
    #fields   ts      rule_id orig_h  orig_p  resp_h  resp_p  expire  location
    #types    time    string  addr    port    addr    port    interval        string
    1398529018.678276 2       192.168.18.50   56981   74.125.239.97   443     20.000000       -
    #close    2018-11-22-14-05-14

     尽管这个阻塞所有连接的例子不是很有用,但也能看出高级API提供了一种采取措施的简单途径,比如当一个主机在做一些有害活动的时候。给一个更加现实的例子,下面的代码自动阻塞一个已经被辨别出来的SSH猜测者(SSH guesser)。

    netcontrol-2-ssh-guesser.bro
    
    @load protocols/ssh/detect-bruteforcing
    
    redef SSH::password_guesses_limit=10;
    
    event NetControl::init()
    	{
    	local debug_plugin = NetControl::create_debug(T);
    	NetControl::activate(debug_plugin, 0);
    	}
    
    hook Notice::policy(n: Notice::Info)
    	{
    	if ( n$note == SSH::Password_Guessing )
    		NetControl::drop_address(n$src, 60min);
    	}
    # bro -C -r ssh/sshguess.pcap netcontrol-2-ssh-guesser.bro
    netcontrol debug (Debug-All): init
    netcontrol debug (Debug-All): add_rule: [ty=NetControl::DROP, target=NetControl::FORWARD, entity=[ty=NetControl::ADDRESS, conn=<uninitialized>, flow=<uninitialized>, ip=192.168.56.1/32, mac=<uninitialized>], expire=1.0 hr, priority=0, location=, out_port=<uninitialized>, mod=<uninitialized>, id=2, cid=2, _plugin_ids={\x0a\x0a}, _active_plugin_ids={\x0a\x0a}, _no_expire_plugins={\x0a\x0a}, _added=F]
    # cat netcontrol.log
    #separator \x09
    #set_separator    ,
    #empty_field      (empty)
    #unset_field      -
    #path     netcontrol
    #open     2018-11-22-14-05-16
    #fields   ts      rule_id category        cmd     state   action  target  entity_type     entity  mod     msg     priority        expire  location        plugin
    #types    time    string  enum    string  enum    string  enum    string  string  string  string  int     interval        string  string
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       activating plugin with priority 0       -       -       -       Debug-All
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       activation finished     -       -       -       Debug-All
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       plugin initialization done      -       -       -       -
    1427726759.303199 2       NetControl::RULE        ADD     NetControl::REQUESTED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     192.168.56.1/32 -       -       0       3600.000000     -       Debug-All
    1427726759.303199 2       NetControl::RULE        ADD     NetControl::SUCCEEDED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     192.168.56.1/32 -       -       0       3600.000000     -       Debug-All
    #close    2018-11-22-14-05-16

     注意在这个例子中,没有直接调用NetControl,我们还使用了通知框架的Notice::ACTION_DROP动作。

    netcontrol-3-ssh-guesser.bro
    
    @load protocols/ssh/detect-bruteforcing
    
    redef SSH::password_guesses_limit=10;
    
    event NetControl::init()
    	{
    	local debug_plugin = NetControl::create_debug(T);
    	NetControl::activate(debug_plugin, 0);
    	}
    
    hook Notice::policy(n: Notice::Info)
    	{
    	if ( n$note == SSH::Password_Guessing )
    		add n$actions[Notice::ACTION_DROP];
    	}
    # bro -C -r ssh/sshguess.pcap netcontrol-3-ssh-guesser.bro
    netcontrol debug (Debug-All): init
    netcontrol debug (Debug-All): add_rule: [ty=NetControl::DROP, target=NetControl::FORWARD, entity=[ty=NetControl::ADDRESS, conn=<uninitialized>, flow=<uninitialized>, ip=192.168.56.1/32, mac=<uninitialized>], expire=10.0 mins, priority=0, location=ACTION_DROP: T, out_port=<uninitialized>, mod=<uninitialized>, id=2, cid=2, _plugin_ids={\x0a\x0a}, _active_plugin_ids={\x0a\x0a}, _no_expire_plugins={\x0a\x0a}, _added=F]

     

    # cat netcontrol.log
    #separator \x09
    #set_separator    ,
    #empty_field      (empty)
    #unset_field      -
    #path     netcontrol
    #open     2018-11-22-14-05-17
    #fields   ts      rule_id category        cmd     state   action  target  entity_type     entity  mod     msg     priority        expire  location        plugin
    #types    time    string  enum    string  enum    string  enum    string  string  string  string  int     interval        string  string
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       activating plugin with priority 0       -       -       -       Debug-All
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       activation finished     -       -       -       Debug-All
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       plugin initialization done      -       -       -       -
    1427726759.303199 2       NetControl::RULE        ADD     NetControl::REQUESTED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     192.168.56.1/32 -       -       0       600.000000      ACTION_DROP: T  Debug-All
    1427726759.303199 2       NetControl::RULE        ADD     NetControl::SUCCEEDED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     192.168.56.1/32 -       -       0       600.000000      ACTION_DROP: T  Debug-All
    #close    2018-11-22-14-05-17

     使用通知框架的Notice::ACTION_DROP动作还会导致每当网络控制框架实施阻塞的时候将notice.log中被丢弃的列置为True:

     

    # cat notice.log
    #separator \x09
    #set_separator    ,
    #empty_field      (empty)
    #unset_field      -
    #path     notice
    #open     2018-11-22-14-05-17
    #fields   ts      uid     id.orig_h       id.orig_p       id.resp_h       id.resp_p       fuid    file_mime_type  file_desc       proto   note    msg     sub     src     dst     p       n       peer_descr      actions suppress_for    dropped remote_location.country_code    remote_location.region  remote_location.city    remote_location.latitude        remote_location.longitude
    #types    time    string  addr    port    addr    port    string  string  string  enum    enum    string  string  addr    addr    port    count   string  set[enum]       interval        bool    string  string  string  double  double
    1427726759.303199 -       -       -       -       -       -       -       -       -       SSH::Password_Guessing  192.168.56.1 appears to be guessing SSH passwords (seen in 10 connections).     Sampled servers:  192.168.56.103, 192.168.56.103, 192.168.56.103, 192.168.56.103, 192.168.56.103        192.168.56.1    -       -       -       bro     Notice::ACTION_DROP,Notice::ACTION_LOG  3600.000000     F       -       -       -       -       -
    #close    2018-11-22-14-05-17

     规则API(Rule API)
    正如上一节中所说的那样,除了高级API,网络控制框架也支持基于规则的API,这种API允许添加规则时的更好的灵活性。实际上,所有的高层次函数都是使用低层次的规则API来实现的,这些高层级的API仅仅将它们的参数转换为低层次规则,并将这些规则直接添加到网络控制框架中去(通过调用NetControl::add_rule方法)。
    下面的图片展示了网络控制规则的主要构件:

     

     用于构成规则的类型定义在base/frameworks/netcontrol/types.bro

    规则被定义为NetControl::Rule形式的记录。规则有类型(type),这个类型表示执行何种动作。可能的动作有:丢弃(drop)包,修改(modify)包,重定向(redirect)包或者用白名单(whitelist)过滤包。规则的目标(target)指示了这个规则是否应用在转发路径上并且影响了那些在网络中转发的包,或者是这个规则是否影响了监视器的路径并仅仅影响了发送给bro的包,而不是贯穿于网络的包。图中的实体(entity)指示了应用了规则的地址、连接。此外,每一个规则有一个timeout(可以留空),一个priority(高优先级的规则覆盖低优先级的规则的)字段。另外location串有关于每个可提供的规则的更多文本信息。

    对某些规则类型,还要多出两个字段。比如,当你插入一个重定向规则的时候,你需要指明包应当要重定向到的端口。诸如此类的字段在NetControl::Rule文本中有详细描述。

    下例给出了如何构建你自己的规则,我们要写我们自己的NetControl::drop_connection函数。我们的函数和网络控制框架提供的函数之间的唯一的区别就是网络控制框架函数有额外的功能,比如日志方面的功能。
    再一次,我们用下面这个简单的例子来测试我们的函数,这个例子简单粗暴地丢弃网络中的所有连接:

    netcontrol-4-drop.bro
    
    function our_drop_connection(c: conn_id, t: interval)
    	{
    	# As a first step, create the NetControl::Entity that we want to block
    	local e = NetControl::Entity($ty=NetControl::CONNECTION, $conn=c);
    	# Then, use the entity to create the rule to drop the entity in the forward path
    	local r = NetControl::Rule($ty=NetControl::DROP,
    		$target=NetControl::FORWARD, $entity=e, $expire=t);
    
    	# Add the rule
    	local id = NetControl::add_rule(r);
    
    	if ( id == "" )
    		print "Error while dropping";
    	}
    
    event NetControl::init()
    	{
    	local debug_plugin = NetControl::create_debug(T);
    	NetControl::activate(debug_plugin, 0);
    	}
    
    event connection_established(c: connection)
    	{
    	our_drop_connection(c$id, 20 secs);
    	}
    # bro -C -r tls/ecdhe.pcap netcontrol-4-drop.bro
    netcontrol debug (Debug-All): init
    netcontrol debug (Debug-All): add_rule: [ty=NetControl::DROP, target=NetControl::FORWARD, entity=[ty=NetControl::CONNECTION, conn=[orig_h=192.168.18.50, orig_p=56981/tcp, resp_h=74.125.239.97, resp_p=443/tcp], flow=<uninitialized>, ip=<uninitialized>, mac=<uninitialized>], expire=20.0 secs, priority=0, location=<uninitialized>, out_port=<uninitialized>, mod=<uninitialized>, id=2, cid=2, _plugin_ids={\x0a\x0a}, _active_plugin_ids={\x0a\x0a}, _no_expire_plugins={\x0a\x0a}, _added=F]
    # cat netcontrol.log
    #separator \x09
    #set_separator    ,
    #empty_field      (empty)
    #unset_field      -
    #path     netcontrol
    #open     2018-11-22-14-05-18
    #fields   ts      rule_id category        cmd     state   action  target  entity_type     entity  mod     msg     priority        expire  location        plugin
    #types    time    string  enum    string  enum    string  enum    string  string  string  string  int     interval        string  string
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       activating plugin with priority 0       -       -       -       Debug-All
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       activation finished     -       -       -       Debug-All
    0.000000  -       NetControl::MESSAGE     -       -       -       -       -       -       -       plugin initialization done      -       -       -       -
    1398529018.678276 2       NetControl::RULE        ADD     NetControl::REQUESTED   NetControl::DROP        NetControl::FORWARD     NetControl::CONNECTION  192.168.18.50/56981<->74.125.239.97/443 -       -       0       20.000000       -       Debug-All
    1398529018.678276 2       NetControl::RULE        ADD     NetControl::SUCCEEDED   NetControl::DROP        NetControl::FORWARD     NetControl::CONNECTION  192.168.18.50/56981<->74.125.239.97/443 -       -       0       20.000000       -       Debug-All
    #close    2018-11-22-14-05-18

     最后的这个例子表明NetControl::add_rule返回一个对于每一个规则而言独一无二的字符串标识符(如果Bro重启了的话就不能保证它是独一无二的了)。这个ID可以在后期用于手动地移除规则(通过使用NetControl::remove_rule)。
    NetControl::add_rule类似,所有的高级函数会返回它们的规则ID,这些规则也可以通过相同的方法移除。

     与规则交互(Interacting with Rules)
    网络控制框架提供了很多不同的方法来和规则交互。在框架应用某条规则之前,你都可以应用各种各样的钩子来修改或者丢弃这些规则。此外,在规则为网络控制框架所管理的时候,也有很多事件,利用这些事件就可以跟踪某条规则的生命周期。也可以查询或者获取当前活动规则的集合。

    规则策略(Rule Policy)
    钩子NetControl::rule_policy提供了在规则被发送到后端之前就可以修改或者丢弃规则的机制。钩子可以看作是多体的函数(multi-bodied functions)并且使用钩子就和事件处理差不多。钩子和事件相比,钩子是立即执行的。钩子也可以有优先级来给出它们应用的顺序。钩子可以使用break关键字来表示处理应该终止,如果任何NetControl::rule_policy钩子使用了break,这条规则应该直接丢弃,不做后续的处理了。
    下面是一个简单的例子,这个例子告诉Bro,丢弃所有来自于192.168.*网络的连接的规则。

    netcontrol-5-hook.bro
    
    hook NetControl::rule_policy(r: NetControl::Rule)
    	{
    	if ( r$ty == NetControl::DROP &&
    	     r$entity$ty == NetControl::CONNECTION &&
    			 r$entity$conn$orig_h in 192.168.0.0/16 )
    			 {
    			 print "Ignored connection from", r$entity$conn$orig_h;
    			 break;
    			 }
    	}
    
    event NetControl::init()
    	{
    	local debug_plugin = NetControl::create_debug(T);
    	NetControl::activate(debug_plugin, 0);
    	}
    
    event connection_established(c: connection)
    	{
    	NetControl::drop_connection(c$id, 20 secs);
    	}
    # bro -C -r tls/ecdhe.pcap netcontrol-5-hook.bro
    netcontrol debug (Debug-All): init
    Ignored connection from, 192.168.18.50

    网络控制事件(NetControl Events)
    除了钩子,网络控制框架提供了一系列事件,这些事件让用户可以追踪规则以及框架的状态。
    我们已经遇到并使用了网络控制框架的一个事件,那就是NetControl::init,这是用于初始化框架的。在框架结束初始化并准备接收规则的时候,NetControl::init_done事件就会产生。
    当给框架添加规则的时候,事件将会按照如下的顺序发生: 

    事件 描述
    NetControl::rule_new

    当网络控制框架通过NetControl::add_rule产生一个新的规则是产生的信号。此时,规则还没有被添加到后端。

    NetControl::rule_added 当一条新的规则成功地添加到后端的时候产生此信号。
    NetControl::rule_exists 当后端告知此条规则已经存在的时候就不产生NetControl::rule_added,而是产生这个事件。
    NetControl::rule_timeout 当达到了一条规则的超时条件的时候,如果硬件不支持自动超时,那么网络控制框架就会自动地调用NetControl::remove_rule
    NetControl::rule_removed 当从后端成功地移除一个规则的时候产生。
    NetControl::rule_destroyed 这个事件附属于NetControl::rule_added,并且报告这个规则再也不由网络控制框架跟踪了。这个事件当某个规则从所有的后端中移除的时候产生。
    NetControl::rule_error 当规则操作的过程中出错的话,就产生这个事件。

    查找活动规则(Finding active rules)
    网络控制框架提供了两个方法来查找当前活动规则:NetControl::find_rules_addr查找所有的影响某个IP地址的规则,NetControl::find_rules_subnet查找所有影响某个特定子网的规则。
    试想,举个例子,Bro实例监视在边界的流量的情况(在没有配置任何防火墙以及交换规则的时候)。这种情况下,Bro可以看到来自那些被阻塞的IP地址所尝试的连接。此例中,NetControl::find_rules_addr方法可以用来检测某个地址是否在过去曾经被阻塞。
    下面是一个简单的例子,使用一个包含来自同一个IP地址的两条连接的跟踪。在第一条连接之后,这个脚本辨识出这个地址已经在第二条连接中被阻塞了。 

    netcontrol-6-find.bro
    
    event NetControl::init()
    	{
    	local netcontrol_debug = NetControl::create_debug(T);
    	NetControl::activate(netcontrol_debug, 0);
    	}
    
    event connection_established(c: connection)
    	{
    	if ( |NetControl::find_rules_addr(c$id$orig_h)| > 0 )
    		{
    		print "Rule already exists";
    		return;
    		}
    
    	NetControl::drop_connection(c$id, 20 secs);
    	print "Rule added";
    	}
    # bro -C -r tls/google-duplicate.trace netcontrol-6-find.bro
    netcontrol debug (Debug-All): init
    netcontrol debug (Debug-All): add_rule: [ty=NetControl::DROP, target=NetControl::FORWARD, entity=[ty=NetControl::CONNECTION, conn=[orig_h=192.168.4.149, orig_p=60623/tcp, resp_h=74.125.239.129, resp_p=443/tcp], flow=<uninitialized>, ip=<uninitialized>, mac=<uninitialized>], expire=20.0 secs, priority=0, location=, out_port=<uninitialized>, mod=<uninitialized>, id=2, cid=2, _plugin_ids={\x0a\x0a}, _active_plugin_ids={\x0a\x0a}, _no_expire_plugins={\x0a\x0a}, _added=F]
    Rule added
    Rule already exists
    

     除了阻塞功能,NetControl::get_catch_release_info函数提供的捕捉和释放功能可以检查某个地址是否已经被阻塞了并获取这个块的信息。函数NetControl::unblock_address_catch_release函数可以将前面的地址解除阻塞。
    注意:
    由于捕捉和释放(Catch and release)有除了网络控制框架所提供的跟踪之外属于它们自己的连接跟踪,所以移除由捕捉和释放添加的规则的时候仅仅调用NetControl::remove_rule是不够的。你还要使用NetControl::unblock_address_catch_release

     网络控制插件(NetControl Plugins)
    使用现存的插件(Using the existing plugins)
    在文档的API部分,我们专门使用了调试插件,将它的输出打印到屏幕上去。除了这个调试插件,Bro提供了一些可用于作为网络控制框架和网络硬件和软件的接口的插件。
    网络控制框架现有的插件有:

    插件名 描述
    OpenFlow插件 这是最具特色的插件,它允许网络控制框架和OpenFlow交换机交互。这个插件的源在base/frameworks/netcontrol/plugins/openflow.bro.
    中间人插件(Broker plugin) 这个库提供了使用新的Bro通信库(Bro)来发送网络控制命令的一般方法。外部程序可以接收规则并采取动作,我们提供了一个示例脚本,这个示例脚本调用用网络控制框架触发的命令行程序。该插件的源是base/frameworks/netcontrol/plugins/broker.bro.
    acld插件 此插件对acld守护进程体工支持,可以和若干交换机和路由器交互。当前版本的acld可以从LBL ftp server中获取。此插件的源在base/frameworks/netcontrol/plugins/acld.bro.
    包过滤器插件 此插件使用Bro进程级包过滤器(请看install_src_net_filterinstall_dst_net_filter)。由于PacketFilter的功能被限制了,此插件一般作演示使用。此插件的源在base/frameworks/netcontrol/plugins/packetfilter.bro.
    调试插件 调试插件简单地将它的动作输出到标准输出中。此插件的源base/frameworks/netcontrol/plugins/debug.bro.

     激活插件(Activating plugins)
    在本文的API引用部分,我们已经使用了调试插件。为了使用插件,我们先通过调用NetControl::create_debug来将它实例化,然后通过调用NetControl::activate将它添加到网络控制框架中去。

    正如我们先前所提示的那样,网络控制框架支持同时拥有多个活动的插件。NetContro::activate的第二个参数是刚刚添加的后端的优先级。每一条规则都会按照顺序发送给所有的插件,从高优先级到低优先级的顺序。后端就可以选择是否接受这个规则以及是否将这个规则推送给它所管理的硬件。在这种情况下,网络控制框架会尝试以稍低一级的优先级来将规则应用到后端上。如果没有后端接受到规则,这次的规则插入就失败了。

    对某条规则是接受还是拒绝的请求和插件的类型有关(按照我的理解翻译的)。我们认为调试插件会接受所有规则。然而,其他的插件你可以指定它们接受那些规则。试想,举个例子,一个网络中有两个OpenFlow交换机。第一个交换机将网络内部的包转发到外部,第二台交换机坐落在你的Bro簇的前面进行包分流。这种情况下,我们可以添加两个OpenFlow后端到网络控制框架中。当你使用NetControl::create_openflow进行实例化的时候,你在NetControl::OfConfig中恰当地设置监视和转发属性。此后,这些后端之一仅会为监视器路径接受规则,其他的后端仅会为转发路径接受规则。

     通常,插件也提供了预测功能,允许用户指明他们想要接受的规则的限制条件。当你有仅对某些子网负责的交换机的时候,这个功能就可以派上用场。预测功能会检测这些规则的子网并且只接受匹配指定交换机所负责的子网的规则的规则。

    再举个例子,下面的脚本给网络控制框架添加了两个后端。一个后端是OpenFlow后端,它使用OpenFlow调试模式,将OpenFlow规则输出到openflow.log文件中去。OpenFlow后端使用了预测功能仅接受规则在192.168.17.0/24网络中的源地址,所有其他的规则都会传送给调试插件。我们在NetControl::init_done事件中手动阻塞一些地址去验证此功能的正确性。

    netcontrol-8-multiple.bro
    
    function our_openflow_check(p: NetControl::PluginState, r: NetControl::Rule): bool
    	{
    	if ( r$ty == NetControl::DROP &&
    		r$entity$ty == NetControl::ADDRESS &&
    		subnet_width(r$entity$ip) == 32 &&
    		subnet_to_addr(r$entity$ip) in 192.168.17.0/24 )
    		return F;
    
    	return T;
    	}
    
    event NetControl::init()
    	{
    	# Add debug plugin with low priority
    	local debug_plugin = NetControl::create_debug(T);
    	NetControl::activate(debug_plugin, 0);
    
    	# Instantiate OpenFlow debug plugin with higher priority
    	local of_controller = OpenFlow::log_new(42);
    	local netcontrol_of = NetControl::create_openflow(of_controller, [$check_pred=our_openflow_check]);
    	NetControl::activate(netcontrol_of, 10);
    	}
    
    event NetControl::init_done()
    	{
    	NetControl::drop_address(10.0.0.1, 1min);
    	NetControl::drop_address(192.168.17.2, 1min);
    	NetControl::drop_address(192.168.18.2, 1min);
    	}
    # bro netcontrol-8-multiple.bro
    netcontrol debug (Debug-All): init
    netcontrol debug (Debug-All): add_rule: [ty=NetControl::DROP, target=NetControl::FORWARD, entity=[ty=NetControl::ADDRESS, conn=<uninitialized>, flow=<uninitialized>, ip=192.168.17.2/32, mac=<uninitialized>], expire=1.0 min, priority=0, location=, out_port=<uninitialized>, mod=<uninitialized>, id=3, cid=3, _plugin_ids={\x0a\x0a}, _active_plugin_ids={\x0a\x0a}, _no_expire_plugins={\x0a\x0a}, _added=F]

    正如你所看到的一样,仅有影响192.168.17.0/24网络的块输出到了命令行。另外两行由OpenFlow插件来处理。我们可以通过查看netcontrol.log文件来验证。插件这一列显示了哪个插件处理了一条规则并揭示了有两条规则是由OpenFlow来处理的。

    # cat netcontrol.log
    #separator \x09
    #set_separator    ,
    #empty_field      (empty)
    #unset_field      -
    #path     netcontrol
    #open     2018-11-22-14-05-23
    #fields   ts      rule_id category        cmd     state   action  target  entity_type     entity  mod     msg     priority        expire  location        plugin
    #types    time    string  enum    string  enum    string  enum    string  string  string  string  int     interval        string  string
    1542895523.407420 -       NetControl::MESSAGE     -       -       -       -       -       -       -       activating plugin with priority 0       -       -       -       Debug-All
    1542895523.407420 -       NetControl::MESSAGE     -       -       -       -       -       -       -       activation finished     -       -       -       Debug-All
    1542895523.407420 -       NetControl::MESSAGE     -       -       -       -       -       -       -       activating plugin with priority 10      -       -       -       Openflow-Log-42
    1542895523.407420 -       NetControl::MESSAGE     -       -       -       -       -       -       -       activation finished     -       -       -       Openflow-Log-42
    1542895523.407420 -       NetControl::MESSAGE     -       -       -       -       -       -       -       plugin initialization done      -       -       -       -
    1542895523.407420 2       NetControl::RULE        ADD     NetControl::REQUESTED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     10.0.0.1/32     -       -       0       60.000000       -       Openflow-Log-42
    1542895523.407420 3       NetControl::RULE        ADD     NetControl::REQUESTED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     192.168.17.2/32 -       -       0       60.000000       -       Debug-All
    1542895523.407420 4       NetControl::RULE        ADD     NetControl::REQUESTED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     192.168.18.2/32 -       -       0       60.000000       -       Openflow-Log-42
    1542895523.407420 3       NetControl::RULE        ADD     NetControl::SUCCEEDED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     192.168.17.2/32 -       -       0       60.000000       -       Debug-All
    1542895523.407420 2       NetControl::RULE        ADD     NetControl::SUCCEEDED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     10.0.0.1/32     -       -       0       60.000000       -       Openflow-Log-42
    1542895523.407420 4       NetControl::RULE        ADD     NetControl::SUCCEEDED   NetControl::DROP        NetControl::FORWARD     NetControl::ADDRESS     192.168.18.2/32 -       -       0       60.000000       -       Openflow-Log-42
    #close    2018-11-22-14-05-23

     此外,openflow.log也显示了这两条规则,转换到OpenFlow流方法:

    # cat openflow.log
    #separator \x09
    #set_separator    ,
    #empty_field      (empty)
    #unset_field      -
    #path     openflow
    #open     2018-11-22-14-05-23
    #fields   ts      dpid    match.in_port   match.dl_src    match.dl_dst    match.dl_vlan   match.dl_vlan_pcp       match.dl_type   match.nw_tos    match.nw_proto  match.nw_src    match.nw_dst    match.tp_src    match.tp_dst    flow_mod.cookie flow_mod.table_id       flow_mod.command        flow_mod.idle_timeout   flow_mod.hard_timeout   flow_mod.priority       flow_mod.out_port       flow_mod.out_group      flow_mod.flags  flow_mod.actions.out_ports      flow_mod.actions.vlan_vid       flow_mod.actions.vlan_pcp       flow_mod.actions.vlan_strip     flow_mod.actions.dl_src flow_mod.actions.dl_dst flow_mod.actions.nw_tos flow_mod.actions.nw_src flow_mod.actions.nw_dst flow_mod.actions.tp_src flow_mod.actions.tp_dst
    #types    time    count   count   string  string  count   count   count   count   count   subnet  subnet  count   count   count   count   enum    count   count   count   count   count   count   vector[count]   count   count   bool    string  string  count   addr    addr    count   count
    1542895523.407420 42      -       -       -       -       -       2048    -       -       10.0.0.1/32     -       -       -       4398046511108   -       OpenFlow::OFPFC_ADD     0       60      0       -       -       1       (empty) -       -       F       -       -       -       -       -       -       -
    1542895523.407420 42      -       -       -       -       -       2048    -       -       -       10.0.0.1/32     -       -       4398046511109   -       OpenFlow::OFPFC_ADD     0       60      0       -       -       1       (empty) -       -       F       -       -       -       -       -       -       -
    1542895523.407420 42      -       -       -       -       -       2048    -       -       192.168.18.2/32 -       -       -       4398046511112   -       OpenFlow::OFPFC_ADD     0       60      0       -       -       1       (empty) -       -       F       -       -       -       -       -       -       -
    1542895523.407420 42      -       -       -       -       -       2048    -       -       -       192.168.18.2/32 -       -       4398046511113   -       OpenFlow::OFPFC_ADD     0       60      0       -       -       1       (empty) -       -       F       -       -       -       -       -       -       -
    #close    2018-11-22-14-05-23

     注意:
    你可能会有疑问,如果添加两个或者多个同样优先级的规则的话,会怎么样?在这种情况下,规则会同时发送给所有的后端。这样是很有用的,比如当你有冗余的交换机的时候(这些交换机应该保持一样的规则状态)。

    和外部硬件交互(Interfacing with external hardware)
    既然我们已经知道了有哪些插件存在以及它们是如何添加到网络控制框架中的,接下来就让我们看看我们如何让Bro和实际硬件交互。实现这种交互的一个典型的方法就是使用Bro通信库(Broker),这个我们可以用于将Bro事件和外部程序以及脚本交换。网络控制插件可以使用Broker来发送事件到外部程序中去,外部程序可以基于这些事件来采取措施。
    下面的图显示了使用OpenFlow插件的例子的体系结构。OpenFlow插件使用Broker来发送事件到一个外部的Python脚本(此脚本使用Ryu SDN controller来和交换机通信)。

     这个Python脚本用于和可获得的NetControl插件交互(包含在bro-netcontrol的github仓库中)。这个仓库包含了OpenFlow和acld插件的脚本。此外,它还包含了用于broker插件调用可配置的命令行程序的脚本。
    此仓库还包含了如何安装这些连接器的文本。在netcontrol目录下包含一个让你写自己的连接器到broker插件的API。

    注意:
    Broker通信库的API还没有完全完结。你在未来的新的Bro版本中可能要重写一些关于它的脚本。

    写插件(Writing plugins)
    除了使用作为网络控制框架的一部分出现的插件,你需要写你自己的插件来与我们尚未专门以盒的形式(是不是封装好弄成现成的插件的意思?)支持的硬件或软件交互。
    创建我们自己的插件实际上很简单,除了套用前面的模子,你只要创建两个函数:一个是当一条规则添加的时候调用的,另一个是当一条规则被移除的时候调用的。注意,你要产生NetControl::rule_addedNetControl::rule_removed事件在你的插件中,让网络控制框架知道一条规则被成功地添加和移除。

    netcontrol-9-skeleton.bro
    
    module NetControl;
    
    export {
    	## Instantiates the plugin.
    	global create_skeleton: function(argument: string) : PluginState;
    }
    
    function skeleton_name(p: PluginState) : string
    	{
    	return "NetControl skeleton plugin";
    	}
    
    function skeleton_add_rule_fun(p: PluginState, r: Rule) : bool
    	{
    	print "add", r;
    	event NetControl::rule_added(r, p);
    	return T;
    	}
    
    function skeleton_remove_rule_fun(p: PluginState, r: Rule, reason: string &default="") : bool
    	{
    	print "remove", r;
    	event NetControl::rule_removed(r, p);
    	return T;
    	}
    
    global skeleton_plugin = Plugin(
    	$name = skeleton_name,
    	$can_expire = F,
    	$add_rule = skeleton_add_rule_fun,
    	$remove_rule = skeleton_remove_rule_fun
    	);
    
    function create_skeleton(argument: string) : PluginState
    	{
    	local p = PluginState($plugin=skeleton_plugin);
    
    	return p;
    	}

    这个例子已经具备完整的功能,我们可以在脚本中使用它,就象我们的第一个例子一样:

    netcontrol-10-use-skeleton.bro
    
    event NetControl::init()
    	{
    	local skeleton_plugin = NetControl::create_skeleton("");
    	NetControl::activate(skeleton_plugin, 0);
    	}
    
    event connection_established(c: connection)
    	{
    	NetControl::drop_connection(c$id, 20 secs);
    	}
    # bro -C -r tls/ecdhe.pcap netcontrol-10-use-skeleton.bro
    add, [ty=NetControl::DROP, target=NetControl::FORWARD, entity=[ty=NetControl::CONNECTION, conn=[orig_h=192.168.18.50, orig_p=56981/tcp, resp_h=74.125.239.97, resp_p=443/tcp], flow=<uninitialized>, ip=<uninitialized>, mac=<uninitialized>], expire=20.0 secs, priority=0, location=, out_port=<uninitialized>, mod=<uninitialized>, id=2, cid=2, _plugin_ids={
    
    }, _active_plugin_ids={
    
    }, _no_expire_plugins={
    
    }, _added=F]
    

     如果你想要写你自己的插件,那么去看看网络控制框架已经有的插件来了解它们是如何定义预测功能的以及它们是如何和Broker交互的,这会很有帮助。

     

     

     

    展开全文
  • Next 常用的框架

    2021-03-02 10:30:51
    常用的框架 分布式缓存框架 Microsoft Velocity:微软自家分布式缓存服务框架。 Memcahed:一套分布式的高速缓存系统,目前被许多网站使用以提升网站的访问速度。 Redis:是一个高性能的KV数据库。它的出现很大...
  • XLua框架搭建——类型导出与黑名单

    千次阅读 2018-04-11 20:08:30
    开始,把其参数的类型全路径全出来。 例如下面是对GameObject的一个属性以及FileInfo的一个方法列入黑名单: [BlackList] public static List string >> BlackList = new List string >>() { new List< ...
  • mina框架详解

    千次阅读 2015-02-13 10:18:36
    Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP、UDP/IP协议栈的通信框架(当然,也可以提供JAVA 对象的序列化服务、虚拟机管道通信服务等),Mina 可以帮助我们快速开发高性能、高...
  • 84PHP框架 1.3.0.zip

    2019-05-23 15:23:19
    新增了对IP(IPV4)黑白名单的操作模块;新增了对客户端信息记录的功能;新增了错误页中,能够取消自动跳转的功能,并将倒计时调整为7秒;新增了示例页面、默认报错页面在IE6-9下的样式兼容性;新增了代码压缩的功能...
  • C# 开源框架

    千次阅读 2017-03-23 13:31:21
    C# 开源框架
  • 淘宝架构框架

    千次阅读 2016-02-15 15:55:19
    转 淘宝架构框架 发表于1年前(2014-11-13 16:14) 阅读(14313) | 评论(9) 105人收藏此文章,我要收藏 赞8  一、个人网站   2003 年 4 月 7 日,马云,在杭州,成立了一个神秘
  • MINA框架

    2013-05-26 21:04:58
    MINA 基本类的描述 先认识几个接口: IoAccepter 相当于网络应用程序中的服务器端 IoConnector 相当于客户端 IoSession 当前客户端...MINA框架的常用类  类NioSocketAcceptor用于创建服务端监听;  类NioSo
  • .NET 各种框架

    2019-09-21 22:12:59
    基于.NET平台常用的框架整理 分布式缓存框架: Microsoft Velocity:微软自家分布式缓存服务框架。 Memcahed:一套分布式的高速缓存系统,目前被许多网站使用以提升网站的访问速度。 Redis:是一个高性能的KV...
  • TensorFlow(GitHub),出身名门谷歌,风姿绰约的功能,并且经过谷歌自己的Gmail和搜索引擎的实战磨练提升,这样的深度学习框架热度第一应该没有多少异议了。开源之后,谷歌长期支持对打算投入资源的公司和开发者也...
  • .NET平台常用框架整理

    2017-12-08 08:29:59
    .Net常用的框架
  • Hadoop整体框架

    千次阅读 多人点赞 2018-07-17 18:08:17
    大数据框架 目录 大数据框架 一、Hodoop 四大组件:HDFS/MapReduce/YARN/Common 二、Zookeeper 三、Hive 四、Spark 五、ETL 六、ngnix 七、Redis 八、Oracle 十一、Jsp/node.js/JQueryEcharts 一、...
  • SpringBoot整合Shiro权限框架

    万次阅读 多人点赞 2018-06-03 20:53:56
    Shiro是一个非常不错的权限框架,它提供了登录和权限验证功能,如果想去了解它的话可以去Shiro官网学习 点击打开 1.创建数据库脚本 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- -----------------------...
  • 简单搭建iOS开发项目框架

    千次阅读 2016-10-09 09:36:12
    今天我们来谈谈如何搭建框架框架需要做一些什么。第一步:找到我们的目标我们的目标是让其他开发人员拿到手后即可写页面,不再需要考虑其他的问题。第二步:我们需要做哪些东西各位跟着我一步一步来进行。假定我们...
  • .NET常用框架

    2018-01-25 10:51:47
    基于.NET平台常用的框架整理 自从学习.NET以来,优雅的编程风格,极度简单的可扩展性,足够强大开发工具,极小的学习曲线,让我对这个平台产生了浓厚的兴趣,在工作和学习中也积累了一些开源的组件,就目前想到的...
  • NET框架汇总

    2019-05-23 16:35:19
    分布式缓存框架: Microsoft Velocity:微软自家分布式缓存服务框架。 Memcahed:一套分布式的高速缓存系统,目前被许多网站使用以提升网站的访问速度。 Redis:是一个高性能的KV数据库。 它的出现很大程度补偿了...
  • .NET平台常见技术框架整理汇总

    万次阅读 多人点赞 2019-02-06 08:51:48
    使用.NET平台开发有段时间了,在网上资料的基础上做了进一步整理,汇集了.NET平台常见的技术框架。 参考资料: 基于.NET平台常用的框架整理 1.知识网络 2.分类清单 2.1.分布式缓存框架 名称 说明 地址 ...
  • Webx框架指南

    千次阅读 2015-11-23 16:43:33
    Webx框架指南 Michael Zhou zyh@alibaba-inc.com> 2010-11-13 引言 1. 阅读向导2. Webx是什么?3. Webx的历史4. 为什么要用Webx而不是其它的开源框架?5. Webx的优势 5.1. 成熟...
  • 图文详解mina框架

    万次阅读 2019-02-23 15:14:20
    Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP、UDP/IP协议栈的通信框架(当然,也可以提供JAVA 对象的序列化服务、虚拟机管道通信服务等),Mina 可以帮助我们快速开发高性能、高...
  • 淘宝框架历程

    千次阅读 2015-07-21 17:56:19
    Oracle 给全球的技术专家颁发一些头衔,其中最高级别的叫 ACE(就是扑克牌的“尖儿”,够大的吧),被授予这个头衔的人目前全球也只有 300 多名(名单在这里:  http://apex.oracle.com/pls/otn/f?p=19297:3  ),...
  • lavarvel框架路由

    2020-12-22 14:58:44
    1 路由 定义路由 Route::get('路由表示',function(){ }); Route::post('路由表示',function(){ ...除了get请求类型以外框架默认会对路由请求做csrf令牌验证 关闭令牌验证的方法 1 将 Http/Kernel.ph
  • IcePHP 框架文档

    千次阅读 2014-06-26 14:42:35
    IcePHP框架文档 简介 2014年6月25日 星期三 10:27   定位 IcePHP是一个私有的,非开源,基于MVC结构的轻量级PHP开发框架. 首要目标 以最低的学习成本提供了一个简单,易用,高效的PHP...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,186
精华内容 4,474
关键字:

列名单框架