改进 跨越

允许所有 CORS
根据 URL 来设置权限
This commit is contained in:
iVampireSP.com 2023-02-17 20:58:20 +08:00
parent cc41963734
commit 5a1d9df976
No known key found for this signature in database
GPG Key ID: 2F7B001CA27A8132
9 changed files with 142 additions and 32 deletions

View File

@ -98,8 +98,6 @@ USER_GROUP_BIRTHDAY=1
DASHBOARD_BASE_URL=https://dash.laecloud.com
DASHBOARD_BIRTHDAY_PATH=/stars
CORS_ORIGINS=${DASHBOARD_BASE_URL},https://web.laecloud.com,http://localhost:5173
# 可信代理,用于获取真实 IP。多个 IP 用逗号分隔。
TRUSTED_PROXIES=

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Public;
use App\Http\Controllers\Controller;
use App\Rules\Domain;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
@ -15,6 +16,7 @@ public function store(Request $request): JsonResponse
$request->validate([
'description' => 'required|string|max:255',
'require_token' => 'nullable|boolean',
'abilities' => 'nullable|array|max:255',
]);
$token = Str::random(128);
@ -24,6 +26,7 @@ public function store(Request $request): JsonResponse
'description' => $request->input('description'),
'token' => $token,
'require_token' => $request->input('require_token', false),
'abilities' => $request->input('abilities'),
],
];

View File

@ -6,14 +6,15 @@
use App\Http\Controllers\Controller;
use App\Notifications\User\UserNotification;
use function back;
use function config;
use App\Rules\Domain;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
use function back;
use function config;
use function redirect;
use function session;
use function view;
@ -33,13 +34,17 @@ public function index(Request $request): View|RedirectResponse
$dashboardHost = parse_url(config('settings.dashboard.base_url'), PHP_URL_HOST);
if ($callbackHost === $dashboardHost) {
if (! $request->user('web')->isRealNamed()) {
if (!$request->user('web')->isRealNamed()) {
return redirect()->route('real_name.create')->with('status', '重定向已被打断,需要先实人认证。');
}
$token = $request->user()->createToken('Dashboard')->plainTextToken;
$requestHost = parse_url($request->header('referer'), PHP_URL_HOST);
return redirect($callback.'?token='.$token);
$token = $request->user()->createToken('Dashboard', [
'domain-access:' . $requestHost,
])->plainTextToken;
return redirect($callback . '?token=' . $token);
}
return redirect()->route('confirm_redirect');
@ -80,9 +85,24 @@ public function newToken(Request $request): RedirectResponse
{
$request->validate([
'name' => 'required|string|max:255',
'domain' => ['nullable', 'string', 'max:255', new Domain],
]);
$token = $request->user()->createToken($request->input('name'));
$abilities = [];
if ($request->has('domain')) {
// 检测是不是一个合格的域名
if (!preg_match('/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/', $request->input('domain'))) {
return back()->with('error', '域名格式不正确。');
}
$abilities = ['domain-access:' . $request->input('domain')];
}
$token = $request->user()->createToken(
$request->input('name'),
$abilities
);
return back()->with('token', $token->plainTextToken);
}
@ -115,7 +135,7 @@ public function exitSudo(): RedirectResponse
public function showAuthRequest($token): View|RedirectResponse
{
$data = Cache::get('auth_request:'.$token);
$data = Cache::get('auth_request:' . $token);
if (empty($data)) {
return redirect()->route('index')->with('error', '登录请求的 Token 不存在或已过期。');
@ -139,7 +159,7 @@ public function storeAuthRequest(Request $request): RedirectResponse
'token' => 'required|string|max:128',
]);
$data = Cache::get('auth_request:'.$request->input('token'));
$data = Cache::get('auth_request:' . $request->input('token'));
if (empty($data)) {
return back()->with('error', '登录请求的 Token 不存在或已过期。');
@ -157,11 +177,13 @@ public function storeAuthRequest(Request $request): RedirectResponse
'real_name_verified_at',
]);
$abilities = $data['meta']['abilities'] ?? ['*'];
if (isset($data['meta']['require_token']) && $data['meta']['require_token']) {
$data['token'] = $user->createToken($data['meta']['description'] ?? Carbon::now()->toDateString())->plainTextToken;
$data['token'] = $user->createToken($data['meta']['description'] ?? Carbon::now()->toDateString(), $abilities)->plainTextToken;
}
Cache::put('auth_request:'.$request->input('token'), $data, 60);
Cache::put('auth_request:' . $request->input('token'), $data, 60);
return redirect()->route('index')->with('success', '登录请求已确认。');
}

View File

