ridibooks/oauth2

Ridibooks OAuth2

v0.3.1 2020-01-06 01:41 UTC

This package is auto-updated.

Last update: 2022-01-04 08:04:08 UTC


README

Build Status

소개

  • OAuth2 클라이언트와 리소스 서버를 구축하기 위한 PHP 라이브러리입니다.
  • Ridi 스타일 가이드(내부 서비스간의 SSO)에 따라 작성 되었습니다.
  • JWK Caching 를 선택적으로 지원합니다. psr-6의 구현체를 JwtTokenValidator에 주입하면 캐싱 기능을 사용할 수 있습니다.

Requirements

  • PHP 7.2 or higher
  • php7.2-gmp web-token decryption 모듈을 위해서는 php7.2-gmp 를 os 내에 설치해줘야 합니다. 따라서 이 라이브러리 클라이언트들의 OS 혹은 도커 이미지 내에 꼭 설치해주시길 바랍니다. 참고 PR
  • silex/silex v1.3.x (optional)
  • symfony/symfony v4.x.x (optional)
  • guzzlehttp/guzzle (optional)

Installation

composer require ridibooks/oauth2

Usage

JwtTokenValidator Without Caching

use Ridibooks\OAuth2\Authorization\Validator\JwtTokenValidator;

$access_token = '...';

try {
    $jwk_url = $this->configs['jwk_url'];
    $validator = new JwtTokenValidator($jwk_url);
    $validator->validateToken($access_token);
} catch (AuthorizationException $e) {
	// handle exception
}

JwtTokenValidator With Caching

use Ridibooks\OAuth2\Authorization\Validator\JwtTokenValidator;

$access_token = '...';

try {
    $jwk_url = $this->configs['jwk_url'];
    $cache_item_pool = new FilesystemAdapter(); // [psr-6](https://www.php-fig.org/psr/psr-6/) Implementation Adaptor
    $validator = new JwtTokenValidator($jwk_url, $cache_item_pool);
    $validator->validateToken($access_token);
} catch (AuthorizationException $e) {
	// handle exception
}

ScopeChecker

$required = ['write', 'read'];
if (ScopeChecker::every($required, $granted)) {
	// pass
}

Granter

$client_info = new ClientInfo('client_id', 'client_secret', ['scope'], 'redirect_uri');
$auth_server_info = new AuthorizationServerInfo('authorization_url', 'token_url');

$granter = new Granter($client_info, $auth_server_info);
$authorization_url = $granter->authorize();
// Redirect to `$authorization_url`

Usage: with Silex Provider

OAuth2ServiceProvider를 Silex 애플리케이션에 등록(register)해 사용한다.

Services

  • OAuth2ProviderKeyConstant::GRANTER
    • authorize(string $state, string $redirect_uri = null, array $scope = null): string: /authorize를 위한 URL을 반환
  • OAuth2ProviderKeyConstant::AUTHORIZER
    • autorize(Request $request): JwtToken: access_token 유효성 검사 후 JwtToken 객체를 반환
  • OAuth2ProviderKeyConstant::MIDDLEWARE
    • authorize(OAuth2ExceptionHandlerInterface $exception_handler = null, UserProviderInterface $user_provider = null, array $required_scopes = []): 미들웨어를 반환

Example: OAuth2ProviderKeyConstant::MIDDLEWARE Service

use Ridibooks\OAuth2\Silex\Constant\OAuth2ProviderKeyConstant as KeyConstant;
use Ridibooks\OAuth2\Silex\Handler\LoginRequiredExceptionHandler;
use Ridibooks\OAuth2\Silex\Provider\OAuth2ServiceProvider;
use Ridibooks\OAuth2\Authorization\Validator\JwtTokenValidator;
use Example\UserProvder;

// `OAuth2ServiceProvider` 등록
$app->register(new OAuth2ServiceProvider(), [
	KeyConstant::CLIENT_ID => 'example-client-id',
	KeyConstant::CLIENT_SECRET => 'example-client-secret',
	KeyConstant::JWT_VALIDATOR => new JwtTokenValidator($jwk_url)
]);

