Python 学习笔记6

来源:互联网 发布:超级基因优化液扫书 编辑:程序博客网 时间:2024/05/18 03:48

6、Python RESTful API 开发

1、RESTful API 概述

1-1、展示微博开放平台的 RESTfulAPI

介绍微博开放平台

open.weibo.com

在linux或mac中,$ curl 请求URL?请求参数

 pro.jsonlint.com上

 

通过HTTP请求,请求到JSON的过程,就是RESTful的调用

 

1-2、RESTful 设计理念

REST - Representational State Transfer 表现层状态转化

 

资源– resources – 网络上的具体信息,如文本,图片

 

URI – 统一资源标识符,用来唯一的标识一个资源

 

URL – 统一资源定位器,用来定位某个特定的资源,如一个网址

 

表现层(Representation)- 把资源具体呈现出来的形式

如:纯文本,HTML,JSON

 

状态转移 - State Transfer

 HTTP协议,是一个无状态的协议,所有的状态都保存在服务器端

  GET– 获取资源

 POST – 新建资源

  PUT– 更新资源

 DELETE – 删除资源

  一个RESTful请求:$ curl –X GEThttps://api.weibo.com/2/users/show.json

 

REST构架设计6原则

  UniformInferface

 Stateless

 Cacheable

 Client-Server

 Layered System

 Code on Demand

 

1-3、Python微型Web框架Flask简介

Flasks是一个基于Werkzeug,Jinja2以及”善意”构建的Python微型Web框架,并且基于BSD 开源证书!

 

 

1-4、一个例程和总结

Debug模式:

If __name__ == ’__main__’:

           App.run(debug=True)

 

Debug模式下,可以看到代码,可以直接调试

 

在浏览器中,使用POST方法较麻烦,但是可以在命令行中进行

$ curl –X POST 127.0.0.1:5000/index/yx

 

2、Python RESTful API开发工具介绍及应用

2-1 Chrome 开发者工具介绍

Network

 

Elements

 

Console

  使用console.table()方法,创建一个表格

  使用console.timeEnd()方法,统计一个操作所花费的时间

  实例:

 

进行移动端的开发,点击手机图标

 

2-2 Python HTTP 库 Requests 介绍

http://requests-docs-cn.readthedocs.org/zh_CN/latest/user/quickstart.html

 

实例:

[root@localhost ~]# python2.7

Python 2.7.11 (default, Apr  6 2016, 00:07:19)

[GCC 4.1.2 20080704 (Red Hat 4.1.2-55)] onlinux2

Type "help","copyright", "credits" or "license" for moreinformation.

>>> import requests

>>> r =requests.get('https://github.com/timeline.json')  //使用requests发送网络请求

/usr/local/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:315:SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject NameIndication) extension to TLS is not available on this platform. This may causethe server to present an incorrect TLS certificate, which can cause validationfailures. For more information, seehttps://urllib3.readthedocs.org/en/latest/security.html#snimissingwarning.

 SNIMissingWarning

>>> r.text

u'{"message":"Hello there,wayfaring stranger. If you\u2019re reading this then you probably didn\u2019tsee our blog post a couple of years back announcing that this API would goaway: http://git.io/17AROg Fear not, you should be able to get what you needfrom the shiny new Events API instead.","documentation_url":"https://developer.github.com/v3/activity/events/#list-public-events"}'

>>> payload = {'key1': 'value1','key2': 'value2'}  //为URL传递参数

>>> r = requests.get("http://httpbin.org/get",params=payload)

>>> r.text  //读取服务器响应的内容,unicode字符集

u'{\n "args": {\n   "key1": "value1", \n    "key2": "value2"\n  }, \n  "headers":{\n    "Accept":"*/*", \n   "Accept-Encoding": "gzip, deflate", \n    "Host": "httpbin.org",\n    "User-Agent":"python-requests/2.9.1"\n  },\n  "origin": "114.255.40.54",\n  "url":"http://httpbin.org/get?key2=value2&key1=value1"\n}\n'

>>> r.url  //访问URL

u'http://httpbin.org/get?key2=value2&key1=value1'

>>> r.json()  //返回服务器JSON格式的内容

{u'origin': u'114.255.40.54', u'headers':{u'Host': u'httpbin.org', u'Accept-Encoding': u'gzip, deflate', u'Accept':u'*/*', u'User-Agent': u'python-requests/2.9.1'}, u'args': {u'key2': u'value2',u'key1': u'value1'}, u'url':u'http://httpbin.org/get?key2=value2&key1=value1'}

>>> type(r.text)

<type 'unicode'>

>>> type(r.json())

<type 'dict'>

>>> 

 

 

C:\Users\Administrator>python

Python 2.7.11 (v2.7.11:6d1b6a68f775,Dec  5 2015, 20:32:19) [MSC v.1500 32 bit(

Intel)] on win32

Type "help","copyright", "credits" or "license" for moreinformation.

>>> from PIL import Image   //以请求返回的二进制数据创建一张图片

>>> from StringIO import StringIO

>>> import requests

