Skip to content

Instantly share code, notes, and snippets.

@limboinf
Created February 8, 2017 09:10
Show Gist options
  • Save limboinf/231f479f30244fa35b7718e41b249490 to your computer and use it in GitHub Desktop.
Save limboinf/231f479f30244fa35b7718e41b249490 to your computer and use it in GitHub Desktop.
Api Key + Security Key + Sign.
# coding=utf-8
"""
desc..
:copyright: (c) 2016 by fangpeng(@beginman.cn).
:license: MIT, see LICENSE for more details.
"""
import hmac
import hashlib
import base64
import redis
_pool = redis.ConnectionPool()
from config import __key__
__all__ = [
'gen_sign',
'encrypt',
'check_sign',
'gen_security_key',
'_pool'
]
def gen_sign(api_key, security_key, timestamp, url, is_hex=False):
"""生成sign"""
msg = api_key + security_key + str(timestamp) + url
key = __key__ + api_key
return encrypt(key, msg, is_hex)
def encrypt(key, msg, is_hex=False):
"""
采用hmac sha256算法构造一个hash值的签名(sign)
:param msg:
:return:
"""
dig = hmac.new(str(key), str(msg), digestmod=hashlib.sha256)
_dig = dig.digest() if not is_hex else dig.hexdigest()
return base64.b64encode(_dig).decode()
def check_sign(sign, api_key, security_key, timestamp, url, is_hex=False):
return gen_sign(api_key, security_key, timestamp, url, is_hex) == sign
def _md5(slat, msg):
m = hashlib.md5()
m.update(slat + msg)
return m.hexdigest()
def gen_security_key(sid):
# 生成私钥
import uuid
import time
slat = str(uuid.uuid4()).replace('-', '') + str(int(time.time()))
return _md5(slat, sid)
# coding=utf-8
"""
desc..
:copyright: (c) 2016 by fangpeng(@beginman.cn).
:license: MIT, see LICENSE for more details.
"""
import logging
import json
import functools
import exceptions
import tornado.gen
import tornado.web
from tornado.escape import json_decode
from tornado.gen import coroutine, Return
from tornado.concurrent import is_future
from ..utility import json_process
from service import APiServiceHandlerRequest
class ServiceException(exceptions.Exception):
def __init__(self, code=0, msg="", data=None):
self.code = code
self.message = msg
self.data = data
def __str__(self):
return repr(self.message)
class BaseHandler(tornado.web.RequestHandler, APiServiceHandlerRequest):
def on_finish(self):
self.json_data = {}
def write_error(self, status_code, **kwargs):
if status_code == 500:
err = kwargs['exc_info'][1]
if isinstance(err, tornado.gen.Return):
self.render_json(err.value)
elif isinstance(err, ServiceException):
self.render_json({'code': 500, 'data': None, 'msg': err.message})
else:
self.render_json({'code': 500, 'data': None, 'msg': err})
else:
super(BaseHandler, self).write_error(status_code, **kwargs)
def return_error(self, code=500, msg=u"请求异常", data=None):
return {'code': code, 'data': data, 'msg': msg}
def raise_error(self, code=500, msg=u'请求异常', data=None):
raise tornado.gen.Return(self.return_error(code, msg, data))
def render_json(self, data):
self.set_header("Content-Type", "application/json")
self.write(self.jsonp(json.dumps(data, default=json_process)))
def jsonp(self, data_json):
jsonp_callback = self.get_argument("jsonpCallback", None)
if jsonp_callback is None or jsonp_callback == "":
return data_json
else:
self.set_header("Content-Type", "application/javascript")
return "%s(%s)" % (jsonp_callback, data_json)
def get_valid_params(self, params=None, **kwargs):
try:
req_data = json_decode(self.request.body)
except:
req_data = {k: ''.join(v) for k, v in
self.request.arguments.iteritems()}
kwargs.update(req_data)
valid = self.valid_request(self.request.path, kwargs)
if valid:
if isinstance(valid, tuple):
return self.return_error(*valid)
return self.return_error(1000, msg=valid)
for p in params:
if p not in kwargs:
return self.return_error(1003, msg=u'missing param:%s' % p)
if hasattr(self, 'user_id'):
kwargs['user_id'] = self.user_id
self.json_data = kwargs
return kwargs
def get_req_params(self):
return self.json_data
def asyn_return(self, value={}):
raise tornado.gen.Return(value)
@coroutine
def yield_valid(self, res, cond=True, msg=u'请求数据异常', code=500):
if is_future(res):
for i in range(2):
result = yield res
if result != cond:
raise ServiceException(code, msg)
else:
raise Return(result)
@coroutine
def yield_valid_count(self, res, cond, msg=u'数据异常', code=500):
if is_future(res):
for i in range(2):
result = yield res
if eval(str(result) + cond):
raise ServiceException(code, msg)
def handler(*args, **kwargs):
"""
usage:
from tornado_route import route
from crm.handler import BaseHandler, handler
@route(r"/api/crm/public/list")
class CommonHandler(BaseHandler):
@handler('name', 'age')
def get(self, *args, **kwargs):
return {'good': 100}
@route(r"/api/crm/public/sleep")
class FutureHandler(BaseHandler):
@handler()
@tornado.gen.coroutine
def get(self, *args, **kwargs):
db = self.settings['db']
ids = []
for x in range(10000):
_id = yield db.crm.insert({
'name': 'fang',
'age':100
})
ids.append(str(_id))
raise tornado.gen.Return(ids)
"""
params = args
params_kw = kwargs
def deal(fn):
@functools.wraps(fn)
@coroutine
def wrapper(self, *args, **kwargs):
result = {'code': 0}
try:
req_datas = self.get_valid_params(params, **params_kw)
if req_datas.get('code', None) and req_datas.get('msg', ''):
result = req_datas
else:
res = fn(self, *args, **kwargs)
if is_future(res):
result['data'] = yield res
if result['data'] and 'code' and 'msg' in result['data']:
result = result['data']
else:
result['data'] = res
except ServiceException as ex:
result['code'] = ex.code
result['data'] = None
result['msg'] = ex.message
except Exception as ex:
result['code'] = 500
result['data'] = str(ex)
result['msg'] = u'服务异常'
logging.exception(ex)
self.render_json(result)
return wrapper
return deal
# coding=utf-8
"""
desc..
:copyright: (c) 2016 by fangpeng(@beginman.cn).
:license: MIT, see LICENSE for more details.
"""
__key__ = "abcsd23w3#@xsdf.."
__crypto_uid_key__ = 12233
ANONYMOUS_URLS = [
'/api/user/login',
'/api/user/register',
'/api/user/send_code',
'/api/user/forget_pass'
]
# coding=utf-8
"""
desc..
:copyright: (c) 2016 by fangpeng(@beginman.cn).
:license: MIT, see LICENSE for more details.
"""
import logging
import base64
import time
import struct
import exceptions
import redis
from functools import wraps, partial
from . import check_sign, _pool
from .config import __crypto_uid_key__, ANONYMOUS_URLS
R = redis.Redis(connection_pool=_pool)
class ServiceException(exceptions.Exception):
def __init__(self, code=400, msg="", data=None):
self.code = code
self.message = msg
self.data = data
def __str__(self):
return self.message
def wrapper_log(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception as ex:
err = "[Err]: " + fn.__name__ + " : " + ex.message
logging.exception(err)
return 'Invalid request.'
return wrapper
class APiServiceHandlerRequest(object):
def __init__(self):
self.user_id = 0
self.sid = ''
self.url = ''
self.api_key = ''
self.security_key = ''
self.device_type = ''
self.version = ''
"""服务端api请求基类"""
@wrapper_log
def valid_request(self, url, req_data):
# 过滤不需要校验的url
if url in ANONYMOUS_URLS:
if url == '/api/app/version':
self.sid = req_data.get(
'api_key',
req_data.get('sid', '')).replace(' ', '+')
uid = self.get_uid(self.sid)
self.user_id = uid
return
self.url = url
funcs = (partial(f, args) for f, args in [
(self.valid_req_field, req_data), # 验证请求字段是否齐全
(self.valid_timestarp, req_data.get('timestamp', '')),
# 验证请求时间戳
(self.valid_version, req_data.get('version', '')), # 校验设备与版本号
(self.valid_security_key, req_data.get('api_key', '')), # 验证user_id 和 # security_key noqa
(self.valid_sign, req_data) # 验证sign签名
])
for func in funcs:
valid = func()
if valid:
return valid
@wrapper_log
def valid_req_field(self, req_data, *args):
"""验证请求字段完整性"""
check_fields = args
if not check_fields:
check_fields=('api_key', 'timestamp', 'sign', 'version')
check = map(lambda x: x in req_data, check_fields)
if all(check) is False:
dic = dict(zip(check_fields, check))
missing_fields = ','.join([k for k, v in dic.items() if v is False])
return 'Missing fields %s' % missing_fields
@wrapper_log
def valid_timestarp(self, timestamp, expires=10*60):
"""判断服务器接到请求的时间
服务器时间和参数中的时间戳是否相差很长一段时间(时间间隔自定义如5min)
如果超过则说明该url已经过期
"""
if not timestamp:
return "Invalid Request, Missing timestamp."
now = int(time.time())
timestamp = int(timestamp)
if abs(now - timestamp) > expires:
return "The request is out of date."
@wrapper_log
def valid_security_key(self, api_key):
if not api_key:
return 'Missing api_key'
self.api_key = api_key.replace(' ', '+')
self.sid = self.api_key
uid = self.get_uid(self.api_key)
if not uid:
return 'UserId not found.'
self.user_id = uid
# get sessionID, 简称sid
sid = '' # get by redis.
if not sid:
return (400, u'您的账号已过期, 请重新登录')
if self.api_key != sid:
return (401, u'账号在其他设备上登陆, 如不是您本人操作请修改账号密码')
if not R.exists(self.api_key): # AK 映射 SK
return (403, u'您的账号已过期, 请重新登录')
self.security_key = R.get(self.api_key)
@wrapper_log
def valid_sign(self, req_data):
is_hex = False
# if self.device_type == 'ios':
# is_hex = True
if not check_sign(req_data['sign'], self.api_key, self.security_key,
req_data['timestamp'], self.url, is_hex):
return 'Invalid signature.'
@wrapper_log
def get_uid(self, sid):
pass
@wrapper_log
def valid_version(self, version):
if not version:
return 'Missing Request Version.'
if device_type.lower() not in ('android', 'ios', 'pc', 'web', 'api'):
return 'Invalid Version.'
self.device_type = device_type.lower()
self.version = v
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment