天天看點

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

功能說明

對使用者表擴充手機号碼字段,允許使用者通過手機号碼與驗證碼的方式進行認證,注冊,重置密碼以及更換手機号。

多因素身份認證 (MFA) 是保護企業 IT 資源通路安全的一種關鍵工具,也是零信任安全模型的核心組成。特别在遠端辦公以及資料洩露事件層出不窮的背景下,越來越多企業都開始考慮實施多因素身份認證政策。

根據近期一項對 IT 專業人員的調查,52.6%的中小型企業已經要求為所有應用程式和系統登入添加多因素身份認證。其中手機推送認證形式(無密碼認證)的二次認證是保證良好使用者體驗的絕佳選擇。

開源IDaaS方舟一賬通ArkID系統内置手機驗證碼認證插件簡單配置、馬上可用,降低實施成本,提升應用內建效率。

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

普通使用者:

  • 在 “注冊” 頁面實作手機号碼+驗證碼使用者注冊
  • 在 “登入” 頁面實作手機号碼+驗證碼使用者登入
  • 在 “更改密碼” 頁面實作手機号碼+驗證碼方式下密碼更改
  • 在 “我的 - 認證管理“ 中添加重置手機号碼的功能

租戶管理者

  • 在”使用者管理 - 使用者清單“中編輯頁面添加手機号碼編輯功能​

前置條件

需配合短信插件使用,系統已預設提供阿裡雲短信插件,如需檢視配置方法請移步阿裡雲短信插件文檔。

配置指南

01 插件租賃

經由左側菜單欄依次進入【租戶管理】->【插件管理】,在插件租賃頁面中找到手機驗證碼認證因素插件卡片,點選租賃

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

02 認證因素配置

經由左側菜單欄依次進入【認證管理】-> 【認證因素】,點選建立按鈕,類型選擇"mobile",短信插件運作時選擇一個合适的已配置的短信插件運作時配置,至此配置完成

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

03 登入界面

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

04 注冊界面

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

05 密碼修改界面

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

06 更換手機号碼界面

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

實作思路

普通使用者:手機号碼+驗證碼使用者注冊/登入/重置密碼:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

普通使用者:重置手機号碼:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

管理者使用者: 更換使用者手機号碼

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

抽象方法實作

load

authenticate

register

reset_password

create_login_page

create_register_page

create_password_page

create_other_page

create_auth_manage_page

check_auth_data

fix_login_page

代碼

extension_root.com_longgui_auth_factor_mobile.MobileAuthFactorExtension (AuthFactorExtension)

手機短信驗證碼認證因素插件

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
class MobileAuthFactorExtension(AuthFactorExtension):
    """手機短信驗證碼認證因素插件
    """
    def load(self):
        """加載插件
        """
        super().load()

        self.create_extension_config_schema()

        self.register_extend_field(UserMobile, "mobile")
        from api.v1.schema.auth import AuthIn
        from api.v1.schema.user import UserCreateIn,UserItemOut,UserUpdateIn,UserListItemOut
        from api.v1.schema.mine import ProfileSchemaOut
        self.register_extend_api(
            AuthIn,
            UserCreateIn, 
            UserItemOut, 
            UserUpdateIn, 
            UserListItemOut,
            mobile=(Optional[str],Field(title=_("電話号碼"))),
            # areacode=(str,Field(title=_("區号")))
        )
        self.register_extend_api(
            ProfileSchemaOut, 
            mobile=(Optional[str],Field(readonly=True))
        )

        # 注冊發送短信接口
        self.url_send_sms_code = self.register_api(
            '/config/{config_id}/send_sms_code/',
            'POST',
            self.send_sms_code,
            tenant_path=True,
            auth=None,
            response=SendSMSCodeOut,
        )
        print(self.url_send_sms_code)

    def authenticate(self, event, **kwargs):
        """ 認證

        通過手機号碼查找使用者并校驗短信驗證碼

        Args:
            event (Event): 事件

        """
        tenant = event.tenant
        request = event.request
        data = request.POST or json.load(request.body)

        mobile = data.get('mobile')
        sms_code = data.get('sms_code')

        user = User.expand_objects.filter(tenant=tenant,mobile=mobile)
        if len(user) > 1:
            logger.error(f'{mobile}在資料庫中比對到多個使用者')
            return self.auth_failed(event, data=self.error(ErrorCode.CONTACT_MANAGER))
        if user:
            user = user[0]
            if check_sms_code(mobile, sms_code):
                user = User.active_objects.get(id=user.get("id"))
                return self.auth_success(user,event)
            else:
                msg = ErrorCode.SMS_CODE_MISMATCH
        else:
            msg = ErrorCode.MOBILE_NOT_EXISTS_ERROR
        return self.auth_failed(event, data=self.error(msg))

    @transaction.atomic()
    def register(self, event, **kwargs):
        """ 注冊使用者

        Args:
            event (Event): 事件
        """
        tenant = event.tenant
        request = event.request
        data = request.POST or json.load(request.body)

        mobile = data.get('mobile')
        sms_code = data.get('sms_code')
        username = data.get('username')

        config = self.get_current_config(event)
        ret, message = self.check_mobile_exists(mobile, tenant)
        if not ret:
            return self.error(message)

        if not check_sms_code(mobile, sms_code):
            return self.error(ErrorCode.SMS_CODE_MISMATCH)

        ret, message = self.check_username_exists(username, tenant)
        if not ret:
            return self.error(message)

        user = User(tenant=tenant)

        user.mobile = mobile
        user.username = username

        user.save()
        tenant.users.add(user)
        tenant.save()

        return user

    def reset_password(self, event, **kwargs):
        """ 重置密碼

        Args:
            event (Event): 事件
        """
        tenant = event.tenant
        request = event.request
        data = request.POST or json.load(request.body)

        mobile = data.get('mobile')
        sms_code = data.get('sms_code')

        password = data.get('password')
        checkpassword = data.get('checkpassword')

        if password != checkpassword:
            return self.error(ErrorCode.PASSWORD_IS_INCONSISTENT)

        if not check_sms_code(mobile, sms_code):
            return self.error(ErrorCode.SMS_CODE_MISMATCH)

        user = User.expand_objects.filter(tenant=tenant,mobile=mobile)

        if len(user) > 1:
            logger.error(f'{mobile}在資料庫中比對到多個使用者')
            return self.error(ErrorCode.CONTACT_MANAGER)
        if user:
            user = user[0]
            user = User.active_objects.get(id=user.get("id"))
            user.password = make_password(password)
            user.save()
            return self.success()

        return self.error(ErrorCode.MOBILE_NOT_EXISTS_ERROR)

    def create_login_page(self, event, config, config_data):
        """ 生成手機驗證碼登入頁面Schema描述

        Args:
            event (Event): 事件
            config (TenantExtensionConfig): 插件運作時配置
        """

        items = [
            {
                "type": "text",
                "name":"mobile",
                "placeholder": "手機号碼",
                "append": {
                    "title": "發送驗證碼",
                    "http": {
                        "url": self.url_send_sms_code,
                        "method": "post",
                        "params": {
                            "mobile": "mobile",
                            "areacode": "86",
                        },
                    },
                    "delay": 60
                }
            },
            {
                "type": "text",
                "name":"sms_code",
                "placeholder": "驗證碼",
            }
        ]
        self.add_page_form(config, self.LOGIN, "手機驗證碼登入", items, config_data)

    def create_register_page(self, event, config, config_data):
        """生成手機驗證碼使用者注冊頁面Schema描述

        因本插件提供重置密碼功能,此處需使用者指定賬号使用者名

        Args:
            event (Event): 事件
            config (TenantExtensionConfig): 插件運作時配置
        """
        items = [
            {
                "type": "text",
                "name": "username",
                "placeholder": "使用者名"
            },
            {
                "type": "text",
                "name":"mobile",
                "placeholder": "手機号碼",
                "append": {
                    "title": "發送驗證碼",
                    "http": {
                        "url": self.url_send_sms_code,
                        "method": "post",
                        "params": {
                            "mobile": "mobile",
                            "areacode": "86",
                        },
                    },
                    "delay": 60
                }
            },
            {
                "type": "text",
                "name":"sms_code",
                "placeholder": "驗證碼"
            }
        ]
        self.add_page_form(config, self.REGISTER, "手機驗證碼注冊", items, config_data)

    def create_password_page(self, event, config, config_data):
        """生成重置密碼頁面Schema描述

        通過手機驗證碼重置密碼時需提供手機号碼以及對應驗證碼,同時此處添加新密碼确認機制

        注意:重置密碼功能需要啟用使用者名密碼認證插件以提供完整支援

        Args:
            event (Event): 事件
            config (TenantExtensionConfig): 插件運作時配置
        """
        items = [
            {
                "type": "text",
                "name":"mobile",
                "placeholder": "手機号碼",
                "append": {
                    "title": "發送驗證碼",
                    "http": {
                        "url": self.url_send_sms_code,
                        "method": "post",
                        "params": {
                            "mobile": "mobile",
                            "areacode": "86",
                        },
                    },
                }
            },
            {
                "type": "text",
                "name":"sms_code",
                "placeholder": "驗證碼"
            },
            {
                "type": "password",
                "name":"password",
                "placeholder": "密碼"
            },
            {
                "type": "password",
                "name":"checkpassword",
                "placeholder": "密碼确認"
            }
        ]
        self.add_page_form(config, self.RESET_PASSWORD, "手機驗證碼重置密碼", items, config_data)

    def create_other_page(self, event, config, config_data):
        """建立其他頁面(本插件無相關頁面)

        Args:
            event (Event): 事件
            config (TenantExtensionConfig): 插件運作時配置
        """
        pass

    def check_mobile_exists(self, mobile, tenant):
        """檢查電話号碼是否已存在

        Args:
            mobile (str): 手機号
            tenant (Tenant): 租戶

        Returns:
            (bool,ErrorCode): mobile是否存在以及對應錯誤
        """
        if not mobile:
            return False, ErrorCode.MOBILE_EMPTY

        if User.expand_objects.filter(tenant=tenant,mobile=mobile).count():
            return False, ErrorCode.MOBILE_EXISTS_ERROR
        return True, None

    def check_username_exists(self,username,tenant):
        """檢查使用者名是否已存在

        Args:
            username (str): 使用者名
            tenant (Tenant): 租戶

        Returns:
            (bool,ErrorCode): username是否存在以及對應錯誤
        """
        # 檢查username是否為空
        if not username:
            return False, ErrorCode.USERNAME_EMPTY
        # 檢查username是否已存在
        if User.expand_objects.filter(tenant=tenant,username=username).count():
            return False, ErrorCode.USERNAME_EXISTS_ERROR

        return True, None

    def check_auth_data(self, event, **kwargs):
        pass

    def fix_login_page(self, event, **kwargs):
        pass

    def create_auth_manage_page(self):
        """ 建立“我的-認證管理”中的更換手機号碼頁面
        """
        _pages = []

        mine_mobile_path = self.register_api(
            "/mine_mobile/",
            "GET",
            self.mine_mobile,
            tenant_path=True,
            auth=GlobalAuth(),
            response=MineMobileOut
        )

        upodate_mine_mobile_path = self.register_api(
            "/mine_mobile/",
            'POST',
            self.update_mine_mobile,
            tenant_path=True,
            auth=GlobalAuth(),
            response=UpdateMineMobileOut
        )

        name = '更改手機号碼'

        page = pages.FormPage(name=name)
        page.create_actions(
            init_action=actions.DirectAction(
                path=mine_mobile_path,
                method=actions.FrontActionMethod.GET,
            ),
            global_actions={
                'confirm': actions.ConfirmAction(
                    path=upodate_mine_mobile_path
                ),
            }
        )

        _pages.append(page)
        return _pages

    def create_extension_config_schema(self):
        """建立插件運作時配置schema描述
        """
        select_sms_page = pages.TablePage(select=True,name=_("指定短信插件運作時"))

        self.register_front_pages(select_sms_page)

        select_sms_page.create_actions(
            init_action=actions.DirectAction(
                path='/api/v1/tenants/{tenant_id}/config_select/?extension__type=sms',
                method=actions.FrontActionMethod.GET
            )
        )

        MobileAuthFactorSchema = create_extension_schema(
            'MobileAuthFactorSchema',
            __file__, 
            [
                (
                    'sms_config', 
                    MobileAuthFactorConfigSchema, 
                    Field(
                        title=_('sms extension config', '短信插件運作時'),
                        page=select_sms_page.tag,
                    ),
                ),
                (
                    'code_length', 
                    int, 
                    Field(
                        title=_('code_length', '驗證碼長度'),
                        default=6
                    )
                ),
                (
                    'expired', 
                    Optional[int],
                    Field(
                        title=_('expired', '有效期/分鐘'),
                        default=10,
                    )
                ),
            ],
            BaseAuthFactorSchema,
        )
        self.register_auth_factor_schema(MobileAuthFactorSchema, 'mobile')

    @operation(SendSMSCodeOut)
    def send_sms_code(self,request,tenant_id,config_id:str,data:SendSMSCodeIn):
        """發送短信驗證碼
        """
        tenant = request.tenant
        mobile = data.mobile
        config = self.get_config_by_id(config_id)
        if not config:
            return self.error(ErrorCode.CONFIG_IS_NOT_EXISTS)

        if not mobile or mobile=="mobile":
            return self.error(ErrorCode.MOBILE_EMPTY)

        code = create_sms_code(tenant,mobile,config.config.get('code_length',6),config.config.get("expired",10)*60)


        responses = dispatch_event(
            Event(
                tag=SEND_SMS,
                tenant=tenant,
                request=request,
                data={
                    "config_id":config.config["sms_config"]["id"],
                    "mobile":data.mobile,
                    "code": code,
                    "areacode": data.areacode,
                    "username": request.user.username if request.user else ""
                },
                packages=config.config["sms_config"]["package"]

            )
        )

        if not responses:
            return self.error(ErrorCode.SMS_EXTENSION_NOT_EXISTS)
        useless, (data, extension) = responses[0]
        if data:
            return self.success()
        else:
            return self.error(ErrorCode.SMS_SEND_FAILED)

    @operation(UpdateMineMobileOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN, NORMAL_USER])
    def update_mine_mobile(self, request, tenant_id: str,data:UpdateMineMobileIn):
        """ 普通使用者:更新手機号碼
        """
        mobile = data.mobile
        ret, message = self.check_mobile_exists(mobile, request.tenant)
        if not ret:
            return self.error(message)

        if not check_sms_code(request.tenant,mobile,data.code):
            return self.error(ErrorCode.SMS_CODE_MISMATCH)

        user = request.user
        user.mobile=data.mobile
        user.save()

        return self.success()

    @operation(MineMobileOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN, NORMAL_USER])
    def mine_mobile(self,request,tenant_id: str):
        user = request.user
        user_expand = User.expand_objects.filter(id=user.id).first()

        config = self.get_tenant_configs(request.tenant).first()

        if not config:
            return self.error(
                ErrorCode.CONFIG_IS_NOT_EXISTS
            )

        return self.success(
            data={
                "current_mobile": user_expand.get("mobile",None),
                "mobile": "",
                "code": "",
                "config_id": config.id.hex,
            },
        )      

authenticate(self, event, **kwargs)

認證

通過手機号碼查找使用者并校驗短信驗證碼

Parameters:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
def authenticate(self, event, **kwargs):
    """ 認證

    通過手機号碼查找使用者并校驗短信驗證碼

    Args:
        event (Event): 事件

    """
    tenant = event.tenant
    request = event.request
    data = request.POST or json.load(request.body)

    mobile = data.get('mobile')
    sms_code = data.get('sms_code')

    user = User.expand_objects.filter(tenant=tenant,mobile=mobile)
    if len(user) > 1:
        logger.error(f'{mobile}在資料庫中比對到多個使用者')
        return self.auth_failed(event, data=self.error(ErrorCode.CONTACT_MANAGER))
    if user:
        user = user[0]
        if check_sms_code(mobile, sms_code):
            user = User.active_objects.get(id=user.get("id"))
            return self.auth_success(user,event)
        else:
            msg = ErrorCode.SMS_CODE_MISMATCH
    else:
        msg = ErrorCode.MOBILE_NOT_EXISTS_ERROR
    return self.auth_failed(event, data=self.error(msg))
th_data(self, event, **kwargs):
    pass      

check_auth_data(self, event, **kwargs)

響應檢查認證憑證事件

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

Returns:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py

def check_auth_data(self, event, **kwargs):
    pass      

