gesdinet/jwt-refresh-token-bundle

在Symfony中实现基于Json Web Tokens的刷新令牌系统

安装次数: 10,888,331

依赖项: 36

建议者: 2

安全: 0

星标: 649

关注者: 10

分支: 159

开放问题: 79

类型:symfony-bundle

v1.3.0 2024-01-10 19:40 UTC

README

Scrutinizer Code Quality Run Tests Code Coverage Latest Stable Version Total Downloads License StyleCI

本扩展包的目的是以简单的方式管理JWT(Json Web Tokens)的刷新令牌。此扩展包使用了LexikJWTAuthenticationBundle。支持Doctrine ORM/ODM。

先决条件

此扩展包需要PHP 7.4或更高版本以及Symfony 4.4、5.4或6.0+。

对于旧版Symfony版本的支持,请使用0.12版本。

技巧:尽管扩展包不会强制您这样做,但强烈建议使用HTTPS。

安装

步骤1:下载扩展包

您还必须安装Doctrine ORM或MongoDB ODM,这些包不会随此扩展包自动安装。未这样做可能会导致安装时出错。

如果使用Symfony 4.4,还需要安装symfony/security-guard包,它仅适用于旧版认证API,并且与Symfony 6.0不兼容。

使用Doctrine ORM

composer require doctrine/orm doctrine/doctrine-bundle gesdinet/jwt-refresh-token-bundle

使用Doctrine MongoDB ODM

composer require doctrine/mongodb-odm doctrine/mongodb-odm-bundle gesdinet/jwt-refresh-token-bundle

或者,手动编辑项目的composer.json文件,添加所需的包

{
  "require": {
    "doctrine/doctrine-bundle": "^2.0",
    "doctrine/mongodb-odm": "^2.0",
    "doctrine/mongodb-odm-bundle": "^4.0",
    "doctrine/orm": "^2.7",
    "gesdinet/jwt-refresh-token-bundle": "^1.0"
  }
}

或者,可以使用自定义持久层。

为此,您必须

步骤2:启用扩展包

Symfony Flex应用程序

对于使用Symfony Flex的应用程序,扩展包应自动注册。如果没有,则需要将其添加到config/bundles.php文件中。

<?php

return [
    //...
    Gesdinet\JWTRefreshTokenBundle\GesdinetJWTRefreshTokenBundle::class => ['all' => true],
];

步骤3:配置扩展包

Symfony Flex应用程序

对于使用Symfony Flex的应用程序,应已应用配方。如果没有,则需要做出以下更改

  1. 配置刷新令牌类。创建config/packages/gesdinet_jwt_refresh_token.yaml文件,内容如下
gesdinet_jwt_refresh_token:
    refresh_token_class: App\Entity\RefreshToken # This is the class name of the refresh token, you will need to adjust this to match the class your application will use
  1. 创建对象类。

如果您使用的是Doctrine ORM,则以下内容应放置在src/Entity/RefreshToken.php(使用注解或属性)

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken as BaseRefreshToken;

/**
 * @ORM\Entity
 * @ORM\Table("refresh_tokens")
 */
#[ORM\Entity]
#[ORM\Table(name: 'refresh_tokens')]
class RefreshToken extends BaseRefreshToken
{
}

如果您使用的是Doctrine MongoDB ODM,则以下内容应放置在src/Document/RefreshToken.php(请记住更新上述refresh_token_class配置以匹配)

<?php

namespace App\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Gesdinet\JWTRefreshTokenBundle\Document\RefreshToken as BaseRefreshToken;

/**
 * @ODM\Document(collection="refresh_tokens")
 */
class RefreshToken extends BaseRefreshToken
{
}

步骤4(Symfony 5.4+)

定义刷新令牌路由

打开您的路由配置文件,并添加以下路由

# config/routes.yaml
api_refresh_token:
    path: /api/token/refresh
# ...

配置认证器

为了启用认证器,您应该将其添加到API防火墙(与json_loginjwt认证器一起)。

完整的防火墙配置应类似于以下内容

