5.用户安全架构设计(一)
用户安全架构属于系统安全架构设计中的一部分,核心目标是保护用户的隐私和数据安全,避免信息泄露、账号被盗等情况的发生。围绕用户的安全设计,需要掌握以下内容。
用户踢出:主动踢出、被动踢出。
用户注册:半封闭式注册、手机验证码注册、邮箱激活注册。
密码存储:密码复杂度、安全检查、密码失效、账户锁定、密码传输。
密码找回:密保问题找回、下行手机验证码找回、上行手机验证码找回、邮箱找回、人工申诉找回。
权限模型:RBAC用户权限设计。
认证技术:Token访问控制、Token延迟与刷新、JWT认证授权等。
5.1 安全设计无小事
安全设计是任何系统必须要考虑的问题,也是架构设计中最重要的部分。一个系统一旦出现安全性问题,往往都是致命性的,企业需要承受巨额的经济损失,以及无法挽回的其他间接影响,甚至企业倒闭。
攻击案例:一次短信攻击让国内某知名企业瞬间损失了八十余万元,间接损失无法估计。
很多网站都有使用手机号发送验证码的功能,攻击者通过浏览器的开发者模式,或者网络抓包的方式获取到其发送短信的接口地址和请求参数。然后使用HTTP工具,或者编写程序进行模拟发送,即可完成攻击。
5.2 主动与被动登录踢出设计
登录踢出可以作为一种保护措施,在很多情况下都需要进行这样的设计。例如,用户在网吧登录某个系统却忘记了退出,这就十分危险。其他人不用登录就可以直接访问该账号,不只个人信息会泄露,甚至对方可以进行一些具有破坏性的操作。
因此,系统不可以保持永久的登录状态,除用户自己主动退出外,还必须具有踢出机制。
注意
退出和踢出的含义是不同的,退出是指用户正常退出,而踢出是一种系统行为,用户无法阻止。
在以下几种场景下需要登录踢出设计。
(1)Session过期:在B/S模式下,最常见的就是用户登录系统后长时间不操作,为了保证用户安全,则会由于Session过期而被踢出。
(2)不允许多设备登录:有些App是不允许多端、多设备同时登录的,如微信,在A手机登录后,再使用B手机登录相同账号,则A手机会被自动踢出。
(3)权限控制:一般在企业内部系统使用,有一些系统用户的功能权限是在登录时加载的,如果管理员修改了此用户的权限,则用户必须退出后重新登录。
(4)升级维护:在一些系统升级之前会将用户主动踢出,并且不允许登录,保障系统发布过程中不被访问。
登录踢出设计有主动踢出和被动踢出两种设计方式。
1.主动踢出设计
不允许多设备登录、权限控制和升级维护的场景,都可以使用主动踢出的方式来解决。
(1)设备A登录时,请求后端服务器。
(2)服务端查看用户是否存在其他设备已经登录,如果存在,则推送消息给个推服务。
(3)个推服务会将通知消息推送给设备B。
(4)设备B接收到消息后,直接将用户踢出到登录页面,并提示“您的账户已经在其他设备登录”。
注意
个推服务的作用是由服务端向用户终端(手机App、计算机、电视等)推送消息,如提醒信息、活动信息等,一般以JSON格式进行发送,App端需要集成相应的SDK。
2.被动踢出设计
Session过期的场景属于典型的被动踢出设计,必须由客户端主动发起请求,服务端才会去检测用户是否依然在保持会话。不允许多设备登录、权限控制和升级维护的场景,除了使用主动踢出的方式,也可以使用被动踢出的方式来实现。
(1)Session模式被动踢出。
Session模式被动踢出流程:
① 客户端发送任意请求(需要登录后才有权限的请求)到服务端。
② 服务端获取客户端的session_id,在内存中查询用户Session是否存在并且有效。
③ 如果Session已经失效,则返回错误,或者重定向到登录页面,从而完成踢出。
(2)Token模式被动踢出。
Token模式下的踢出与Session模式并没有本质的区别,只是第二步有点区别。后端服务接收到前端请求后,需要先去Redis、Memcached等缓存中间件或数据库中获取Token,然后再验证其是否存在并且有效。
(3)多设备登录模式被动踢出。
多设备登录也可以进行被动踢出,主要是借助Redis等缓存来实现。
① 设备A向服务端发起登录请求。
② 服务端将设备ID、用户ID作为Key,将用户信息存储到Redis等缓存中。
③ 设备B使用相同账号登录,向服务端发起请求。
④ 服务端查询该账号是否存在其他设备已经登录,如果存在,则将其他设备的Redis记录清除。
⑤ 当设备A再发起任意请求时,服务端检测到用户设备没有Redis记录,则会返回错误,踢出到登录页面。
这种方式存在的问题是用户体验较差,但是稳定性较好。主动踢出模式虽然用户体验好,但是很容易因为消息推送失败,而导致无法踢出其他登录设备,造成安全隐患。
5.3 5种密码安全性设计
密码就是打开大门的钥匙,要是不保管好,一旦被窃取、丢失、复制,别人就可以随意进出,就没有任何隐私和安全可讲。因此,在进行系统设计时,密码相关功能就是重中之重,千万不可以大意。
密码安全性设计主要有5种:
- 密码复杂度设计
- 密码安全检查设计
- 密码失效设计
- 账户锁定设计
- 密码传输和存储设计。
5.3.1 密码复杂度设计
提升密码复杂度是一种性价比最高的处理方式。例如,在用户注册时,密码长度必须大于等于8位,必须同时包含数字、大小写字母及特殊字符,这样就可以很好地保障密码安全。
安全性和用户体验总是成对出现的,系统安全性越高,往往用户体验度越低。密码复杂度越高,安全性越高,但是用户要录入的内容就会变多,记忆和使用难度加大。密码复杂度越低,安全性 越低,密码被破解的可能性就会提高,但是用户记忆和使用会更加方便。
如何抉择?这就要根据系统的安全等级要求而定。如果是财务、银行、保险、资金划拨等重要系统,则一般强制要求采用最高的密码复杂度。如果是娱乐、休闲类系统,则只会提醒用户的密码安全等级偏低,而不会强制要求。
为了兼顾安全性和用户体验,可以根据系统特点限定密码的安全等级,可以通过以下方法设计密码复杂度。
5.3.2 密码安全检查设计
为了防止用户随意设置过于简单的密码(如123456、1q2w3e等),或者经常被攻击的、易破解的密码,可以使用密码安全检查库来增强密码安全性。
密码安全检查库中存储着所有不建议设置的密码,如123456、abc123、1q2w3ed等,当用户进行密码设置时进行比对,如果匹配,则提示“您设置的密码不安全,请更换”。
密码安全检查流程包含以下3个步骤。
(1)用户提交要设置的密码。
(2)服务端使用此密码到密码安全检查库中进行检索。
(3)如果有匹配数据,则返回密码存在风险。
这种设计思想就是病毒库的思想,杀毒软件都会有自己的病毒库,通过收集漏洞和用户的反馈,不断地完善病毒库。扫描病毒时,就是与病毒库比对的过程。也可以将密码安全检查库当作一种病毒库来看待。
这种设计还有一个变种,就是历史密码库比对,该设计的前提是必须存储用户使用过的所有历史密码,在用户注册、修改密码、重置密码时都要进行记录。
历史密码库检查流程包含以下3个步骤。
(1)用户提交要设置的密码。
(2)服务端使用此密码到用户历史密码库中进行检索。
(3)如果有匹配数据,则返回“您曾经使用过该密码,请更换”。
5.3.3 密码失效设计
大多数人都没有定期更换密码的习惯,所以密码遗失或被破解的概率就比较高,因此对于高安全等级的系统,可以采用密码失效策略。
例如,在用户注册、修改密码时,可以设置存储该密码的有效期为90天,当90天后用户再次登录,则会提示用户密码过期,并跳转至密码修改页面,要求用户输入用户名、原始密码、新密码进行更换。
对于密码失效的天数设置可以根据系统的安全等级进行配置,如30天、3个月、1年等。密码失效是一种十分有效的安全措施,为了避免用户每次密码失效时都设置相同的密码,或者几个密码反复使用,一般密码失效策略要与历史密码库同时使用,确保用户定期更换不同的密码。
5.3.4 账户锁定设计
在使用银行ATM机时,如果同一天连续错误输入3次密码,则银行卡就会被吞,并且账户进入锁定状态而无法操作。在一些高级别的安全系统中,如果用户连续错误输入多次密码,则密码被锁定,这种设计存在多种变种,从而达到不同的用户体验。
用户连续错误输入N次,则自动进入锁定状态,必须等待N小时之后才可以再次重试,每次输入错误都会提醒用户当前剩余的可重试次数。这种设计方式既保证了安全性,也提升了用户体验。
用户连续错误输入N次,则自动进入锁定状态,并且必须人工介入,才可以恢复正常。这种一般为银行或金融类软件为了保证绝对安全所采用的方式。
5.3.5 密码传输和存储设计
密码在传输过程中为了防止网络嗅探,可使用HTTPS进行传输;为了防止在客户端和服务端被泄露,则需要进行加密。
早期密码都是采用MD5算法进行单次哈希的方式加密的,但是随着技术的发展,这种方式已经不够安全了,算法过于简单,容易被暴力破解。因此,可以采用加盐(salt)的方式来设计。所谓加盐,就是增加一个只有自己知道的字符串,一起参与加密,从而让原来的“味道”变了,这样可以保证密码存储在数据库中也不会被窃取。
加密算法:MD5(MD5(原始密码)+ salt)= 加密后的密码。
例如,原始密码=123456,salt=abcdef,则加密后的密码=MD5(MD5(123456)+abcdef)。
安全设计:盐并不是固定不变的,而是每个用户都是不同的(随机产生),并且在用户修改密码时可以修改,从而带来更高的安全性。
例如,使用yinhongliang这个账号进行登录,交互流程如图5-10所示。
(1)使用用户名yinhongliang、密码123456登录。
(2)服务端接收到请求后,使用用户名yinhongliang到数据库中查询用户数据,得到salt=abcdef,password=14e1b600b1fd579f47433b88e8d85291。
(3)使用MD5(MD5(123456)+abcdef)进行加密,得到加密后的密码。
(4)使用加密后的密码与数据库中获取的password做比对。
(5)根据密码比对结果,向用户返回登录成功或密码错误。
思考1:还有更安全的方法吗?
只要增加密码被破解的成本,提高加密的复杂度的方法都可以,如可以做多次加密:MD5(MD5(MD5(密码))),MD5(MD5(密码)+ MD5(密码))。或者更换更为安全的算法,如SHA1、SHA256(又称为SHA2)等。因此,可以设计各种个性化的加密方法来提高破解复杂度。
思考2:加密变得复杂,会有什么负面影响吗?
加密算法越复杂,对服务器的性能要求越高,因为加密属于非常耗CPU的操作,在高并发的场景下也会导致CPU占用率升高。由于在用户登录、修改密码、重置密码的过程中都会进行密码加密的操作,所以对接口的响应速度也会产生一定的影响。
5.4 5种密码找回设计
当用户忘记密码时,通常都要使用找回密码、重置密码功能。找回密码并不是真正地将原来的密码告诉用户,因为这个密码在数据库中的存储也是加密的,根本无法获取到原始的明文密码是什么。
找回密码的本质是根据用户现有的信息做比对,来进行身份核实。核实通过之后允许用户进行密码修改,进而达到找回密码的目的。
用户可以采用哪种方式找回密码,完全与系统所持有的用户信息完整度相关。例如,用户根本没有绑定手机号和邮箱,则无法通过手机号或邮箱找回密码。同时,密码修改是一项十分敏感的操作,必须保证整个流程的绝对安全。
1.密保问题类找回密码设计
利用密保问题来找回密码是最为简单的设计,注册时进行问题和答案的采集,这些问题有系统预先设置的,也有用户自己设置的。例如,“您父亲的姓名?”“您母亲的姓名?”“您小学的名称?”等。
密保找回密码流程包含以下6个步骤。
(1)用户进行注册,填写密保问题和答案。
(2)将用户所选的题目和答案进行存储。
(3)当用户找回密码时,要求先输入用户名。
(4)根据用户名从数据库拉取密保问题,并要求用户回答。
(5)比对用户当前提交的答案与注册时填写的答案。
(6)如果答案正确,则进入密码修改环节。
密保问题很容易泄露,如父母、配偶、学校、生日等信息是很容易被获取的。如果用户自己设置密保问题,则又很容易遗忘。
一旦密保问题无法回答正确,又没有其他密码找回手段,就只能通过人工申诉进行找回,十分不便。因此,密保问题类找回密码设计已经渐渐地废弃不用了,或者只是作为找回密码的备用手段。
2.下行短信验证码找回密码设计
首先要理解上行短信和下行短信的概念。上行短信是指用户主动给运营方发送的短信,如用户主动给10086发送的短信。下行短信是指运营方主动给用户发送的短信,如淘宝给用户发送了一条“双11”促销短信。下行短信验证码的方式是现在大多数系统所采用的设计。
下行短信验证码找回密码流程,流程短,体验好,是现在最主流的设计方式,前提条件也很简单,系统必须提供手机号注册或绑定功能。通常只有4个步骤,即录入手机号、获取验证码、填写验证码和修改密码。
然而,此流程也存在安全隐患,可以引入Token的多重校验机制,避免验证码被窃取。
下行短信验证码找回密码的安全流程如图5-13所示,包含以下11个步骤。
(1)用户进入找回密码流程,输入手机号。
(2)客户端向服务端发送请求,获取短信验证码。
(3)服务端校验手机号是否存在,如果存在,则向用户手机发送短信验证码。
(4)服务端生成Token并返回给客户端,目的是将验证码、手机号、Token绑定,增加安全校验,这一步也是被大多数设计者经常忽略的一步。
(5)用户将接收到的短信验证码录入页面中,点击“提交”按钮,客户端携带用户录入的验证码及Token请求服务端。
(6)服务端校验用户提交的验证码与Token所对应的验证码是否匹配并且有效。
(7)服务端返回客户端验证成功,客户端跳转到修改密码页面。
(8)用户录入要修改的新密码,点击“提交”按钮。
(9)客户端携带手机号、新密码、Token、短信验证码请求后端修改密码。这一步也经常被忽略,如果只将新密码发送给了后端来执行修改操作,则有巨大的安全隐患。
(10)服务端验证手机号、Token、短信验证码全部匹配并且有效,才会执行修改操作。
(11)返回修改结果(成功、失败)。
思考1:如果没有Token机制,会有什么危险?
例如,在你的手机上植入木马读取短信内容,一旦获取到验证码就可以直接在其他地方使用,而与具体的执行环境无关。然而,对方无法同时获取短信和Token,从而保证了即使短信验证码被盗取,也无法使用。
思考2:如果不增加Token、手机号、验证码的校验,则可以通过模拟请求的方式来随意修改密码。
思考3:验证码的特点是有效期短,使用一次立即失效,并且只对单一业务有效。在密码修改功能中获取了一个验证码,则只能在这个业务功能使用,而不能在转账付款功能中使用。很多人将获取验证码、校验验证码的功能作为统一服务,而忘记了增加业务隔离限制,就会导致验证码可以重复使用。
统一的短信验证码服务接口,必须包含Token和验证码类型两个字段,这样,在请求验证码获取接口时,只要传入不同的验证码类型,就可以得到不同类型的验证码了。其中1、2、3用户可以在非登录状态下获取,而4、5、6用户必须在登录状态下才能获取。为了防止验证码接口被攻击,一般还要在业务流程中加入图片、文字验证码环节。
3.上行短信验证码找回密码设计
上行短信验证码的方式一般在使用账号找回密码的场景中使用,用户使用注册手机或绑定手机向指定的运营商号码发送指定内容,完成验证。上行短信验证码找回密码流程如图5-14所示,包含以下9个步骤。
(1)用户录入要找回密码的账号,点击“确定”按钮。
(2)将账号提交到服务端。
(3)服务端使用账号查询账号锁绑定的手机号。
(4)脱敏后返回给客户端,脱敏就是以18*****3052这种形式返回,避免用户的敏感信息被窃取。
(5)提示用户以下信息,如“请使用18*****3052号码给123456789运营商号码发送短信,短信内容为czmm”,czmm为指令,代表重置密码(指令可以由服务端随意指定)。
(6)用户使用该手机号发送指定短信内容给短信运营商,短信运营商会将此信息转发给服务端系统,服务端系统进行发送方手机号、短信内容的验证。
(7)用户发送完短信后,在页面上点击“我已发送”按钮,目的是通知服务端。
(8)服务端验证是否接收到上行短信,并且验证手机号和短信内容是否全部正确,如果正确,则向客户端返回验证通过。
(9)客户端接收到验证通过的消息后,跳转到修改密码页面,完成后续流程。
上行短信验证码的方式可以有效防止短信接口被攻击,更加安全,但是会让用户操作更加烦琐,并且用户会产生短信费用,所以体验性有所下降。
思考:是否可以通过上行短信直接设置密码?
答案是肯定的。例如,可以发送 “姓名#身份证号#账号#新密码” 给服务端,直接通过信息比对,然后修改密码,这种方式虽然简单高效,但是短信遗失、被窃取的风险很高。一旦用户短信泄露,则账号将处于“裸奔”状态,因此这种设计应该尽量避免使用。
4.邮箱找回密码设计
邮箱找回密码也是一种常用设计,同样要求系统必须提供邮箱注册或绑定功能,否则无法利用邮箱找回。
邮箱找回密码流程如图5-15所示,包含以下9个步骤。
(1)用户在客户端录入邮箱。
(2)将邮箱地址提交给服务端。
(3)服务端校验邮箱是否存在,如果存在,则生成Token串,Token具有有效期,如1小时。同时,对邮箱地址进行哈希加密(使用MD5、SHA等算法,防止邮箱地址被篡改)。
(4)给用户邮箱发送邮件,邮件的内容为用户修改密码的地址,地址后面拼接Token和加密后的邮箱参数,如https://xxx:xx/restPassword?token=6wUU0Xwecav1TsFIiLwTS4wFaiLJtEvmrhwMIv9VFQq5VX55fXjEOAJgeFFAYMqk&email=5B0mDIsukVgkp2AY36O9。
(5)用户点击邮箱中的链接,跳转到客户端,进入密码修改页面。
(6)用户录入新的密码,并提交。
(7)客户端携带新密码、Token(从URL中获取)、加密后的邮箱地址提交到服务端。
(8)服务端校验Token是否存在并且有效,邮箱是否匹配,如果校验通过,则执行修改操作。
(9)服务端将修改结果返回给客户端。如果Token已经过期,则提示链接已失效;如果无法找到匹配的数据,则提示非法链接。
在这个设计中,最重要的就是Token和E-mail加密串的使用,避免了密码修改链接被篡改的风险。
5.人工申诉找回密码设计
人工申诉找回密码是一项十分重要,而又经常被忽略的设计。因为用户更换了手机号和邮箱,或者忘记了当时注册所使用的手机号和邮箱,导致无法找回密码。而系统上又没有任何人工申诉的入口和联系方式,就会导致用户陷入死循环。
人工申诉找回密码可以使用在线客服、电话客服、邮箱客服,可以不开发任何功能,但是必须让用户需要时能够找到解决路径,这就是设计中的闭环思维。
5.5 密码修改设计
密码修改与密码找回是有本质区别的,密码修改是用户已经登录了系统,然后对密码进行修改。而密码找回是用户还没有登录系统,由于忘记了密码而申请找回。
对于一些企业内网PC端管理系统,一般采用输入原密码和新密码的方式来修改密码;对于互联网App端产品,一般不通过这种方式处理,而是结合手机号、邮箱、人脸识别、指纹识别来修改密码;对于互联网PC端产品,一般采用邮箱、手机验证码的方式来修改密码。
对于App端产品可以有效地结合人工智能校验,增强系统安全性,在密码修改前先进行人脸识别验证、指纹识别验证来增强系统安全性,其中人脸识别活体检测的安全性最高,可以验证是用户本人在操作。
手机验证码的方式使用最为广泛,主要还是因为其实现简单,性价比较高。一般的公司并没有人脸识别的技术能力,付费使用第三方接口的成本十分高昂。
密码修改时需要记录密码修改轨迹信息,这样当发生一些安全性问题时才有迹可循,能进行反向追查。同时,通过密码轨迹信息可以统计用户密码修改的频率、修改密码时所发生的位置变化、使用的设备变化等。
如果用户的密码修改频繁,而且这次在北京,下次在云南,或者设备经常变化,则可以对账户进行风险标记,并且发送短信提醒,告知账号存在风险。