增加 OAuth 登录
This commit is contained in:
parent
f12758b3a2
commit
162b7a6b4e
@ -107,3 +107,9 @@ ROADRUNNER_VERSION=
|
|||||||
# 实人认证的配置
|
# 实人认证的配置
|
||||||
SUPPORT_REAL_NAME_APP_CODE=
|
SUPPORT_REAL_NAME_APP_CODE=
|
||||||
|
|
||||||
|
# OAuth
|
||||||
|
OAUTH_CLIENT_ID=
|
||||||
|
OAUTH_CLIENT_SECRET=
|
||||||
|
OAUTH_REDIRECT=${APP_URL}/auth/callback
|
||||||
|
OAUTH_DOMAIN=http://oauth.test
|
||||||
|
|
||||||
|
@ -5,15 +5,19 @@
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\User\UserNotification;
|
use App\Notifications\User\UserNotification;
|
||||||
use function back;
|
use Exception;
|
||||||
use function config;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
use function back;
|
||||||
|
use function config;
|
||||||
use function redirect;
|
use function redirect;
|
||||||
use function session;
|
use function session;
|
||||||
use function view;
|
use function view;
|
||||||
@ -33,13 +37,13 @@ public function index(Request $request): View|RedirectResponse
|
|||||||
$dashboardHost = parse_url(config('settings.dashboard.base_url'), PHP_URL_HOST);
|
$dashboardHost = parse_url(config('settings.dashboard.base_url'), PHP_URL_HOST);
|
||||||
|
|
||||||
if ($callbackHost === $dashboardHost) {
|
if ($callbackHost === $dashboardHost) {
|
||||||
if (! $request->user('web')->isRealNamed()) {
|
if (!$request->user('web')->isRealNamed()) {
|
||||||
return redirect()->route('real_name.create')->with('status', '重定向已被打断,需要先实人认证。');
|
return redirect()->route('real_name.create')->with('status', '重定向已被打断,需要先实人认证。');
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = $request->user()->createToken('Dashboard')->plainTextToken;
|
$token = $request->user()->createToken('Dashboard')->plainTextToken;
|
||||||
|
|
||||||
return redirect($callback.'?token='.$token);
|
return redirect($callback . '?token=' . $token);
|
||||||
}
|
}
|
||||||
|
|
||||||
session(['referer.domain' => parse_url($request->header('referer'), PHP_URL_HOST)]);
|
session(['referer.domain' => parse_url($request->header('referer'), PHP_URL_HOST)]);
|
||||||
@ -124,7 +128,7 @@ public function exitSudo(): RedirectResponse
|
|||||||
|
|
||||||
public function showAuthRequest($token): View|RedirectResponse
|
public function showAuthRequest($token): View|RedirectResponse
|
||||||
{
|
{
|
||||||
$data = Cache::get('auth_request:'.$token);
|
$data = Cache::get('auth_request:' . $token);
|
||||||
|
|
||||||
if (empty($data)) {
|
if (empty($data)) {
|
||||||
return redirect()->route('index')->with('error', '登录请求的 Token 不存在或已过期。');
|
return redirect()->route('index')->with('error', '登录请求的 Token 不存在或已过期。');
|
||||||
@ -148,7 +152,7 @@ public function storeAuthRequest(Request $request): RedirectResponse|View
|
|||||||
'token' => 'required|string|max:128',
|
'token' => 'required|string|max:128',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$data = Cache::get('auth_request:'.$request->input('token'));
|
$data = Cache::get('auth_request:' . $request->input('token'));
|
||||||
|
|
||||||
if (empty($data)) {
|
if (empty($data)) {
|
||||||
return back()->with('error', '登录请求的 Token 不存在或已过期。');
|
return back()->with('error', '登录请求的 Token 不存在或已过期。');
|
||||||
@ -172,10 +176,10 @@ public function storeAuthRequest(Request $request): RedirectResponse|View
|
|||||||
$data['token'] = $user->createToken($data['meta']['description'] ?? Carbon::now()->toDateString(), $abilities)->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);
|
||||||
|
|
||||||
if (isset($data['meta']['return_url']) && $data['meta']['return_url']) {
|
if (isset($data['meta']['return_url']) && $data['meta']['return_url']) {
|
||||||
return redirect()->to($data['meta']['return_url'].'?auth_request='.$request->input('token'));
|
return redirect()->to($data['meta']['return_url'] . '?auth_request=' . $request->input('token'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('index')->with('success', '登录请求已确认。');
|
return redirect()->route('index')->with('success', '登录请求已确认。');
|
||||||
@ -183,8 +187,8 @@ public function storeAuthRequest(Request $request): RedirectResponse|View
|
|||||||
|
|
||||||
public function fastLogin(string $token): RedirectResponse
|
public function fastLogin(string $token): RedirectResponse
|
||||||
{
|
{
|
||||||
$cache_key = 'session_login:'.$token;
|
$cache_key = 'session_login:' . $token;
|
||||||
$user_id = Cache::get('session_login:'.$token);
|
$user_id = Cache::get('session_login:' . $token);
|
||||||
|
|
||||||
if (empty($user_id)) {
|
if (empty($user_id)) {
|
||||||
return redirect()->route('index')->with('error', '登录请求的 Token 不存在或已过期。');
|
return redirect()->route('index')->with('error', '登录请求的 Token 不存在或已过期。');
|
||||||
@ -202,4 +206,81 @@ public function fastLogin(string $token): RedirectResponse
|
|||||||
|
|
||||||
return redirect()->route('index');
|
return redirect()->route('index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function redirect(Request $request)
|
||||||
|
{
|
||||||
|
$request->session()->put('state', $state = Str::random(40));
|
||||||
|
|
||||||
|
$query = http_build_query([
|
||||||
|
'client_id' => config('oauth.client_id'),
|
||||||
|
'redirect_uri' => config('oauth.callback_uri'),
|
||||||
|
'response_type' => 'code',
|
||||||
|
'scope' => config('oauth.scope'),
|
||||||
|
'state' => $state,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->to(config('oauth.oauth_auth_url') . '?' . $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function callback(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$authorize = Http::asForm()->throw()->post(config('oauth.oauth_token_url'), [
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'client_id' => config('oauth.client_id'),
|
||||||
|
'client_secret' => config('oauth.client_secret'),
|
||||||
|
'redirect_uri' => config('oauth.callback_uri'),
|
||||||
|
'code' => $request->input('code'),
|
||||||
|
])->getBody();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return redirect()->route('index')->with('error', '无法获取授权。' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$authorize = json_decode($authorize);
|
||||||
|
|
||||||
|
$http = Http::asForm()->withHeaders([
|
||||||
|
'Accept' => 'application/json',
|
||||||
|
'Authorization' => 'Bearer ' . $authorize->access_token
|
||||||
|
])->throw();
|
||||||
|
|
||||||
|
$oauth_user = $http->get(config('oauth.oauth_user_url'))->object();
|
||||||
|
$real_name = $http->get(config('oauth.oauth_real_name_url'))->object();
|
||||||
|
|
||||||
|
$user_sql = User::where('email', $oauth_user->email);
|
||||||
|
$user = $user_sql->first();
|
||||||
|
|
||||||
|
if (is_null($user)) {
|
||||||
|
$name = $oauth_user->name;
|
||||||
|
$email = $oauth_user->email;
|
||||||
|
$email_verified_at = $oauth_user->email_verified_at;
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
|
||||||
|
$user->name = $name;
|
||||||
|
$user->email = $email;
|
||||||
|
$user->email_verified_at = $email_verified_at;
|
||||||
|
$user->real_name_verified_at = $real_name->real_name_verified_at;
|
||||||
|
$user->real_name = $real_name->real_name;
|
||||||
|
$user->id_card = $real_name->id_card;
|
||||||
|
|
||||||
|
|
||||||
|
$user->password = Hash::make(Str::random(16));
|
||||||
|
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$request->session()->put('auth.password_confirmed_at', time());
|
||||||
|
} else {
|
||||||
|
if ($user->name != $oauth_user->name) {
|
||||||
|
User::where('email', $oauth_user->email)->update([
|
||||||
|
'name' => $oauth_user->name
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth::guard('web')->loginUsingId($user->id, true);
|
||||||
|
|
||||||
|
return redirect()->route('index');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
12
config/oauth.php
Normal file
12
config/oauth.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'callback_uri' => env('OAUTH_REDIRECT'),
|
||||||
|
'client_id' => env('OAUTH_CLIENT_ID'),
|
||||||
|
'client_secret' => env('OAUTH_CLIENT_SECRET'),
|
||||||
|
'oauth_auth_url' => env('OAUTH_DOMAIN') . '/oauth/authorize',
|
||||||
|
'oauth_token_url' => env('OAUTH_DOMAIN') . '/oauth/token',
|
||||||
|
'oauth_user_url' => env('OAUTH_DOMAIN') . '/api/user',
|
||||||
|
'oauth_real_name_url' => env('OAUTH_DOMAIN') . '/api/real-name',
|
||||||
|
'scope' => 'realname login user',
|
||||||
|
];
|
@ -8,169 +8,15 @@
|
|||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<h2 id="form-title">注册 或 登录</h2>
|
<h2 id="form-title">使用 LoliArt Account 登录。</h2>
|
||||||
|
|
||||||
<form id="main-form" method="POST" onsubmit="return canSubmit()">
|
<a class="btn btn-primary" href="{{route('login')}}">使用 LoliArt Account 登录</a>
|
||||||
@csrf
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="email" name="email" id="email" class="form-control mb-3 text-center" placeholder="邮箱"
|
|
||||||
aria-label="邮箱" required autofocus>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="suffix-form"></div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<a class="link" href="{{ route('password.request') }}">
|
|
||||||
{{ __('Forgot Your Password?') }}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="d-none">
|
|
||||||
|
|
||||||
<div id="password-input">
|
|
||||||
<div class="form-group mt-2">
|
|
||||||
<input type="password" id="password" name="password"
|
|
||||||
class="form-control rounded-right text-center @error('password') is-invalid @enderror" required
|
|
||||||
placeholder="密码" aria-label="密码">
|
|
||||||
@error('password')
|
|
||||||
<span class="invalid-feedback" role="alert">
|
|
||||||
<strong>{{ $message }}</strong>
|
|
||||||
</span>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group mt-2" id="password-confirm-input">
|
|
||||||
<label for="password-confirm">确认密码</label>
|
|
||||||
<input type="password" id="password-confirm" name="password_confirmation"
|
|
||||||
class="form-control rounded-right" required autocomplete="new-password"
|
|
||||||
placeholder="再次输入您的密码">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="remember-form">
|
|
||||||
<input class="form-check-input" type="hidden" id="remember" name="remember" value="1">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<small id="tip" class="d-block"></small>
|
|
||||||
|
|
||||||
<div class="mt-1" id="tos">如果您继续,则代表您已经阅读并同意 <a
|
|
||||||
href="https://www.laecloud.com/tos/"
|
|
||||||
target="_blank"
|
|
||||||
class="text-decoration-underline">服务条款</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary btn-block mt-2" type="submit" id="login-btn">
|
|
||||||
继续
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const login = "{{ route('login') }}"
|
|
||||||
const register = "{{ route('register') }}"
|
|
||||||
|
|
||||||
const mainIcon = document.getElementById('main-icon')
|
|
||||||
const email = document.getElementById('email');
|
|
||||||
const title = document.getElementById('form-title');
|
|
||||||
const formSuffix = document.getElementById('suffix-form')
|
|
||||||
const rememberForm = document.getElementById('remember-form')
|
|
||||||
const passwordInput = document.getElementById('password-input')
|
|
||||||
const passwordConfirmInput = document.getElementById('password-confirm-input')
|
|
||||||
const loginBtn = document.getElementById('login-btn')
|
|
||||||
const nameInput = document.getElementById('name')
|
|
||||||
const mainForm = document.getElementById('main-form')
|
|
||||||
const tos = document.getElementById('tos')
|
|
||||||
const tip = document.getElementById('tip')
|
|
||||||
|
|
||||||
@error('password')
|
|
||||||
title.innerText = "注册莱云"
|
|
||||||
formSuffix.appendChild(rememberForm)
|
|
||||||
|
|
||||||
|
|
||||||
@enderror
|
|
||||||
|
|
||||||
@error('email')
|
|
||||||
title.innerText = "密码错误"
|
|
||||||
email.value = "{{ old('email') }}"
|
|
||||||
formSuffix.appendChild(passwordInput)
|
|
||||||
formSuffix.appendChild(rememberForm)
|
|
||||||
formSuffix.appendChild(tos)
|
|
||||||
formSuffix.appendChild(loginBtn)
|
|
||||||
loginBtn.innerText = '登录'
|
|
||||||
@enderror
|
|
||||||
|
|
||||||
let canSubmit = function () {
|
|
||||||
return (email.value !== '' && passwordInput.value !== '')
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateUrl = "{{ route('login.exists-if-user') }}"
|
|
||||||
|
|
||||||
email.onchange = function (ele) {
|
|
||||||
const target = ele.target
|
|
||||||
|
|
||||||
if (email.value === '') {
|
|
||||||
title.innerText = "输入邮箱"
|
|
||||||
|
|
||||||
formSuffix.innerHTML = ''
|
|
||||||
|
|
||||||
|
|
||||||
mainIcon.classList.remove(...mainIcon.classList)
|
|
||||||
mainIcon.classList.add('bi', 'bi-person-circle')
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
formSuffix.innerHTML = ''
|
|
||||||
formSuffix.appendChild(passwordInput)
|
|
||||||
|
|
||||||
axios.post(validateUrl, {
|
|
||||||
email: target.value
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
mainForm.action = login
|
|
||||||
|
|
||||||
mainIcon.classList.remove(...mainIcon.classList)
|
|
||||||
mainIcon.classList.add('bi', 'bi-person-check')
|
|
||||||
|
|
||||||
title.innerText = "欢迎, " + res.data.name
|
|
||||||
|
|
||||||
formSuffix.appendChild(passwordInput)
|
|
||||||
formSuffix.appendChild(rememberForm)
|
|
||||||
formSuffix.appendChild(tos)
|
|
||||||
formSuffix.appendChild(loginBtn)
|
|
||||||
loginBtn.innerText = '登录'
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
mainForm.action = register
|
|
||||||
|
|
||||||
title.innerText = "注册莱云"
|
|
||||||
|
|
||||||
mainIcon.classList.remove(...mainIcon.classList)
|
|
||||||
mainIcon.classList.add('bi', 'bi-person-plus')
|
|
||||||
|
|
||||||
formSuffix.appendChild(passwordInput)
|
|
||||||
formSuffix.appendChild(tos)
|
|
||||||
formSuffix.appendChild(tip)
|
|
||||||
|
|
||||||
formSuffix.appendChild(loginBtn)
|
|
||||||
|
|
||||||
tip.innerText = '当您注册后,我们将为您分配随机用户名。'
|
|
||||||
|
|
||||||
loginBtn.innerText = '注册'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
Route::get('/', [AuthController::class, 'index'])->middleware('banned')->name('index');
|
Route::get('/', [AuthController::class, 'index'])->middleware('banned')->name('index');
|
||||||
|
|
||||||
Route::prefix('auth')->group(function () {
|
Route::prefix('auth')->group(function () {
|
||||||
Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
|
// Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
|
||||||
Route::post('login', [LoginController::class, 'login']);
|
// Route::post('login', [LoginController::class, 'login']);
|
||||||
Route::post('logout', [LoginController::class, 'logout'])->name('logout');
|
Route::post('logout', [LoginController::class, 'logout'])->name('logout');
|
||||||
Route::post('exists', [LoginController::class, 'userIfExists'])->name('login.exists-if-user');
|
Route::post('exists', [LoginController::class, 'userIfExists'])->name('login.exists-if-user');
|
||||||
|
|
||||||
@ -40,6 +40,9 @@
|
|||||||
Route::post('email/resend', [VerificationController::class, 'resend'])->name('verification.resend');
|
Route::post('email/resend', [VerificationController::class, 'resend'])->name('verification.resend');
|
||||||
|
|
||||||
Route::get('token/{token}', [AuthController::class, 'fastLogin'])->name('auth.fast-login');
|
Route::get('token/{token}', [AuthController::class, 'fastLogin'])->name('auth.fast-login');
|
||||||
|
|
||||||
|
Route::get('redirect', [AuthController::class, 'redirect'])->name('login');
|
||||||
|
Route::get('callback', [AuthController::class, 'callback'])->name('callback');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware(['auth:web', 'banned', 'verified'])->group(
|
Route::middleware(['auth:web', 'banned', 'verified'])->group(
|
||||||
|
Loading…
Reference in New Issue
Block a user