Git Product home page Git Product logo

Comments (11)

neronkl avatar neronkl commented on June 12, 2024

开发方案:20220531-20220601
开发:20220602,20220606-20220608

from bk-user.

wklken avatar wklken commented on June 12, 2024
  1. 确定这个会成为产品的默认功能? 即, 登录支持两步验证? 是的话, 产品设计和交互在哪里? 是否经过评审? @Xmandon
  2. 当前登录链路: 登录页面->login->login check->bk_user api; 如果改造, 需要考虑的是, bkuser api校验密码通过后, 发现需要两步验证, 那么需要让login服务知道, login服务获取用户的相关信息(手机号/邮箱), 负责完成两步校验`逻辑;
  3. bklogin和bkuser是两个项目, 严格做职责分离, bkuser本身并不负责做两步验证
  4. 方案: bkuser相关, 考虑如何让用户配置开启两步验证/ 以及, 绑定手机号/邮箱等逻辑
  5. 方案: login相关, 考虑如何完成两步验证流程; 并且, 这里是有自定义登录逻辑的
  6. 建议: 配置上, 做成开关, 默认不开启的话, 代码行为跟现在没有任何区别;

from bk-user.

abbyWJM avatar abbyWJM commented on June 12, 2024

原型:https://v94edr.axshare.com

from bk-user.

neronkl avatar neronkl commented on June 12, 2024

开发排期:6.15-6.17 6.20-6.22

from bk-user.

wklken avatar wklken commented on June 12, 2024
  1. global_settings, 参考 user_settings_settingmeta + user_settings_setting 做设计, 需要有严格的字段名/类型/是否必须等校验
  2. global_settings, range to scope; type 过于泛, 应该是发送方式send_method, limit_time不够精确, 应该是expire_seconds
  3. global_settings, 需要考虑除了双因子, 未来可能会有其他类型, 所以 考虑两层配置, authentication_type = none/two_factor |
  4. 邮件和短信模板直接配置到global_settings中, 跟category/user_settings没关系
  5. 职责分离:
    • 3.1 bk_user_api 只提供如下接口:
      • 3.1.1 查询 global_settings配置
      • 3.1.2 发送验证码
      • 3.1.3 校验验证码
      • 3.1.4 查询用户信息: 邮箱/手机号/是否绑定? (应该已有, 确认字段)
      • 3.1.5 更新用户信息: 进行邮箱/手机号绑定 (应该已有, 确认字段)
    • 3.2 login增加two_factor逻辑, 调用接口, 执行操作; 注意这里是否绑定的逻辑在login处理的
    • 3.3 验证码发送/校验逻辑, 需要区分渠道 => 未来在非login的页面, 也会加入相关逻辑, 需要避免冲突 重要
    • 3.4 验证码发送/校验失败, 需要返回详情到页面
  6. 查新画流程图, 重新设计表结构(注意字段), 按照login/bk-user-api重新设计接口列表(api/v2, 需要接入esb),
  7. 接口需要有详细的文档说明: 有详细的url/入参/状态码/返回值说明

from bk-user.

neronkl avatar neronkl commented on June 12, 2024

@wklken
image

class SettingMeta(TimestampedModel):
    """配置项元信息"""

    global_key = models.CharField("全局配置键", max_length=64)
    value_type = model.charField("全局配置内容字段类型", max_length=64)
    value = jsonfield.JSONField("全局配置内容", default={})
    required = models.BooleanField("是否必要", default=False) 

    class Meta:
        verbose_name = "全局配置信息表"
        verbose_name_plural = "全局配置信息表"
  1. 想问一个问题,参考user_settings_meta和user_setting进行模型设计,因子校验的配置统一使用一个key管理,还是分别生成key进行管理???
  2. 接口的说明文档会在功能开发的时候进行编写.

from bk-user.

wklken avatar wklken commented on June 12, 2024
  1. user_settings_meta 定义字段namespace/类型/default/是否必填等等, user_setting存真正的key-value值; 只有这样, 才是一个严格schema; global_settings未来还会加入其他的, 全局性质的开关; 所以严格参照user_setttings设计就好(去掉category相关字段)
  2. 提供通用的接口, 查询global_settings某个namespace下的所有key-value
  3. 文档在编码前先写完, method/url/参数/返回值+大体逻辑的描述

from bk-user.

neronkl avatar neronkl commented on June 12, 2024

双因子认证方案设计

背景:

二级校验,增强用户安全性

bk_user

方案设计

1. 配置

1.1 全局配置

由于涉及到全局配置,区别于字段的设置,属于一种系统级别上的设置,所以需要新增一张表global_settings来承载系统设置,表设计和user_settings_meta无差,除了某些字段需排除

class GlobalSettingModel(TimestampedModel):
    global_key = models.CharField("全局配置键", max_length=64)
    value = jsonfield.JSONField("全局配置内容", default={})
    enabled = models.BooleanField(default=True)
    required = models.BooleanField("是否必要", default=True)
    namespace = models.CharField(
        "命名空间",
        max_length=32,
        db_index=True,
        choices=GlobalSettingsEnableNamespaces.get_choices(),
        default=GlobalSettingsEnableNamespaces.GENERAL.value,
    )

    class Meta:
        verbose_name = "全局配置信息表"
        verbose_name_plural = "全局配置信息表"

class GlobalSettingsEnableNamespaces(AutoLowerEnum):
    GENERAL = auto()
    SECONDARY_VERIFICATION = auto()

    _choices_labels = (
        (GENERAL, "通用"),
        (TWO_FACTOR_VERIFICATION, "双因子认证"),
    )

字段初始化1:key=authentication_type, value=two_factor_verification

字段初始化2 如下

字段 字段类型 释义 默认值 namespace
send_method string 发送方式(mail/phone) mail two_factor_verification
expire_seconds int 验证码过期时间,默认5分钟。单位:秒 300 two_factor_verification
scope json 应用范围 {"categories":[],"departments": [],"profiles": []} two_factor_verification
mail_config json 邮件模板 下文说明 two_factor_verification
sms_config json 短信模板 下文说明 two_factor_verification

range协议设置

{
	"categories":[],
	"departments": [],
	"profiles": []
}

协议说明

字段 字段类型 释义 默认值
categories list 用户目录 []
departments list 组织 []
profiles list 人员 []

三者为空为空的时候,应用范围为全体用户。

sms_config = {
    "sender": "蓝鲸智云",
    "content": "【蓝鲸智云】您的验证码为{captcha},请在{expire_seconds}分钟内输入验证码。如非本人操作请忽略",
    "content_html": '<p>【蓝鲸智云】您的验证码为{captcha},请在{expire_seconds}分钟内输入验证码。如非本人操作请忽略</p>',
}
mail_config = {
    "title": "鲸智云-登录申请",
    "sender": "蓝鲸智云",
    "content": "您正在申请登录平台 "
               "验证码为:{captcha}"
               "为保证您账号的安全性,该验证码有效期为{expire_seconds}分钟。如非本人操作,请忽略此邮件",
}

2. 单个用户因子设置

​ 用户的因子设置,有个大前提条件是双因子全局化配置:用户信息中的的双因子认证状态会根据双因子全局化配置而设定展示。

3. 新增接口

3.1 Api层--(esb注册)全局配置视图

  1. 查询全局配置接口, 返回所有全局配置(参考user_settings)
url: /api/v2/global_settings/
desc: 获取全局配置
method: Get
response:{
    "result": true,
    "code": 0,
    "message": "success",
    "data": [
        {
            "global_key": "",
            "value": "",
            "namespace": "",
            "enabled": true
        },
        ....
    ]
}

返回参数说明

参数命 说明 字段类型
global_key 配置项key string
value 配置项设置值 string
value_type 配置项设置值的类型 string
namespace 配置项来源 string
enabled 开关 bool
  1. 更新全局配置(参考user_settings)
url: /api/v2/global_settings/
desc: 更新全局配置
method: put
request_data: {
	[
		{
			global_key: "",
			value: "",
        },
        ...
	]
}
response:{
    "result": true,
    "code": 0,
    "message": "success",
    "data": [
        {
            "global_key": "",
            "value": "",
            "namespace": "",
            "enabled": true
        },
        ....
    ]
}

请求参数说明

参数命 说明 字段类型
global_key 配置项key string
value 配置项设置值 string

返回参数说明

参数命 说明 字段类型
global_key 配置项key string
value 配置项设置值 string
value_type 配置项设置值的类型 string
namespace 配置项来源 string
enabled 开关 bool

3.2 Api层--(esb注册)验证码发送接口

​ login层通过该接口进行验证码的发送。该接口会生成8位验证码,根据设置进行发送发验证。根据username生成一个token。并以token为key,保存username,captcha到redis中,设置时效性。该接口会返回token给login层。 当请求来源时绑定发送页面的时候,had_bind=False,且必须根据发送方式,选择mail或者phone填写

url: /api/v2/profile/send_captcha/
desc: 发送验证码
method: Get
query_params: {
	username: "", 
	mail: "",  
	phone: ""  
	had_bind, "" 
}
response:
{
    "result": true,
    "code": 0,
    "message": "success",
    "data": {
            "token": "",
        },
}

请求参数说明

参数命 是否必填 说明 字段类型
username 用户名 string
had_bind 请求页面来源 bool
mail 当hand_bind=True,且发送方式为mail时,必填 string
phone 当hand_bind=True,且发送方式为phone时必填 string

返回参数说明

参数命 说明 字段类型
token 后续携带进行校验,具有时效性 string

3.3 Api层--(esb注册)验证码校验接口

​ login层调用,进行验证码校验。login层携带发送验证码接口返回的token以及captcha到此接口进行校验。校验成功后删除redis中key为token的值,并返回该用户的profile信息

url: /api/v2/profile/verify_captcha/
desc: 发送验证码
method: Post
request_body: {
	"username": ""
	"token": "",
	"captcha": ""
}
response:
{
    "result": true,
    "code": 0,
    "message": "success",
    "data": {
            
        },
}

请求参数说明

参数命 是否必填 说明 字段类型
username 用户命 string
token string
captcha 验证码 string

返回参数说明

返回用户相关信息,不做详细说明

bk_login

在bk_login中不管是走默认登录,还是自定义登录,账号密码验证成功后会调用login_success_response,对用户信息做处理。因此以login_success_response作为切入口。def login_success_response新增一个默认参数secondary_verify=False,进行是蓝鲸官方登录模式的时候该参数置为True,当自定义登录模式需要开启该参数变为True即可开启双因子认证

方案解析:

image

  1. 设置用户登录前,发送请求到api后台返回因子状态以及因子验证方式;当两种因子状态没有开启或者全局因子开启,人员因子关闭,直接返回的因子状态为0。

    当需要进行因子验证的时候,根据因子的验证方式,判断用户的邮箱/手机号是否存在从而跳转对应页面中并发返回验证方式和username

  2. 跳转到对应页面时候,调用login的验证码发送接口。login会调用api的验证发送接口,发送接口返回token。

    在未绑定页面,需提供mail/phone,详细请看接口设计

{
	token: ""
}
  1. 在进行调用因子验证的接口的时候,会判断token是否过期,校验验证码。校验成功会返回用户信息。校验失败则返回相关信息。

  2. 若前端是在绑定页面进行的发送验证码,在进行跳转前,需发送用户绑定信息的请求,绑定mail/phone。

4. 新增接口,改造函数

4.1 login_success_response函数改造

增加默认参数secondary_verify=False,兼容原本已存在的自定义登录,在蓝鲸官方登录的时候后默认开启

def login_success_response(request, user_or_form, redirect_to, app_id, secondary_verify=False):
    """
    用户验证成功后,登录处理
    """
    # 判读是form还是user
    if isinstance(user_or_form, AuthenticationForm):
        user = user_or_form.get_user()
        username = user.username
        # username = user_or_form.cleaned_data.get('username', '')
    else:
        user = user_or_form
        username = user.username

    # 二级验证
    if secondary_verify:
        redirect_secondary_authenticate(user, redirect_to)
     。。。。
    
def redirect_secondary_authenticate(user, original_redirect_to):
    nd_auth, authenticate_type = usermgr_api.get_nd_authenticate_settings()
    if nd_auth:
        response_data = {
            "authenticate_type": authenticate_type,
            "username": user.username,
            "authenticate_value": "",
            "original_redirect_to": original_redirect_to
        }

        if getattr(user, authenticate_type):
            redirect_to = "captcha.html"
            response_data["authenticate_value"] = user.authenticate_type
            return HttpResponseRedirect(redirect_to=redirect_to, content=response_data)
        else:
            redirect_to = "captcha_bind.html"
            return HttpResponseRedirect(redirect_to=redirect_to, content=response_data)

当需要进行因子验证的时候,根据因子的验证方式,判断用户的邮箱/手机号是否存在从而跳转对应页面中并返回验证方式和username

4.2 login层--新增验证码发送

调用api层的验证码发送接口。当请求来源时绑定发送页面的时候,had_bind=False,且必须根据发送方式,选择mail或者phone填写

url:  /send_captcha/
desc: 发送验证码
method: Post
query_params: {
	username: "", 
	mail: "",  
	phone: ""  
	had_bind, "" 
}
response:
{
    "result": true,
    "code": 0,
    "message": "success",
    "data": {
            "token": "",
        },
}

请求参数说明

参数命 是否必填 说明 字段类型
username 用户名 string
had_bind 请求页面来源 bool
mail 当hand_bind=False,且发送方式为mail时,必填 string
phone 当hand_bind=False,且发送方式为phone时必填 string

返回参数说明

参数命 说明 字段类型
token 后续携带进行校验,具有时效性 string

4.3 login层--新增因子验证接口

调用api的验证接口, 当请求来源为绑定发送页面,验证成功后对用户进行绑定。然后进行跳转。

url: /verify_captcha/
desc: 发送验证码
method: Post
request_body: {
	"token": "",
	"captcha": "",
	"username": "",
	"redirect_to": "",
	"send_method": "",
	"autheniated_value": "",
}
response: 设置登录态,进行跳转

请求参数说明

参数命 是否必填 说明 字段类型
token string
captcha 验证码 string
username 用户名 string
redirect_to 登录来源地址 string
had_bind 是否已经绑定 bool
send_method had_bind=False,必填。发送验证码方式 string
autheniated_value had_bind=False,必填。地址 string

from bk-user.

wklken avatar wklken commented on June 12, 2024

前置:

  1. 用户管理profile中, 邮箱是email, 所有其他地方, 统一使用email, 不要用mail
  2. 绑定和验证码, 是两套逻辑

1. 表结构

global_settings, 参考 user_settings_settingmeta + user_settings_setting 做设计, 需要有严格的字段名/类型/是否必须等校验

CREATE TABLE `global_settings_settingmeta` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `create_time` datetime(6) NOT NULL,
  `update_time` datetime(6) NOT NULL,
  `key` varchar(64) NOT NULL,
  `enabled` tinyint(1) NOT NULL,
  `example` longtext NOT NULL,
  `default` longtext NOT NULL,
  `choices` longtext NOT NULL,
  `required` tinyint(1) NOT NULL,
  `namespace` varchar(32) NOT NULL,
  `region` varchar(32) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_settings_settingmet_key_namespace_category_t_42ca50ce_uniq` (`key`,`namespace`),
  KEY `user_settings_settingmeta_namespace_82a8a263` (`namespace`)
) ENGINE=InnoDB AUTO_INCREMENT=69 DEFAULT CHARSET=utf8

