提交 38d1d2722e9c83f99e58c40558389d9fcdde61f0
Merge branch 'master' of http://gitlab.ctune.cn/weijunh/DMapManager
merge
正在显示
11 个修改的文件
包含
322 行增加
和
51 行删除
1 | import decimal | 1 | import decimal |
2 | +from re import template | ||
2 | 3 | ||
3 | from flask import Flask as _Flask | 4 | from flask import Flask as _Flask |
4 | from flask.json import JSONEncoder as _JSONEncoder | 5 | from flask.json import JSONEncoder as _JSONEncoder |
@@ -58,18 +59,47 @@ def create_app(): | @@ -58,18 +59,47 @@ def create_app(): | ||
58 | app.config['OAUTH2_JWT_KEY'] = 'secret-key' | 59 | app.config['OAUTH2_JWT_KEY'] = 'secret-key' |
59 | app.config['OAUTH2_JWT_ALG'] = 'HS256' | 60 | app.config['OAUTH2_JWT_ALG'] = 'HS256' |
60 | # app.config['SQLALCHEMY_ECHO'] = True | 61 | # app.config['SQLALCHEMY_ECHO'] = True |
61 | - | 62 | + |
62 | # allows cookies and credentials to be submitted across domains | 63 | # allows cookies and credentials to be submitted across domains |
63 | app.config['CORS_SUPPORTS_CREDENTIALS'] = true | 64 | app.config['CORS_SUPPORTS_CREDENTIALS'] = true |
64 | - app.config['CORS_ORIGINS ']="*" | 65 | + app.config['CORS_ORIGINS '] = "*" |
65 | 66 | ||
66 | # 跨域设置 | 67 | # 跨域设置 |
67 | CORS(app) | 68 | CORS(app) |
68 | 69 | ||
69 | # swagger设置 | 70 | # swagger设置 |
70 | swagger_config = Swagger.DEFAULT_CONFIG | 71 | swagger_config = Swagger.DEFAULT_CONFIG |
71 | - swagger_config.update({"title": "DMapManager"}) | ||
72 | - Swagger(app, config=swagger_config) | 72 | + |
73 | + SWAGGER_TEMPLATE = { | ||
74 | + # "openapi": "3.0.0", | ||
75 | + # "components": { | ||
76 | + # "securitySchemes": { | ||
77 | + # "bearerAuth": { | ||
78 | + # "type": "http", | ||
79 | + # "scheme": "bearer", | ||
80 | + # "bearerFormat": "JWT" | ||
81 | + # }, | ||
82 | + # } | ||
83 | + # }, | ||
84 | + "securityDefinitions": { | ||
85 | + "APIKeyHeader": { | ||
86 | + "type": "apiKey", | ||
87 | + "in": "header", | ||
88 | + "name": "Authorization" | ||
89 | + } | ||
90 | + }, | ||
91 | + | ||
92 | + "security": [{ | ||
93 | + "APIKeyHeader": [] | ||
94 | + } | ||
95 | + ] | ||
96 | + } | ||
97 | + | ||
98 | + swagger_config = Swagger.DEFAULT_CONFIG | ||
99 | + swagger_config.update(configure.swagger_configure) | ||
100 | + | ||
101 | + Swagger(app, config=swagger_config, | ||
102 | + template=SWAGGER_TEMPLATE) | ||
73 | 103 | ||
74 | # 创建数据库 | 104 | # 创建数据库 |
75 | db.init_app(app) | 105 | db.init_app(app) |
@@ -78,9 +108,12 @@ def create_app(): | @@ -78,9 +108,12 @@ def create_app(): | ||
78 | # 日志 | 108 | # 日志 |
79 | 109 | ||
80 | logging.basicConfig(level=configure.log_level) | 110 | logging.basicConfig(level=configure.log_level) |
81 | - log_file = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "logs", "log.txt") | ||
82 | - handler = logging.FileHandler(log_file, encoding='UTF-8') # 设置日志字符集和存储路径名字 | ||
83 | - logging_format = logging.Formatter('[%(levelname)s] %(asctime)s %(message)s') | 111 | + log_file = os.path.join(os.path.dirname(os.path.dirname( |
112 | + os.path.realpath(__file__))), "logs", "log.txt") | ||
113 | + handler = logging.FileHandler( | ||
114 | + log_file, encoding='UTF-8') # 设置日志字符集和存储路径名字 | ||
115 | + logging_format = logging.Formatter( | ||
116 | + '[%(levelname)s] %(asctime)s %(message)s') | ||
84 | handler.setFormatter(logging_format) | 117 | handler.setFormatter(logging_format) |
85 | 118 | ||
86 | app.logger.addHandler(handler) | 119 | app.logger.addHandler(handler) |
@@ -99,6 +132,7 @@ def create_app(): | @@ -99,6 +132,7 @@ def create_app(): | ||
99 | # start_schedule() | 132 | # start_schedule() |
100 | return app | 133 | return app |
101 | 134 | ||
135 | + | ||
102 | def create_schedule(): | 136 | def create_schedule(): |
103 | monitor = Flask(__name__) | 137 | monitor = Flask(__name__) |
104 | monitor.config['SQLALCHEMY_DATABASE_URI'] = configure.SQLALCHEMY_DATABASE_URI | 138 | monitor.config['SQLALCHEMY_DATABASE_URI'] = configure.SQLALCHEMY_DATABASE_URI |
@@ -109,12 +143,12 @@ def create_schedule(): | @@ -109,12 +143,12 @@ def create_schedule(): | ||
109 | 143 | ||
110 | # allows cookies and credentials to be submitted across domains | 144 | # allows cookies and credentials to be submitted across domains |
111 | monitor.config['CORS_SUPPORTS_CREDENTIALS'] = true | 145 | monitor.config['CORS_SUPPORTS_CREDENTIALS'] = true |
112 | - monitor.config['CORS_ORIGINS ']="*" | ||
113 | - | 146 | + monitor.config['CORS_ORIGINS '] = "*" |
147 | + | ||
114 | # swagger设置 | 148 | # swagger设置 |
115 | swagger_config = Swagger.DEFAULT_CONFIG | 149 | swagger_config = Swagger.DEFAULT_CONFIG |
116 | Swagger(monitor, config=swagger_config) | 150 | Swagger(monitor, config=swagger_config) |
117 | - | 151 | + |
118 | # 创建数据库 | 152 | # 创建数据库 |
119 | db.init_app(monitor) | 153 | db.init_app(monitor) |
120 | db.create_all(app=monitor) | 154 | db.create_all(app=monitor) |
@@ -124,9 +158,12 @@ def create_schedule(): | @@ -124,9 +158,12 @@ def create_schedule(): | ||
124 | 158 | ||
125 | # 日志 | 159 | # 日志 |
126 | logging.basicConfig(level=configure.log_level) | 160 | logging.basicConfig(level=configure.log_level) |
127 | - log_file = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "logs", "monitor_log.txt") | ||
128 | - handler = logging.FileHandler(log_file, encoding='UTF-8') # 设置日志字符集和存储路径名字 | ||
129 | - logging_format = logging.Formatter('[%(levelname)s] %(asctime)s %(message)s') | 161 | + log_file = os.path.join(os.path.dirname(os.path.dirname( |
162 | + os.path.realpath(__file__))), "logs", "monitor_log.txt") | ||
163 | + handler = logging.FileHandler( | ||
164 | + log_file, encoding='UTF-8') # 设置日志字符集和存储路径名字 | ||
165 | + logging_format = logging.Formatter( | ||
166 | + '[%(levelname)s] %(asctime)s %(message)s') | ||
130 | handler.setFormatter(logging_format) | 167 | handler.setFormatter(logging_format) |
131 | monitor.logger.addHandler(handler) | 168 | monitor.logger.addHandler(handler) |
132 | 169 |
@@ -4,20 +4,62 @@ import os | @@ -4,20 +4,62 @@ import os | ||
4 | from flask_sqlalchemy import SQLAlchemy | 4 | from flask_sqlalchemy import SQLAlchemy |
5 | import glob | 5 | import glob |
6 | import traceback | 6 | import traceback |
7 | -from pyDes import des,PAD_PKCS5,ECB | ||
8 | - | 7 | +from pyDes import des, PAD_PKCS5, ECB |
8 | +import json | ||
9 | import base64 | 9 | import base64 |
10 | +from gmssl import sm3, func | ||
11 | +from Crypto.Cipher import AES | ||
12 | +from Crypto.Util.Padding import unpad, pad | ||
13 | +from cryptography.hazmat.primitives import padding | ||
14 | +from cryptography.hazmat.primitives.ciphers import algorithms | ||
15 | +from binascii import b2a_hex, a2b_hex | ||
16 | + | ||
17 | + | ||
18 | +class AESHelper(): | ||
19 | + key = 'w03MyIgc3zMHM5Qe'.encode('utf-8') | ||
20 | + iv = '8765432187654321'.encode('utf-8') | ||
21 | + | ||
22 | + @classmethod | ||
23 | + def encode(self, instr): | ||
24 | + plaintxt = self._pad(instr) | ||
25 | + aes = AES.new(self.key, AES.MODE_CBC, self.iv) | ||
26 | + ciphertext = aes.encrypt(plaintxt) | ||
27 | + return b2a_hex(ciphertext).decode('utf-8') | ||
28 | + | ||
29 | + @classmethod | ||
30 | + def decode(self, instr): | ||
31 | + encryptedData = instr | ||
32 | + aes = AES.new(self.key, AES.MODE_CBC, self.iv) | ||
33 | + plaintxt = aes.decrypt(a2b_hex(encryptedData)) | ||
34 | + return bytes.decode(unpad(plaintxt, AES.block_size)) | ||
35 | + | ||
36 | + def _pad(data): | ||
37 | + if not isinstance(data, bytes): | ||
38 | + data = data.encode() | ||
39 | + padder = padding.PKCS7(algorithms.AES.block_size).padder() | ||
40 | + padded_data = padder.update(data)+padder.finalize() | ||
41 | + return padded_data | ||
42 | + | ||
43 | + | ||
44 | +class SM3(): | ||
45 | + @classmethod | ||
46 | + def encode(self, data=""): | ||
47 | + by_str = bytes(data, 'utf-8') | ||
48 | + result = sm3.sm3_hash(func.bytes_to_list(by_str)) | ||
49 | + return result | ||
50 | + | ||
51 | + | ||
10 | class DES(): | 52 | class DES(): |
11 | ''' | 53 | ''' |
12 | DES密码加解密 | 54 | DES密码加解密 |
13 | ''' | 55 | ''' |
14 | - Des: des = des("Chinadci", ECB, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5) | 56 | + Des: des = des("Chinadci", ECB, "\0\0\0\0\0\0\0\0", |
57 | + pad=None, padmode=PAD_PKCS5) | ||
15 | 58 | ||
16 | @classmethod | 59 | @classmethod |
17 | def encode(cls, data): | 60 | def encode(cls, data): |
18 | return str(base64.b64encode(cls.Des.encrypt(data)), encoding="utf8") | 61 | return str(base64.b64encode(cls.Des.encrypt(data)), encoding="utf8") |
19 | 62 | ||
20 | - | ||
21 | @classmethod | 63 | @classmethod |
22 | def decode(cls, data): | 64 | def decode(cls, data): |
23 | if data: | 65 | if data: |
@@ -26,6 +68,7 @@ class DES(): | @@ -26,6 +68,7 @@ class DES(): | ||
26 | else: | 68 | else: |
27 | return data | 69 | return data |
28 | 70 | ||
71 | + | ||
29 | db = SQLAlchemy() | 72 | db = SQLAlchemy() |
30 | 73 | ||
31 | # 动态加载Model | 74 | # 动态加载Model |
@@ -33,13 +76,14 @@ current_dir = os.path.abspath(os.path.dirname(__file__)) | @@ -33,13 +76,14 @@ current_dir = os.path.abspath(os.path.dirname(__file__)) | ||
33 | pkgs = list(glob.glob('%s/modules/*/models' % (current_dir))) | 76 | pkgs = list(glob.glob('%s/modules/*/models' % (current_dir))) |
34 | pkgs.extend(list(glob.glob('%s/modules/*/*/models' % (current_dir)))) | 77 | pkgs.extend(list(glob.glob('%s/modules/*/*/models' % (current_dir)))) |
35 | 78 | ||
36 | -for pkg in pkgs : | 79 | +for pkg in pkgs: |
37 | pkg = os.path.normpath(pkg) | 80 | pkg = os.path.normpath(pkg) |
38 | node_list = pkg.split(os.path.sep) | 81 | node_list = pkg.split(os.path.sep) |
39 | - pkg_name = "app.{}".format(".".join(node_list[node_list.index("modules"):])) | 82 | + pkg_name = "app.{}".format( |
83 | + ".".join(node_list[node_list.index("modules"):])) | ||
40 | try: | 84 | try: |
41 | if pkg_name.__contains__("models"): | 85 | if pkg_name.__contains__("models"): |
42 | __import__(pkg_name) | 86 | __import__(pkg_name) |
43 | except Exception as e: | 87 | except Exception as e: |
44 | print(traceback.format_exc()) | 88 | print(traceback.format_exc()) |
45 | - pass | ||
89 | + pass |
1 | from enum import auto | 1 | from enum import auto |
2 | from logging import error | 2 | from logging import error |
3 | +from unittest import result | ||
3 | from flasgger import swag_from | 4 | from flasgger import swag_from |
4 | from app.util import BlueprintApi | 5 | from app.util import BlueprintApi |
5 | from app.util import BlueprintApi | 6 | from app.util import BlueprintApi |
6 | from flask import Blueprint, render_template, redirect, request, session, jsonify | 7 | from flask import Blueprint, render_template, redirect, request, session, jsonify |
7 | -from sqlalchemy import and_ | ||
8 | from .models import * | 8 | from .models import * |
9 | -from .oauth2 import authorization, generate_user_info,require_oauth | 9 | +from .oauth2 import authorization, generate_user_info, require_oauth |
10 | from authlib.oauth2 import OAuth2Error | 10 | from authlib.oauth2 import OAuth2Error |
11 | from authlib.integrations.flask_oauth2 import current_token | 11 | from authlib.integrations.flask_oauth2 import current_token |
12 | from . import user_create, client_create, client_query, user_query, user_update, user_delete | 12 | from . import user_create, client_create, client_query, user_query, user_update, user_delete |
13 | import configure | 13 | import configure |
14 | from app.decorators.auth_decorator import auth_decorator | 14 | from app.decorators.auth_decorator import auth_decorator |
15 | +import time | ||
16 | +from app.models import SM3, AESHelper | ||
15 | 17 | ||
16 | 18 | ||
17 | def current_user(): | 19 | def current_user(): |
@@ -45,17 +47,22 @@ class DataManager(BlueprintApi): | @@ -45,17 +47,22 @@ class DataManager(BlueprintApi): | ||
45 | except OAuth2Error as error: | 47 | except OAuth2Error as error: |
46 | return jsonify(dict(error.get_body())) | 48 | return jsonify(dict(error.get_body())) |
47 | if not user: | 49 | if not user: |
48 | - return render_template("auth/authorize.html", user=user, grant=grant) | ||
49 | - # return render_template("auth/login1.html", user=user, grant=grant) | 50 | + # 生成验证码 |
51 | + | ||
52 | + return render_template("auth/authorize.html", | ||
53 | + user=user, | ||
54 | + grant=grant) | ||
50 | error = "" | 55 | error = "" |
51 | if not user: | 56 | if not user: |
57 | + # 验证码校验 | ||
58 | + | ||
52 | if not "username" in request.form or not request.form.get("username"): | 59 | if not "username" in request.form or not request.form.get("username"): |
53 | error = "用户名不可为空" | 60 | error = "用户名不可为空" |
54 | elif not "password" in request.form or not request.form.get("password"): | 61 | elif not "password" in request.form or not request.form.get("password"): |
55 | error = "密码不可为空" | 62 | error = "密码不可为空" |
56 | else: | 63 | else: |
57 | username = request.form.get("username") | 64 | username = request.form.get("username") |
58 | - password = request.form.get("password") | 65 | + password = SM3.encode(request.form.get("password")) |
59 | user = User.query.filter_by( | 66 | user = User.query.filter_by( |
60 | username=username, password=password).first() | 67 | username=username, password=password).first() |
61 | if not user: | 68 | if not user: |
@@ -64,15 +71,14 @@ class DataManager(BlueprintApi): | @@ -64,15 +71,14 @@ class DataManager(BlueprintApi): | ||
64 | if user: | 71 | if user: |
65 | session["id"] = user.id | 72 | session["id"] = user.id |
66 | grant_user = user | 73 | grant_user = user |
67 | - return authorization.create_authorization_response(grant_user=grant_user) | 74 | + return authorization.create_authorization_response(request=request, grant_user=grant_user) |
68 | 75 | ||
69 | try: | 76 | try: |
70 | grant = authorization.validate_consent_request(end_user=user) | 77 | grant = authorization.validate_consent_request(end_user=user) |
71 | except OAuth2Error as error: | 78 | except OAuth2Error as error: |
72 | return jsonify(dict(error.get_body())) | 79 | return jsonify(dict(error.get_body())) |
73 | - return render_template("auth/authorize.html", user=user, grant=grant, error=error) | ||
74 | - | ||
75 | - | 80 | + # return render_template("auth/authorize.html", user=user, grant=grant, error=error) |
81 | + return authorization.create_authorization_response(grant_user=None) | ||
76 | 82 | ||
77 | @staticmethod | 83 | @staticmethod |
78 | @bp.route("/token", methods=["POST"]) | 84 | @bp.route("/token", methods=["POST"]) |
@@ -106,8 +112,7 @@ class DataManager(BlueprintApi): | @@ -106,8 +112,7 @@ class DataManager(BlueprintApi): | ||
106 | except OAuth2Error as error: | 112 | except OAuth2Error as error: |
107 | return jsonify(dict(error.get_body())) | 113 | return jsonify(dict(error.get_body())) |
108 | return redirect(url) | 114 | return redirect(url) |
109 | - | ||
110 | - | 115 | + |
111 | """接口""" | 116 | """接口""" |
112 | @staticmethod | 117 | @staticmethod |
113 | @bp.route("/users", methods=["GET"]) | 118 | @bp.route("/users", methods=["GET"]) |
@@ -166,3 +171,29 @@ class DataManager(BlueprintApi): | @@ -166,3 +171,29 @@ class DataManager(BlueprintApi): | ||
166 | 获取client列表 | 171 | 获取client列表 |
167 | """ | 172 | """ |
168 | return client_query.Api().result | 173 | return client_query.Api().result |
174 | + | ||
175 | + @staticmethod | ||
176 | + @bp.route("/init", methods=["GET"]) | ||
177 | + def init(): | ||
178 | + username = 'admin' | ||
179 | + password = 'admin' | ||
180 | + if not User.query.filter_by(username=username).one_or_none(): | ||
181 | + user = User(username=username, password=password, role='admin', | ||
182 | + phone='', company='', position='', email='', | ||
183 | + create_time=time.strftime( | ||
184 | + "%Y-%m-%d %H:%M:%S", time.localtime()), | ||
185 | + update_time=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) | ||
186 | + db.sesion.add(user) | ||
187 | + db.session.commit() | ||
188 | + return "创建默认用户成功" | ||
189 | + else: | ||
190 | + return "默认用户已存在" | ||
191 | + | ||
192 | + @staticmethod | ||
193 | + @bp.route("/test", methods=["GET"]) | ||
194 | + def test(): | ||
195 | + result = 'c78ca1de8139e62c99d4c4d2050ddd16' | ||
196 | + result = AESHelper.encode('dataman2') | ||
197 | + # result=DES.decode('U2FsdGVkX199Ncuh7+0/HVmonwM9Uz7SYnmF73wtW7c=') | ||
198 | + result2 = AESHelper.decode(result) | ||
199 | + return result2 |
@@ -40,6 +40,13 @@ class OAuth2Client(db.Model, OAuth2ClientMixin): | @@ -40,6 +40,13 @@ class OAuth2Client(db.Model, OAuth2ClientMixin): | ||
40 | Integer, ForeignKey('dmap_user.id', ondelete='CASCADE')) | 40 | Integer, ForeignKey('dmap_user.id', ondelete='CASCADE')) |
41 | user = relationship('User') | 41 | user = relationship('User') |
42 | 42 | ||
43 | + def get_default_redirect_uri(self): | ||
44 | + if self.redirect_uris: | ||
45 | + return self.redirect_uris[0] | ||
46 | + | ||
47 | + def check_redirect_uri(self, redirect_uri): | ||
48 | + return redirect_uri in self.redirect_uris | ||
49 | + | ||
43 | 50 | ||
44 | class OAuth2AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin): | 51 | class OAuth2AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin): |
45 | __tablename__ = 'dmap_oauth2_code' | 52 | __tablename__ = 'dmap_oauth2_code' |
@@ -57,4 +64,4 @@ class OAuth2Token(db.Model, OAuth2TokenMixin): | @@ -57,4 +64,4 @@ class OAuth2Token(db.Model, OAuth2TokenMixin): | ||
57 | user_id = Column( | 64 | user_id = Column( |
58 | Integer, ForeignKey('dmap_user.id', ondelete='CASCADE')) | 65 | Integer, ForeignKey('dmap_user.id', ondelete='CASCADE')) |
59 | # name = Column(Text) | 66 | # name = Column(Text) |
60 | - user = relationship('User') | ||
67 | + user = relationship('User') |
1 | +from authlib.oauth2.rfc6749 import grants | ||
1 | from os import access, remove | 2 | from os import access, remove |
2 | from time import time | 3 | from time import time |
3 | from authlib.integrations.flask_oauth2 import ( | 4 | from authlib.integrations.flask_oauth2 import ( |
@@ -29,10 +30,11 @@ DUMMY_JWT_CONFIG = { | @@ -29,10 +30,11 @@ DUMMY_JWT_CONFIG = { | ||
29 | 'exp': 7200, | 30 | 'exp': 7200, |
30 | } | 31 | } |
31 | 32 | ||
33 | + | ||
32 | class myCodeIDToken(CodeIDToken): | 34 | class myCodeIDToken(CodeIDToken): |
33 | def validate(self, now, leeway): | 35 | def validate(self, now, leeway): |
34 | return super().validate(now=now, leeway=leeway) | 36 | return super().validate(now=now, leeway=leeway) |
35 | - | 37 | + |
36 | def validate_exp(self, now, leeway): | 38 | def validate_exp(self, now, leeway): |
37 | return super().validate_exp(now, leeway) | 39 | return super().validate_exp(now, leeway) |
38 | 40 | ||
@@ -68,7 +70,34 @@ def create_authorization_code(client, grant_user, request): | @@ -68,7 +70,34 @@ def create_authorization_code(client, grant_user, request): | ||
68 | return code | 70 | return code |
69 | 71 | ||
70 | 72 | ||
71 | -class AuthorizationCodeGrant(_AuthorizationCodeGrant): | 73 | +class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): |
74 | + | ||
75 | + def save_authorization_code(self, code, request): | ||
76 | + client = request.client | ||
77 | + auth_code = OAuth2AuthorizationCode( | ||
78 | + code=code, | ||
79 | + client_id=client.client_id, | ||
80 | + redirect_uri=request.redirect_uri, | ||
81 | + scope=request.scope, | ||
82 | + user_id=request.user.id, | ||
83 | + ) | ||
84 | + db.session.add(auth_code) | ||
85 | + db.session.commit() | ||
86 | + return auth_code | ||
87 | + | ||
88 | + def query_authorization_code(self, code, client): | ||
89 | + item = OAuth2AuthorizationCode.query.filter_by( | ||
90 | + code=code, client_id=client.client_id).first() | ||
91 | + if item and not item.is_expired(): | ||
92 | + return item | ||
93 | + | ||
94 | + def delete_authorization_code(self, authorization_code): | ||
95 | + db.session.delete(authorization_code) | ||
96 | + db.session.commit() | ||
97 | + | ||
98 | + def authenticate_user(self, authorization_code): | ||
99 | + return User.query.get(authorization_code.user_id) | ||
100 | + | ||
72 | def create_authorization_code(self, client, grant_user, request): | 101 | def create_authorization_code(self, client, grant_user, request): |
73 | return create_authorization_code(client, grant_user, request) | 102 | return create_authorization_code(client, grant_user, request) |
74 | 103 | ||
@@ -122,6 +151,17 @@ class HybridGrant(_OpenIDHybridGrant): | @@ -122,6 +151,17 @@ class HybridGrant(_OpenIDHybridGrant): | ||
122 | return generate_user_info(user, scope) | 151 | return generate_user_info(user, scope) |
123 | 152 | ||
124 | 153 | ||
154 | +class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant): | ||
155 | + def authenticate_user(self, username, password): | ||
156 | + user = User.query.filter_by(username=username).first() | ||
157 | + if user.check_password(password): | ||
158 | + return user | ||
159 | + | ||
160 | + TOKEN_ENDPOINT_AUTH_METHODS = [ | ||
161 | + 'client_secret_basic', 'client_secret_post' | ||
162 | + ] | ||
163 | + | ||
164 | + | ||
125 | authorization = AuthorizationServer() | 165 | authorization = AuthorizationServer() |
126 | require_oauth = ResourceProtector() | 166 | require_oauth = ResourceProtector() |
127 | 167 | ||
@@ -142,6 +182,7 @@ def config_oauth(app): | @@ -142,6 +182,7 @@ def config_oauth(app): | ||
142 | ]) | 182 | ]) |
143 | authorization.register_grant(ImplicitGrant) | 183 | authorization.register_grant(ImplicitGrant) |
144 | authorization.register_grant(HybridGrant) | 184 | authorization.register_grant(HybridGrant) |
185 | + authorization.register_grant(PasswordGrant) | ||
145 | 186 | ||
146 | # protect resource | 187 | # protect resource |
147 | bearer_cls = create_bearer_token_validator(db.session, OAuth2Token) | 188 | bearer_cls = create_bearer_token_validator(db.session, OAuth2Token) |
@@ -6,6 +6,8 @@ | @@ -6,6 +6,8 @@ | ||
6 | from .models import * | 6 | from .models import * |
7 | from app.util.component.ApiTemplate import ApiTemplate | 7 | from app.util.component.ApiTemplate import ApiTemplate |
8 | import time | 8 | import time |
9 | +from app.models import SM3 | ||
10 | +from app.models import AESHelper | ||
9 | 11 | ||
10 | 12 | ||
11 | class Api(ApiTemplate): | 13 | class Api(ApiTemplate): |
@@ -26,13 +28,13 @@ class Api(ApiTemplate): | @@ -26,13 +28,13 @@ class Api(ApiTemplate): | ||
26 | res["result"] = False | 28 | res["result"] = False |
27 | try: | 29 | try: |
28 | # 业务逻辑 | 30 | # 业务逻辑 |
29 | - username = self.para.get("username") | ||
30 | - password = self.para.get("pwd") | ||
31 | - role = self.para.get("role") | ||
32 | - company = self.para.get("company", None) | ||
33 | - position = self.para.get("position", None) | ||
34 | - email = self.para.get("email", None) | ||
35 | - phone = self.para.get("phone", None) | 31 | + username = AESHelper.decode( self.para.get("username", '')) |
32 | + password = SM3.encode(AESHelper.decode(self.para.get("pwd", ''))) | ||
33 | + role = AESHelper.decode(self.para.get("role", '')) | ||
34 | + company = AESHelper.decode(self.para.get("company", '')) | ||
35 | + position = AESHelper.decode(self.para.get("position", '')) | ||
36 | + email = AESHelper.decode(self.para.get("email", '')) | ||
37 | + phone = AESHelper.decode(self.para.get("phone", '')) | ||
36 | # 是否重名 | 38 | # 是否重名 |
37 | if(User.query.filter_by(username=username).one_or_none()): | 39 | if(User.query.filter_by(username=username).one_or_none()): |
38 | res["msg"] = "username 已存在" | 40 | res["msg"] = "username 已存在" |
1 | from app.util.component.ApiTemplate import ApiTemplate | 1 | from app.util.component.ApiTemplate import ApiTemplate |
2 | -import time | 2 | +from app.models import SM3 |
3 | from .models import * | 3 | from .models import * |
4 | +from app.models import AESHelper | ||
4 | 5 | ||
5 | 6 | ||
6 | class Api(ApiTemplate): | 7 | class Api(ApiTemplate): |
@@ -25,10 +26,13 @@ class Api(ApiTemplate): | @@ -25,10 +26,13 @@ class Api(ApiTemplate): | ||
25 | else: | 26 | else: |
26 | # 更新密码要求同时输入pwd/newPwd/reNewPwd | 27 | # 更新密码要求同时输入pwd/newPwd/reNewPwd |
27 | if self.para.__contains__("pwd") or self.para.__contains__("newPwd") or self.para.__contains__("reNewPwd"): | 28 | if self.para.__contains__("pwd") or self.para.__contains__("newPwd") or self.para.__contains__("reNewPwd"): |
28 | - password = self.para.get("pwd") | ||
29 | - new_password = self.para.get("newPwd") | ||
30 | - re_new_password = self.para.get("reNewPwd") | ||
31 | - | 29 | + password = SM3.encode( |
30 | + AESHelper.decode(self.para.get("pwd"))) | ||
31 | + new_password = SM3.encode( | ||
32 | + AESHelper.decode(self.para.get("newPwd"))) | ||
33 | + re_new_password = SM3.encode( | ||
34 | + AESHelper.decode(self.para.get("reNewPwd"))) | ||
35 | + | ||
32 | # validate pwd | 36 | # validate pwd |
33 | if not password: | 37 | if not password: |
34 | res["result"] = False | 38 | res["result"] = False |
@@ -42,17 +46,17 @@ class Api(ApiTemplate): | @@ -42,17 +46,17 @@ class Api(ApiTemplate): | ||
42 | res["result"] = False | 46 | res["result"] = False |
43 | res["msg"] = "原密码输入错误" | 47 | res["msg"] = "原密码输入错误" |
44 | return res | 48 | return res |
45 | - | 49 | + |
46 | # 更新密码 | 50 | # 更新密码 |
47 | userinfo.update({"password": new_password}) | 51 | userinfo.update({"password": new_password}) |
48 | - | ||
49 | - #更新用户基本信息 | 52 | + |
53 | + # 更新用户基本信息 | ||
50 | for key in obj_value: | 54 | for key in obj_value: |
51 | if self.para.__contains__(obj_value[key]): | 55 | if self.para.__contains__(obj_value[key]): |
52 | - value = self.para.get(obj_value[key]) | 56 | + value = self.para.get(AESHelper.decode(obj_value[key])) |
53 | value = "" if value == "None" or value == "none" else value | 57 | value = "" if value == "None" or value == "none" else value |
54 | userinfo.update({key: value}) | 58 | userinfo.update({key: value}) |
55 | - | 59 | + |
56 | db.session.commit() | 60 | db.session.commit() |
57 | res["result"] = True | 61 | res["result"] = True |
58 | res["msg"] = "更新用户信息成功" | 62 | res["msg"] = "更新用户信息成功" |
app/util/component/captcha.py
0 → 100644
1 | +''' | ||
2 | +生成验证码图片 | ||
3 | +''' | ||
4 | + | ||
5 | +from PIL import Image, ImageDraw, ImageFont, ImageFilter | ||
6 | +import random | ||
7 | + | ||
8 | + | ||
9 | +# 随机字符 | ||
10 | +def rndChar(): | ||
11 | + num = 0 | ||
12 | + while num == 0 and ((num >= 58 and num <= 64) or (num >= 91 and num <= 96)): | ||
13 | + num = random.randint(48, 122) | ||
14 | + return num | ||
15 | + | ||
16 | +# 随机颜色 | ||
17 | + | ||
18 | + | ||
19 | +def rndColor(): | ||
20 | + return '' |
@@ -5,6 +5,7 @@ deploy_ip_host = "172.26.40.105:8840" | @@ -5,6 +5,7 @@ deploy_ip_host = "172.26.40.105:8840" | ||
5 | # 系统数据库 | 5 | # 系统数据库 |
6 | SQLALCHEMY_DATABASE_URI = "postgresql://postgres:chinadci@172.26.60.101:5432/dmap_manager" | 6 | SQLALCHEMY_DATABASE_URI = "postgresql://postgres:chinadci@172.26.60.101:5432/dmap_manager" |
7 | 7 | ||
8 | + | ||
8 | # 指定精华表所在位置(必须为空间库),设置为None则存放在各自的实体库中 | 9 | # 指定精华表所在位置(必须为空间库),设置为None则存放在各自的实体库中 |
9 | #VACUATE_DB_URI = None | 10 | #VACUATE_DB_URI = None |
10 | VACUATE_DB_URI = SQLALCHEMY_DATABASE_URI | 11 | VACUATE_DB_URI = SQLALCHEMY_DATABASE_URI |
@@ -13,6 +14,8 @@ VACUATE_DB_URI = SQLALCHEMY_DATABASE_URI | @@ -13,6 +14,8 @@ VACUATE_DB_URI = SQLALCHEMY_DATABASE_URI | ||
13 | dmap_engine = "http://172.26.99.160:8820" | 14 | dmap_engine = "http://172.26.99.160:8820" |
14 | 15 | ||
15 | # 固定配置不需要修改 | 16 | # 固定配置不需要修改 |
17 | + | ||
18 | +swagger_configure = {"title": "DMapManager"} | ||
16 | entry_data_thread = 3 | 19 | entry_data_thread = 3 |
17 | SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/' | 20 | SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/' |
18 | # 权限 | 21 | # 权限 |
docs/问题记录.md
0 → 100644
1 | +101映射域名 | ||
2 | +dmap.apps.chinadci.com | ||
3 | +# 鉴权 | ||
4 | +Authlib | ||
5 | +## 1 授权服务器 | ||
6 | +为授权、颁发令牌、刷新令牌和撤销令牌提供多个端点。当资源拥有者(用户)获得授权时,授权服务器会向客户端颁发访问令牌。 | ||
7 | +### 资源拥有者:用户 | ||
8 | +### 客户端:客户端是一个代表资源拥有者并在授权情况下请求受保护资源的应用 | ||
9 | + * client_id是唯一标识 | ||
10 | + * client_secret是密码 | ||
11 | + * 客户端令牌端点认证方法 | ||
12 | +### 令牌 | ||
13 | +使用令牌访问用户资源,令牌发布时带有有效期,有访问范围等等。它至少包括: | ||
14 | +* access_token | ||
15 | +* refresh_token | ||
16 | +*... | ||
17 | +### 服务器 | ||
18 | +authlib使用工具AuthorizationServer管理请求和响应 | ||
19 | + | ||
20 | + | ||
21 | + | ||
22 | +# Web安全 | ||
23 | +## 1 接口权限控制 | ||
24 | +[swagger](https://swagger.io/docs/specification/2-0/what-is-swagger/) | ||
25 | + | ||
26 | +**需求:api需要授权** | ||
27 | + | ||
28 | +1.确定当前使用openapi2版本 | ||
29 | +2.了解简单YAML使用方式,文档使用YAML格式 | ||
30 | +3.声明Swagger,传入配置template(json或者yaml) | ||
31 | +```python | ||
32 | +Swagger(app, config=swagger_config,template=SWAGGER_TEMPLATE) | ||
33 | +``` | ||
34 | + | ||
35 | +## 2 用户密码加密 | ||
36 | +[pipy](https://pypi.org/project/gmssl/) | ||
37 | +[github](https://github.com/guanzhi/GmSSL) | ||
38 | +[使用gmssl demo](https://www.cnblogs.com/rocedu/p/15518988.html) | ||
39 | + | ||
40 | +**用户密码加密不可逆,兼容国产化,使用sm3算法,采用gmssl开源组件** | ||
41 | + | ||
42 | +封装好的类使用如下: | ||
43 | +```python | ||
44 | +from app.models import SM3 | ||
45 | +password = SM3.encode('test') | ||
46 | +``` | ||
47 | +## 3 通过网络传输的敏感信息要加密 | ||
48 | +~~使用sm2,通过gmssl工具生成sm2的公钥、私钥。~~ | ||
49 | +~~[教程](https://github.com/guanzhi/GmSSL)~~ | ||
50 | + | ||
51 | +~~1.生成sm2公钥、私钥~~ | ||
52 | +~~privatekey使用的pem是`zhou@123`~~ | ||
53 | + | ||
54 | +~~2. 使用公钥加密,私钥解密~~ | ||
55 | +~~前端使用sm-crypto,用户与python-gmssl互通~~ | ||
56 | +~~[npm_sm-crypto](https://www.npmjs.com/package/sm-crypto)~~ | ||
57 | + | ||
58 | +使用AES对称加密敏感信息,前端加密,后端解密。偏移量iv、加密密钥key与前端保持一致,保证解密正确。封装在models.py中。 | ||
59 | + | ||
60 | +依赖组件: | ||
61 | +* pycryptodome | ||
62 | +* Crypto | ||
63 | +* binascii | ||
64 | +参考资料 | ||
65 | +[pycryptodome]() | ||
66 | + | ||
67 | +**demo** | ||
68 | +```python | ||
69 | +from app.models import AESHelper | ||
70 | + | ||
71 | +encryption=AESHelper.encode('test_data') | ||
72 | +result=AESHelper.decode(encryption) | ||
73 | +``` | ||
74 | + | ||
75 | +## 4 使用验证码,防止恶意破解密码、刷票、论坛灌水、刷页 | ||
76 | +验证码又叫CAPTCHA | ||
77 | + | ||
78 | +[验证码基础知识](https://baike.baidu.com/item/%E9%AA%8C%E8%AF%81%E7%A0%81/31701) | ||
79 | +[使用python图像处理标准库](https://www.liaoxuefeng.com/wiki/1016959663602400/1017785454949568) | ||
80 | +[pillow](https://pillow.readthedocs.io/en/stable/index.html) |
请
注册
或
登录
后发表评论