├── README.md ├── python ├── common │ ├── __init__.py │ └── config.py ├── processors.py ├── user.py ├── authorization.py └── rest_api.py ├── .gitignore ├── angular ├── app.js ├── login.js └── authorization.js └── reference document /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea 3 | db.sqlite3 4 | nohup.out 5 | *.log 6 | *.out 7 | *~ 8 | node_modules/ 9 | bower_components/ 10 | -------------------------------------------------------------------------------- /python/common/config.py: -------------------------------------------------------------------------------- 1 | # json web token config 2 | JWT_SECRET_KEY = 'sumscope_ias_secret' 3 | JWT_ACCESS_TOKEN_EXP_SECONDS = 60*0.5 4 | JWT_REFRESH_TOKEN_EXP_SECONDS = 60*60*8 5 | JWT_ALGORITHM = 'HS256' 6 | -------------------------------------------------------------------------------- /angular/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Declare app level module which depends on views, and components 4 | angular.module('myApp', [ 5 | 'ngRoute', 6 | 'ngResource', 7 | 'myApp.login', 8 | 'myApp.loading' 9 | ]) 10 | .config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { 11 | $httpProvider.interceptors.push('httpInterceptor'); 12 | 13 | $routeProvider.when("/", { 14 | controller: 'loginCtrl', 15 | templateUrl: 'login/login.html' 16 | }).when('/loading', { 17 | controller: 'loadingAppCtrl', 18 | templateUrl: 'loading/loading.html' 19 | }).otherwise({ 20 | redirectTo: '/' 21 | }); 22 | }]) 23 | .controller('appCtrl', function ($scope, $location) { 24 | $location.path('/login'); 25 | }); 26 | -------------------------------------------------------------------------------- /angular/login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.login', ['ngRoute']) 4 | .controller('loginCtrl', function ($scope, $location, loginInfo, authService, messageBox) { 5 | $scope.user = { 6 | login_name: "", 7 | password: "" 8 | }; 9 | 10 | $scope.loginServer = function () { 11 | authService.login($scope.user.login_name, $scope.user.password).then(function success(data) { 12 | if (data.message != 'success') { 13 | messageBox.error(data.message); 14 | } else { 15 | loginInfo.super = data.super; 16 | $location.path('/loading'); 17 | } 18 | }, function failed() { 19 | }); 20 | }; 21 | 22 | $scope.$on('$destroy', function() { 23 | $scope = null; 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /reference document: -------------------------------------------------------------------------------- 1 | python: 2 | Flask扩展系列(九)–HTTP认证: http://www.bjhee.com/flask-ext9.html 3 | OAuth Authentication with Flask:https://blog.miguelgrinberg.com/post/oauth-authentication-with-flask 4 | OAuth Refresh Tokens in AngularJS App :http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/ 5 | Flask-RESTful资源方法装饰器: http://www.pythondoc.com/Flask-RESTful/extending.html 6 | 理解 Python 装饰器: https://foofish.net/python-decorator.html 7 | 8 | web: 9 | Angular 应用中的登陆与身份验证: https://juejin.im/entry/57906a507db2a20054c5ac16 10 | 单页应用 - Token 验证:https://juejin.im/post/58da720b570c350058ecd40f 11 | 12 | 13 | 资料: 14 | OAuth: http://blog.toxicjohann.com/2016/04/30/talk-about-wechat-oauth/ 15 | 理解OAuth 2.0: http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html 16 | JSON Web Token: https://github.com/smilingsun/blog/issues/1 17 | JSON Web Tokens: https://juejin.im/entry/577b7b56a3413100618c2938 18 | 19 | JWT (JSON Web Token) automatic prolongation of expiration: http://stackoverflow.com/questions/26739167/jwt-json-web-token-automatic-prolongation-of-expiration 20 | 21 | Authenticate REST: http://blog.appliedinformaticsinc.com/how-to-authenticate-rest-requests/ 22 | 使用Flask设计带认证token的RESTful API接口: http://www.cnblogs.com/vovlie/p/4182814.html 23 | 使用JSON Web令牌无Cookie验证:http://qqucg.com/211.html 24 | 25 | JSON网络令牌库中出现严重漏洞:http://bobao.360.cn/news/detail/1377.html 26 | -------------------------------------------------------------------------------- /python/processors.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | reload(sys) 4 | sys.setdefaultencoding('utf8') 5 | from user import UserProcess 6 | 7 | 8 | class ApiProcessor(object): 9 | def __init__(self, logger): 10 | self.logger = logger 11 | self.methods = { 12 | # user process 13 | 'sumscope_login': self.method_sumscope_login, 14 | 'refresh_token': self.method_refresh_token, 15 | 'company_users': self.method_all_user, 16 | } 17 | 18 | def process(self, content=None, correlation_id=None): 19 | if correlation_id in self.methods: 20 | method = self.methods.get(correlation_id) 21 | return method(content) 22 | else: 23 | self.logger.warning('No such service: {}'.format(correlation_id)) 24 | return None 25 | 26 | def method_sumscope_login(self, content): 27 | self.logger.info('message requesting for login admin server') 28 | self.logger.info('content: {}'.format(content)) 29 | return UserProcess.login(content) 30 | 31 | def method_refresh_token(self, content): 32 | self.logger.info('message requesting to refresh user token') 33 | return UserProcess.refresh_token(content) 34 | 35 | def method_all_user(self, content): 36 | self.logger.info('message requesting to get company users') 37 | return UserProcess.get_all_users() 38 | -------------------------------------------------------------------------------- /python/user.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | from authorization import Authorization 4 | 5 | logger = logging.getLogger() 6 | 7 | 8 | class AdminUser(object): 9 | def __init__(self, user_id, user_name, password): 10 | self.user_id = user_id 11 | self.user_name = user_name 12 | self.password = password 13 | 14 | def to_json(self): 15 | return { 16 | 'user_name': self.user_name 17 | } 18 | 19 | 20 | class UserProcess(object): 21 | users = [ 22 | AdminUser('000001', 'super_admin', '123456'), 23 | AdminUser('000002', 'ias_admin', 'ias_group123456') 24 | ] 25 | user_map = {user.user_name: user for user in users} 26 | 27 | @classmethod 28 | def login(cls, content): 29 | account_name = content.get('account_name', None) 30 | password = content.get('password', None) 31 | 32 | user = UserProcess.user_map.get(account_name, None) 33 | if not user or user.password != password: 34 | return {'message': '账户或密码不正确!'} 35 | 36 | return { 37 | 'message': 'success', 38 | 'super': '1' if account_name == 'ias_admin' else '0', 39 | 'access_token': Authorization.generate_access_token(user.user_id), 40 | 'refresh_token': Authorization.generate_refresh_token(user.user_name) 41 | } 42 | 43 | @classmethod 44 | def refresh_token(cls, content): 45 | token = content.get('token', None) 46 | result, msg = Authorization.verify_refresh_token(token) 47 | if result: 48 | user = UserProcess.user_map.get(msg, None) 49 | if user: 50 | token = Authorization.generate_access_token(user.user_id) 51 | return {'access_token': token} 52 | return None 53 | 54 | @classmethod 55 | def get_all_users(cls): 56 | result = [] 57 | for usr in cls.users: 58 | result.append(usr.to_json()) 59 | return result -------------------------------------------------------------------------------- /python/authorization.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | import datetime 3 | import logging 4 | from common.config import JWT_SECRET_KEY, JWT_ACCESS_TOKEN_EXP_SECONDS, JWT_ALGORITHM, JWT_REFRESH_TOKEN_EXP_SECONDS 5 | from flask_restful import abort 6 | from flask import request 7 | from functools import wraps 8 | from flask_restful import Resource 9 | 10 | logger = logging.getLogger() 11 | 12 | 13 | class Authorization(object): 14 | @staticmethod 15 | def generate_access_token(user_id): 16 | access_token = jwt.encode({ 17 | 'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=JWT_ACCESS_TOKEN_EXP_SECONDS), 18 | 'iat': datetime.datetime.utcnow(), 19 | 'user_id': user_id 20 | }, algorithm=JWT_ALGORITHM, key=JWT_SECRET_KEY) 21 | return access_token 22 | 23 | @staticmethod 24 | def verify_access_token(token): 25 | try: 26 | play_load = jwt.decode(token, key=JWT_SECRET_KEY) 27 | return True, play_load['user_id'] 28 | except jwt.ExpiredSignatureError: 29 | logger.warn('verified access token failed for expired token') 30 | return False, 'expired token' 31 | except jwt.InvalidTokenError: 32 | logger.warn('verified access token failed for invalid token') 33 | return False, 'invalid token' 34 | 35 | @staticmethod 36 | def generate_refresh_token(account_name): 37 | refresh_token = jwt.encode({ 38 | 'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=JWT_REFRESH_TOKEN_EXP_SECONDS), 39 | 'iat': datetime.datetime.utcnow(), 40 | 'account_name': account_name 41 | }, algorithm=JWT_ALGORITHM, key=JWT_SECRET_KEY) 42 | return refresh_token 43 | 44 | @staticmethod 45 | def verify_refresh_token(token): 46 | logger.info('verifying refresh token') 47 | try: 48 | play_load = jwt.decode(token, key=JWT_SECRET_KEY) 49 | account_name = play_load['account_name'] 50 | return True, account_name 51 | except jwt.ExpiredSignatureError: 52 | logger.warn('verified refresh token failed for expired token') 53 | return False, 'expired token' 54 | except jwt.InvalidTokenError: 55 | logger.warn('verified refresh token for invalid token') 56 | return False, 'invalid token' 57 | 58 | 59 | def authenticate_access_token(func): 60 | @wraps(func) 61 | def wrapper(*args, **kwargs): 62 | if not getattr(func, 'authenticated', True): 63 | return func(*args, **kwargs) 64 | token = request.headers.get('Authorization', None) 65 | result, msg = Authorization.verify_access_token(token) 66 | 67 | if result: 68 | return func(*args, **kwargs) 69 | 70 | abort(401, message=msg) 71 | return wrapper 72 | 73 | 74 | class AuthResource(Resource): 75 | method_decorators = [authenticate_access_token] 76 | 77 | -------------------------------------------------------------------------------- /python/rest_api.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | import logging.config 4 | import sys 5 | reload(sys) 6 | sys.setdefaultencoding('utf8') 7 | import os 8 | import time 9 | import traceback 10 | from gevent.wsgi import WSGIServer 11 | from flask import Flask, make_response, request 12 | from flask_restful import Resource, Api, reqparse, abort 13 | from flask_cors import CORS 14 | from flask_compress import Compress 15 | from processors import ApiProcessor 16 | from authorization import AuthResource 17 | 18 | 19 | app = Flask(__name__) 20 | api = Api(app) 21 | CORS(app) 22 | Compress(app) 23 | 24 | logger = logging.getLogger() 25 | api_processor = ApiProcessor(logger) 26 | 27 | 28 | class Login(Resource): 29 | def post(self): 30 | parser = reqparse.RequestParser() 31 | parser.add_argument('account_name') 32 | parser.add_argument('password') 33 | args = parser.parse_args() 34 | account_name = args['account_name'] 35 | password = args['password'] 36 | t = time.time() 37 | response = api_processor.process( 38 | content={'account_name': account_name, 'password': password}, correlation_id='sumscope_login') 39 | 40 | logger.info('sumscope login') 41 | logger.info('account_name: {}, password: {}'.format(account_name, password)) 42 | logger.info('cost time: {}'.format(time.time() - t)) 43 | if response: 44 | try: 45 | resp = make_response(json.dumps(response)) 46 | resp.headers['Content-Type'] = 'application/json' 47 | return resp 48 | except: 49 | pass 50 | abort(404, message="login failed.") 51 | 52 | 53 | class RefreshToken(Resource): 54 | def post(self): 55 | token = request.headers.get('Authorization', None) 56 | response = api_processor.process(content={'token': token}, correlation_id='refresh_token') 57 | if response is not None: 58 | try: 59 | resp = make_response(json.dumps(response)) 60 | resp.headers['Content-Tpye'] = 'application/json' 61 | return resp 62 | except: 63 | pass 64 | 65 | abort(404, message= 'invalid request') 66 | 67 | 68 | class CompanyList(AuthResource): 69 | def get(self): 70 | t = time.time() 71 | response = api_processor.process(correlation_id='company_users') 72 | 73 | print 'get cost time: {}'.format(time.time() - t) 74 | if response is not None: 75 | try: 76 | resp = make_response(json.dumps(response)) 77 | resp.headers['Content-Type'] = 'application/json' 78 | return resp 79 | except: 80 | pass 81 | abort(404, message="fetch company user list failed.") 82 | 83 | 84 | api.add_resource(Login, '/login') 85 | api.add_resource(RefreshToken, '/refresh_token') 86 | api.add_resource(CompanyList, '/companies') 87 | 88 | if __name__ == '__main__': 89 | try: 90 | # gevent wsgi 91 | http_server = WSGIServer(('', 5000), app) 92 | http_server.serve_forever() 93 | except: 94 | logger.error(traceback.format_exc()) 95 | -------------------------------------------------------------------------------- /angular/authorization.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | angular.module('myApp') 3 | .factory('authService', function($resource, apiAddress, $q) { 4 | var _login = (function () { 5 | return $resource(apiAddress + "/sumscope_login", {}, { 6 | post: {method: 'POST', params: {}, isArray: false} 7 | }); 8 | })(); 9 | var _refreshToken = (function () { 10 | return $resource(apiAddress + '/refresh_token', {}, { 11 | post: {method: 'POST', params: {}, isArray: false} 12 | }) 13 | })(); 14 | var _setAuthorizationData = function (data) { 15 | if (data.access_token) { 16 | sessionStorage.setItem('access_token', data.access_token); 17 | } 18 | if (data.refresh_token) { 19 | sessionStorage.setItem('refresh_token', data.refresh_token); 20 | } 21 | }; 22 | return { 23 | login: function(name, password) { 24 | var deferred = $q.defer(); 25 | _login.post({account_name: name, password: password}, function success (response) { 26 | _setAuthorizationData(response); 27 | deferred.resolve(response); 28 | }, function failed () { 29 | deferred.reject(); 30 | }); 31 | 32 | return deferred.promise; 33 | }, 34 | refreshToken: function() { 35 | var deferred = $q.defer(); 36 | _refreshToken.post({}, function success(data) { 37 | _setAuthorizationData(data); 38 | deferred.resolve(); 39 | }, function failed() { 40 | deferred.reject() 41 | }); 42 | return deferred.promise; 43 | }, 44 | clear: function() { 45 | sessionStorage.removeItem('access_token'); 46 | sessionStorage.removeItem('refresh_token'); 47 | } 48 | } 49 | }) 50 | .factory('httpInterceptor', function($q, $injector, $location) { 51 | var _httpRequest = function(config) { 52 | if (config.url.indexOf('refresh_token') != -1) { 53 | config.headers.Authorization = sessionStorage.getItem('refresh_token'); 54 | } else { 55 | config.headers.Authorization = sessionStorage.getItem('access_token'); 56 | } 57 | return config 58 | }; 59 | var _responseError = function(rejection) { 60 | if (401 === rejection.status){ 61 | var deferred = $q.defer(); 62 | var authService = $injector.get('authService'); 63 | authService.refreshToken().then(function success() { 64 | // 消息重发 65 | $injector.get("$http")(rejection.config).then(function(resp) { 66 | deferred.resolve(resp); 67 | },function() { 68 | deferred.reject(); 69 | }); 70 | }, function failed() { 71 | deferred.reject(); 72 | $location.path('/login'); 73 | }); 74 | return deferred.promise; 75 | } 76 | return $q.reject(rejection); 77 | }; 78 | 79 | return { 80 | request: _httpRequest, 81 | 82 | responseError: _responseError 83 | } 84 | }); --------------------------------------------------------------------------------