CREATE TABLE `global_settings_setting` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `create_time` datetime(6) NOT NULL,
  `update_time` datetime(6) NOT NULL,
  `value` longtext NOT NULL,
  `enabled` tinyint(1) NOT NULL,
  `meta_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `global_settings_setting_meta_id_bc909709_uniq` (`meta_id`),
  KEY `global_settings_settin_meta_id_8b0b975f_fk_user_sett` (`meta_id`),
  CONSTRAINT `global_settings_settin_meta_id_8b0b975f_fk_global_sett` FOREIGN KEY (`meta_id`) REFERENCES `global_settings_settingmeta` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8

初始化:

  • 全局auth类型: namespace=authentication type=none/two_factor
  • auth类型: two_factor 双因子具体: namespace=two_factor send_method/expire_seconds/scope/email_config/sms_config
  1. 可以大量参考现有的代码逻辑, 提供 API, 直接查某个Namespace下所有生效的key-value列表
  2. 可扩展, 未来加其他auth类型, 可以直接新增namespace及定义

2. 接口列表

单一职责! 单一职责! 单一职责!

2.1 获取全局配置下某个namespace的所有配置

GET /api/v2/global_settings/?namespace=two_factor&only_enabled=true

注意, 这个接口会在login被调用两次, 先判断authentication类型, 如果是two_factor, 再获取two_factor的所有全局配置

2. 全局配置的修改, 给saas用 (增删改查)

参考: https://github.com/wklken/bk-user/blob/32d98d550859ab1bf8e3c08a988ec4c6c0a54bb9/src/api/bkuser_core/user_settings/urls.py#L18

POST/PUT

只是, 这里只有meta_id/namespace之类的, 没有category_id逻辑

3. 登陆成功后查询用户信息

已有接口: https://github.com/wklken/bk-user/blob/32d98d550859ab1bf8e3c08a988ec4c6c0a54bb9/src/login/bklogin/common/usermgr.py#L61

_batch_query_users 注意使用v2, 查询结果中有phone/email

此时, 如果phone/email都是空的, 那么以为这走双因子需要先绑定; 否则, 不一定需要绑定, 可以使用用户现成的phone/email

4. 发送验证码/校验验证码

单一职责! 用户管理不care 请求页面来源

POST /api/v2/profile/captcha/send/

入参:

    1. username
    1. 发送方式 email or phone, 非绑定页面, 用户管理根据发送方式获取username的email/phone
    1. email 如果是绑定页面且发送方式是email, 必填
    1. phone 如果是绑定页面且发送方式是phone, 必填

返回:

  • token

POST /api/v2/profile/captcha/verify/

入参:

    1. token
    1. 验证码

返回:

  • 无, 不返回任何信息!!!!!!!!!!!

5. 如果页面来源是: 绑定, 并且验证码校验通过 => 执行绑定

此时, 知道页面来源是绑定, 验证码校验也通过了(能拿到手机号或邮箱)

调用更新接口, 进行绑定

已有接口: https://github.com/wklken/bk-user/blob/32d98d550859ab1bf8e3c08a988ec4c6c0a54bb9/src/login/bklogin/bkauth/models.py#L104

只更新要绑定的phone/email即可完成绑定


登录改造

1. login_success_response

login_success_response 原先负责写登录日志, 然后写登录态, 跳转回来源

如果改造, 相当于多了一道 跳转到双因子

双因子认证通过后, 一样要写 登陆日志, 写登录态, 跳转回来源 (可以复用login_success_response接口, 连续两次进入)

即, 从 login_success_response -> secondary_authenticate -> verify capcha -> 来源页面;

2. 接口

POST /captcha/send/

POST /captcha/verify/

had_bind:

  1. 这个不是一个好名字
  2. 不应该暴露在前端接口, 是否绑定后台自己知道 (进入双因子页面前就知道, 如果不需要绑定, 页面展示有差异- 1. 已绑定用户, 手机/邮箱不可变更 2. 未绑定用户, 提示绑定, 填手机或邮箱)

3. 安全

  • token存活多久?
  • 用户反复发送验证码怎么处理?
  • 同一个用户, 同一时刻, 只能有一个token? 如果切手机, 之后又切邮箱, 连续发送验证码?

from bk-user.

neronkl avatar neronkl commented on June 12, 2024

问题:

  1. global_settings的管理,为什么需要和user_settings一样做两张表形成映射进行管理?不可合二为一?
  2. 方案基于你的review建议进行修改,还需继续提供修正后方案review?存在延期的风险

安全:

  1. token存活时间为全局设置的expire_time
  2. token值是由username进行生成的,token没有过期,用户无法重复发送
  3. 同上

@wklken

from bk-user.

wklken avatar wklken commented on June 12, 2024
  1. 原先user_settings表结构已经很好地满足各种定制的需求, 使用同样的结构, 保持一致性, 降低理解成本; 一套系统不应该有两种不同的; (不是说不能合并一张表, 而是一套系统所不应该存在两种实现)
  2. 上面的review点已经非常详细了, 剩余的就是流程图/流转, 想清楚就可以直接做. 总之明确流程, 明确单一职责, 并且流程清晰;
  3. 时间上需要去协调, 方案设计涉及调整是需要额外时间的, 但是这个时间是值得的, 只实现功能并不代表会被approve合并, 用户管理这边始终会确保整体架构和接口的合理性/可扩展性.

from bk-user.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.