在 BookStack 中添加 reCAPTCHA v2 注册验证
请为此 Issue 点赞以在未来获得官方支持:https://github.com/BookStackApp/BookStack/issues/1114
本文对应版本:BookStack v22.04.2
说难也不难,因为作者已经提供了示例代码,但是你可能会遇到 CSP 问题。
BookStack 使用了 strict-dynamic 所以需要使用 CSP3 中记录的基于 nonce 的方法来插入脚本。
但是我们并不知道 BookStack 的 nonce 随机数变量是什么。很惭愧,我也是问了作者才知道的:Issue 3066。
然后 reCAPTCHA V2 复选框会以 iframe 的形式插入,所以你需要在 .env 的 ALLOWED_IFRAME_SOURCES 中添加对应的源,
国际用户应该添加:https://www.google.com
, https://www.gstatic.com
,https://www.google.com
, https://recaptcha.google.com
国内用户应该添加:https://recaptcha.net
, https://*.gstatic.cn
, https://*.gstatic.com
recaptcha.net 是 Google 提供的官方代理:https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally
专门提供给中国大陆用户使用,其他地区使用 google.com
即可
首先在 Issues 1114 中我们可以找到作者提供的简单示例:https://github.com/BookStackApp/BookStack/commit/c49454da2833eb31b426e6b6ecfd790fad58567b
打开示例,对应路径将代码插入的你的 BookStack 中。
如果你的版本和我一样的话,也可以直接复制使用,记得更改变量。
1. 打开 /app/Http/Controllers/Auth/RegisterController.php
对照下文修改,我已经注释出来了。
代码
<?php
namespace BookStack\Http\Controllers\Auth;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Auth\User;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
//自定义recaptcha
use GuzzleHttp\Client;
//自定义recaptcha
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
protected $socialAuthService;
protected $registrationService;
protected $loginService;
/**
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/';
protected $redirectPath = '/';
/**
* Create a new controller instance.
*/
public function __construct(
SocialAuthService $socialAuthService,
RegistrationService $registrationService,
LoginService $loginService
) {
$this->middleware('guest');
$this->middleware('guard:standard');
$this->socialAuthService = $socialAuthService;
$this->registrationService = $registrationService;
$this->loginService = $loginService;
$this->redirectTo = url('/');
$this->redirectPath = url('/');
}
/**
* Get a validator for an incoming registration request.
*
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'min:2', 'max:255'],
'email' => ['required', 'email', 'max:255', 'unique:users'],
'password' => ['required', Password::default()],
]);
}
/**
* Show the application registration form.
*
* @throws UserRegistrationException
*/
public function getRegister()
{
$this->registrationService->ensureRegistrationAllowed();
$socialDrivers = $this->socialAuthService->getActiveDrivers();
return view('auth.register', [
'socialDrivers' => $socialDrivers,
]);
}
/**
* Handle a registration request for the application.
*
* @throws UserRegistrationException
* @throws StoppedAuthenticationException
*/
public function postRegister(Request $request)
{
$this->registrationService->ensureRegistrationAllowed();
$this->validator($request->all())->validate();
//自定义recaptcha
$captcha = $request->get('g-recaptcha-response');
$resp = (new Client())->post('https://www.recaptcha.net/recaptcha/api/siteverify', [
'form_params' => [
'response' => $captcha,
'secret' => '你的 reCAPTCHA 秘钥',
]
]);
$respBody = json_decode($resp->getBody());
if (!$respBody->success) {
return redirect()->back()->withInput()->withErrors([
'g-recaptcha-response' => '请完成人机验证,如果未显示验证码,请联系管理员',
]);
}
//自定义recaptcha
$userData = $request->all();
try {
$user = $this->registrationService->registerUser($userData);
$this->loginService->login($user, auth()->getDefaultDriver());
} catch (UserRegistrationException $exception) {
if ($exception->getMessage()) {
$this->showErrorNotification($exception->getMessage());
}
return redirect($exception->redirectLocation);
}
$this->showSuccessNotification(trans('auth.register_success'));
return redirect($this->redirectPath());
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
*
* @return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}
2.打开 /resources/views/auth/register.blade.php
对照下文修改,我已经注释出来了。
代码
@extends('layouts.simple')
@section('content')
<div class="container very-small">
<div class="my-l"> </div>
<div class="card content-wrap auto-height">
<h1 class="list-heading">{{ Str::title(trans('auth.sign_up')) }}</h1>
<form action="{{ url("/register") }}" method="POST" class="mt-l stretch-inputs">
{!! csrf_field() !!}
<div class="form-group">
<label for="email">{{ trans('auth.name') }}</label>
@include('form.text', ['name' => 'name'])
</div>
<div class="form-group">
<label for="email">{{ trans('auth.email') }}</label>
@include('form.text', ['name' => 'email'])
</div>
<div class="form-group">
<label for="password">{{ trans('auth.password') }}</label>
@include('form.password', ['name' => 'password', 'placeholder' => trans('auth.password_hint')])
</div>
<!-- 自定义recaptcha -->
<div class="form-group">
<label for="recaptcha">验证码</label>
<script src="https://recaptcha.net/recaptcha/api.js" nonce="{{ $cspNonce }}"></script>
<div class="g-recaptcha" data-sitekey="你的 reCAPTCHA 网站秘钥"></div>
<br/>
@if($errors->has('g-recaptcha-response'))
<div class="text-neg text-small">{{ $errors->first('g-recaptcha-response') }}</div>
@endif
</div>
<!-- 自定义recaptcha -->
<div class="grid half collapse-xs gap-xl v-center mt-m">
<div class="text-small">
<a href="{{ url('/login') }}">{{ trans('auth.already_have_account') }}</a>
</div>
<div class="from-group text-right">
<button class="button">{{ trans('auth.create_account') }}</button>
</div>
</div>
</form>
@if(count($socialDrivers) > 0)
<hr class="my-l">
@foreach($socialDrivers as $driver => $name)
<div>
<a id="social-register-{{$driver}}" class="button outline svg" href="{{ url("/register/service/" . $driver) }}">
@icon('auth/' . $driver)
<span>{{ trans('auth.sign_up_with', ['socialDriver' => $name]) }}</span>
</a>
</div>
@endforeach
@endif
</div>
</div>
@stop
3.打开 .env
找到 ALLOWED_IFRAME_SOURCES
,修改为:
ALLOWED_IFRAME_SOURCES="https://recaptcha.net https://*.gstatic.cn https://*.gstatic.com"
4.大功告成
No Comments