新增用户密码加入请求校验,如果密码发生改变需要重新登陆,便于使用密码进行强制退出

This commit is contained in:
ktianc 2024-03-06 19:04:20 +08:00
parent 3ccbc2c4b2
commit 953cdda006
7 changed files with 75 additions and 50 deletions

View File

@ -102,7 +102,8 @@ const save = async () => {
const res = await postCurrentUserResetPassword(formData) const res = await postCurrentUserResetPassword(formData)
if (res) { if (res) {
elForm?.resetFields() elForm?.resetFields()
ElMessage.success('保存成功') authStore.logout()
ElMessage.warning('请重新登录')
} }
} finally { } finally {
loading.value = false loading.value = false

View File

@ -1,4 +1,4 @@
from .user import UserOut, UserUpdate, User, UserIn, UserSimpleOut, ResetPwd, UserUpdateBaseInfo from .user import UserOut, UserUpdate, User, UserIn, UserSimpleOut, ResetPwd, UserUpdateBaseInfo, UserPasswordOut
from .role import Role, RoleOut, RoleIn, RoleOptionsOut, RoleSimpleOut from .role import Role, RoleOut, RoleIn, RoleOptionsOut, RoleSimpleOut
from .menu import Menu, MenuSimpleOut, RouterOut, Meta, MenuTreeListOut from .menu import Menu, MenuSimpleOut, RouterOut, Meta, MenuTreeListOut
from .dept import Dept, DeptSimpleOut, DeptTreeListOut from .dept import Dept, DeptSimpleOut, DeptTreeListOut

View File

@ -74,6 +74,12 @@ class UserSimpleOut(User):
last_ip: str | None = None last_ip: str | None = None
class UserPasswordOut(UserSimpleOut):
model_config = ConfigDict(from_attributes=True)
password: str
class UserOut(UserSimpleOut): class UserOut(UserSimpleOut):
model_config = ConfigDict(from_attributes=True) model_config = ConfigDict(from_attributes=True)

View File

@ -27,10 +27,10 @@ class OpenAuth(AuthValidation):
""" """
async def __call__( async def __call__(
self, self,
request: Request, request: Request,
token: Annotated[str, Depends(settings.oauth2_scheme)], token: Annotated[str, Depends(settings.oauth2_scheme)],
db: AsyncSession = Depends(db_getter) db: AsyncSession = Depends(db_getter)
): ):
""" """
每次调用依赖此类的接口会执行该方法 每次调用依赖此类的接口会执行该方法
@ -38,8 +38,8 @@ class OpenAuth(AuthValidation):
if not settings.OAUTH_ENABLE: if not settings.OAUTH_ENABLE:
return Auth(db=db) return Auth(db=db)
try: try:
telephone = self.validate_token(request, token) telephone, password = self.validate_token(request, token)
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True) user = await UserDal(db).get_data(telephone=telephone, password=password, v_return_none=True)
return await self.validate_user(request, user, db, is_all=True) return await self.validate_user(request, user, db, is_all=True)
except CustomException: except CustomException:
return Auth(db=db) return Auth(db=db)
@ -53,18 +53,18 @@ class AllUserAuth(AuthValidation):
""" """
async def __call__( async def __call__(
self, self,
request: Request, request: Request,
token: str = Depends(settings.oauth2_scheme), token: str = Depends(settings.oauth2_scheme),
db: AsyncSession = Depends(db_getter) db: AsyncSession = Depends(db_getter)
): ):
""" """
每次调用依赖此类的接口会执行该方法 每次调用依赖此类的接口会执行该方法
""" """
if not settings.OAUTH_ENABLE: if not settings.OAUTH_ENABLE:
return Auth(db=db) return Auth(db=db)
telephone = self.validate_token(request, token) telephone, password = self.validate_token(request, token)
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True) user = await UserDal(db).get_data(telephone=telephone, password=password, v_return_none=True)
return await self.validate_user(request, user, db, is_all=True) return await self.validate_user(request, user, db, is_all=True)
@ -83,23 +83,28 @@ class FullAdminAuth(AuthValidation):
self.permissions = None self.permissions = None
async def __call__( async def __call__(
self, self,
request: Request, request: Request,
token: str = Depends(settings.oauth2_scheme), token: str = Depends(settings.oauth2_scheme),
db: AsyncSession = Depends(db_getter) db: AsyncSession = Depends(db_getter)
) -> Auth: ) -> Auth:
""" """
每次调用依赖此类的接口会执行该方法 每次调用依赖此类的接口会执行该方法
""" """
if not settings.OAUTH_ENABLE: if not settings.OAUTH_ENABLE:
return Auth(db=db) return Auth(db=db)
telephone = self.validate_token(request, token) telephone, password = self.validate_token(request, token)
options = [joinedload(VadminUser.roles).subqueryload(VadminRole.menus), joinedload(VadminUser.depts)] options = [joinedload(VadminUser.roles).subqueryload(VadminRole.menus), joinedload(VadminUser.depts)]
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True, v_options=options, is_staff=True) user = await UserDal(db).get_data(
telephone=telephone,
password=password,
v_return_none=True,
v_options=options,
is_staff=True
)
result = await self.validate_user(request, user, db, is_all=False) result = await self.validate_user(request, user, db, is_all=False)
permissions = self.get_user_permissions(user) permissions = self.get_user_permissions(user)
if permissions != {'*.*.*'} and self.permissions: if permissions != {'*.*.*'} and self.permissions:
if not (self.permissions & permissions): if not (self.permissions & permissions):
raise CustomException(msg="无权限操作", code=status.HTTP_403_FORBIDDEN) raise CustomException(msg="无权限操作", code=status.HTTP_403_FORBIDDEN)
return result return result

