auth.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. # -*- coding: utf-8 -*-
  2. from fastapi import APIRouter, Depends, Request, Header, Form, Body
  3. from fastapi.responses import FileResponse, StreamingResponse
  4. from sqlalchemy.orm import Session
  5. from sqlalchemy import text, exists, and_, or_, not_
  6. from fastapi.responses import JSONResponse
  7. from database import get_db
  8. from utils import *
  9. from utils.vcode import *
  10. from utils.redis_util import *
  11. import base64
  12. from common.const import *
  13. from io import BytesIO
  14. from utils.StripTagsHTMLParser import *
  15. from common import security
  16. from datetime import timedelta
  17. from common.security import verify_password
  18. from utils import ase_utils
  19. from common.auth_user import *
  20. from common import YzyApi, TassApi
  21. from models import *
  22. from urllib.parse import quote
  23. from exceptions import *
  24. import traceback
  25. from common.db import db_czrz
  26. from common.enc import mpfun, sys_user_data
  27. router = APIRouter()
  28. @router.get('/tenant/list')
  29. async def tenant_list(
  30. request: Request,
  31. db: Session = Depends(get_db)
  32. ):
  33. return {
  34. "code": 200,
  35. "msg": "操作成功",
  36. "data": {
  37. "tenantEnabled": False,
  38. "voList": []
  39. }
  40. }
  41. def buildVerificationCodeRedisKey(uuid: str) -> str:
  42. return VERIFICATION_CODE_REDIS_PREFIX.format(uuid)
  43. @router.get('/code')
  44. async def code(
  45. request: Request,
  46. db: Session = Depends(get_db)
  47. ):
  48. uuid_str = md5(new_guid())
  49. image, code = getVerifyCode()
  50. buf = BytesIO()
  51. image.save(buf, 'png')
  52. img_data = buf.getvalue()
  53. image_bytes_io = BytesIO(img_data)
  54. image_bytes_io.seek(0)
  55. image_bytes = image_bytes_io.read()
  56. base64_str = base64.b64encode(image_bytes).decode('utf-8')
  57. redis_key = "kaptcha_" + buildVerificationCodeRedisKey(uuid_str)
  58. redis_set(redis_key, code)
  59. return {
  60. "code": 200,
  61. "msg": "操作成功",
  62. "data": {
  63. "captchaEnabled": True,
  64. "uuid": uuid_str,
  65. "img": base64_str
  66. }
  67. }
  68. @router.post('/login')
  69. async def login(
  70. request: Request,
  71. db: Session = Depends(get_db),
  72. data: dict = Depends(remove_xss_json)
  73. ):
  74. try:
  75. # tenantId = data['tenantId']
  76. username = data['username']
  77. password = data['password']
  78. # rememberMe = data['rememberMe']
  79. uuid_str = data['uuid']
  80. code = data['code']
  81. # clientId = data['clientId']
  82. fromSystem = ''
  83. if 'fromSystem' in data:
  84. fromSystem = data['fromSystem']
  85. logger.info("fromSystem: {}", fromSystem)
  86. # 仅为了可能的兼容
  87. clientId = "e5cd7e4891bf95d1d19206ce24a7b32e"
  88. grantType = "password"
  89. uuid = buildVerificationCodeRedisKey(uuid_str)
  90. redis_key = "kaptcha_" + uuid
  91. redis_code = redis_get(redis_key)
  92. if code is None or code != redis_code:
  93. raise AppException(500, "图片验证码不正确")
  94. redis_login_key = "login_user_" + username
  95. login_error_times = redis_get(redis_login_key)
  96. if login_error_times is None:
  97. login_error_times = 0
  98. else:
  99. login_error_times = int(login_error_times)
  100. if login_error_times >= 5:
  101. raise AppException(500, "登录错误多,请5分钟后再尝试!")
  102. # 对用户账号进行密码机接口加密处理
  103. username = mpfun.enc_data(username)
  104. password = ase_utils.aesDecrypt(uuid_str, password)
  105. logger.info('userpass: {}', password)
  106. row = db.query(SysUser).filter(and_(SysUser.user_name == username, SysUser.del_flag == '0')).first()
  107. if sys_user_data.sign_valid_row(row) == False:
  108. raise AppException(500, "系统用户表验证异常,已被非法篡改")
  109. if row is None:
  110. login_error_times = login_error_times + 1
  111. redis_set_with_time(redis_login_key, str(login_error_times), 300)
  112. raise AppException(500, "帐号或者密码错误")
  113. logger.info('row.plain_user_name: {}', row.plain_user_name)
  114. # bcrypt 加密校验
  115. # if verify_password(password, row.password) == False:
  116. # 密码机加密校验
  117. if mpfun.enc_data(password) != row.password:
  118. login_error_times = login_error_times + 1
  119. redis_set_with_time(redis_login_key, str(login_error_times), 300)
  120. raise AppException(500, "帐号或者密码错误")
  121. # 校验账号是否停用
  122. if row.status != "0":
  123. raise AppException(500, "帐号已停用")
  124. # 校验账号是否过期
  125. if row.expire_time is not None and row.expire_time < datetime.now():
  126. raise AppException(500, "帐号已过期")
  127. # 校验长期(超过1个月)未使用的账号和及开通后未及时(如72小时)修改初始密码的账号做清除
  128. login_date = row.login_date
  129. if row.login == 0:
  130. # 计算初始化的时间和当前时间相差的小时数
  131. diff_hour = (datetime.now() - login_date).seconds/3600
  132. if diff_hour > 72:
  133. raise AppException(500, "你的账号在开通后(72小时)内未登录及修改初始密码,账号已被锁定,请联系管理员处理,否则将被清除。")
  134. else:
  135. # 计算上次登录到当前时间的相差天数
  136. diff_day = (datetime.now() - login_date).days
  137. if diff_day > 30:
  138. raise AppException(500, "你的账号在超过30天未登录使用,账号已被锁定,请联系管理员处理,否则将被清除。")
  139. redis_set_with_time(redis_login_key, str(0), 1)
  140. user_id = str(row.user_id)
  141. auth = {
  142. "user_id": user_id,
  143. "user_name": mpfun.dec_data(row.user_name),
  144. "nick_name": row.nick_name,
  145. "is_yzy_user": "0"
  146. }
  147. request.session.update({
  148. 'user_auth': auth,
  149. 'user_auth_sign': data_auth_sign(auth),
  150. 'user_name': username
  151. })
  152. action = '登录'
  153. czrz = '后台管理登录成功'
  154. if fromSystem == 'yjdp':
  155. action = '应急一张图'
  156. czrz = '大屏登录成功'
  157. db_czrz.log_username(db, row.user_id, auth['user_name'], row.nick_name, action, czrz, request.client.host)
  158. row.login_date = datetime.now()
  159. row.login_ip = request.client.host
  160. row.login = row.login + 1
  161. db.commit()
  162. access_token_expires = timedelta(days = 5)
  163. access_token = security.create_access_token(
  164. data={"sub": user_id}, expires_delta = access_token_expires
  165. )
  166. refresh_token_expires = timedelta(days = 10)
  167. refresh_token = security.create_access_token(
  168. data={"sub": user_id}, expires_delta = refresh_token_expires
  169. )
  170. return {
  171. "code": 200,
  172. "msg": "操作成功",
  173. "data": {
  174. "access_token": access_token,
  175. "refresh_token": refresh_token,
  176. "expire_in": 7200,
  177. "refresh_expire_in": 7200,
  178. "client_id": clientId,
  179. "scope": "",
  180. "openid": ""
  181. }
  182. }
  183. except AppException as e:
  184. return {
  185. "code": e.code,
  186. "msg": e.msg
  187. }
  188. except Exception as e:
  189. traceback.print_exc()
  190. return {
  191. "code": 500,
  192. "msg": "帐号或者密码错误"
  193. }
  194. @router.post('/logout')
  195. async def logout(
  196. request: Request,
  197. user: AuthUser = Depends(get_auth_user),
  198. db: Session = Depends(get_db)
  199. ):
  200. logger.info("logout ok")
  201. request.session.clear()
  202. try:
  203. # db_czrz.log(db, user, "退出", "后台管理退出成功", request.client.host)
  204. if user.is_yzy_user == 1:
  205. logout_url = settings.TYRZ_LOGOUT.format(settings.TYRZ_CLIENT_ID) + quote(settings.HOME_URL+"/yjzp/")
  206. logger.info(logout_url)
  207. else:
  208. logout_url = settings.HOME_URL + "/yjzp/"
  209. except Exception as e:
  210. traceback.print_exc()
  211. logout_url = settings.HOME_URL+"/yjzp/"
  212. return {
  213. "code": 200,
  214. "msg": "退出成功",
  215. "data": logout_url
  216. }
  217. # 小屏专用
  218. @router.post('/yzy/callback')
  219. async def yzy(
  220. request: Request,
  221. db: Session = Depends(get_db),
  222. data: dict = Depends(remove_xss_json)
  223. ):
  224. code = data['code']
  225. state = data['state']
  226. logger.info("code:{}, {}", code, state)
  227. '''
  228. resp = YzyApi.get_user_info(code)
  229. if resp['errcode'] != 0:
  230. return {
  231. "code": 500,
  232. "msg": "Code异常"
  233. }
  234. user_id = resp['UserId']
  235. '''
  236. # 管理中心通过授权码获取用户信息接口
  237. # 获取用户基本信息
  238. result = YzyApi.getuserbycode(code)
  239. errcode = int(result['errcode'])
  240. if errcode == 0:
  241. data = result['data']
  242. user_id = data['userid']
  243. username = data['username']
  244. phone = ''
  245. try:
  246. # 敏感数据加密算法(DES 对称加密)
  247. phone = YzyApi.desDecryptValue(settings.YZY_CORPSECRET, data['mobile'])
  248. except:
  249. traceback.print_exc()
  250. '''
  251. row = db.query(YzyOrgUserEntity).filter(YzyOrgUserEntity.userid == user_id).first()
  252. if row is None:
  253. return {
  254. "code": 500,
  255. "msg": "user_id异常"
  256. }
  257. yzy_account = row.account
  258. '''
  259. # where = and_(SysUser.del_flag == '0', SysUser.yzy_account == user_id)
  260. where = and_(SysUser.del_flag == '0', SysUser.user_name == mpfun.enc_data('mmyj_admin'))
  261. row = db.query(SysUser).filter(where).first()
  262. if row is None:
  263. return {
  264. "code": 500,
  265. "msg": "用户不是本系统用户"
  266. }
  267. user_id = str(row.user_id)
  268. access_token_expires = timedelta(seconds = 7200)
  269. access_token = security.create_access_token(
  270. data={"sub": user_id}, expires_delta = access_token_expires
  271. )
  272. refresh_token_expires = timedelta(seconds = 7200)
  273. refresh_token = security.create_access_token(
  274. data={"sub": user_id}, expires_delta = refresh_token_expires
  275. )
  276. return {
  277. "code": 200,
  278. "msg": "小屏粤政易登录成功",
  279. "data": {
  280. "access_token": access_token,
  281. "refresh_token": refresh_token,
  282. "expire_in": 7200,
  283. "refresh_expire_in": 7200,
  284. "scope": ""
  285. }
  286. }
  287. # 统一身份认证(粤政易扫码登录)token登录,中屏、大屏使用
  288. @router.post('/yzylogin')
  289. async def login(
  290. request: Request,
  291. data: dict = Depends(remove_xss_json),
  292. db: Session = Depends(get_db)
  293. ):
  294. code = data['code']
  295. redis_key = "yzy_" + code
  296. user_id = redis_get(redis_key)
  297. if user_id is None:
  298. return {
  299. "code": 500,
  300. "msg": "用户不是本系统用户"
  301. }
  302. row = db.query(SysUser).filter(SysUser.user_id == int(user_id)).first()
  303. if row is None:
  304. return {
  305. "code": 500,
  306. "msg": "用户不是本系统用户"
  307. }
  308. user_id = str(row.user_id)
  309. auth = {
  310. "user_id": user_id,
  311. "user_name": row.user_name,
  312. "nick_name": row.nick_name,
  313. "is_yzy_user": "1"
  314. }
  315. logger.info('auth {}', auth)
  316. request.session['user_auth'] = auth
  317. request.session['user_auth_sign'] = data_auth_sign(auth)
  318. request.session['user_name'] = row.user_name
  319. db_czrz.log_username(db, row.user_id, auth['user_name'], row.nick_name, "登录", "粤政易登录登录成功", request.client.host)
  320. row.login_date = datetime.now()
  321. row.login_ip = request.client.host
  322. row.login = row.login + 1
  323. db.commit()
  324. access_token_expires = timedelta(days = 5)
  325. access_token = security.create_access_token(
  326. data={"sub": user_id}, expires_delta = access_token_expires
  327. )
  328. refresh_token_expires = timedelta(days = 10)
  329. refresh_token = security.create_access_token(
  330. data={"sub": user_id}, expires_delta = refresh_token_expires
  331. )
  332. return {
  333. "code": 200,
  334. "msg": "操作成功",
  335. "data": {
  336. "access_token": access_token,
  337. "refresh_token": refresh_token,
  338. "expire_in": 7200,
  339. "refresh_expire_in": 7200,
  340. "client_id": 0,
  341. "scope": "",
  342. "openid": ""
  343. }
  344. }
  345. # USBKEY登录
  346. @router.post("/login_with_usbkey")
  347. def login_with_usbkey(
  348. request: Request,
  349. username: str = Body(...),
  350. keyID: str = Body(...),
  351. p7SignData: str = Body(...),
  352. p7SignValue: str = Body(...),
  353. db: Session = Depends(get_db)
  354. ):
  355. result = TassApi.verifyP7Sign(p7SignData, p7SignValue)
  356. if result is None:
  357. return {
  358. "code": 500,
  359. "msg": "证书验签失败",
  360. }
  361. logger.info('keyID: {}', keyID)
  362. logger.info('verifyP7Sign: {}', result)
  363. try:
  364. # 对用户账号进行密码机接口加密处理
  365. username = mpfun.enc_data(username)
  366. redis_login_key = "login_user_" + username
  367. login_error_times = redis_get(redis_login_key)
  368. if login_error_times is None:
  369. login_error_times = 0
  370. else:
  371. login_error_times = int(login_error_times)
  372. if login_error_times >= 5:
  373. raise AppException(500, "登录错误多,请5分钟后再尝试!")
  374. row = db.query(SysUser).filter(SysUser.user_name == username).first()
  375. if row is None:
  376. login_error_times = login_error_times + 1
  377. redis_set_with_time(redis_login_key, str(login_error_times), 300)
  378. raise AppException(500, "帐号或者密码错误")
  379. if sys_user_data.sign_valid_row(row) == False:
  380. raise AppException(500, "系统用户表验证异常,已被非法篡改")
  381. # 校验账号是否停用
  382. if row.status != "0":
  383. raise AppException(500, "帐号已停用")
  384. # 校验长期(超过1个月)未使用的账号和及开通后未及时(如72小时)修改初始密码的账号做清除
  385. login_date = row.login_date
  386. if row.login == 0:
  387. # 计算初始化的时间和当前时间相差的小时数
  388. diff_hour = (datetime.now() - login_date).seconds/3600
  389. if diff_hour > 72:
  390. raise AppException(500, "你的账号在开通后(72小时)内未登录及修改初始密码,账号已被锁定,请联系管理员处理,否则将被清除。")
  391. else:
  392. # 计算上次登录到当前时间的相差天数
  393. diff_day = (datetime.now() - login_date).days
  394. if diff_day > 30:
  395. raise AppException(500, "你的账号在超过30天未登录使用,账号已被锁定,请联系管理员处理,否则将被清除。")
  396. redis_set_with_time(redis_login_key, str(0), 1)
  397. user_id = str(row.user_id)
  398. auth = {
  399. "user_id": user_id,
  400. "user_name": mpfun.dec_data(row.user_name),
  401. "nick_name": row.nick_name,
  402. "is_yzy_user": "0"
  403. }
  404. logger.info('auth {}', auth)
  405. request.session['user_auth'] = auth
  406. request.session['user_auth_sign'] = data_auth_sign(auth)
  407. request.session['username'] = username
  408. db_czrz.log_username(db, row.user_id, auth['user_name'], row.nick_name, "登录", "后台USBKEY登录成功", request.client.host)
  409. row.login_date = datetime.now()
  410. row.login_ip = request.client.host
  411. row.login = row.login + 1
  412. db.commit()
  413. access_token_expires = timedelta(days = 5)
  414. access_token = security.create_access_token(
  415. data={"sub": user_id}, expires_delta = access_token_expires
  416. )
  417. refresh_token_expires = timedelta(days = 10)
  418. refresh_token = security.create_access_token(
  419. data={"sub": user_id}, expires_delta = refresh_token_expires
  420. )
  421. return {
  422. "code": 200,
  423. "msg": "操作成功",
  424. "data": {
  425. "access_token": access_token,
  426. "refresh_token": refresh_token,
  427. "expire_in": 7200,
  428. "refresh_expire_in": 7200,
  429. "client_id": "e5cd7e4891bf95d1d19206ce24a7b32e",
  430. "scope": "",
  431. "openid": ""
  432. }
  433. }
  434. except AppException as e:
  435. return {
  436. "code": e.code,
  437. "msg": e.msg
  438. }
  439. except Exception as e:
  440. traceback.print_exc()
  441. return {
  442. "code": 500,
  443. "msg": "帐号或者密码错误"
  444. }