>>> r =requests.get('http://requests-docs-cn.readthedocs.org/zh_CN/latest/_stat

ic/requests-sidebar.png')

>>> i =Image.open(StringIO(r.content))

>>> i.show()

>>> 

//定制请求头 – 模拟一个浏览器

# requests_t.py - Browser

import requests

hdr ={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110Safari/537.36"}

r = requests.get('http://127.0.0.1:5000/test1', headers = hdr)
print r.text

 

# app.py - Server

from flask import Flask,request

 

app = Flask(__name__)

 

@app.route('/')

def index():

   print request.headers

   return 'Hello restful'

 

if __name__ == '__main__':

   app.run(host='0.0.0.0', port=5000, debug=True)

 

2-3 实践:动手编写一个验证登录的程序

token是一个加密的字符串,其中包含用户名,过期时间,以及一些随机信息

 base64对token进行加密

代码:

# app.py

import base64import randomimport timefrom flask import Flask, requestapp = Flask(__name__)users = {    "magigo": ["123456"]}def gen_token(uid):    token = base64.b64encode(':'.join([str(uid), str(random.random()), str(time.time() + 7200)]))    users[uid].append(token)    return tokendef verify_token(token):    _token = base64.b64decode(token)    if not users.get(_token.split(':')[0])[-1] == token:        return -1    if float(_token.split(':')[-1]) >= time.time():        return 1    else:        return 0@app.route('/index', methods=['POST', 'GET'])def index():    print request.headers    return 'hello'@app.route('/login', methods=['POST', 'GET'])def login():    uid, pw = base64.b64decode(request.headers['Authorization'].split(' ')[-1]).split(':')    if users.get(uid)[0] == pw:        return gen_token(uid)    else:        return 'error'@app.route('/test1', methods=['POST', 'GET'])def test():    token = request.args.get('token')    if verify_token(token) == 1:        return 'data'    else:        return 'error'if __name__ == '__main__':    app.run(debug=True)

 

#requests_r.py

import requests# r = requests.get('http://127.0.0.1:5000/login', auth=('magigo', '123456'))# print r.text
token ='bWFnaWdvOjAuMzE4MTUxNTA1MjQ4OjE0MjU4MzkzMjMuODk='r = requests.get('http://127.0.0.1:5000/test1', params={'token': token})print r.text# bWFnaWdvOjAuMzE4MTUxNTA1MjQ4OjE0MjU4MzkzMjMuODk=
 

3、OAuth 2.0介绍和实现(上)

3-1 OAuth 2.0的原理介绍

OAuth – 开放授权 – 一个正式的互联网标准协议

  三方协作的过程:用户、网站、微博

Token – 令牌

OAuth概述 – 其中,B步骤是关键

OAuth四种授权模式

  授权码模式

  简化模式

  密码模式

  客户端模式

 

授权码模式

3-2 实现OAuth 2.0协议中的必选方法

1)实现重定向的机制

# app.py

@app.route('/client/login')

def client_login():

   uri = 'http://localhost:5000/oauth'

   return redirect(uri)

 

# request_t.py

r =requests.get('http://localhost:5000/client/login')

print r.text

print '======='

print r.history    //通过history属性,取得重定向情况

 

# app.py

@app.route('/oauth')

def oauth():

   return 'Please login'

 

返回结果:

Please login

=======

[<Response [302]>]   //表示重定向

 

2)实现授权码的机制

auth_code = {}

def gen_code(uri):

   code = random.randint(0,10000)

   auth_code[code] = uri

return code

 

3)实现token发放的机制

@app.route('/oauth')

def oauth():

   if request.args.get('code'):

       if auth_code.get(int(request.args.get('code'))) ==request.args.get('redirect_uri'):

           return gen_token(request.args.get('client_id'))

return 'Pleaselogin'

 

3-3 编写OAuth授权服务器

知识点:

清除OAuth 2.0协议的内容

掌握Flask的重定向操作

掌握OAuth 2.0协议中的授权模式

代码:

#app.py
import base64
import random
import time

from flask importFlask, request, redirect

app = Flask(__name__)

users = {
    "yx": ["35"]
}

redirect_uri='http://localhost:5000/client/passport'

client_id = '35'
users[client_id] = []
auth_code = {}
oauth_redirect_uri = []

# 授权码生成器
def gen_auth_code(uri):
    code = random.randint(0,10000)
    auth_code[code] = uri
    return code

def gen_token(uid):
    token = base64.b64encode(':'.join([str(uid),str(random.random()), str(time.time() +7200)]))
    users[uid].append(token)
    return token

def verify_token(token):
    _token = base64.b64decode(token)
    if not users.get(_token.split(':')[0])[-1] == token:
        return -1
   
if float(_token.split(':')[-1]) >= time.time():
        return 1
   
else:
        return 0

@app.route('/index',methods=['POST','GET'])
def index():
    print request.headers
    return 'hello'

@app.route('/login',methods=['POST','GET'])
def login():
    uid, pw =base64.b64decode(request.headers['Authorization'].split(' ')[-1]).split(':')
    if users.get(uid)[0] == pw:
        return gen_token(uid)
    else:
        return 'error'

@app.route('/oauth',methods=['POST','GET'])
def oauth():
    # 登录
   
if request.args.get('user'):
        if users.get(request.args.get('user'))[0] == request.args.get('pw')and oauth_redirect_uri:
            uri = oauth_redirect_uri[0] + '?code=%s' % gen_auth_code(oauth_redirect_uri[0])
            return redirect(uri)
    # 验证授权码,发放token
   
if request.args.get('code'):
        if auth_code.get(int(request.args.get('code'))) == request.args.get('redirect_uri'):
            return gen_token(request.args.get('client_id'))
    #
   
if request.args.get('redirect_uri'):
       oauth_redirect_uri.append(request.args.get('redirect_uri'))
    return 'please login'

# 客户端
@app.route('/client/login',methods=['POST','GET'])
def client_login():
    uri = 'http://localhost:5000/oauth?response_type=code&client_id=%s&redirect_uri=%s'% (client_id, redirect_uri)
    return redirect(uri)

@app.route('/client/passport',methods=['POST','GET'])
def client_passport():
    code = request.args.get('code')
    uri = 'http://localhost:5000/oauth?grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s'% (code, redirect_uri, client_id)
    return redirect(uri)

@app.route('/test1',methods=['POST','GET'])
def test():
    token = request.args.get('token')
    if verify_token(token) == 1:
        return 'data'
   
else
:
        return 'error'

if
__name__ == '__main__':
    app.run(debug=True)

 

 

# requests.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests

r = requests.get('http://127.0.0.1:5000/client/login')
print r.text
print '========='
print
r.history
print '========='
print
r.url
print '========='
uri_login = r.url.split('?')[0] +'?user=yx&pw=35'
r2 = requests.get(uri_login)
print r2.text
print r2.history
print '========='
r3 = requests.get('http://127.0.0.1:5000/test1',params={'token': r2.text})
print r3.text

 

4、OAuth 2.0介绍和实现(下)

4-1 Flask渲染页面及Cookies

Flask中的表单

  提交表单时,使用POST方法

  http://dormousehole.readthedocs.org/en/latest/quickstart.html#sessions

 

Flask响应对象

  ResponseHeaders – 浏览器的响应头

  http://dormousehole.readthedocs.org/en/latest/quickstart.html#about-responses

 

Cookies简介 – 小型文本文件

  1)分类:Cookies总是存在客户端中,按在客户端中的存储位置,可分为内存Cookies和硬盘Cookies

  2)用途:Cookies可以用于购物车,以及免登陆

  3)缺陷:Cookies增加了网络流量,造成安全问题,无法存储大量数据

  http://dormousehole.readthedocs.org/en/latest/quickstart.html#id11

 

4-2 Token的设计以及加密方法

知识点:

  无需核对用户名密码的token验证机制 – 无法被伪造的token

  hmac(哈希运算消息认证码)简介

 

Token的基本设计原则:

  Token不携带用户敏感信息

  无需查询数据库,Token可以进行自我验证

 

hmac简介

作用:验证消息是否被篡改

公式:

使用Python的hmac模块直接计算:hmac.new(‘secret123’,value).digest()

缺陷:无法抵御重放攻击

 

验证token的有效性:真的没过期合法请求

  验证验证码是否一致

  验证是否过期 – 生存期10分钟左右

  验证这个token是否属于被请求数据的用户

 

4-3 最终的编码

总结:

  掌握Flask渲染页面和处理响应对象

  了解hmac加密验证的内容

安全建议:使用HTTPS传输,使用请求头而不是参数传递Token

 

实战:制作一个基于OAuth 2.0的验证登录服务

在Chrome开发者工具中,resources-> Cookies->localhost中,可以看到用户名和密码

代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import base64
import random
import time
import json
import hmac
from datetime importdatetime,timedelta

from flask importFlask, request, redirect, make_response

app = Flask(__name__)

users = {
    "yx": ["35"]
}

redirect_uri='http://localhost:5000/client/passport'
client_id = '123456'
users[client_id] = []
auth_code = {}
oauth_redirect_uri = []

TIMEOUT = 3600 * 2


# 新版本的token生成器
def gen_token(data):
    '''
   
:param data: dict type
   
:return: base64 str
    '''
   
data = data.copy()
    if "salt" not in data:
        data["salt"] = unicode(random.random()).decode("ascii")
    if "expires"not in data:
        data["expires"] = time.time() + TIMEOUT
    payload = json.dumps(data).encode("utf8")
    # 生成签名
   
sig =_get_signature(payload)
    return encode_token_bytes(payload + sig)

# 授权码生成器
def gen_auth_code(uri, user_id):
    code = random.randint(0,10000)
    auth_code[code] = [uri, user_id]
    return code

# 新版本的token验证
def verify_token(token):
    '''
   
:param token: base64 str
   
:return: dict type
    '''
   
decoded_token =decode_token_bytes(str(token))
    payload = decoded_token[:-16]
    sig = decoded_token[-16:]
    # 生成签名
   