@ -13,11 +13,11 @@
use App\Http\Middleware\RedirectIfAuthenticated;
use App\Http\Middleware\ReportRequestToCluster;
use App\Http\Middleware\TrimStrings;
use App\Http\Middleware\TrustedDomain;
use App\Http\Middleware\TrustProxies;
use App\Http\Middleware\ValidateSignature;
use App\Http\Middleware\ValidateUserIfBanned;
use App\Http\Middleware\VerifyCsrfToken;
use Fruitcake\Cors\HandleCors;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Auth\Middleware\EnsureEmailIsVerified;
@ -26,6 +26,7 @@
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Http\Middleware\HandleCors;
use Illuminate\Http\Middleware\SetCacheHeaders;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Routing\Middleware\ThrottleRequests;
@ -45,12 +46,11 @@ class Kernel extends HttpKernel
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
TrustProxies::class,
// \Illuminate\Http\Middleware\HandleCors::class,
HandleCors::class,
PreventRequestsDuringMaintenance::class,
ValidatePostSize::class,
TrimStrings::class,
ConvertEmptyStringsToNull::class,
HandleCors::class,
AddHeaders::class,
];
@ -72,6 +72,7 @@ class Kernel extends HttpKernel
'api' => [
JsonRequest::class,
TrustedDomain::class,
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
SubstituteBindings::class,

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class TrustedDomain
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure(Request): (Response|RedirectResponse) $next
*
* @return mixed
*/
public function handle(Request $request, Closure $next): mixed
{
$user = $request->user('sanctum');
if (!$user) {
return $next($request);
}
// 获取请求的域名
$requestHost = parse_url($request->header('referer'), PHP_URL_HOST);
if ($requestHost) {
// 获取当前域名
$currentHost = parse_url(config('app.url'), PHP_URL_HOST);
// 如果请求的域名和当前域名相同,则直接放行
if ($requestHost === $currentHost) {
return $next($request);
}
return $user->tokenCan('domain-access:' . $requestHost) ? $next($request) : response()->json([
'message' => 'Token 无权访问此域名。',
], 401);
}
return $next($request);
}
}

31
app/Rules/Domain.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class Domain implements Rule
{
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value): bool
{
// 验证域名是否合法
return preg_match('/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/', $value);
}
/**
* Get the validation error message.
*
* @return string
*/
public function message(): string
{
return '域名格式不正确。';
}
}

View File

@ -1,11 +1,5 @@
<?php
$cors_origin = explode(',', env('CORS_ORIGINS'));
if (env('APP_ENV') === 'local') {
$cors_origin = ['*'];
}
return [
/*
@ -25,7 +19,7 @@
'allowed_methods' => ['*'],
'allowed_origins' => $cors_origin,
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],

View File

@ -7,7 +7,6 @@
<h3>
<code>
@if (isset($data['module']) && !is_null($data['module']))
)
<span>模块:{{ $data['module']['name'] }}</span>
@elseif (isset($data['applications']) && !is_null($data['application']))
<span>应用程序:{{ $data['application']['name'] }}</span>
@ -23,18 +22,26 @@
<p>{{ $data['meta']['description'] }}</p>
<br/>
<p>
在您同意后,您的 <b>ID</b>, <b>UUID</b>, <b>昵称</b>, <b>邮件信息 实人认证成功的时间(不包含个人信息)</b>, <b>余额</b>,
<b>用户组 ID</b> 将会被发送给它们。
@if ($data['meta']['require_token'])
<br />
你的 <b>Token</b> 将会新建一个,并发送给它们。
@endif
</p>
在您同意后,您的 <b>ID</b>, <b>UUID</b>, <b>昵称</b>, <b>邮件信息 实人认证成功的时间(不包含个人信息)</b>,
<b>余额</b>,
<b>用户组 ID</b> 将会被发送给它们。
@if ($data['meta']['require_token'])
<br/>
你的 <b>Token</b> 将会新建一个,并发送给它们。
@endif
@if (isset($data['meta']['abilities']))
<div>
权限列表:
@foreach($data['meta']['abilities'] as $ability)
<b>{{ $ability }}</b>
@endforeach
</div>
@endif
@auth('web')
<form method="POST" action="{{ route('auth_request.store') }}">
<form method="POST" action="{{ route('auth_request.store') }}" class="mt-3">
@csrf
<input type="hidden" name="token" value="{{ $data['meta']['token'] }}">
<button type="submit" class="btn btn-primary">同意</button>

View File

@ -45,12 +45,19 @@
<form action="{{ route('token.new') }}" name="newToken" method="POST">
@csrf
<div class="form-floating mb-2">
<input type="text" class="form-control" placeholder="Token 名称"
aria-label="密钥名称" name="name" required maxlength="25">
<label>Token 名称</label>
</div>
<div class="form-floating mb-2">
<input type="text" class="form-control" placeholder="授权的域名"
aria-label="授权的域名" name="domain" maxlength="255">
<label>授权的域名</label>
</div>
<button type="submit" class="btn btn-primary visually-hidden">
创建
</button>