Back-end apis often require legitimate validation of the client sending the request to ensure that the API is "protected". Flask's resting-API for a mobile application is exactly what this is about, and it uses some python metaprogramming techniques.

The above content is the abstract of the article

How to authenticate?

For standards-loving engineers, HTTP standard Auth is a good choice, or simply oAuth. But in the spirit of research, we implement an API signature mechanism by ourselves, and patch to the resource class corresponding to the API requiring authentication.

Authentication idea

We need to know about the certification background and ideas:

  1. A valid client has the app_id and app_key delivered by the server.
  2. The server knows the app_key for each app_id.
  3. When the client requests interface data, it must encrypt the specified rule with the local APP_key, timestamp, and app_ID to obtain a signature string.
  4. Each request is submitted to the back-end API with the APP_id, timestamp, signature string, and business data;
  5. The backend API can verify the validity of the client based on the submitted app_id, timestamp, and signature string.

Without further ado, on the code and detailed comments:

Let’s start by forging a certification service key_gen.py

#! /usr/bin/env python # -*- coding: utf-8 -*- import hashlib import random from string import digits def sign_api(time_stamp, _id, _key): "" "signature algorithm test "" "print hashlib. Sha1 ('. Join ([STR (time_stamp), _id, _key]]). Hexdigest (def) ge_app_id () : "" ": return: app_id """ return ''.join(random.sample(digits, 9)) def ge_app_key(_id): """ :return: app_key """ return hashlib.sha1(_id).hexdigest() if __name__ == '__main__': app_id = ge_app_id() app_key = ge_app_key(app_id) sign_api(159874265148, '710628459', 'bd7d14ed5e0b9bf3c3ac28c224b322d271f6ae6c')Copy the code

Flask uses metaclass authorization to wrap auth.py

#! /usr/bin/python # -*- coding:utf-8 -*- import hashlib from types import FunctionType from flask.views import MethodViewType from flask import request from flask_restful import Resource, Abort from app.setting import ACCESS_CLIENT, ENV_DEBUG def check_sign_API (args): """ :param args: authentication parameter :return: "" global PRE_TIMESTAMP # To pass KeyError _time_stamp = args['timestamp'] _app_id = args['app_id'] _signature =  args['signature'] if _time_stamp PRE_TIMESTAMP: return False PRE_TIMESTAMP = _time_stamp return ( _signature == hashlib.sha1(''.join([ str(_time_stamp), _app_id, ACCESS_CLIENT[_app_id]])). Hexdigest ()) def _get_resource_base(): """ :return: obtain resource base class """ if ENV_DEBUG: return Resource else: return with_meta_class(RequireAuthClass, Resource) def with_meta_class(meta, *bases): """ :param meta: specifies the MetaClass :param bases: specifies the base class :param bases: specifies the base class :return: specifies the temporary class that inherits from the base class created from the specified MetaClass. __call__ = type.__call__ __init__ = type.__init__ def __new__(cls, name, this_bases, d): if this_bases is None: return type.__new__(cls, name, (), d) return meta(name, bases, d) return MetaClass('temporary_class', None, {}) def api_require_auth(http_handler): """ :param http_handler: "" def wrapper(self, *params, **kwargs): try: args = request.get_json() if check_sign_api(args): return http_handler(self, *params, **kwargs) else: abort(500, message='access fail') except KeyError as error: abort(500, message=u'access need a {} param'.format(error.message)) return wrapper class RequireAuthClass(MethodViewType): """ The basic idea of the interface authorization unified processing class is: All classes that specify RequireAuthClass as metaclass will automatically wrap their post, PUT, and delete methods when instantiated as type.__new__. RequireAuthClass must be a subclass of MethodViewType and not a subclass of type. "" def __new__(MCS, name, bases, DCT): for name, value in dct.iteritems(): if ( name in ['post', 'put', 'delete', 'get'] # restful methods and type(value) == FunctionType ): value = api_require_auth(value) dct[name] = value return MethodViewType.__new__(mcs, name, bases, dct)Copy the code

The business-layer API is much easier

Small Tip:

The _get_resource_base method exported by the app.auth module is controlled by the environment variable

If you are in debug mode, authorization is disabled

# -*- coding: utF-8 -*- # Flask from Flask import Flask, jsonify from flask_restful import Api, Reqparse from flask import g from werkzeug.local import LocalProxy from app.auth import _get_resource_base # Configure from app.setting import ( CURRENT_API_VERSION, SET_VERSION_ROUTE, _db_settings ) from util.mongo_util import Mongo, Close_db, json_serializable app = Flask(__name__) API = API (app) db = getattr(g, '_database', None) if db is None: Db = g._database = Mongo(_db_settings) return DB # global DB instance db = LocalProxy(get_DB) # @app.teardown_appContext when the application environment is destroyed Def teardown_db(exception): # def teardown_db = getattr(g, '_database', None) if db is not None: Errorhandler (404) def not_found_error(error): return jsonify({'code': error.code, 'message': error.description }), 404 class WelcomeToApi(_get_resource_base()): "" def get(self): return jsonify({'name': 'ZhiYuanHelper API ', 'version': CURRENT_API_VERSION}) class User(_get_resource_base()): """ "def post(self, user_id): Return jsonify({'user_id': user_id, 'name': 'test account'}) class Register(_get_resource_base()): """ user registration interface """ pass class Login(_get_resource_base()): Class HigherVocationalCollege(_get_resource_base()): def __init__(self): self.reqparse = reqparse.RequestParser() ''' Defaults to :class:`unicode` in python2 and :class:`str` in python3. ''' self.reqparse.add_argument('province', type = unicode, required = True, location = 'json') self.reqparse.add_argument('description', type = unicode, default = "", location = 'json') self.args = self.reqparse.parse_args() @close_db('db') def post(self): res = db.hvc.find() res = json_serializable(res) return jsonify({ 'args':self.args, 'data':res}) # API list api.add_resource(WelcomeToApi, '/ API ') api.add_resource(User, SET_VERSION_ROUTE('/user/')) api.add_resource(HigherVocationalCollege, SET_VERSION_ROUTE('/hvc'))Copy the code

Welcome to clap brick ~