webapi应用架构详解
webapi适用场景
常见的应用包括以下四类,PC客户端程序,APP程序,网站程序,H5程序。
这些应用需要的数据,服务可由同一个接口服务程序提供,这样,大大提高了产品多平台设计开发的效率,避免了重复的编码。
什么是webapi
webapi一般采用restful风格。以tornado为例,url路由配置如下:
url = [ (r'/test', testfc.testHandler) ]
业务逻辑层,返回数据一般为json格式:
class testHandler(RequestHandler): def get(self): self.post() def post(self): result={} if True: result["code"]="200" result["status"]="true" result["result"]="success" else: result["code"]="300" result["status"]="false" result["result"]="fail" self.write(json_encode(result)) return
webapi访问方式如下:
http://127.0.0.1:9999/test
返回结果:
{"status": "true", "code": "200", "result": "success"}
webapi架构设计
如何确认一个app能够访问webapi?
使用AppID验证app访问webapi的合法性,AppID为服务端给出的一个ID标志。
我们可以为web/app/winform分别分配一个ID,从而可以确定访问的合法性,和访问的渠道。
如何保证app参数的正确性,没有被篡改?
客户端使用AppID对应的AppSecert,对参数进行签名(MD5/SHA等)
服务端使用同样的方式签名,和客户端签名校验。
如何防止webapi url被截获,重新访问?
客户端访问webapi时带上时间戳参数,服务端对时间戳进行校验,如10分钟内的访问才是有效的。
涉及具体用户的操作时,如何验证用户?
可以使用username,psw参数的方式访问webapi。但是这种方式很不安全。
使用授权token是很好的解决办法。在用户登录成功时,服务端生成一个授权码,对应用户信息。
访问时带上token参数,服务端查询token有效性,和token对应的用户信息。
使用示例
签名代码如下:
#coding:utf-8 __author__ = 'jy' import sys reload(sys) sys.setdefaultencoding('utf-8') sys.path.append('..') import datetime import time import math import hashlib import urllib def md5encode(source): m2 = hashlib.md5() m2.update(source) return m2.hexdigest() def dataSort(data): dataKeys=data.keys() dataKeys.sort() result="" for key in dataKeys: result+=key.strip() result+=data[key].strip() return result def dataSecret(data): secret="af4d2c92-4bb7-11e5-8111-00163e001071" data=secret+data+secret return data def getSign(postdata): try: #升序排列,合并为字符串 dataSign=dataSort(postdata) #加密钥 dataSign=dataSecret(dataSign) #md5编码 dataSign=md5encode(dataSign) return dataSign except Exception as error: pass
验证url参数代码如下:
#coding:utf-8 __author__ = 'haoy' import sys reload(sys) sys.setdefaultencoding('utf-8') sys.path.append('..') from tornado.escape import json_decode, json_encode import datetime import time import math from utils.sign import * def valiUrldata(data): result={} try: #判断appKey是否合法 appKey=data.get("appKey","") if appKey!="21ec85ec-30ca-491b-8bfb-c05e479eadc0": result["code"]="300" result["status"]="false" result["result"]="账号不合法" return result #appKey对应的secret appSecret="af4d2c92-4bb7-11e5-8111-00163e001071" #检验时间 timeStamp=data.get("timeStamp","0") timeStamp=int(timeStamp) nowtime=int(round(time.time())) if abs(timeStamp-nowtime)>600: result["code"]="300" result["status"]="false" result["result"]="请求时间戳不合法" return result #校验签名 clientSign=data.get("sign","") if clientSign=="": result["code"]="300" result["status"]="false" result["result"]="请求参数签名不能为空" return result data.pop("sign") serverSign=getSign(data) print serverSign if clientSign!=serverSign: result["code"]="300" result["status"]="false" result["result"]="请求参数签名不合法" return result result["code"]="200" result["status"]="true" result["result"]="通过验证" return result except Exception as error: result["code"]="300" result["status"]="false" result["result"]="异常:"+error.message return result
登录代码如下:
import uuid import redis class testLoginHandler(BaseHandler): def get(self): self.post() def post(self): #获取url参数 args=self.request.arguments data={} for key in args: data[key]=self.get_argument(key,"") #验证url参数 valiResult=valiUrldata(data) if valiResult["code"]=="300": self.write(json_encode(valiResult).decode("unicode_escape")) return username=self.get_argument("username","") psw=self.get_argument("psw","") if username=="shijingjing07" and psw=="123456": token = uuid.uuid1() redisPool = redis.ConnectionPool(host='127.0.0.1',password='123456', port=6379, db=0) cache = redis.Redis(connection_pool=redisPool) cache.setex(token,username,300) result["code"] = 200 result["status"] = "true" result["result"] = token else: result["code"] = 300 result["status"] = "false" result["result"] = "account illegal" self.write(json_encode(result))
返回结果:
{"status": "true", "code": 200, "result": "5720c334-dbcc-11e6-84f1-00163e001071"}