# config/packages/security.yaml
security:
    # this config is only required on Symfony 5.4, you can leave it out on Symfony 6
    enable_authenticator_manager: true

    firewalls:
        api:
            pattern: ^/api
            stateless: true
            entry_point: jwt
            json_login:
                check_path: /api/login # or, if you have defined a route for your login path, the route name you used
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
            jwt: ~
            refresh_jwt:
                check_path: /api/token/refresh # or, you may use the `api_refresh_token` route name
                # or if you have more than one user provider
                # provider: user_provider_name
    # ...

    access_control:
        # ...
        - { path: ^/api/(login|token/refresh), roles: PUBLIC_ACCESS }
        # ...
# ...

步骤4(Symfony 4.4)

定义刷新令牌路由

打开您的路由配置文件,并添加以下路由

# config/routes.yaml
api_refresh_token:
    path:       /api/token/refresh
    controller: gesdinet.jwtrefreshtoken::refresh
# ...

配置安全防火墙

将以下内容添加到您的安全配置文件中

# config/packages/security.yaml
security:
    firewalls:
        # put it before all your other firewall API entries
        refresh:
            pattern:  ^/api/token/refresh
            stateless: true
            anonymous: true
            # or if you have more than one user provider
            #provider: user_provider_name
    # ...

    access_control:
        # ...
        - { path: ^/api/token/refresh, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        # ...
# ...

步骤5:更新您的数据库模式

您需要将刷新令牌表添加到应用程序的数据库中。

使用迁移

# If using the MakerBundle:
php bin/console make:migration
# Without the MakerBundle:
php bin/console doctrine:migrations:diff

php bin/console doctrine:migrations:migrate

不使用迁移(不建议

php bin/console doctrine:schema:update --force

用法

以下选项可以通过扩展包的配置在config/packages/gesdinet_jwt_refresh_token.yaml文件中配置(如果尚未创建,请确保创建它)。

令牌TTL

您可以定义刷新令牌的TTL,此值以秒为单位,默认为1个月。您可以通过添加以下行到配置来更改此值

gesdinet_jwt_refresh_token:
    ttl: 2592000

更新令牌TTL

您可以通过在配置文件中添加以下行来配置捆绑包,使其在刷新令牌被使用时刷新TTL,默认情况下此功能是禁用的。

gesdinet_jwt_refresh_token:
    ttl_update: true

配置防火墙名称

注意 此设置已弃用,并且不与 refresh_jwt 认证器一起使用。

您可以定义防火墙名称。默认值是 api。您可以通过在配置文件中添加以下行来更改此值。

gesdinet_jwt_refresh_token:
    firewall: api

刷新令牌参数名称

您可以在从请求中读取刷新令牌时定义参数名称,默认值是 refresh_token。您可以通过在配置文件中添加以下行来更改此值。

gesdinet_jwt_refresh_token:
    token_parameter_name: refreshToken

返回过期时间戳

如果设置为true,将向响应中添加过期Unix时间戳。

gesdinet_jwt_refresh_token:
    return_expiration: true

默认参数名称是 refresh_token_expiration。您可以通过向配置文件中添加以下行来更改参数名称并更改它。

gesdinet_jwt_refresh_token:
    return_expiration_parameter_name: refresh_token_expiration

设置用户提供者

Symfony 5.4+

您可以为认证器的配置定义一个用户提供者。

注意,如果您的应用程序有多个用户提供者,您 必须 为防火墙或提供者配置此值。

# app/config/security.yml or config/packages/security.yaml
security:
    firewalls:
        api:
            pattern: ^/api
            stateless: true
            entry_point: jwt
            json_login: ~
            jwt: ~
            refresh_jwt:
                check_path: /api/token/refresh
                provider: user_provider_service_id

默认情况下,如果没有指定用户提供者,则使用防火墙的用户提供者。

Symfony 4.4

注意 此设置已弃用,并且不与 refresh_jwt 认证器一起使用。

您可以通过默认使用 gesdinet.jwtrefreshtoken.user_provider 服务来定义自己的用户提供者。您可以通过在配置文件中添加以下行来更改此值。

gesdinet_jwt_refresh_token:
    user_provider: user_provider_service_id

例如,如果您正在使用FOSUserBundle,则必须将 user_provider 设置为 fos_user.user_provider.username_email

对于Doctrine ORM UserProvider,必须将 user_provider 设置为 security.user.provider.concrete.<your_user_provider_name_in_security_yaml>

例如,在您的 config/packages/security.yaml 文件中

security:
    # ...
    providers:
        app_user_provider:
            # ...
    firewalls:
    # ...
# ...

则您的用户提供者服务ID是 security.user.provider.concrete.app_user_provider

设置用户检查器

Symfony 5.4+

您可以为防火墙配置定义一个用户检查器。

# app/config/security.yml or config/packages/security.yaml
security:
    firewalls:
        api_token_refresh:
            pattern: ^/api/token/refresh
            stateless: true
            user_checker: user_checker_service_id
            refresh_jwt: ~

Symfony 4.4

注意 此设置已弃用,并且不与 refresh_jwt 认证器一起使用。

您可以通过在配置文件中添加以下行来定义自己的用户检查器,默认情况下使用 security.user_checker 服务。您可以通过更改此值来更改它。

gesdinet_jwt_refresh_token:
    user_checker: user_checker_service_id

您可能想要使用自定义用户提供者以及您的用户检查器,以确保检查器接收正确的用户类型。

单次使用令牌

您可以将刷新令牌配置为只能使用一次。如果设置为 true 且刷新令牌被使用,将提供新的刷新令牌。

要启用此行为,请向配置文件中添加以下行。

gesdinet_jwt_refresh_token:
    single_use: true

在cookie中设置刷新令牌

默认情况下,刷新令牌返回在JSON响应体中。您可以使用以下配置将其设置在HttpOnly cookie中。刷新令牌在刷新时自动从cookie中提取。

要允许用户在使用cookie时注销,您需要配置 LogoutEvent 在特定路由上触发,并在注销期间调用该路由。

gesdinet_jwt_refresh_token:
    cookie:
      enabled: true
      same_site: lax               # default value
      path: /                      # default value
      domain: null                 # default value
      http_only: true              # default value
      secure: true                 # default value
      partitioned: false           # default value
      remove_token_from_body: true # default value

注销时无效化刷新令牌

此包自动注册一个 EventListener,该监听器在特定防火墙(默认: api)的 LogoutEvent 上触发。

LogoutEventListener 自动使指定的刷新令牌无效,如果启用,则取消设置cookie。如果没有提供刷新令牌,则返回错误,并且cookie保持不变。如果提供的刷新令牌(已经)无效,则取消设置cookie。

您只需确保 LogoutEvent 在特定路由上触发,并在注销期间调用该路由即可。

# in security.yaml
security:
    firewalls:
        api:
            logout:
                path: api_token_invalidate
# in routes.yaml
api_token_invalidate:
    path: /api/token/invalidate

如果您想配置LogoutEvent在另一个防火墙中触发,必须配置该防火墙的名称

# in security.yaml
security:
    firewalls:
        myfirewall:
            logout:
                path: api_token_invalidate
# in routes.yaml
api_token_invalidate:
    path: /api/token/invalidate
# in gesdinet_jwt_refresh_token.yaml
gesdinet_jwt_refresh_token:
    logout_firewall: myfirewall

Doctrine 管理器类型

默认情况下,该捆绑包将尝试使用以下逻辑设置适当的应用程序 Doctrine 对象管理器来定义管理器类型

  • 如果manager_type配置键设置为“mongodb”,则使用 MongoDB ODM
  • 如果manager_type配置键设置为“orm”(默认),且 ORM 未安装但 MongoDB ODM 已安装,则使用 MongoDB ODM
  • 默认情况下,manager_type为“orm”,并使用 ORM

您可以使用manager_type配置来自定义管理器类型

gesdinet_jwt_refresh_token:
    manager_type: mongodb

使用另一个对象管理器

您可以使用object_manager配置来配置捆绑包使用任何对象管理器。注意,显式定义的object_manager配置将覆盖基于manager_type的任何自动配置。

gesdinet_jwt_refresh_token:
    object_manager: my.specific.entity_manager.id

使用另一个用于刷新令牌的类

您可以通过创建从该捆绑包提供的类扩展的类来为您的项目定义自己的刷新令牌类。这也允许您自定义刷新令牌,例如向令牌添加额外数据。

当使用 Doctrine ORM 时,在您的应用程序中创建一个从Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken扩展的类

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken;

/**
 * This class extends Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken to have another table name.
 *
 * @ORM\Table("jwt_refresh_token")
 */
class JwtRefreshToken extends RefreshToken
{
}

当使用 Doctrine MongoDB ODM 时,在您的应用程序中创建一个从Gesdinet\JWTRefreshTokenBundle\Document\RefreshToken扩展的类

<?php

namespace App\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Gesdinet\JWTRefreshTokenBundle\Document\RefreshToken;

/**
 * This class extends Gesdinet\JWTRefreshTokenBundle\Document\RefreshToken to have another collection name.
 *
 * @MongoDB\Document(collection="jwt_refresh_token")
 */
class JwtRefreshToken extends RefreshToken
{
}

然后声明此类,将以下行添加到您的 config.yml 文件中

gesdinet_jwt_refresh_token:
    refresh_token_class: App\Entity\JwtRefreshToken

注意:如果使用其他对象管理器,建议您的对象类从Gesdinet\JWTRefreshTokenBundle\Model\AbstractRefreshToken扩展,该类实现了从Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface的所有必需方法。

禁用自动 Doctrine 映射

注意:此设置已弃用,不再使用

在某些情况下,您可能不希望启用默认对象管理器的 Doctrine 映射,因为您既不使用 ORM 也不使用 ODM,而是使用 DoctrineBundle 的 DBAL。

要禁用动态 Doctrine 映射,请将此行添加到您的配置中

gesdinet_jwt_refresh_token:
    doctrine_mappings: false

生成令牌

当您通过/api/login_check 使用用户/密码凭据进行身份验证时,LexikJWTAuthenticationBundle 现在返回 JWT 令牌和刷新令牌数据。

{
  "token": "eyxxxGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJleHAiOjE0NDI0MDM3NTgsImVtYWlsIjoid2VibWFzdGVyQGdlc2RpbmV0LmNvbSIsImlhdCI6IjE0NDI0MDM3MzgifQ.bo5pre_v0moCXVOZOj-s85gVnBLzdSdsltPn3XrkmJaE8eaBo_zcU2pnjs4dUc9hhwNZK8PL6SmSNcQuTUj4OMK7sUDfXr62a05Ds-UgQP8B2Kpc-ZOmSts_vhgo6xJNCy8Oub9-pRA_78WzUUxt294w0IArrNlgQAGewk65RSMThOif9G6L7HzBM4ajFZ-kMDypz2zVQea1kry-m-XXKNDbERCSHnMeV3rANN48SX645_WEvwaHy0agChR4hTnThzLof2bShA7j7HmnSPpODxQszS5ZBHdMgTvYhlcWJmwYswCWCTPl3lsqVq_UOFI5_4arpSNlUwZsichqxXVAHX5idZqCWtoaqAbvNQe2IpinYajoXw-MlYKvcN2TLUF_8sy529olLUagf4FCpCO6JFxovv0E7ll9tUOVvx9LlannqV8976q5XCOoXszKonZSH7DhsBlW5Emjv7PailbARZ-hfl4YlamyY2QbnxAswYycfoxqJxbbIKYGA8dlebdvMyC7m9VATnasTuKeEKS3mP5iyDgWALBHNYXm1FM-12zHBdN3PbOgxmy_OBGvk05thYFEf2WVmyedtFHy4TGlI0-otUTAf2swQAXWhKtkLWzokWWF7l5iNzam1kkEgql5EOztXHDZpmdKVHWBVNvN3J5ivPjjJBm6sGusf-radcw",
  "refresh_token": "xxx00a7a9e970f9bbe076e05743e00648908c38366c551a8cdf524ba424fc3e520988f6320a54989bbe85931ffe1bfcc63e33fd8b45d58564039943bfbd8dxxx"
}

刷新令牌作为RefreshTokenInterface对象持久化。之后,当您的 JWT 有效令牌过期时,如果您想要获取一个新的,您可以采取两种方式

  • 再次将您的用户凭据发送到/api/login_check。这会生成另一个 JWT 和另一个刷新令牌。
  • 使用刷新令牌来更新有效的 JWT。向/api/token/refresh URL 发送 POST 请求,其中刷新令牌作为有效载荷。这样,您始终可以获取有效的 JWT 而不需要请求用户凭据。但是 您必须检查 刷新令牌是否仍然有效。您的刷新令牌不会更改,但其 TTL 会增加。

注意,当刷新令牌被消耗且配置选项single_use设置为true时,令牌将不再有效。

curl -X POST -d refresh_token="xxxx4b54b0076d2fcc5a51a6e60c0fb83b0bc90b47e2c886accb70850795fb311973c9d101fa0111f12eec739db063ec09d7dd79331e3148f5fc6e9cb362xxxx" 'http://xxxx/token/refresh'

此调用返回一个新的有效 JWT 令牌,更新刷新令牌的有效日期。

有用的命令

吊销所有无效令牌

如果您想吊销所有无效(日期已过期)的刷新令牌,您可以执行

php bin/console gesdinet:jwt:clear

该命令可选地接受一个日期参数,将删除所有给定时间之前的老令牌。这可以是任何可以由DateTime类解析的值。

php bin/console gesdinet:jwt:clear 2015-08-08

我们建议将此命令作为 cronjob 执行,以定期删除无效的刷新令牌。

吊销令牌

如果您想吊销单个令牌,您可以使用此命令

php bin/console gesdinet:jwt:revoke TOKEN

事件

令牌已刷新

当令牌被刷新时,将使用gesdinet.refresh_token事件发布一个Gesdinet\JWTRefreshTokenBundle\Event\RefreshEvent对象。

刷新令牌失败

注意 此事件仅在使用与Symfony 5.4+一起使用的refresh_jwt验证器时可用。

当验证刷新令牌失败时,将使用Gesdinet\JWTRefreshTokenBundle\Event\RefreshAuthenticationFailureEvent对象触发gesdinet.refresh_token_failure事件。

未找到刷新令牌

注意 此事件仅在使用与Symfony 5.4+一起使用的refresh_jwt验证器时可用。

当验证刷新令牌失败时,将使用Gesdinet\JWTRefreshTokenBundle\Event\RefreshTokenNotFoundEvent对象触发gesdinet.refresh_token_not_found事件。

令牌提取器

该组件提供Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface以定义可以从请求中读取刷新令牌的类。

默认情况下,使用Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ChainExtractor,这允许检查请求的多个方面以获取令牌。找到的第一个令牌将被使用。

您可以通过向应用程序中添加实现该接口的类来创建自定义提取器。例如,要添加一个检查“X-Refresh-Token”头部的提取器

<?php

namespace App\Request\Extractor;

use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface;
use Symfony\Component\HttpFoundation\Request;

final class HeaderExtractor implements ExtractorInterface
{
    public function getRefreshToken(Request $request, string $parameter): ?string
    {
        return $request->headers->get('X-Refresh-Token');
    }
}

此组件会自动配置ExtractorInterface对象,并在您的应用程序使用自动配置(在services.yaml文件中设置autoconfigure: true)时自动设置gesdinet_jwt_refresh_token.request_extractor容器标签。如果不使用自动配置,您需要手动配置此标签

services:
    App\Request\Extractor\HeaderExtractor:
        tags:
            - { name: gesdinet_jwt_refresh_token.request_extractor }

提取器的优先级

gesdinet_jwt_refresh_token.request_extractor容器标签支持提取器的优先级,您可以通过添加一个priority属性来设置提取器的首选顺序。数字越高,提取器运行的越早。

services:
    App\Request\Extractor\HeaderExtractor:
        tags:
            - { name: gesdinet_jwt_refresh_token.request_extractor, priority: 25 }