diff --git a/README.md b/README.md index a090ed9..f5256fa 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ require_once 'vendor/autoload.php'; //Create your auth strategy $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']); +//Optionnal: create your persistence strategy +$persistenceStrategy = null; + $baseUri = 'http://api.example.org/'; // Create authClient @@ -42,6 +45,7 @@ $authClient = new Client(['base_uri' => $baseUri]); $jwtManager = new JwtManager( $authClient, $authStrategy, + $persistenceStrategy, [ 'token_url' => '/api/token', ] @@ -115,6 +119,102 @@ $authStrategy = new JsonAuthStrategy( ); ``` +## Persistence + +To avoid requesting a token everytime php runs, you can pass to `JwtManager` an implementation of `TokenPersistenceInterface`. +By default `NullTokenPersistence` will be used. + +### Simpe cache adapter (PSR-16) + +If you have any [PSR-16 compatible cache](https://www.php-fig.org/psr/psr-16/), you can use it as a persistence handler: + +```php + '/api/token', 'token_key' => 'access_token', @@ -165,6 +266,7 @@ Json example: $jwtManager = new JwtManager( $authClient, $authStrategy, + $persistenceStrategy, [ 'token_url' => '/api/token', 'token_key' => 'access_token', diff --git a/Tests/Manager/JwtManagerTest.php b/Tests/Manager/JwtManagerTest.php index cb9eb31..0af6435 100644 --- a/Tests/Manager/JwtManagerTest.php +++ b/Tests/Manager/JwtManagerTest.php @@ -49,6 +49,7 @@ function (RequestInterface $request) { $jwtManager = new JwtManager( $authClient, $authStrategy, + null, ['token_url' => '/api/token', 'timeout' => 3] ); $token = $jwtManager->getJwtToken(); @@ -87,6 +88,7 @@ function (RequestInterface $request) { $jwtManager = new JwtManager( $authClient, $authStrategy, + null, ['token_url' => '/api/token', 'timeout' => 3, 'token_key' => 'tokenkey'] ); $token = $jwtManager->getJwtToken(); @@ -141,6 +143,7 @@ function (RequestInterface $request) { $jwtManager = new JwtManager( $authClient, $authStrategy, + null, ['token_url' => '/api/token', 'timeout' => 3] ); $token = $jwtManager->getJwtToken(); @@ -200,6 +203,7 @@ function (RequestInterface $request) { $jwtManager = new JwtManager( $authClient, $authStrategy, + null, ['token_url' => '/api/token', 'timeout' => 3] ); $token = $jwtManager->getJwtToken(); @@ -263,6 +267,7 @@ function (RequestInterface $request) { $jwtManager = new JwtManager( $authClient, $authStrategy, + null, ['token_url' => '/api/token', 'timeout' => 3] ); $token = $jwtManager->getJwtToken(); diff --git a/Tests/Persistence/TokenPersistenceTest.php b/Tests/Persistence/TokenPersistenceTest.php new file mode 100644 index 0000000..c391565 --- /dev/null +++ b/Tests/Persistence/TokenPersistenceTest.php @@ -0,0 +1,48 @@ +saveToken($token); + + $this->assertFalse($tokenPersistence->hasToken()); + $this->assertNull($tokenPersistence->restoreToken()); + } + + /** + * testSimpleCacheTokenPersistence. + */ + public function testSimpleCacheTokenPersistence() + { + $simpleCache = new FilesystemCache(); + $tokenPersistence = new SimpleCacheTokenPersistence($simpleCache); + $token = new JwtToken('foo', new \DateTime('now')); + + $tokenPersistence->saveToken($token); + + $this->assertTrue($tokenPersistence->hasToken()); + $this->assertEquals($tokenPersistence->restoreToken()->getToken(), $token->getToken()); + + $tokenPersistence->deleteToken(); + + $this->assertFalse($tokenPersistence->hasToken()); + $this->assertNull($tokenPersistence->restoreToken()); + } +} diff --git a/composer.json b/composer.json index 5175186..729f68a 100644 --- a/composer.json +++ b/composer.json @@ -17,11 +17,13 @@ }, "require-dev": { "phpunit/phpunit": "4.5", - "satooshi/php-coveralls": "^0.6.1" + "satooshi/php-coveralls": "^0.6.1", + "symfony/cache": ">=3.3" }, "require": { "php" : ">=5.5.0", "guzzlehttp/guzzle": "~6.0", + "psr/simple-cache": "^1.0", "symfony/options-resolver": ">=2.8" }, "config": { diff --git a/src/Manager/JwtManager.php b/src/Manager/JwtManager.php index fb96de1..0d2f1b4 100644 --- a/src/Manager/JwtManager.php +++ b/src/Manager/JwtManager.php @@ -3,6 +3,8 @@ namespace Eljam\GuzzleJwt\Manager; use Eljam\GuzzleJwt\JwtToken; +use Eljam\GuzzleJwt\Persistence\NullTokenPersistence; +use Eljam\GuzzleJwt\Persistence\TokenPersistenceInterface; use Eljam\GuzzleJwt\Strategy\Auth\AuthStrategyInterface; use GuzzleHttp\ClientInterface; use GuzzleHttp\request; @@ -41,21 +43,33 @@ class JwtManager */ protected $token; + /** + * @var TokenPersistenceInterface + */ + protected $tokenPersistence; + /** * Constructor. * - * @param ClientInterface $client - * @param AuthStrategyInterface $auth - * @param array $options + * @param ClientInterface $client + * @param AuthStrategyInterface $auth + * @param TokenPersistenceInterface $tokenPersistence + * @param array $options */ public function __construct( ClientInterface $client, AuthStrategyInterface $auth, + TokenPersistenceInterface $tokenPersistence = null, array $options = [] ) { $this->client = $client; $this->auth = $auth; + if ($tokenPersistence === null) { + $tokenPersistence = new NullTokenPersistence(); + } + $this->tokenPersistence = $tokenPersistence; + $resolver = new OptionsResolver(); $resolver->setDefaults([ 'token_url' => '/token', @@ -76,10 +90,17 @@ public function __construct( */ public function getJwtToken() { + // If token is not set try to get it from the persistent storage. + if ($this->token === null) { + $this->token = $this->tokenPersistence->restoreToken(); + } + if ($this->token && $this->token->isValid()) { return $this->token; } + $this->tokenPersistence->deleteToken(); + $url = $this->options['token_url']; $requestOptions = array_merge( @@ -106,6 +127,7 @@ public function getJwtToken() } $this->token = new JwtToken($body[$this->options['token_key']], $expiration); + $this->tokenPersistence->saveToken($this->token); return $this->token; } diff --git a/src/Persistence/NullTokenPersistence.php b/src/Persistence/NullTokenPersistence.php new file mode 100644 index 0000000..0c40bc7 --- /dev/null +++ b/src/Persistence/NullTokenPersistence.php @@ -0,0 +1,31 @@ + + */ +class NullTokenPersistence implements TokenPersistenceInterface +{ + public function saveToken(JwtToken $token) + { + return; + } + + public function restoreToken() + { + return null; + } + + public function deleteToken() + { + return; + } + + public function hasToken() + { + return false; + } +} diff --git a/src/Persistence/SimpleCacheTokenPersistence.php b/src/Persistence/SimpleCacheTokenPersistence.php new file mode 100644 index 0000000..7179d9d --- /dev/null +++ b/src/Persistence/SimpleCacheTokenPersistence.php @@ -0,0 +1,75 @@ +cache = $cache; + $this->ttl = $ttl; + $this->cacheKey = $cacheKey; + } + + /** + * @inheritDoc + */ + public function saveToken(JwtToken $token) + { + /* + * TTL does not need to match token expiration, + * it'll be revalidated by manager so we can safely + * return a stale token. + */ + $this->cache->set($this->cacheKey, $token, $this->ttl); + return; + } + + /** + * @inheritDoc + */ + public function restoreToken() + { + return $this->cache->get($this->cacheKey); + } + + /** + * @inheritDoc + */ + public function deleteToken() + { + $this->cache->deleteItem($this->cacheKey); + return; + } + + /** + * @inheritDoc + */ + public function hasToken() + { + return $this->cache->has($this->cacheKey); + } +} diff --git a/src/Persistence/TokenPersistenceInterface.php b/src/Persistence/TokenPersistenceInterface.php new file mode 100644 index 0000000..375c4d6 --- /dev/null +++ b/src/Persistence/TokenPersistenceInterface.php @@ -0,0 +1,37 @@ + + */ +interface TokenPersistenceInterface +{ + /** + * Restore the token data into the give token. + * + * @return JwtToken Restored token + */ + public function restoreToken(); + + /** + * Save the token data. + * + * @param JwtToken $token + */ + public function saveToken(JwtToken $token); + + /** + * Delete the saved token data. + */ + public function deleteToken(); + + /** + * Returns true if a token exists (although it may not be valid) + * + * @return bool + */ + public function hasToken(); +}