expected_sig =_get_signature(payload)
    if sig != expected_sig:
        return {}
    data = json.loads(payload.decode("utf8"))
    if data.get('expires') >=time.time():
        return data
    return 0

# 使用hmac为消息生成签名
def _get_signature(value):
    """Calculatethe HMAC signature for the given value."""
   
return hmac.new('secret123456', value).digest()

# 下面两个函数将base64编码和解码单独封装
def encode_token_bytes(data):
    return base64.urlsafe_b64encode(data)

def decode_token_bytes(data):
    return base64.urlsafe_b64decode(data)


# 验证服务器端
@app.route('/index',methods=['POST','GET'])
def index():
    return 'hello'

@app.route('/login',methods=['POST','GET'])
def login():
    uid, pw =base64.b64decode(request.headers['Authorization'].split(' ')[-1]).split(':')
    if users.get(uid)[0] == pw:
        return gen_token(dict(user=uid,pw=pw))
    else:
        return 'error'

@app.route('/oauth',methods=['POST','GET'])
def oauth():
    # 处理表单登录, 同时设置Cookie
   
if request.method =='POST' and request.form['user']:
        u = request.form['user']
        p = request.form['pw']
        if users.get(u)[0] == pand oauth_redirect_uri:
            uri = oauth_redirect_uri[0] + '?code=%s' % gen_auth_code(oauth_redirect_uri[0], u)
            expire_date = datetime.now()+ timedelta(minutes=1)
            resp =make_response(redirect(uri))
            resp.set_cookie('login','_'.join([u, p]), expires=expire_date)
            return resp
    # 验证授权码,发放token
   
if request.args.get('code'):
        auth_info = auth_code.get(int(request.args.get('code')))
        if auth_info[0] ==request.args.get('redirect_uri'):
            # 可以在授权码的auth_code中存储用户名,编进token
           
return gen_token(dict(client_id=request.args.get('client_id'),user_id=auth_info[1]))
    # 如果登录用户有Cookie,则直接验证成功,否则需要填写登录表单
   
if request.args.get('redirect_uri'):
       oauth_redirect_uri.append(request.args.get('redirect_uri'))
        if request.cookies.get('login'):
            u, p = request.cookies.get('login').split('_')
            if users.get(u)[0] == p:
                uri = oauth_redirect_uri[0] + '?code=%s' % gen_auth_code(oauth_redirect_uri[0], u)
                return redirect(uri)
        return '''
            <form action=""method="post">
                <p><inputtype=text name=user>
                <p><inputtype=text name=pw>
                <p><inputtype=submit value=Login>
            </form>
        '''


# 客户端
@app.route('/client/login',methods=['POST','GET'])
def client_login():
    uri = 'http://localhost:5000/oauth?response_type=code&client_id=%s&redirect_uri=%s'% (client_id, redirect_uri)
    return redirect(uri)

@app.route('/client/passport',methods=['POST','GET'])
def client_passport():
    code = request.args.get('code')
    uri = 'http://localhost:5000/oauth?grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s'% (code, redirect_uri, client_id)
    return redirect(uri)


# 资源服务器端
@app.route('/test1',methods=['POST','GET'])
def test():
    token = request.args.get('token')
    ret = verify_token(token)
    if ret:
        return json.dumps(ret)
    else:
        return 'error'

if
__name__ == '__main__':
    app.run(debug=True)

 

5、Flask-RESTful插件介绍及应用

5-1 Flask-RESTful插件介绍

Flask-RESTful概述

Flask-RESTful 是一个Flask扩展,它添加了快速构建REST APIs的支持。它当然也是一个能够跟你现有的ORM/库协同工作的轻量级的扩展。Flask-RESTful鼓励以最小设置的最佳实践。如果你熟悉 Flask 的话,Flask-RESTful 应该很容易上手。

 

安装:>pip install flask-restful

 

资源路由

Flask-restful不在使用装饰器作为路由处理请求的部分是类,而不是flask的函数

可插拔视图:

http://dormousehole.readthedocs.org/en/latest/views.html

代码:

  from flask import Flaskfrom flask.ext import restfulapp = Flask(__name__)api = restful.Api(app)class HelloWorld(restful.Resource):    def get(self):        return {'hello': 'world'}api.add_resource(HelloWorld, '/')if __name__ == '__main__':    app.run(debug=True)

 

参数解析

代码:

  # 1. 关于参数解析的部分# 一组虚拟的数据TODOS = {    'todo1': {'task': 1},    'todo2': {'task': 2},    'todo3': {'task': 3},}# 定义允许的参数为task,类型为int,以及错误时的提示parser = reqparse.RequestParser()parser.add_argument('task', type=int, help='Please set a int task content!')# 真正处理请求的地方class TodoList(Resource):    def get(self):        return TODOS, 200, {'Etag': 'some-opaque-string'}    def post(self):        args = parser.parse_args()        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1        todo_id = 'todo%i' % todo_id        TODOS[todo_id] = {'task': args['task']}        return TODOS[todo_id], 201# 实际定义路由的地方api.add_resource(TodoList, '/todos', '/all_tasks')

Linux下测试:

% curl http://localhost:5000/todos -d “task=4” –X POST

% curl http://localhost:5000/todos

 

响应域

代码:

    # 2. 关于响应域的部分# ORM的数据模型class TodoDao(object):    def __init__(self, todo_id, task):        self.todo_id = todo_id        self.task = task        # 这个域不会被返回        self.status = 'active'# marshal-蒙版resource_fields = {    'task':   fields.String,    'uri':    fields.Url('todo_ep')}# 真正处理请求的地方class Todo(Resource):    # 蒙版    @marshal_with(resource_fields)    def get(self, todo_id):        return TodoDao(todo_id=todo_id, task='Remember the milk'), 200# 实际定义路由的地方api.add_resource(Todo, '/todos/<todo_id>', endpoint='todo_ep')

 

Linux下测试:

% curl http://localhost:5000/todos/4

结果:

{

    "task": "Remember themilk",

    "uri": "/todos/4"

}

 

5-2 Flask-RESTful请求解析

知识点:

必选参数与多值参数的设定

指定获取参数的位置

代码:

  from flask importFlask
from flask.ext.restful importreqparse, Api,Resource

app = Flask(__name__)
api = Api(app)

USERS = {
    'row1': {'name':'jilu', 'rate': [70,65]},
    'row2': {'name':'bob', 'rate': [80,90, 68]},
    'row3': {'name':'tim', 'rate': [90,80]},
}

parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True)  //必选参数
parser.add_argument('rate', type=int, help='rate is a number',action='append')
parser.add_argument('User-Agent',type=str, location='headers') //指定获取参数的位置

# 真正处理请求的地方class UserInfo(Resource):    def get(self):        return USERS, 200    def post(self):        args = parser.parse_args()        user_id = int(max(USERS.keys()).lstrip('row')) + 1        user_id = 'row%i' % user_id        USERS[user_id] = {'name': args['name'], 'rate': args['rate']}        USERS[user_id]['ua'] = args.get('User-Agent')        return USERS[user_id], 201api.add_resource(UserInfo, '/')if __name__ == '__main__':    app.run(debug=True)

 

参数解析器的继承

代码:

  # 解析器继承parser_copy = parser.copy()parser_copy.add_argument('bar', type=int)parser_copy.replace_argument('bar', type=str, required=True) // 覆盖原先已经设定好的参数parser_copy.remove_argument('User-Agent') //删除原先已有的参数
 

5-3 Flask-RESTful的响应域

Flask-RESTful提供的一种控制响应对象的方法,可以和各种ORM结合,过滤掉不想对用户暴露的内部数据结构。

 

知识点:

重命名域名和设置默认域名

定制响应域的字段

生成复杂、嵌套结构的响应

 

代码:

import json

from datetime import datetime

 

from flask.ext.restful import Resource,fields, marshal_with, marshal

 

 

# 基本例子

resource_fields = {

   'name': fields.String,

   'address': fields.String,

   'date_updated': fields.DateTime(dt_format='rfc822'),

}

 

 

class UserInfo(object):

   def __init__(self, name, address, date_updated=datetime.now()):

       self.name = name

       self.address = address

       self.date_updated = date_updated

 

print json.dumps(marshal(UserInfo('magi', 'beijing'),resource_fields))//使用marshal()而不使用marshal_with()

 

 

# class Todo(Resource):

#    @marshal_with(resource_fields, envelope='resource')

#    def get(self, **kwargs):

#        return UserInfo('magi', 'beijing')

 

 

# 输出域别名

resource_fields2 = {

   'open_name': fields.String(attribute='name'),

   'address': fields.String,

   'date_updated': fields.DateTime(dt_format='rfc822'),

}

print json.dumps(marshal(UserInfo('magi','beijing'), resource_fields2))

 

# 输出域默认值

class UserInfo2(object):

 

   def __init__(self, address):

       self.address = address

 

resource_fields3 = {

   'open_name': fields.String(default='add_magi'),

   'address': fields.String,

   'date_updated': fields.DateTime(dt_format='rfc822', default=str(datetime.now())),

}

printjson.dumps(marshal(UserInfo2(address='beijing'), resource_fields3))

 

# 自定义输出域

class UserInfo2(object):

 

   def __init__(self, address, flag, date_updated=datetime.now()):

       self.address = address

       self.date_updated = date_updated

       self.flags = flag

 

class UrgentItem(fields.Raw):

   def format(self, value):

       return "Urgent" if value & 0x01 else "Normal"

 

resource_fields4 = {

   'open_name': fields.String(default='add_magi'),

   'address': fields.String,

   'date_updated': fields.DateTime(dt_format='rfc822'),

   'priority': UrgentItem(attribute='flags'),

}

printjson.dumps(marshal(UserInfo2(address='beijing', flag=1), resource_fields4))

 

# 复杂结构

resource_fields = {'name': fields.String}

resource_fields['address'] = {}

resource_fields['address']['line 1'] =fields.String(attribute='addr1')

resource_fields['address']['line 2'] =fields.String(attribute='addr2')

resource_fields['address']['city'] =fields.String

