providers:
# users_in_memory: { memory: null }
app_user_provider:
entity:
class: App\Entity\User
firewalls:
login:
pattern: ^/api/(login|token/refresh)
stateless: true
json_login:
check_path: /api/login_check
success_handler: App\Security\CustomAuthenticationSuccessHandler
failure_handler: App\Security\CustomAuthenticationFailureHandler
refresh_jwt:
check_path: /api/token/refresh
provider: app_user_provider
api:
pattern: ^/api
stateless: true
jwt:
provider: app_user_provider
authenticator: app.custom_jwt_authenticator
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/api/login_check, roles: PUBLIC_ACCESS }
- { path: ^/api/(login|token/refresh), roles: PUBLIC_ACCESS }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
主要修改内容为:
修改了默认的 success_handler 和 failure_handler
对于 api 防火墙,添加了自定义的jwt认证
//CustomAuthenticationSuccessHandler
<?php
namespace App\Security;
use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface;
use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface
{
public function __construct(
private JWTTokenManagerInterface $jwtManager,
private RefreshTokenManagerInterface $refreshTokenManager,
private ParameterBagInterface $params,
) {}
public function onAuthenticationSuccess(Request $request, TokenInterface $token): JsonResponse
{
$user = $token->getUser();
$jwt = $this->jwtManager->create($user);
// 读取配置的 ttl
$ttl = $this->params->get('gesdinet_jwt_refresh_token.ttl');
$validUntil = (new \DateTimeImmutable())->modify("+$ttl seconds");
$refreshTokenString = $this->generateSecureRefreshToken();
$refreshToken = new RefreshToken();
$refreshToken
->setRefreshToken($refreshTokenString)
->setUsername($user->getUserIdentifier())
->setValid($validUntil);
$this->refreshTokenManager->save($refreshToken);
return new JsonResponse([
'access_token' => $jwt,
'refresh_token' => $refreshTokenString,
]);
}
private function generateSecureRefreshToken(): string
{
try {
return 'rt_' . bin2hex(random_bytes(40)); // 40 bytes = 80 hex chars
} catch (\Exception $e) {
throw new \RuntimeException('can not generate a security refresh token', 0, $e);
}
}
}
//CustomAuthenticationFailureHandler
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
class CustomAuthenticationFailureHandler implements AuthenticationFailureHandlerInterface
{
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
{
$message = 'Authentication failed.';
if ($exception instanceof BadCredentialsException) {
$message = 'Invalid username or password.';
} elseif ($exception instanceof CustomUserMessageAuthenticationException) {
$message = $exception->getMessage();
}
return new JsonResponse(['status' => 401, 'message' => $message], JsonResponse::HTTP_UNAUTHORIZED);
}
}
//CustomJwtAuthenticator
<?php
namespace App\Security;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\JWTAuthenticator;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException as SymfonyAuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class CustomJwtAuthenticator extends JWTAuthenticator
{
public function __construct(
JWTTokenManagerInterface $jwtManager,
EventDispatcherInterface $eventDispatcher,
TokenExtractorInterface $tokenExtractor,
UserProviderInterface $userProvider,
?TranslatorInterface $translator = null,
) {
parent::__construct($jwtManager, $eventDispatcher, $tokenExtractor, $userProvider, $translator);
}
public function supports(Request $request): ?bool
{
return str_starts_with($request->getPathInfo(), '/api/');
}
public function authenticate(Request $request): Passport
{
try {
$passport = parent::authenticate($request);
} catch (\LogicException $exception) {
throw new CustomUserMessageAuthenticationException("User not logged in. Please authenticate to proceed.:".$exception->getMessage());
} catch (SymfonyAuthenticationException $exception) {
throw new CustomUserMessageAuthenticationException("Login failed.:".$exception->getMessage());
}
return $passport;
}
}
然后在services.yaml下配置
app.custom_jwt_authenticator:
class: App\Security\CustomJwtAuthenticator
parent: lexik_jwt_authentication.security.jwt_authenticator
请求地址:
http://symfony.api.local/api/lucky/number
参数:
header 参数
Authorization:Bearer {{access_token}}
响应:
{
"code": 401,
"message": "Login failed.:"
}
发现是使用的是错误的token
使用最新的access_token:
<html>
<body>
Lucky number: 44
</body>
</html>
请求成功