Ataques de força bruta para adivinhar senhas ainda são uma ameaça real à segurança. Este tutorial ensina a proteger o formulário de login no framework Yii2 ativando a validação CAPTCHA automaticamente após várias tentativas falhas, adicionando uma camada robusta de defesa.
Por que o CAPTCHA é Crucial para o Login?
Os ataques de força bruta são um método antigo, mas ainda muito usado. Hackers utilizam bots para testar automaticamente milhares de combinações de nome de usuário e senha. Um formulário desprotegido pode levar à invasão de contas administrativas.
- Bloqueia Bots: O CAPTCHA (“Teste de Turing público completamente automatizado para diferenciar computadores de humanos”) é feito para distinguir pessoas de máquinas.
- Aumenta a Dificuldade: Ativar um CAPTCHA após várias falhas aumenta muito o esforço necessário para o invasor.
- Estratégia de Defesa: O CAPTCHA é uma camada adicional de segurança essencial.
Implementar CAPTCHA de forma condicional (apenas quando necessário) mantém uma boa experiência para o usuário legítimo e cria uma barreira para bots.
Passo 1: Modificar o Modelo LoginForm.php
Primeiro, adicione uma propriedade para o CAPTCHA e sua lógica de validação ao modelo `LoginForm`. A chave é tornar o CAPTCHA obrigatório apenas após um certo número de tentativas falhas.
<?php
namespace app\models;
use Yii;
use yii\base\Model;
class LoginForm extends Model
{
public $username;
public $password;
public $rememberMe = true;
public $verifyCode; // Nova propriedade para o CAPTCHA
// ... outro código existente ...
public function rules()
{
return [
// Regras para usuário e senha
[['username', 'password'], 'required'],
['rememberMe', 'boolean'],
['password', 'validatePassword'],
// REGRAS CAPTCHA: Obrigatório apenas se loginFailed for true
['verifyCode', 'required', 'when' => function($model) {
return $model->loginFailed;
}, 'message' => 'O código CAPTCHA é obrigatório após várias falhas.'],
['verifyCode', 'captcha', 'when' => function($model) {
return $model->loginFailed;
}],
];
}
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
// LÓGICA PRINCIPAL: Incrementar o contador de falhas
$attempts = Yii::$app->session->get('_loginAttempts', 0) + 1;
Yii::$app->session->set('_loginAttempts', $attempts);
$this->addError($attribute, 'Nome de usuário ou senha incorretos.');
} else {
// Zerar o contador no sucesso
Yii::$app->session->remove('_loginAttempts');
}
}
}
// Getter para verificar o status de falha
public function getLoginFailed()
{
// CAPTCHA ativa após 3 ou mais falhas
return Yii::$app->session->get('_loginAttempts', 0) >= 3;
}
// ... método getUser() e outros ...
}Explicação do Código:
- A propriedade
$verifyCodearmazena a entrada do CAPTCHA. - O método
getLoginFailed()verifica a variável de sessão_loginAttempts. Retornatruese as falhas forem ≥ 3. - O parâmetro
'when'garante que as regras doverifyCodesó se apliquem seloginFailedfortrue.
Passo 2: Exibir o CAPTCHA na View Login.php
Em seguida, modifique o arquivo da view de login para renderizar o widget CAPTCHA somente quando a condição $model->loginFailed for atendida.
<?php
use yii\helpers\Html;
use yii\bootstrap5\ActiveForm; // ou yii\widgets\ActiveForm
/* @var $this yii\web\View */
/* @var $form yii\widgets\ActiveForm */
/* @var $model app\models\LoginForm */
$this->title = 'Login';
?>
<div class="site-login">
<h1><?= Html::encode($this->title) ?></h1>
<div class="row">
<div class="col-lg-5">
<?php $form = ActiveForm::begin(['id' => 'login-form']); ?>
<?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<?= $form->field($model, 'rememberMe')->checkbox() ?>
<!-- SEÇÃO CONDICIONAL DO CAPTCHA -->
<?php if ($model->loginFailed): ?>
<div class="alert alert-warning">
Muitas tentativas de login falhas. Verifique que você é humano.
</div>
<?= $form->field($model, 'verifyCode')->widget(\yii\captcha\Captcha::className()) ?>
<?php endif; ?>
<div class="form-group">
<?= Html::submitButton('Entrar', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
</div>
</div>