resource_fields['address']['state'] =fields.String

resource_fields['address']['zip'] =fields.String

data = {'name': 'bob', 'addr1': '123 fakestreet', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'}

print json.dumps(marshal(data,resource_fields))

 

# 嵌套域

address_fields = {}

address_fields['line 1'] =fields.String(attribute='addr1')

address_fields['line 2'] =fields.String(attribute='addr2')

address_fields['city'] =fields.String(attribute='city')

address_fields['state'] =fields.String(attribute='state')

address_fields['zip'] =fields.String(attribute='zip')

 

resource_fields = {}

resource_fields['name'] = fields.String

resource_fields['billing_address'] = fields.Nested(address_fields)

resource_fields['shipping_address'] =fields.Nested(address_fields)

address1 = {'addr1': '123 fake street','city': 'New York', 'state': 'NY', 'zip': '10468'}

address2 = {'addr1': '555 nowhere', 'city':'New York', 'state': 'NY', 'zip': '10468'}

data = { 'name': 'bob', 'billing_address':address1, 'shipping_address': address2}

 

print json.dumps(marshal(data,resource_fields))

 

5-4 重构程序

对资源服务器进行:

  添加数据模型

  使用marshal

  将flask函数替换为restful形式的类

 

from flask.ext import restful
from flask.ext.restful importfields,marshal_with

app = Flask(__name__)
api = restful.Api(app)

 

 

# 资源服务器端
# 数据模型
class Test1Data(object):
    def __init__(self, client_id, expires, salt, user_id):
        self.client_id = client_id
        self.expires = expires
        self.salt = salt
        self.user_id = user_id

# marshal-蒙版
resource_fields = {
    'client_id': fields.String(default=''),
    'expires': fields.Float(default=0.0),
    #'salt':fields.Float(default=0.0),
   
'user_id': fields.String(default=''),
    #'date':fields.DateTime(default=str(datetime.now()))

}

# 新的资源服务器
class Test1(restful.Resource):
   @marshal_with(resource_fields)
    def get(self):
        token = request.args.get('token')
        ret = verify_token(token)
        if ret:
            return ret
        else:
            return 'error'

api.add_resource(Test1, '/test1')

 

 

知识点总结:

  掌握Flask-RESTful的基本使用

  掌握Flask-RESTful的如何解析请求

  掌握Flask-RESTful的如何处理响应域

 


 

6、HTTPs以及Flask-OAuthlib插件使用

1 HTTPs介绍及搭建 Flask HTTPs 环境

HTTPs介绍

HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。是超文本传输协议和SSL/TLS的组合,用以提供加密通讯及对网络服务器身份的鉴定。HTTPS连接经常被用于万维网上的交易支付和企业信息系统中敏感信息的传输。这个系统的最初研发由网景公司(Netscape)进行,并内置于其浏览器NetscapeNavigator中,提供了身份验证与加密通讯方法。

 

HTTP访问

 

HTTPs访问

 

证书

 

HTTPs握手过程

在Linux或mac下,搭建Flask HTTPs开发环境

需要的软件:

  OpenSSL

  stunnel

 

$ openssl req -new -x509 -days 365 -nodes-out magigo.pem -keyout magigo.pem

$ chmod 600 magigo.pem

$ vim https_st

代码:

1 pid =                                                                                   

  2cert = /home/yuxiang/magigo.pem

  3debug = 7                 

  4foreground = yes

  5

  6[https]                   

  7accept = 443              

  8connect = 5000

 

$ sudo stunnel https_st

然后启动程序,输入https://localhost:5000/client/login,会出现安全连接问题

 

2 使用Flask-OAuthlib插件搭建OAuth 2Server

Flask-OAuthlib的基本使用

https://flask-oauthlib.readthedocs.org/en/latest/

 

安装:

  pipinstall Flask-OAuthlib

 

原理:

#client.py – 客户端(8000)

1)  将用户重定向到oauth授权服务器

2)  使用得到的授权码去换取token

 

#app.py - 授权服务器(5000)

 

使用Flask-OAuthlib搭建OAuth2 Server

代码:

# client.py

# coding: utf-8

from flask import Flask, url_for, session, request,jsonify
from flask_oauthlib.client import OAuth


CLIENT_ID= 'LyofOAKrBZnSW5GQlp7xcg9DtbgK8lo6p641lY8t'
CLIENT_SECRET ='uCysaCWYh4aGUPIE19zMstcom9kYVUz9oXIrNwmMuyU1Y6hKl6'


app = Flask(__name__)
app.debug = True
app.secret_key = 'secret'
oauth = OAuth(app)

remote= oauth.remote_app(
    'remote',
    consumer_key=CLIENT_ID,
    consumer_secret=CLIENT_SECRET,
    request_token_params={'scope':'email'},
   base_url='http://127.0.0.1:5000/api/',
    request_token_url=None,
   access_token_url='http://127.0.0.1:5000/oauth/token',
   authorize_url='http://127.0.0.1:5000/oauth/authorize'
)