check_username_exists(self, username, tenant)

檢查使用者名是否已存在

Parameters:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

Returns:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py

def check_mobile_exists(self, mobile, tenant):
    """檢查電話号碼是否已存在

    Args:
        mobile (str): 手機号
        tenant (Tenant): 租戶

    Returns:
        (bool,ErrorCode): mobile是否存在以及對應錯誤
    """
    if not mobile:
        return False, ErrorCode.MOBILE_EMPTY

    if User.expand_objects.filter(tenant=tenant,mobile=mobile).count():
        return False, ErrorCode.MOBILE_EXISTS_ERROR
    return True, None
      

create_auth_manage_page(self)

建立“我的-認證管理”中的更換手機号碼頁面

 Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
def check_username_exists(self,username,tenant):
    """檢查使用者名是否已存在

    Args:
        username (str): 使用者名
        tenant (Tenant): 租戶

    Returns:
        (bool,ErrorCode): username是否存在以及對應錯誤
    """
    # 檢查username是否為空
    if not username:
        return False, ErrorCode.USERNAME_EMPTY
    # 檢查username是否已存在
    if User.expand_objects.filter(tenant=tenant,username=username).count():
        return False, ErrorCode.USERNAME_EXISTS_ERROR

    return True, None
      

create_extension_config_schema(self)

