BastionSSO/app/Http/Controllers/Api/OauthTokenController.php

123 lines
4.5 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Exceptions\OAuthProtocolException;
use App\Http\Controllers\Controller;
use App\Models\OauthClient;
use App\Services\OAuth\OAuthClientAuthService;
use App\Services\OAuth\OAuthTokenService;
use hg\apidoc\annotation as Apidoc;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
#[Apidoc\Title('OAuth 协议令牌端点')]
class OauthTokenController extends Controller
{
public function __construct(
private readonly OAuthClientAuthService $clientAuthService,
private readonly OAuthTokenService $tokenService
) {}
#[Apidoc\Title('OAuth Token'), Apidoc\Method('POST'), Apidoc\Url('/oauth/token')]
public function token(Request $request): JsonResponse
{
try {
$client = $this->clientAuthService->authenticateConfidentialClient($request);
$grantType = trim((string) $request->input('grant_type', ''));
$responseData = match ($grantType) {
'authorization_code' => $this->handleAuthorizationCodeGrant($request, $client),
'refresh_token' => $this->handleRefreshTokenGrant($request, $client),
default => throw new OAuthProtocolException('unsupported_grant_type', 'Unsupported grant_type.'),
};
return response()
->json($responseData)
->header('Cache-Control', 'no-store')
->header('Pragma', 'no-cache');
} catch (OAuthProtocolException $exception) {
$response = response()
->json($exception->toResponsePayload(), $exception->httpStatus)
->header('Cache-Control', 'no-store')
->header('Pragma', 'no-cache');
if ($exception->oauthError === 'invalid_client') {
$response->header('WWW-Authenticate', 'Basic realm="OAuth"');
}
return $response;
}
}
#[Apidoc\Title('OAuth Revoke'), Apidoc\Method('POST'), Apidoc\Url('/oauth/revoke')]
public function revoke(Request $request): JsonResponse
{
try {
$client = $this->clientAuthService->authenticateConfidentialClient($request);
$validator = Validator::make($request->all(), [
'token' => ['required', 'string'],
'token_type_hint' => ['nullable', 'string'],
]);
if ($validator->fails()) {
throw new OAuthProtocolException('invalid_request', (string) $validator->errors()->first());
}
$this->tokenService->revokeTokenForClient($client, trim((string) $request->input('token')));
return response()->json([], 200);
} catch (OAuthProtocolException $exception) {
$response = response()->json($exception->toResponsePayload(), $exception->httpStatus);
if ($exception->oauthError === 'invalid_client') {
$response->header('WWW-Authenticate', 'Basic realm="OAuth"');
}
return $response;
}
}
/**
* @return array<string, mixed>
*/
private function handleAuthorizationCodeGrant(Request $request, OauthClient $client): array
{
$validator = Validator::make($request->all(), [
'code' => ['required', 'string'],
'redirect_uri' => ['required', 'url:http,https'],
]);
if ($validator->fails()) {
throw new OAuthProtocolException('invalid_request', (string) $validator->errors()->first());
}
$redirectUri = (string) $request->input('redirect_uri');
if (! $this->clientAuthService->isRedirectUriAllowed($client, $redirectUri)) {
throw new OAuthProtocolException('invalid_grant', 'redirect_uri mismatch.');
}
return $this->tokenService->exchangeAuthorizationCode(
client: $client,
code: trim((string) $request->input('code')),
redirectUri: $redirectUri
);
}
/**
* @return array<string, mixed>
*/
private function handleRefreshTokenGrant(Request $request, OauthClient $client): array
{
$validator = Validator::make($request->all(), [
'refresh_token' => ['required', 'string'],
]);
if ($validator->fails()) {
throw new OAuthProtocolException('invalid_request', (string) $validator->errors()->first());
}
return $this->tokenService->exchangeRefreshToken(
client: $client,
refreshToken: trim((string) $request->input('refresh_token'))
);
}
}