Drupal 10:增强用户账户保护
Drupal的一大优势在于它能创建用户社区,让用户为网站内容贡献力量。不管你拥有的是用户可自行创建账户的开放论坛,还是仅有少数编辑的封闭杂志网站,都得认真对待用户的安全问题。
默认情况下,Drupal具备诸多账户保护功能,能确保用户得到正确的身份验证。
例如,用户登录页面受暴力破解防护系统保护,若在短时间内多次输入错误密码,账户将会被锁定。
你还能采取一些其他措施来保护网站用户,这些措施适用于任何Drupal网站。在本文中,我将介绍一些不同的Drupal模块开发出的模块和技术,你可以利用它们来保护网站上的用户账户,同时还会分析每种方法的优缺点。
一、流量控制
Drupal的登录表单内置了暴力破解防护机制,若同一IP地址在一小时内尝试登录某个用户账户超过5次且未输入正确密码,该账户将被锁定。这能防止自动化机器人通过数千次猜测用户账户密码来尝试破解。
通过Drupal开发,流量控制模块允许你调整这些设置,使其更加严格(或宽松)。
例如,你可能希望将每10分钟内每个IP地址的登录失败尝试次数设置为10次。要是你所在的办公室使用单一IP地址,而用户又经常因多次输入错误密码导致账户被锁定,那么这个设置会很有用。
流量控制模块还提供了一个“解除锁定”表单,你可以通过它清除Drupal对用户设置的任何锁定。这实际上是个非常实用的功能,因为清除这些锁定的唯一其他方法是在数据库中进行操作。
还有一个名为“登录安全”的模块,它涵盖了许多与流量控制模块相同的功能,允许管理员配置流量设置。该模块的不同之处在于,如果在登录表单上检测到暴力破解登录尝试,它会发出通知(包括Nagios警报)。
✅ 允许网站管理员调整Drupal中的流量设置,增强了Drupal现有的安全功能。
✅ 允许用户通过清除其他用户的流量限制状态来自我管理网站。
✅ 如果你调整了流量控制模块的设置,发现用户经常被锁定,可以轻松再次调整设置。
❌ 你可能会将流量限制设置得过低,从而使该功能基本失去作用。
二、防止用户名枚举
Drupal中的流量控制功能只有在攻击者知道或能够猜测到用户的用户名时才会起作用,这就是“防止用户名枚举”模块发挥作用的地方。该模块具有多种功能,它会通过对用户账户页面返回404错误,防止任何人试图猜测你有多少用户以及他们的用户名是什么。
当然,你可能允许匿名用户查看账户页面。在这种情况下,该模块会在Drupal状态页面上提醒你。不过,通常情况下,除非有充分的理由,否则最好不要允许所有人访问用户个人资料页面。
较新版本的Drupal已经更改了登录页面周围的提示信息,因此无法再从登录表单中猜测用户的用户名。如果用户输入的密码错误,Drupal会显示“用户名或密码未识别”的消息,而不是告知他们只是用户名或密码不正确。
如果攻击者无法获取你的用户信息,那么他们破解Drupal用户账户的难度就会大大增加。
“兔子洞”模块是另一个可以实现相同功能的模块,它会隐藏你网站上的用户个人资料。“兔子洞”模块可能更好,因为它还能为其他类型的内容提供相同的保护,不过这取决于你的使用场景。
✅ 简单易用的即插即用模块,提供了简单的功能。
❌ 如果你的匿名用户可以查看用户页面,该模块实际上没有任何功能;不过,不建议这样设置。
三、调整会话设置
PHP有许多可以调整的设置,用于改变会话的工作方式,这些设置在构成Drupal安装一部分的services.yml文件中公开。
你需要关注的主要设置如下:
-
gc_maxlifetime - 这里的“gc”代表“垃圾回收”,是会话数据被视为垃圾并被PHP删除的秒数。当用户访问页面时,此设置会重置,因此PHP只会回收一段时间未使用的会话。
此设置与gc_probability和gc_divisor设置密切相关,这两个设置配置为在1%的页面请求中运行一次垃圾回收过程。gc_maxlifetime的默认设置是200,000秒,即55小时(约2天)。在此时间过后,PHP有1%的概率会删除用户的会话,用户将不得不重新登录。你可以调整这些其他设置来改变旧会话的清除频率,但1%通常是一个不错的值。
-
cookie_lifetime - 这是会话cookie的生命周期,以秒为单位,浏览器将保留会话数据的时间。此值的默认值是2,000,000秒,即555小时(约23天)。这意味着即使用户每天都使用该网站,23天后他们也会被注销,需要重新进行身份验证。
将此值设置为0意味着当用户关闭浏览器窗口时,会话将被清除。用户仍然可以正常使用网站,但如果关闭浏览器窗口,他们的会话将被删除。
例如,我们可以在services.yml文件中将这些设置设置如下。
parameters:
session.storage.options:
gc_maxlifetime: 25200
cookie_lifetime: 0
这意味着会话的最长生命周期为25,200秒,即7小时,并且当浏览器关闭时,会话将被删除。设置这些值意味着用户可以在工作日内使用该网站,当他们关闭浏览器或过夜时,会话将被删除,以先发生者为准。
会话持续23天可能是一段相当长的时间,特别是在包含安全内容的网站上。建议根据你的使用策略减少此设置。
✅ 此设置无需任何模块,只需调整几个值即可强制执行良好的会话管理。
❌ 调整这些值可能会很烦人,因为设置得过低意味着你的用户将不得不频繁重新登录网站。
四、持久登录
除了调整会话设置外,还可以使用“持久登录”这个通过Drupal模块开发出的模块来调整用户的会话时间。
该模块在登录页面上添加了一个小复选框,勾选后,会话的生命周期将延长一个可配置的时间。默认情况下,会话将延长30天。
持久登录的工作原理是创建另一个与会话主cookie并存的会话cookie。当用户选择持久登录时,如果他们的主会话过期,这个额外的会话cookie将用于通过重新验证用户来延长其会话的生命周期。
该模块要求将cookie_lifetime设置为0。你还需要注意网站使用的任何反向代理缓存,因为它可能会干扰该模块提供的会话管理。如果未正确配置,反向代理缓存将忽略持久会话cookie,而提供缓存的页面。
✅ 与PHP中的普通会话cookie分开工作。
✅ 可以配置为每次使用会话时延长cookie的生命周期。
❌ 在使用前需要对Drupal的services.yml文件和任何反向代理缓存进行配置。
❌ 不建议在所有网站上使用。
五、自动注销
另一个处理会话的模块是“自动注销”模块,它与“持久登录”模块的功能大致相反。
“自动注销”模块有多种可用选项,但基本上,如果用户的会话超过某个预先配置的时间限制,该模块将自动注销用户。默认情况下,该模块将此时间设置为1800秒(30分钟)。如果发生这种情况,该模块可以选择通知用户他们的会话即将过期,用户可以点击按钮重新登录。
当注销模块的会话即将过期时,将显示以下对话框,让用户有机会延长会话或直接注销账户。
当然,也可以关闭此对话框,这样用户将被直接注销并重定向到登录页面。注销操作通过JavaScript完成,因此如果网站无人操作,它将基本上锁定自己。
这种行为常见于银行网站,出于安全考虑,用户的会话通常在几分钟后就会过期。其理念是,如果用户离开并忘记注销,网站将代表用户注销。
此限制可以全局配置,也可以按用户角色甚至按用户配置,这意味着你可以为网站管理员设置比其他类型用户更严格的会话限制。
✅ 如果你想严格控制用户的会话时间,这非常有用。
✅ 可以通过一个复选框全局关闭。
✅ 按角色控制超时意味着你可以根据角色的安全影响设置不同级别的超时。
❌ 如果网站频繁注销用户,可能会让最终用户感到烦恼,确保为你的网站设置合理的自动注销限制。
六、按IP地址限制
如果你想真正锁定你的Drupal网站,可以通过用户的IP地址限制对管理界面的访问。从服务器配置的角度来看,这是可以实现的,但也有一些通过Drupal模块开发出的模块可以实现这一功能。
“按IP限制路由”模块可能是为你的网站添加此安全功能的最佳选择,因为它可以用于按IP地址限制对某些路由的访问。基本上,你可以使用该模块阻止对“user.login”路由的访问,这样你的网站可以正常运行,但外部IP地址的用户将无法访问用户登录页面。
这种方法的一个缺点是你需要谨慎考虑如何限制IP地址。一些用户没有静态IP地址,因此如果是这种情况,他们将无法使用该网站。一个好的解决方法是使用具有已知IP地址的VPN服务,并使用它来锁定登录表单。
“限制IP”模块是一个不错的模块;但如果你想允许匿名用户查看你的网站,它就不太理想了。该模块更多地是按IP地址对整个网站进行限制,只有具有已知IP地址的用户才能访问该网站;这在某些情况下可能是有用的。
✅ 对于用户数量较少的网站,这是一种简单有效的管理界面保护方法。
✅ 可用于限制对网站其他部分的访问,而不仅仅是登录表单。
❌ 给没有静态IP地址的用户带来不便。可以通过VPN服务解决这个问题,但这会给大多数用户带来额外的负担。
❌ 如果你不知道自己在做什么,很有可能会将自己锁定在自己的网站之外。
❌ 如果你希望来自世界各地的大量用户登录你的网站,这种方法不太合适。
七、防止或限制访问“用户1”
Drupal一直有一个名为“用户1”的“特殊”用户,它是网站上创建的第一个用户(用户ID为1)。这个用户基本上可以绕过网站上的任何权限或访问检查,从而可以完全访问所有内容。“用户1”通常是一种开发工具,网站构建者在网站上线前使用它来配置Drupal网站。
如果你认为这是个糟糕的主意,那么你并不孤单。事实上,Drupal社区最近一直在努力从系统中移除这个用户。这意味着在Drupal 11发布(预计2024年某个时间)时,“用户1”将不复存在。
在此之前,限制对“用户1”的访问是个好主意。你可以阻止该用户,或者经常更改该用户的用户名和密码(理想情况下每次部署时都更改)。
✅ 阻止或至少限制对“用户1”的访问通常是个好主意。
八、登录验证码
验证码(或完全自动化公共图灵测试以区分计算机和人类)是添加到表单中的一个小元素,可用于防止自动化流量。虽然这对于防止垃圾邮件提交很有用,但也可以在登录表单上使用,以防止对网站认证区域的自动化提交。
“验证码”模块可用于为Drupal网站添加不同类型的验证码,并且通常与“reCaptcha”模块结合使用,以允许使用Google的reCaptcha服务。Google的reCaptcha服务会进行一些检查,以确定你是否是人类,如果未通过这些检查,会向你展示某种验证码。
在登录表单上添加验证码模块来挑战用户非常简单。但它是否能创造良好的用户体验或为账户提供一定的安全性,可能就不那么容易回答了。Drupal已经自带了防止机器人猜测用户密码的流量保护系统,因此在登录表单上添加验证码可能会显得有些多余。
在网站上添加验证码以防止垃圾邮件提交是很常见的做法,但在添加验证码之前,应该尝试许多其他的防垃圾邮件机制。
✅ 易于在登录表单上添加不同类型的验证码,以防止自动化访问。
❌ 可能会带来糟糕的用户体验,特别是如果你强迫用户频繁登录。
❌ 在用户登录表单上添加验证码所带来的安全益处值得怀疑。
❌ 许多不同类型的验证码存在可访问性问题。
九、会话检查器
用户可能会从不同的位置、不同的设备甚至不同的浏览器登录Drupal网站。让用户能够查看他们当前活跃的会话是一项有用的安全措施,并且能让用户对自己的用户资料拥有更多的控制权。
“会话检查器”模块会向用户展示他们在网站上当前打开的活跃会话信息。利用这些信息,他们可以决定哪些会话是他们已知的,以及这些会话是否应该仍然处于活跃状态。该模块允许用户删除他们不想要的会话,这实际上会使他们从该浏览器或设备上注销。
对于不熟悉这些信息的用户来说,会话的位置和浏览器信息看起来可能有点复杂。为了解决这个问题,该模块允许安装不同的插件,这些插件会相应地格式化会话输出的这些部分。例如,“BrowserDetector浏览器格式化器”模块会以几种不同的方式格式化浏览器信息,比如将冗长的用户代理字符串简化为“Chrome”。“Geocoder主机名格式化器”模块可以用来将IP地址转换为实际位置,而不仅仅是显示IP地址,这肯定有助于用户了解会话的大致位置。
如果权限设置正确,网站管理员也可以查看用户的会话信息。这使管理员能够控制网站上用户的会话。
完全披露:我公司创建并维护了“会话检查器”模块。如果你觉得它有用,或者能想到任何改进建议,请告诉我!
在这个领域中,还有其他控制用户会话的模块。你还可以安装“会话限制”模块,以防止用户同时打开多个会话,或者安装“登录历史”模块,在用户的仪表板上显示他们以前的登录记录。我认为向用户提供信息并让他们对自己的会话有一定的控制权,可以带来更好的用户体验。
✅ 在Drupal中创建了许多用户在其他平台上能看到的信息。
✅ 用户可以控制他们在网站上的活跃会话。如果他们认为自己的账户已被泄露,这会很有用。
❌ 并非所有用户都能理解这些信息的含义。
❌ 可能需要更多的工作才能与单点登录服务兼容。
❌ 有时仅删除会话是不够的。如果账户已被泄露,应该采取更多措施来保护它。
十、记录用户活动
每次用户登录网站时,Drupal都会创建一条日志,这有助于确定用户进行身份验证的时间。“登录历史”模块可以用来记录这些信息,并向用户展示,但如果你想跟踪用户个人资料的更改,则需要更多的日志记录。
“事件日志跟踪”模块是为你的Drupal网站提供额外日志记录的有用方法。它包含一个名为“事件日志跟踪用户”的子模块,该子模块允许记录用户个人资料的更改,甚至可以选择将其记录到Drupal主日志中。虽然这些信息很有用,但日志中不会说明发生了什么更改,只是表明发生了更改。你还需要做一些额外的工作才能将这些信息展示给用户,但这对于审计和故障排除很有用。
为了记录密码的更改,必须创建一些自定义代码。以下是一个使用hook_form_FORM_ID_alter()钩子将自定义提交处理程序注入用户个人资料表单的示例。如果提交处理程序检测到密码已更改,它将生成一条日志。
/**
* Implements hook_form_FORM_ID_alter().
*/
function mymodule_form_user_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
$form['actions']['submit']['#submit'][] = 'mymodule_password_submit_handler';
}
/**
* Submit handler for the "user_form" form.
*
* @param array $form
* The form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
*/
function mymodule_password_submit_handler(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
if (!empty($form_state->getValue('pass') && $form_state->getValue('pass') != $form_state->getValue('current_pass'))) {
// Log password change.
$user = $form_state->getStorage()['user'];
\Drupal::service('logger.factory')->get('mymodule')->info('Password changed for user {username}', ['username' => $user->name->value]);
}
}
同样,除非用户可以访问最新的日志报告,否则他们不会知道密码已更改。
“消息”模块可以用来生成用户可以看到的消息,以确认他们的密码已更改。你可以按以下方式调整上述代码,生成消息实体以通知用户。
function mymodule_password_submit_handler(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
if (!empty($form_state->getValue('pass') && $form_state->getValue('pass') != $form_state->getValue('current_pass'))) {
// Log password change.
$user = $form_state->getStorage()['user'];
\Drupal::service('logger.factory')->get('mymodule')->info('Password changed for user {username}', ['username' => $user->name->value]);
// Create message to inform the user of the change.
$message = Message::create([
'template' => 'user_password_changed',
'uid' => $account->id(),
]);
$message->set('langcode', 'en');
$message->save();
}
}
如果你想了解更多关于为用户生成消息的信息,可以阅读更多关于使用“消息”和“ECA”模块创建通知系统的内容。
许多网站还会在用户密码更改时给用户发送电子邮件,这是个好主意,因为它会通知用户他们的账户发生了这一重要更改。这可以通过消息系统来处理,例如使用“消息通知”模块。即使是向更改自己账户密码的用户发送电子邮件也是有用的,因为这可以让他们知道如果密码真的更改了,会收到什么样的电子邮件。
✅ 创建用户交互历史记录对于多种目的都很有用。
✅ 告知用户他们的账户在未经他们同意的情况下发生了更改,可以帮助用户应对账户被泄露的情况。
❌ 需要额外的编码才能向用户展示日志或发送电子邮件。
十一、密码策略
在任何网站上设置强密码都是个好主意,但要让用户输入强密码可能是个挑战。这就是“密码策略”模块派上用场的地方。使用这个模块,你可以为所有用户或特定角色的用户强制实施更严格的密码标准。
Drupal的密码创建字段中已经内置了一个密码质量工具。但这只是为了提供信息,因为用户可以简单地忽略建议,输入他们想要的任何密码。
使用“密码策略”模块意味着用户输入的密码必须符合你规定的标准。
“密码策略”模块实际上是一套约束模块,它们共同作用以增强你网站上的密码安全性。密码策略带有以下核心约束条件。
- 字符类型 - 密码中必须包含的不同字符类型的数量。支持的4种字符类型是大写字母、小写字母、数字和特殊字符。
- 字符类型数量 - 密码中必须包含的特定字符类型的数量。例如,你可以规定密码必须至少包含1个大写字母。
- 延迟 - 密码重置前必须经过的小时数。
- 历史记录 - 存储用户以前的哈希密码,并检查他们的新密码哈希值是否与以前的哈希值匹配。
- 长度 - 密码允许的最小和最大长度。
- 用户名 - 防止用户将自己的用户名作为密码的一部分。
- 连续字符 - 密码中包含的连续字符数量不得超过配置的数量。
- 禁用列表 - 配置一个不能构成密码全部或部分的密码列表。
其中一些约束会影响用户输入的密码,而另一些则会影响密码的使用和历史记录。你还可以将此模块与“密码策略泄露检查”模块结合使用,添加一条策略以防止用户输入已知被泄露的密码。
此外,“密码策略”模块允许你强制所有用户或特定角色的用户重置密码。如果你发现某个账户已被泄露或某个账户的密码已被泄露,这将非常有用。
你在这里需要注意的唯一一点是,不要强制用户遵循过于严格的密码策略,导致他们难以创建新密码。他们会开始害怕创建新密码,这通常会导致他们采用不良的密码存储策略,比如把密码写在便利贴上。如果你的密码页面开始变得像“密码游戏”一样复杂,那你真的需要退后一步,思考你试图解决的问题是什么。
✅ 提供了一种强制用户使用安全密码的方法。
✅ 能够对特定用户角色实施策略或强制重置密码是很好的。
❌ 密码页面可能还需要进行一些主题设计,以便正确显示密码策略。
❌ 以一种既能强制使用复杂密码又不会惹恼用户的方式配置该模块可能具有挑战性。
十二、通用数据保护条例(GDPR)
换个角度来看,你可以安装“GDPR”模块,让用户知道你认真对待他们的数据隐私,而不是仅仅关注用户账户的安全。GDPR即通用数据保护条例,是欧盟和英国的一项法律,它规定网站要对用户的个人数据负责,并允许用户查看(和删除)网站可能持有的任何个人数据。
最基本的情况下,这个模块只提供了一个清单,你可以按照这个清单来确保你的网站符合基本的GDPR原则。
该模块还有一些额外的子模块可以安装,以提供其他功能,比如“被遗忘权”请求和数据清理。用户可以生成数据检索请求,网站管理员可以完成该请求,为用户提供网站存储的关于他们的所有个人信息。
由于设置较为复杂,最好在一开始就安装这个模块,而不是事后再进行设置。
✅ 允许网站管理员认真对待用户的个人数据。
✅ 让用户能够控制自己的数据。
❌ 网站建立后再进行设置可能会很痛苦。
❌ 一些操作需要使用Drush命令。
十三、双因素认证
双因素认证系统会为系统添加二级认证,这意味着攻击者需要同时拥有密码和一个设备才能访问账户。你可以使用“TFA”模块来添加双因素认证,但这需要进行一些配置。
在配置“TFA”模块之前,需要先正确安装和配置“密钥”和“加密”模块。幸运的是,“TFA”模块的自述文件中有大量关于如何进行此操作的文档。
一旦所有配置完成,该模块将独立运行。无需设置外部服务,因为认证系统将使用该模块提供的服务。这包括向用户展示一个二维码,以便他们轻松设置,以及所有常见的配置选项,用于为特定用户角色启用或禁用该系统。
这可能会给需要使用第二个设备才能登录网站的用户带来障碍。如果该设备丢失或无法使用,用户将无法访问网站。然而,双因素认证的好处是显而易见的,它将大大保护使用该功能的所有用户的账户安全。
注意:截至撰写本文时,2.0.0-alpha2版本不安全,因为它存在已知的访问绕过问题。请使用当前的1.6版本。
✅ 一种不单纯依赖密码的安全认证机制。
❌ 实施起来相当复杂。有很多组件,并且所有组件都需要正确配置才能使该模块正常工作。
❌ 如果用户需要频繁进行身份验证,而他们必须使用TFA设备时,可能会给用户带来困扰。
十四、禁用登录
一种略有不同的账户保护方法是完全禁用登录表单。你可以使用“禁用登录”模块来实现这一点,该模块可以在用户未在URL中提供特殊参数的情况下禁用登录表单。
激活该模块后,你可以设置一个访问密钥,用户访问登录页面时,该密钥必须出现在路径中才能登录。上述配置设置将登录路径更改为以下内容:
这意味着,如果没有该路径的用户试图查看登录页面,他们将看到一个禁用的表单,并收到一条消息,告知他们该网站的访问受到限制。
“禁用登录”模块还有一个不错的功能,即激活时可以注销当前活跃的用户。
如果你有一个杂志或内部网站,并且想在用户不知道特殊密钥的情况下阻止他们登录,那么这个模块会很有用。你可以为用户提供一个书签,让他们点击以启用登录表单,这样会更方便用户。使用这种技术,你可以有效地阻止对网站的访问,但如果用户手头没有密钥,可能会给他们带来困扰。
我公司过去曾使用过这个模块,以便在活动繁忙期间让有会员区的网站节省资源。通过在大型活动期间阻止具有特定角色的用户登录,我们避免了网站处理大量经过身份验证的流量,并可以利用这些资源更好地处理活动。
✅ 对于不知道如何登录的用户,禁用用户登录表单。这对于杂志网站和内部网站很有用。
✅ 如果出于任何原因需要阻止用户访问,可以临时启用该功能。
❌ 并非对所有Drupal网站都有用。
十五、结论
在为用户提供“安全”和“便利”之间需要谨慎权衡。显然,你希望网站尽可能安全,但也很可能会走向极端,给用户带来障碍。添加过多的安全措施最终可能会阻止用户访问你的网站。
虽然这里介绍的通过Drupal模块开发出的模块和技术很有用,但安装所有这些模块并认为一切都没问题可能不是个好主意;这不可避免地会引发一些问题。最好的做法是从诸如“流量控制”之类的功能开始,逐步增加安全措施,因为第一步不会给用户带来任何问题。像双因素认证这样更具侵入性的模块,只有在你绝对确定需要时才应该安装。根据我的经验,短会话时间和双因素认证等措施的组合可能会让用户很快感到厌烦并停止使用网站。
事实上,错误地配置用户的安全设置实际上可能会使情况变得更不安全。例如,设置过于严格的密码策略会导致密码安全性降低,并且往往会导致这些密码被泄露甚至共享。
会话时间过短也可能是个问题。我曾经在英国的一家汽车经销商处(此处不提及名称),当时我正在和一位销售人员聊天,他正在往汽车经销商的网站上输入详细信息。该网站的会话超时时间设置得非常短,以至于这位销售人员在我访问期间不得不多次输入密码。他输入密码的次数太多,以至于我最终记住了密码,并建议他在我们离开后最好更改密码。
那些不尊重用户的安全措施会产生不良激励,导致用户以其他方式绕过安全机制。
这里介绍的模块不应成为你保护用户账户的唯一关注点。Drupal网站上的所有用户都有角色和权限,因此确保正确配置网站的权限至关重要。
我公司在处理权限时的方法一直是给予用户最少的控制权,只有在必要时才添加更多权限。这可以避免出现令人尴尬的安全漏洞,即用户拥有过多的权限。
虽然并非所有网站都能做到这一点,但我公司也倾向于设置角色和权限,使所有用户只拥有一两个角色。这样做可以更容易地查看每个用户拥有哪些权限。
最后,不用说,你应该保持网站的更新。进行Drupal升级,安装一堆安全模块却因为网站存在访问绕过问题而导致网站被入侵,那就毫无意义了。
我公司这里是否遗漏了任何用户账户安全措施?你是否有关于安全措施产生不良激励的精彩故事?请在评论中告诉我!


