来源:juejin.im/post/6859214952704999438
很多程序员入门学习Web后台开发时,实现的第一个功能往往就是登录模块。然而,在与一些工作经验尚浅的同学交流时,我发现虽然很多简历上都写着“负责登录/注册功能模块的开发与设计”,但实际实现往往只停留在基础功能层面,对安全性的考量明显不足。
这篇文章我们就来深入探讨一下,在设计一个登录接口时,除了实现核心业务逻辑,在安全方面我们还需要关注哪些关键点。
安全风险
暴力破解
只要你的网站暴露在公网上,就极有可能成为攻击者的目标。其中,暴力破解是一种简单却非常有效的攻击方式:攻击者在通过某些途径获取网站的用户名后,通过编写脚本程序,遍历所有可能的密码组合,直至找到正确的密码为止。
攻击者的伪代码可能如下所示:
# 密码字典
password_dict = []
# 登录接口
login_url = ''
def attack(username):
for password in password_dict:
data = {'username': username, 'password': password}
content = requests.post(login_url, data).content.decode('utf-8')
if 'login success' in content:
print('got it! password is : %s' % password)
那么,面对这种威胁,我们应该如何有效防范呢?
验证码
一个直接的思路是引入验证码。例如,我们可以设定,当用户连续输错密码达到一定次数(比如3次)后,要求用户必须输入正确的图片验证码才能继续尝试登录。
对应的后端逻辑伪代码如下(注:未考虑并发场景,实际开发中需加锁处理):
fail_count = get_from_redis(fail_username)
if fail_count >= 3:
if captcha is None:
return error('需要验证码')
check_captcha(captcha)
success = do_login(username, password)
if not success:
set_redis(fail_username, fail_count + 1)
这种方法确实能过滤掉一部分自动化攻击脚本。但是,以目前成熟的OCR技术来看,普通的图片验证码很难完全阻止高水平的机器人攻击(我们在这方面有过深刻的教训)。当然,也可以接入第三方提供的滑动验证等高级验证方案,但其安全性也并非100%,同样存在被破解的风险。
登录限制
既然验证码不够可靠,那能否直接限制异常登录行为呢?例如,当某个账号的登录失败次数达到一个阈值(如10次)时,直接锁定该账号,在一段时间内(如5分钟)拒绝其所有登录尝试。
伪代码实现如下:
fail_count = get_from_redis(fail_username)
locked = get_from_redis(lock_username)
if locked:
return error('拒绝登录')
if fail_count >= 3:
if captcha is None:
return error('需要验证码')
check_captcha(captcha)
success = do_login(username, password)
if not success:
set_redis(fail_username, fail_count + 1)
if fail_count + 1 >= 10:
# 失败超过10次,设置锁定标记,有效期300秒
set_redis(lock_username, true, 300s)
这种方法确实能有效防止针对特定账号的密码爆破。然而,它引入了一个新的风险:攻击者虽然无法获取用户信息,却能让网站的所有用户都无法登录!攻击者只需简单地遍历或随机生成大量用户名进行登录尝试,就能触发这些账号的锁定机制,从而导致正常用户被“误伤”。
IP限制
既然针对用户名有限制,那能否从攻击源IP入手呢?我们可以设定,当某个IP地址在短时间内发起过多失败的登录请求时,暂时封禁该IP。
伪代码示例:
ip = request['IP']
fail_count = get_from_redis(fail_ip)
if fail_count > 10:
return error('拒绝登录')
# 其它登录逻辑
# do_something()
success = do_login(username, password)
if not success:
set_redis(fail_ip, true, 300s)
这也是常见的限流思路,例如Nginx的限流模块就可以很方便地限制单个IP的访问频率。但这种方式同样存在明显缺陷:
- 许多学校、公司内部共用同一个出口IP,按IP封禁极易误伤正常用户。
- 攻击者可以使用VPN或代理池轻松更换IP,绕过限制。
手机验证
那么,是否存在更可靠的防范手段呢?答案是肯定的。近年来,随着实名制政策的推进和移动互联网的普及,手机号几乎已成为用户的第二张“身份证”。因此,基于手机验证的安全策略被广泛应用,登录环节也不例外。
一个综合性的防御策略可以这样设计:
- 当用户输入密码错误次数大于3次时,要求输入验证码(推荐使用对抗机器人能力更强的滑动验证码)。
- 当错误次数大于10次时,则必须通过“密码 + 手机验证码”的双重认证方式完成登录。
手机验证码本身也有防刷的问题,这里暂不展开,未来可以单独探讨。上述策略的伪代码实现如下:
fail_count = get_from_redis(fail_username)
if fail_count > 3:
if captcha is None:
return error('需要验证码')
check_captcha(captcha)
if fail_count > 10:
# 大于10次,必须使用验证码和密码登录
if dynamic_code is None:
return error('请输入手机验证码')
if not validate_dynamic_code(username, dynamic_code):
delete_dynamic_code(username)
return error('手机验证码错误')
success = do_login(username, password, dynamic_code)
if not success:
set_redis(fail_username, fail_count + 1)
通过结合验证码、频率限制和手机二次验证,我们能极大提高攻击者的成本,有效阻止大部分自动化攻击。但必须清醒认识到,没有绝对安全的系统,安全设计是一个持续博弈和升级的过程。开发者需要根据自身业务的实际安全等级和用户体验要求,选择合适的组合策略。
中间人攻击
什么是中间人攻击
中间人攻击(Man-in-the-Middle Attack, MITM),简单来说,就是在通信双方(A和B)不知情的情况下,攻击者通过拦截、嗅探甚至篡改他们的通信内容。
举个例子:小白给小黄寄快递,中途经过转运点。攻击者小黑可以潜伏在转运点,或者干脆自己伪装成一个转运点。他就能偷偷拆开包裹查看内容,甚至替换掉包裹里的东西,再重新打包发给小黄。
在登录场景中,如果登录请求以明文(HTTP)传输,攻击者一旦在网络链路上嗅探到数据包,就能轻易获取用户的账号和密码等敏感信息。这类网络攻击是Web应用常见的安全威胁之一。
HTTPS
防范中间人攻击最直接有效的方法,就是将整个网站升级为HTTPS,并强制所有流量使用HTTPS协议。
为什么HTTPS能防御中间人攻击?HTTPS在HTTP和TCP协议层之间加入了SSL/TLS协议层,专门负责数据传输的安全。相比于HTTP,HTTPS主要提供了三大保障:
- 内容加密:对传输数据进行加密,防止被窃听。
- 数据完整性:防止数据在传输过程中被篡改。
- 身份验证:通过证书机制验证服务器身份,防止伪装。
具体的HTTPS工作原理涉及密码学,此处不再深入,读者可以自行搜索学习。
加密传输
在启用HTTPS的基础上,我们还可以对敏感数据进行额外的客户端加密,提供更深一层的保护:
- 用户名:可以在客户端使用非对称加密算法(如RSA)加密,服务端再用私钥解密。
- 密码:绝对避免明文传输。客户端应先对密码进行哈希处理(如加盐的MD5或更安全的bcrypt),再将哈希值传输到服务端进行验证。这样即使数据包被截获,攻击者得到的也不是原始密码。
其它安全考量
除了上述核心的攻防点,一个健壮的登录系统还应考虑以下方面:
- 操作日志:完整记录用户的每次登录和敏感操作日志,内容应包括时间、IP地址、设备信息、操作结果等。这是事后审计和追溯的基础。
- 异常操作提醒:基于操作日志建立风险监控规则。当发现用户从非常用地点登录、短时间内频繁修改密码或出现登录异常时,及时通过短信或邮件通知用户本人。
- 拒绝弱密码:在用户注册或修改密码时,强制要求密码满足一定的复杂度规则(如长度、字符组合),从源头杜绝弱密码。
- 防止用户名被遍历:有些网站在注册时,输入用户名后会实时提示“用户名已存在”。这个功能虽然友好,但可能导致网站的所有注册用户名被恶意遍历获取。需要在产品交互或接口频率上加以限制。
- ... (安全是一个体系,还有很多细节可以完善)
结语
随着《网络安全法》、《数据安全法》等法规的出台,用户数据与隐私保护被提到了前所未有的高度。作为开发者,我们肩负着守护用户数据安全的重要责任。希望本文讨论的登录接口安全设计思路,能为大家的实际开发工作带来一些启发和帮助。欢迎大家在云栈社区交流更多关于系统设计与安全实践的经验。