View File

@ -39,27 +39,27 @@ from .validation.auth import Auth
from utils.wx.oauth import WXOAuth from utils.wx.oauth import WXOAuth
import jwt import jwt
app = APIRouter() app = APIRouter()
@app.post("/api/login", summary="API 手机号密码登录", description="Swagger API 文档登录认证") @app.post("/api/login", summary="API 手机号密码登录", description="Swagger API 文档登录认证")
async def api_login_for_access_token( async def api_login_for_access_token(
request: Request, request: Request,
data: OAuth2PasswordRequestForm = Depends(), data: OAuth2PasswordRequestForm = Depends(),
db: AsyncSession = Depends(db_getter) db: AsyncSession = Depends(db_getter)
): ):
user = await UserDal(db).get_data(telephone=data.username, v_return_none=True) user = await UserDal(db).get_data(telephone=data.username, v_return_none=True)
error_code = status.HTTP_401_UNAUTHORIZED
if not user: if not user:
raise CustomException(status_code=401, code=401, msg="该手机号不存在") raise CustomException(status_code=error_code, code=error_code, msg="该手机号不存在")
result = VadminUser.verify_password(data.password, user.password) result = VadminUser.verify_password(data.password, user.password)
if not result: if not result:
raise CustomException(status_code=401, code=401, msg="手机号或密码错误") raise CustomException(status_code=error_code, code=error_code, msg="手机号或密码错误")
if not user.is_active: if not user.is_active:
raise CustomException(status_code=401, code=401, msg="此手机号已被冻结") raise CustomException(status_code=error_code, code=error_code, msg="此手机号已被冻结")
elif not user.is_staff: elif not user.is_staff:
raise CustomException(status_code=401, code=401, msg="此手机号无权限") raise CustomException(status_code=error_code, code=error_code, msg="此手机号无权限")
access_token = LoginManage.create_token({"sub": user.telephone}) access_token = LoginManage.create_token({"sub": user.telephone, "password": user.password})
record = LoginForm(platform='2', method='0', telephone=data.username, password=data.password) record = LoginForm(platform='2', method='0', telephone=data.username, password=data.password)
resp = {"access_token": access_token, "token_type": "bearer"} resp = {"access_token": access_token, "token_type": "bearer"}
await VadminLoginRecord.create_login_record(db, record, True, request, resp) await VadminLoginRecord.create_login_record(db, record, True, request, resp)
@ -68,10 +68,10 @@ async def api_login_for_access_token(
@app.post("/login", summary="手机号密码登录", description="员工登录通道限制最多输错次数达到最大值后将is_active=False") @app.post("/login", summary="手机号密码登录", description="员工登录通道限制最多输错次数达到最大值后将is_active=False")
async def login_for_access_token( async def login_for_access_token(
request: Request, request: Request,
data: LoginForm, data: LoginForm,
manage: LoginManage = Depends(), manage: LoginManage = Depends(),
db: AsyncSession = Depends(db_getter) db: AsyncSession = Depends(db_getter)
): ):
try: try:
if data.method == "0": if data.method == "0":
@ -84,9 +84,14 @@ async def login_for_access_token(
if not result.status: if not result.status:
raise ValueError(result.msg) raise ValueError(result.msg)
access_token = LoginManage.create_token({"sub": result.user.telephone, "is_refresh": False}) access_token = LoginManage.create_token(
{"sub": result.user.telephone, "is_refresh": False, "password": result.user.password}
)
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES) expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
refresh_token = LoginManage.create_token({"sub": result.user.telephone, "is_refresh": True}, expires=expires) refresh_token = LoginManage.create_token(
payload={"sub": result.user.telephone, "is_refresh": True, "password": result.user.password},
expires=expires
)
resp = { resp = {
"access_token": access_token, "access_token": access_token,
"refresh_token": refresh_token, "refresh_token": refresh_token,
@ -103,10 +108,10 @@ async def login_for_access_token(
@app.post("/wx/login", summary="微信服务端一键登录", description="员工登录通道") @app.post("/wx/login", summary="微信服务端一键登录", description="员工登录通道")
async def wx_login_for_access_token( async def wx_login_for_access_token(
request: Request, request: Request,
data: WXLoginForm, data: WXLoginForm,
db: AsyncSession = Depends(db_getter), db: AsyncSession = Depends(db_getter),
rd: Redis = Depends(redis_getter) rd: Redis = Depends(redis_getter)
): ):
try: try:
if data.platform != "1" or data.method != "2": if data.platform != "1" or data.method != "2":
@ -129,9 +134,12 @@ async def wx_login_for_access_token(
await UserDal(db).update_login_info(user, request.client.host) await UserDal(db).update_login_info(user, request.client.host)
# 登录成功创建 token # 登录成功创建 token
access_token = LoginManage.create_token({"sub": user.telephone, "is_refresh": False}) access_token = LoginManage.create_token({"sub": user.telephone, "is_refresh": False, "password": user.password})
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES) expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
refresh_token = LoginManage.create_token({"sub": user.telephone, "is_refresh": True}, expires=expires) refresh_token = LoginManage.create_token(
payload={"sub": user.telephone, "is_refresh": True, "password": user.password},
expires=expires
)
resp = { resp = {
"access_token": access_token, "access_token": access_token,
"refresh_token": refresh_token, "refresh_token": refresh_token,
@ -155,16 +163,20 @@ async def token_refresh(refresh: str = Body(..., title="刷新Token")):
payload = jwt.decode(refresh, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) payload = jwt.decode(refresh, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
telephone: str = payload.get("sub") telephone: str = payload.get("sub")
is_refresh: bool = payload.get("is_refresh") is_refresh: bool = payload.get("is_refresh")
if telephone is None or not is_refresh: password: str = payload.get("password")
if not telephone or not is_refresh or not password:
return ErrorResponse("未认证,请您重新登录", code=error_code, status=error_code) return ErrorResponse("未认证,请您重新登录", code=error_code, status=error_code)
except jwt.exceptions.InvalidSignatureError: except jwt.exceptions.InvalidSignatureError:
return ErrorResponse("无效认证,请您重新登录", code=error_code, status=error_code) return ErrorResponse("无效认证,请您重新登录", code=error_code, status=error_code)
except jwt.exceptions.ExpiredSignatureError: except jwt.exceptions.ExpiredSignatureError:
return ErrorResponse("登录已超时,请您重新登录", code=error_code, status=error_code) return ErrorResponse("登录已超时,请您重新登录", code=error_code, status=error_code)
access_token = LoginManage.create_token({"sub": telephone, "is_refresh": False}) access_token = LoginManage.create_token({"sub": telephone, "is_refresh": False, "password": password})
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES) expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
refresh_token = LoginManage.create_token({"sub": telephone, "is_refresh": True}, expires=expires) refresh_token = LoginManage.create_token(
payload={"sub": telephone, "is_refresh": True, "password": password},
expires=expires
)
resp = { resp = {
"access_token": access_token, "access_token": access_token,
"refresh_token": refresh_token, "refresh_token": refresh_token,

View File

@ -57,7 +57,8 @@ class AuthValidation:
telephone: str = payload.get("sub") telephone: str = payload.get("sub")
exp: int = payload.get("exp") exp: int = payload.get("exp")
is_refresh: bool = payload.get("is_refresh") is_refresh: bool = payload.get("is_refresh")
if telephone is None or is_refresh: password: bool = payload.get("password")
if not telephone or is_refresh or not password:
raise CustomException( raise CustomException(
msg="未认证,请您重新登录", msg="未认证,请您重新登录",
code=status.HTTP_403_FORBIDDEN, code=status.HTTP_403_FORBIDDEN,
@ -79,8 +80,8 @@ class AuthValidation:
status_code=status.HTTP_403_FORBIDDEN status_code=status.HTTP_403_FORBIDDEN
) )
except jwt.exceptions.ExpiredSignatureError: except jwt.exceptions.ExpiredSignatureError:
raise CustomException(msg="认证已过期,请您重新登录", code=cls.error_code, status_code=cls.error_code) raise CustomException(msg="认证已失效,请您重新登录", code=cls.error_code, status_code=cls.error_code)
return telephone return telephone, password
@classmethod @classmethod
async def validate_user(cls, request: Request, user: VadminUser, db: AsyncSession, is_all: bool = True) -> Auth: async def validate_user(cls, request: Request, user: VadminUser, db: AsyncSession, is_all: bool = True) -> Auth:

View File

@ -35,7 +35,7 @@ class WXLoginForm(BaseModel):
class LoginResult(BaseModel): class LoginResult(BaseModel):
status: bool | None = False status: bool | None = False
user: schemas.UserOut | None = None user: schemas.UserPasswordOut | None = None
msg: str | None = None msg: str | None = None
class Config: class Config:
@ -87,6 +87,6 @@ class LoginValidation:
await count.delete() await count.delete()
self.result.msg = "OK" self.result.msg = "OK"
self.result.status = True self.result.status = True
self.result.user = schemas.UserSimpleOut.model_validate(user) self.result.user = schemas.UserPasswordOut.model_validate(user)
await crud.UserDal(db).update_login_info(user, request.client.host) await crud.UserDal(db).update_login_info(user, request.client.host)
return self.result return self.result