auth.py 18 KB

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