建立插件運作時配置schema描述

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py

def create_extension_config_schema(self):
    """建立插件運作時配置schema描述
    """
    select_sms_page = pages.TablePage(select=True,name=_("指定短信插件運作時"))

    self.register_front_pages(select_sms_page)

    select_sms_page.create_actions(
        init_action=actions.DirectAction(
            path='/api/v1/tenants/{tenant_id}/config_select/?extension__type=sms',
            method=actions.FrontActionMethod.GET
        )
    )

    MobileAuthFactorSchema = create_extension_schema(
        'MobileAuthFactorSchema',
        __file__, 
        [
            (
                'sms_config', 
                MobileAuthFactorConfigSchema, 
                Field(
                    title=_('sms extension config', '短信插件運作時'),
                    page=select_sms_page.tag,
                ),
            ),
            (
                'code_length', 
                int, 
                Field(
                    title=_('code_length', '驗證碼長度'),
                    default=6
                )
            ),
            (
                'expired', 
                Optional[int],
                Field(
                    title=_('expired', '有效期/分鐘'),
                    default=10,
                )
            ),
        ],
        BaseAuthFactorSchema,
    )
    self.register_auth_factor_schema(MobileAuthFactorSchema, 'mobile')
      

create_login_page(self, event, config, config_data)

生成手機驗證碼登入頁面Schema描述

Parameters:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
def create_login_page(self, event, config, config_data):
    """ 生成手機驗證碼登入頁面Schema描述

    Args:
        event (Event): 事件
        config (TenantExtensionConfig): 插件運作時配置
    """

    items = [
        {
            "type": "text",
            "name":"mobile",
            "placeholder": "手機号碼",
            "append": {
                "title": "發送驗證碼",
                "http": {
                    "url": self.url_send_sms_code,
                    "method": "post",
                    "params": {
                        "mobile": "mobile",
                        "areacode": "86",
                    },
                },
                "delay": 60
            }
        },
        {
            "type": "text",
            "name":"sms_code",
            "placeholder": "驗證碼",
        }
    ]
    self.add_page_form(config, self.LOGIN, "手機驗證碼登入", items, config_data)
      

create_other_page(self, event, config, config_data)

建立其他頁面(本插件無相關頁面)

Parameters:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py

def create_other_page(self, event, config, config_data):
    """建立其他頁面(本插件無相關頁面)

    Args:
        event (Event): 事件
        config (TenantExtensionConfig): 插件運作時配置
    """
    pass      

create_password_page(self, event, config, config_data) 

生成重置密碼頁面Schema描述

通過手機驗證碼重置密碼時需提供手機号碼以及對應驗證碼,同時此處添加新密碼确認機制

注意:重置密碼功能需要啟用使用者名密碼認證插件以提供完整支援

Parameters:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py