# 相当于/client/login,用于重定向用户登录
@app.route('/')
def index():
    if 'remote_oauth' in session:
        resp = remote.get('me')
        return jsonify(resp.data)
    next_url = request.args.get('next')or request.referrer or None
    return remote.authorize(
        callback=url_for('authorized',next=next_url, _external=True)
    )

# 相当于/client/passport,用于获取token,并存储在Session
@app.route('/authorized')
def authorized():
    resp = remote.authorized_response()
    if resp is None:
        return 'Access denied: reason=%serror=%s' % (
            request.args['error_reason'],
           request.args['error_description']
        )
    print resp
    session['remote_oauth'] =(resp['access_token'], '')
    returnjsonify(oauth_token=resp['access_token'])


@remote.tokengetter
def get_oauth_token():
    return session.get('remote_oauth')


if __name__ == '__main__':
    import os
    os.environ['DEBUG'] = 'true'
   os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true'
    app.run(host='localhost', port=8000)

 

 

# app.py

# coding: utf-8

from datetime import datetime,timedelta

from flask importFlask
from flask importsession, request
from flask importrender_template, redirect, jsonify
from flask_sqlalchemy importSQLAlchemy
from
werkzeug.security importgen_salt
from flask_oauthlib.providerimport OAuth2Provider
from flask.ext importrestful
from flask.ext.restful importfields,marshal_with

app = Flask(__name__, template_folder='templates')
app.debug = True
app.secret_key = 'secret'
app.config.update({
    'SQLALCHEMY_DATABASE_URI': 'sqlite:///db.sqlite',
})
db = SQLAlchemy(app)
oauth = OAuth2Provider(app)

# 相当于/
@app.route('/',methods=('GET','POST'))
def home():
    if request.method == 'POST':
        username = request.form.get('username')
        user = User.query.filter_by(username=username).first()
        if not user:
            user = User(username=username)
            db.session.add(user)
            db.session.commit()
        session['id'] = user.id
        return redirect('/')
    user = current_user()
    return render_template('home.html',user=user)


@app.route('/client')  // 生成client_id 和 client_secret
def client():
    '''
   
为登录用户注册一个新的客户端
   
:return:
    '''
   
user =current_user()
    if not user:
        return redirect('/')
    item = Client(
        client_id=gen_salt(40),
        client_secret=gen_salt(50),
        _redirect_uris=' '.join([
            'http://localhost:8000/authorized',
            'http://127.0.0.1:8000/authorized',
            'http://127.0.1:8000/authorized',
            'http://127.1:8000/authorized',
            ]),
        _default_scopes='email',
        user_id=user.id,
    )
    db.session.add(item)
    db.session.commit()
    return jsonify(
        client_id=item.client_id,
        client_secret=item.client_secret,
    )

# 相当于oauth
@app.route('/oauth/token',methods=['GET','POST'])
@oauth.token_handler
def access_token():
    return None

# 相当于login
@app.route('/oauth/authorize',methods=['GET','POST'])
@oauth.authorize_handler
def authorize(*args, **kwargs):
    user = current_user()
    if not user:
        return redirect('/')
    if request.method == 'GET':
        client_id = kwargs.get('client_id')
        client = Client.query.filter_by(client_id=client_id).first()
        kwargs['client'] = client
        kwargs['user'] = user
        return render_template('authorize.html', **kwargs)

    confirm = request.form.get('confirm','no')
    return confirm == 'yes'

# 老式的资源服务器的写法

@app.route(‘/api/me’)

@oauth.require_oauth()

def me():

user = request.oauth.user

return jsonify(username=user.username)

 

if __name__ == '__main__':
    db.create_all()
    app.run()

 

3 使用Flask-OAuthlib提供的装饰器保护资源服务器

ORM - 对象关系映射 – 其实是创建了一个可在编程语言使用的“虚拟对象数据库”

 

属性装饰器

@property

 

代码:

1)

# 存储用户信息的ORM
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(40), unique=True)

# 存储客户端信息的ORM
class Client(db.Model):
    client_id = db.Column(db.String(40), primary_key=True)
    client_secret = db.Column(db.String(55), nullable=False)

    user_id = db.Column(db.ForeignKey('user.id'))
    user = db.relationship('User')

    _redirect_uris = db.Column(db.Text)
    _default_scopes = db.Column(db.Text)

    @property
   
def client_type(self):
        return 'pub lic'

   
@property
   
def redirect_uris(self):
        if self._redirect_uris:
            return self._redirect_uris.split()
        return []

    @property
   
def default_redirect_uri(self):
        return self.redirect_uris[0]

    @property
   
def default_scopes(self):
        if self._default_scopes:
            return self._default_scopes.split()
        return []

# 存储授权码信息的ORM
class Grant(db.Model):
    id = db.Column(db.Integer, primary_key=True)

    user_id = db.Column(
        db.Integer, db.ForeignKey('user.id',ondelete='CASCADE')
    )
    user = db.relationship('User')

    client_id = db.Column(
        db.String(40), db.ForeignKey('client.client_id'),
        nullable=False,
    )
    client = db.relationship('Client')

    code = db.Column(db.String(255), index=True, nullable=False)

    redirect_uri = db.Column(db.String(255))
    expires = db.Column(db.DateTime)

    _scopes = db.Column(db.Text)

    def delete(self):
        db.session.delete(self)
        db.session.commit()
        return self

   