// 미들웨어 등록
$app->get('/auth-required', [$this, 'authRequiredApi'])
	->before($app[KeyConstant::MIDDLEWARE]->authorize(new LoginRequiredExceptionHandler(), new UserProvider());
	
public function authRequiredApi(Application $app)
{
	// 사용자 추출
	$user = $app[KeyConstant::USER];
	...
}

Example: OAuth2ProviderKeyConstant::AUTHORIZER Service

use Ridibooks\OAuth2\Authorization\Authorizer;
use Ridibooks\OAuth2\Authorization\Exception\AuthorizationException;
use Ridibooks\OAuth2\Silex\Constant\OAuth2ProviderKeyConstant;
use Ridibooks\OAuth2\Silex\Provider\OAuth2ServiceProvider;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Ridibooks\OAuth2\Authorization\Validator\JwtTokenValidator;

...

// `OAuth2ServiceProvider` 등록
$app->register(new OAuth2ServiceProvider(), [
	KeyConstant::CLIENT_ID => 'example-client-id',
	KeyConstant::CLIENT_SECRET => 'example-client-secret',
    KeyConstant::JWT_VALIDATOR => new JwtTokenValidator($jwk_url)
]);

...

$app->get('/', function (Application $app, Request $request) {
	/** @var Authorizer $authorizer */
	$authorizer = $app[OAuth2ProviderKeyConstant::AUTHORIZER];
	try {
		$token = $authorizer->authorize($request);
		return $token->getSubject();
	} catch (AuthorizationException $e) {
		// handle authorization error ...
	}
});

Usage: with Symfony Bundle

Services

  • Granter()
    • OAuth2ServiceProvider::getGranter()
    • Granter::authorize(string $state, string $redirect_uri = null, array $scope = null): string: /authorize를 위한 URL을 반환
  • Authorizer()
    • OAuth2ServiceProvider::getAuthorizer()
    • Authorizer::autorize(Request $request): JwtToken: access_token 유효성 검사 후 JwtToken 객체를 반환
  • OAuth2Middleware
    • OAuth2ServiceProvider::getMiddleware()
    • OAuth2ServiceProvider 생성 시, Symfony Event Subscriber로 등록

Example: OAuth2Middleware Service

Configuration

  • 설정 예시는 tests/Symfony에서 살펴볼 수 있습니다.
1. OAuth2ServiceProviderBundle 등록
# example: <project_root>/config/bundles.php

return [
    ...,
    Ridibooks\OAuth2\Symfony\OAuth2ServiceProviderBundle::class => ['all' => true]
];
2. Parameter 및 Service 설정
  • '%env(VARIABLE)%'을 이용해 environment variable을 이용할 수 있습니다.
  • Required
    • client_id
    • client_secret
    • authorize_url
    • token_url
    • jwk_url
    • user_info_url
    • token_cookie_domain
    • default_exception_handler
  • optional
    • client_default_scope
    • client_default_redirect_uri
    • default_user_provider
    • cache_item_pool # psr-6의 구현체를 주입하면 Jwk 요청 시 캐싱 기능을 사용할 수 있습니다.
# example: <project_root>/config/packages/o_auth2_service_provider.yml

o_auth2_service_provider:
  client_id: '%env(CLIENT_ID)%'
  client_secret: '%env(CLIENT_SECRET)%'
  authorize_url: https://account.dev.ridi.io/ridi/authorize/
  token_url: https://account.dev.ridi.io/oauth2/token/
  jwk_url: https://account.dev.ridi.io/oauth2/keys/public
  user_info_url: https://account.dev.ridi.io/accounts/me/
  token_cookie_domain: .ridi.io
  
  default_exception_handler: Ridibooks\OAuth2\Example\DefaultExceptionHandler
  cache_item_pool: Symfony\Component\Cache\Adapter\FilesystemAdapter
# example: <project_root>/config/services.yml

services:
  Ridibooks\OAuth2\Example\ExampleController:
    class: Ridibooks\OAuth2\Example\ExampleController
    autowire: true
    autoconfigure: true
    public: false
    arguments:
      - '@oauth2_service_provider'
3. Controller 설정
namespace Ridibooks\OAuth2\Example;

use Ridibooks\OAuth2\Symfony\Annotation\OAuth2;
use Ridibooks\OAuth2\Symfony\Provider\OAuth2ServiceProvider;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class ExampleController extends Controller
{
    /** @var OAuth2ServiceProvider */
    private $oauth2_service_provider;

    /**
     * @param OAuth2ServiceProvider $oauth2_service_provider
     */
    public function __construct(OAuth2ServiceProvider $oauth2_service_provider)
    {
        $this->oauth2_service_provider = $oauth2_service_provider;
    }

    /**
     * @Route("/oauth2", methods={"GET"})
     * @OAuth2()
     *
     * @param Request $request
     * @return Response
     */
    public function normal(Request $request): Response
    {
        $user = $this->oauth2_service_provider->getMiddleware()->getUser();

        return new JsonResponse([
            'u_idx' => $user->getUidx(),
            'u_id' => $user->getUid()
        ]);
    }
}

OAuth2 Exception Handler 설정

  • Exception Handler는 OAuth2 과정 중, 오류 발생 시 Exception 상황을 처리하는 역할을 담당합니다.
  • Application Controller에서 default_exception_handler 파라미터로 지정한 Exception Handler가 아닌 별도의 Exception Handler를 이용하려는 경우, 아래 절차를 따릅니다.
    • Ridibooks\OAuth2\Symfony\Handler\OAuth2ExceptionHandlerInterface를 implement한 Exception Handler를 생성합니다.
      • example: Ridibooks\Test\OAuth2\Symfony\TestExceptionHandler
    • Application Controller의 @OAuth2 Annotation에서 exception_handler 속성을 지정합니다.
      • example: @OAuth2(exception_handler="Ridibooks\Test\OAuth2\Symfony\TestExceptionHandler")

Custom User Provider 설정

  • User Provider는 인증 이후, User 정보를 가져오는 역할을 담당합니다.
  • default_user_provider 파라미터를 지정하지 않은 경우, 기본적으로 Ridibooks\OAuth2\Symfony\Provider\DefaultUserProvider를 이용합니다.
  • Application Controller에서 default_user_provider 파라미터로 지정한 User Provider가 아닌 별도의 User Provider를 이용하려는 경우, 아래 절차를 따릅니다.
    • Ridibooks\OAuth2\Symfony\Provider\UserProviderInterface를 implement한 User Provider를 생성합니다.
    • Application Controller의 @OAuth2 Annotation에서 user_provider 속성을 지정합니다.
namespace Ridibooks\OAuth2\Example;

use Ridibooks\OAuth2\Authorization\Token\JwtToken;
use Ridibooks\OAuth2\Symfony\Provider\OAuth2ServiceProvider;
use Ridibooks\OAuth2\Symfony\Provider\UserProviderInterface;
use Symfony\Component\HttpFoundation\Request;

class CustomUserProvider implement UserProviderInterface
{
    /**
     * @param JwtToken $token
     * @param Request $request
     * @param OAuth2ServiceProvider $oauth2_service_provider
     * @return User
     */
    public function getUser(JwtToken $token, Request $request, OAuth2ServiceProvider $oauth2_service_provider): User
    {
        ...
    }
}
namespace Ridibooks\OAuth2\Example;

use Ridibooks\OAuth2\Symfony\Annotation\OAuth2;
use Ridibooks\OAuth2\Symfony\Provider\OAuth2ServiceProvider;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class ExampleController extends Controller
{
    /**
     * @Route("/oauth2", methods={"GET"})
     * @OAuth2(user_provider="Ridibooks\OAuth2\Example\CustomUserProvider")
     *
     * @param Request $request
     * @return Response
     */
    public function normal(Request $request): Response
    {
        ...
    }
}

Cache Item Pool 설정

  • Cache Item Pool 은 Jwk 를 캐싱하는 역할을 담당합니다.

유의할 점

  • Jwk Multi signatures 를 지원하지 않습니다. 오직 첫 번째 인덱스의 시그니쳐를 가져와서 decode 합니다.
  • Jwk Cache File 의 TTL(Time To Live)는 5분 입니다.