def create_password_page(self, event, config, config_data):
    """生成重置密碼頁面Schema描述

    通過手機驗證碼重置密碼時需提供手機号碼以及對應驗證碼,同時此處添加新密碼确認機制

    注意:重置密碼功能需要啟用使用者名密碼認證插件以提供完整支援

    Args:
        event (Event): 事件
        config (TenantExtensionConfig): 插件運作時配置
    """
    items = [
        {
            "type": "text",
            "name":"mobile",
            "placeholder": "手機号碼",
            "append": {
                "title": "發送驗證碼",
                "http": {
                    "url": self.url_send_sms_code,
                    "method": "post",
                    "params": {
                        "mobile": "mobile",
                        "areacode": "86",
                    },
                },
            }
        },
        {
            "type": "text",
            "name":"sms_code",
            "placeholder": "驗證碼"
        },
        {
            "type": "password",
            "name":"password",
            "placeholder": "密碼"
        },
        {
            "type": "password",
            "name":"checkpassword",
            "placeholder": "密碼确認"
        }
    ]
    self.add_page_form(config, self.RESET_PASSWORD, "手機驗證碼重置密碼", items, config_data)
      

create_register_page(self, event, config, config_data)

生成手機驗證碼使用者注冊頁面Schema描述

因本插件提供重置密碼功能,此處需使用者指定賬号使用者名

Parameters:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py

def create_register_page(self, event, config, config_data):
    """生成手機驗證碼使用者注冊頁面Schema描述

    因本插件提供重置密碼功能,此處需使用者指定賬号使用者名

    Args:
        event (Event): 事件
        config (TenantExtensionConfig): 插件運作時配置
    """
    items = [
        {
            "type": "text",
            "name": "username",
            "placeholder": "使用者名"
        },
        {
            "type": "text",
            "name":"mobile",
            "placeholder": "手機号碼",
            "append": {
                "title": "發送驗證碼",
                "http": {
                    "url": self.url_send_sms_code,
                    "method": "post",
                    "params": {
                        "mobile": "mobile",
                        "areacode": "86",
                    },
                },
                "delay": 60
            }
        },
        {
            "type": "text",
            "name":"sms_code",
            "placeholder": "驗證碼"
        }
    ]
    self.add_page_form(config, self.REGISTER, "手機驗證碼注冊", items, config_data)
      

fix_login_page(self, event, **kwargs)

向login_pages填入認證元素

Parameters:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
def fix_login_page(self, event, **kwargs):
    pass      

load(self)

加載插件

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
def load(self):
    """加載插件
    """
    super().load()

    self.create_extension_config_schema()

    self.register_extend_field(UserMobile, "mobile")
    from api.v1.schema.auth import AuthIn
    from api.v1.schema.user import UserCreateIn,UserItemOut,UserUpdateIn,UserListItemOut
    from api.v1.schema.mine import ProfileSchemaOut
    self.register_extend_api(
        AuthIn,
        UserCreateIn, 
        UserItemOut, 
        UserUpdateIn, 
        UserListItemOut,
        mobile=(Optional[str],Field(title=_("電話号碼"))),
        # areacode=(str,Field(title=_("區号")))
    )
    self.register_extend_api(
        ProfileSchemaOut, 
        mobile=(Optional[str],Field(readonly=True))
    )

    # 注冊發送短信接口
    self.url_send_sms_code = self.register_api(
        '/config/{config_id}/send_sms_code/',
        'POST',
        self.send_sms_code,
        tenant_path=True,
        auth=None,
        response=SendSMSCodeOut,
    )
    print(self.url_send_sms_code)
      

register(self, event, **kwargs)

注冊使用者

Parameters:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程
Source code in extension_root/com_longgui_auth_factor_mobile/init.py
@transaction.atomic()
def register(self, event, **kwargs):
    """ 注冊使用者

    Args:
        event (Event): 事件
    """
    tenant = event.tenant
    request = event.request
    data = request.POST or json.load(request.body)

    mobile = data.get('mobile')
    sms_code = data.get('sms_code')
    username = data.get('username')

    config = self.get_current_config(event)
    ret, message = self.check_mobile_exists(mobile, tenant)
    if not ret:
        return self.error(message)

    if not check_sms_code(mobile, sms_code):
        return self.error(ErrorCode.SMS_CODE_MISMATCH)

    ret, message = self.check_username_exists(username, tenant)
    if not ret:
        return self.error(message)

    user = User(tenant=tenant)

    user.mobile = mobile
    user.username = username

    user.save()
    tenant.users.add(user)
    tenant.save()

    return user      