@property
   
def scopes(self):
        if self._scopes:
            return self._scopes.split()
        return []

# 存储token信息的ORM
class Token(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    client_id = db.Column(
        db.String(40), db.ForeignKey('client.client_id'),
        nullable=False,
    )
    client = db.relationship('Client')

    user_id = db.Column(
        db.Integer, db.ForeignKey('user.id')
    )
    user = db.relationship('User')

    # currently onlybearer is supported
   
token_type =db.Column(db.String(40))

    access_token = db.Column(db.String(255), unique=True)
    refresh_token = db.Column(db.String(255), unique=True)
    expires = db.Column(db.DateTime)
    _scopes = db.Column(db.Text)

    @property
   
def scopes(self):
        if self._scopes:
            return self._scopes.split()
        return []

2)

@oauth.clientgetter
def load_client(client_id):
    return Client.query.filter_by(client_id=client_id).first()


@oauth.grantgetter
def load_grant(client_id, code):
    return Grant.query.filter_by(client_id=client_id,code=code).first()


@oauth.grantsetter
def save_grant(client_id, code, request, *args, **kwargs):
    # decide theexpires time yourself
   
expires =datetime.utcnow() + timedelta(seconds=100)
    grant = Grant(
        client_id=client_id,
        code=code['code'],
        redirect_uri=request.redirect_uri,
        _scopes=' '.join(request.scopes),
        user=current_user(),
        expires=expires
    )
    db.session.add(grant)
    db.session.commit()
    return grant


@oauth.tokengetter
def load_token(access_token=None, refresh_token=None):
    if access_token:
        return Token.query.filter_by(access_token=access_token).first()
    elif refresh_token:
        return Token.query.filter_by(refresh_token=refresh_token).first()


@oauth.tokensetter
def save_token(token, request, *args, **kwargs):
    toks = Token.query.filter_by(
        client_id=request.client.client_id,
        user_id=request.user.id
    )
    # make sure thatevery client has only one token connected to a user
   
for t intoks:
        db.session.delete(t)

    expires_in = token.pop('expires_in')
    expires = datetime.utcnow() +timedelta(seconds=expires_in)

    tok = Token(
        access_token=token['access_token'],
        refresh_token=token['refresh_token'],
        token_type=token['token_type'],
        _scopes=token['scope'],
        expires=expires,
        client_id=request.client.client_id,
        user_id=request.user.id,
    )
    db.session.add(tok)
    db.session.commit()
    return tok

 

3)

def current_user():
    if 'id' in session:
        uid = session['id']
        return User.query.get(uid)
    return None

 

 

4 重构程序

代码:

# 新的资源服务器
api = restful.Api(app, decorators=[oauth.require_oauth('email')])

resource_fields = {
    'username': fields.String(),
    'date': fields.DateTime(default=str(datetime.now())),
    'id': fields.Integer()
}

class ApiMe(restful.Resource):
    @marshal_with(resource_fields)
    def get(self):
        user = request.oauth.user
        return user

api.add_resource(ApiMe, '/api/me')

 

总结:

  掌握HTTPs的基本概念以及搭建Flask开发环境

  掌握使用Flask-OAuthlib搭建OAuth2 Server

  掌握使用Flask-OAuthlib保护资源服务器

8 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 米兔电话手表停机了怎么办 糖猫电话手表停机了怎么办 360电话手表停机了怎么办 小天才电话手表停机怎么办 移动手机卡不知道号码怎么办 天猫退款手机号换了怎么办 科目二考试脚抖怎么办 面试新工作没打电话怎么办 怀孕5个月胎位低怎么办 做业务很害怕打电话怎么办 固定电话总是接到骚扰电话怎么办 电话卡通话被限制了怎么办? 手机名单拉黑了怎么办 被苹果6s被拉黑怎么办 重庆福利企业解聘残疾职工怎么办 被银行拉入黑名单怎么办 借的钱还不起了怎么办 支付宝手机号空号了怎么办 到处贷不到钱了怎么办 还不起钱借不到怎么办 闯红灯收到短信不去扣分怎么办 被一家公司骗了怎么办 oppo手机无线网信号差怎么办 oppo手机无线网网速慢怎么办 电脑无法解析dns地址怎么办 vivox9手机卡顿反应慢怎么办 vivo手机设置成英文怎么办 wi-fi模块坏了怎么办 苹果手机dns被劫持怎么办 圆通快递一直不派送怎么办 凯越没有高压火怎么办 理财回执单丢了怎么办 余额宝超10万怎么办 商场主题经营改变商户怎么办 一个好的项目需要资金怎么办 没有做暂估入库的凭证怎么办 电脑显示宽带连接已断开怎么办 电脑ip地址连不上网怎么办 百度网盘资源打不开怎么办 百度网盘视频格式不支持怎么办 origin注册邮箱填错了怎么办