reset_password(self, event, **kwargs)

重置密碼

Parameters:

多因素身份認證 (MFA) 插件:手機驗證碼認證因素配置流程

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
def reset_password(self, event, **kwargs):
    """ 重置密碼

    Args:
        event (Event): 事件
    """
    tenant = event.tenant
    request = event.request
    data = request.POST or json.load(request.body)

    mobile = data.get('mobile')
    sms_code = data.get('sms_code')

    password = data.get('password')
    checkpassword = data.get('checkpassword')

    if password != checkpassword:
        return self.error(ErrorCode.PASSWORD_IS_INCONSISTENT)

    if not check_sms_code(mobile, sms_code):
        return self.error(ErrorCode.SMS_CODE_MISMATCH)

    user = User.expand_objects.filter(tenant=tenant,mobile=mobile)

    if len(user) > 1:
        logger.error(f'{mobile}在資料庫中比對到多個使用者')
        return self.error(ErrorCode.CONTACT_MANAGER)
    if user:
        user = user[0]
        user = User.active_objects.get(id=user.get("id"))
        user.password = make_password(password)
        user.save()
        return self.success()

    return self.error(ErrorCode.MOBILE_NOT_EXISTS_ERROR)
      

send_sms_code(self, request, tenant_id, config_id, data)

發送短信驗證碼

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
@operation(SendSMSCodeOut)
def send_sms_code(self,request,tenant_id,config_id:str,data:SendSMSCodeIn):
    """發送短信驗證碼
    """
    tenant = request.tenant
    mobile = data.mobile
    config = self.get_config_by_id(config_id)
    if not config:
        return self.error(ErrorCode.CONFIG_IS_NOT_EXISTS)

    if not mobile or mobile=="mobile":
        return self.error(ErrorCode.MOBILE_EMPTY)

    code = create_sms_code(tenant,mobile,config.config.get('code_length',6),config.config.get("expired",10)*60)


    responses = dispatch_event(
        Event(
            tag=SEND_SMS,
            tenant=tenant,
            request=request,
            data={
                "config_id":config.config["sms_config"]["id"],
                "mobile":data.mobile,
                "code": code,
                "areacode": data.areacode,
                "username": request.user.username if request.user else ""
            },
            packages=config.config["sms_config"]["package"]

        )
    )

    if not responses:
        return self.error(ErrorCode.SMS_EXTENSION_NOT_EXISTS)
    useless, (data, extension) = responses[0]
    if data:
        return self.success()
    else:
        return self.error(ErrorCode.SMS_SEND_FAILED)
      

update_mine_mobile(self, request, tenant_id, data)

普通使用者:更新手機号碼

Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
@operation(UpdateMineMobileOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN, NORMAL_USER])
def update_mine_mobile(self, request, tenant_id: str,data:UpdateMineMobileIn):
    """ 普通使用者:更新手機号碼
    """
    mobile = data.mobile
    ret, message = self.check_mobile_exists(mobile, request.tenant)
    if not ret:
        return self.error(message)

    if not check_sms_code(request.tenant,mobile,data.code):
        return self.error(ErrorCode.SMS_CODE_MISMATCH)

    user = request.user
    user.mobile=data.mobile
    user.save()

    return self.success()
      

extension_root.com_longgui_auth_factor_mobile.sms

check_sms_code(tenant, mobile, code)

驗證短信驗證碼

Source code in extension_root/com_longgui_auth_factor_mobile/sms.py
def check_sms_code(tenant,mobile, code):
    """ 驗證短信驗證碼
    """
    c_code = cache.get(tenant,gen_sms_code_key(mobile))
    return c_code == code      

create_sms_code(tenant, phone_number, uth_code_length=6, expired=None)

生成短信驗證碼并存儲至緩存

def create_sms_code(tenant,phone_number,uth_code_length=6,expired:Optional[int]=None):
    """生成短信驗證碼并存儲至緩存
    """
    code = gen_sms_code(uth_code_length)
    cache.set(tenant,gen_sms_code_key(phone_number), code, expired=expired)
    return code      
def gen_sms_code_key(mobile):
    '''
    生成短信驗證碼的key
    '''
    return f'sms:{mobile}'      

繼續閱讀