LoginSignup
0
0

ECCUBE4にJWTを用いたAPIを実装する方法

Last updated at Posted at 2024-06-22

lexik/jwt-authentication-bundleを利用してECCUBE4にJWTを用いたAPIを実装しました。

こちらの記事を参考にしております。

環境

•	ECCUBEバージョン: 4.0.5
•	PHPバージョン: 7.4
•	Symfonyバージョン: 3.4.49

jwt-authentication-bundleを導入

まず、JWT Authentication Bundleをプロジェクトに導入します。

composer require lexik/jwt-authentication-bundle:^2.1

キーペアの作成

キーペアをECCUBEのディレクトリ構成に合わせて作成します。

mkdir -p app/config/eccube/jwt
openssl genrsa -out app/config/eccube/jwt/private.pem
openssl rsa -in app/config/eccube/jwt/private.pem -pubout > app/config/eccube/jwt/public.pem 

envファイルに追記

###> lexik/jwt-authentication-bundle ###
JWT_SECRET_KEY=%kernel.project_dir%/app/config/eccube/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/app/config/eccube/jwt/public.pem
JWT_PASSPHRASE=**********
###< lexik/jwt-authentication-bundle ###

lexik_jwt_authentication.yamlを作成

app/config/eccube/packages/lexik_jwt_authentication.yamlを作成し、以下を追加します。
以下の設定ではトークンの有効期限は1日です。

lexik_jwt_authentication:
    secret_key: '%env(resolve:JWT_SECRET_KEY)%'
    public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
    pass_phrase: '%env(JWT_PASSPHRASE)%'
    token_ttl: 86400

ユーザー情報

API接続用のユーザーテーブルを追加しました。以下はサンプルのApiUserクラスです。

ApiUser.php

<?php

namespace Eccube\Entity;

use Eccube\Repository\ApiUserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

if (!class_exists('\Eccube\Entity\ApiUser')) {
    /**
     * ApiUser
     * @ORM\Table(name="dtb_api_user")
     * @ORM\Entity(repositoryClass=ApiUserRepository::class)
     * @ORM\InheritanceType("SINGLE_TABLE")
     * @ORM\DiscriminatorColumn(name="type", type="string", length=255)
     * @ORM\DiscriminatorMap({"api_user" = "ApiUser"})
     */
    class ApiUser implements UserInterface
    {
        /**
         * @ORM\Id
         * @ORM\GeneratedValue
         * @ORM\Column(type="integer")
         */
        private $id;

        /**
         * @ORM\Column(type="string", length=180, unique=true)
         */
        private $username;

        /**
         * @var string The hashed password
         * @ORM\Column(type="string")
         */
        private $password;

        public function getId(): ?int
        {
            return $this->id;
        }

        /**
         * A visual identifier that represents this user.
         *
         * @see UserInterface
         */
        public function getUsername(): string
        {
            return (string) $this->username;
        }

        public function setUsername(string $username): self
        {
            $this->username = $username;

            return $this;
        }

        /**
         * 使用しない
         * @see UserInterface
         */
        public function getRoles(): array
        {
            return [];
        }

        /**
         * @see UserInterface
         */
        public function getPassword(): string
        {
            return (string) $this->password;
        }

        public function setPassword(string $password): self
        {
            $this->password = $password;

            return $this;
        }

        /**
         * 使用しない
         * Returning a salt is only needed, if you are not using a modern
         * hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.
         *
         * @see UserInterface
         */
        public function getSalt(): ?string
        {
            return null;
        }

        /**
         * 使用しない
         * @see UserInterface
         */
        public function eraseCredentials()
        {
            // If you store any temporary, sensitive data on the user, clear it here
            // $this->plainPassword = null;
        }
    }
}

ApiUserRepository.php

<?php

namespace Eccube\Repository;

use Eccube\Entity\ApiUser;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @method ApiUser|null find($id, $lockMode = null, $lockVersion = null)
 * @method ApiUser|null findOneBy(array $criteria, array $orderBy = null)
 * @method ApiUser[]    findAll()
 * @method ApiUser[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class ApiUserRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, ApiUser::class);
    }

    /**
     * @param int $id
     *
     * @return ApiUser
     */
    public function get($id = 1)
    {
        return $this->find($id);
    }
}

マイグレーション

差分を取りマイグレーションを実行します。

php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate

ApiUserの登録画面を追加しましたが、ここでは割愛します。INSERT文で直接登録してもOKです。

firewalls の設定

app/config/eccube/packages/security.yamlにfirewallsの設定を追記します。今回はget_token、api_1およびapi_2を追加します。

security:
    providers:
        api_get_token:
            pattern: ^/%api_route%/get_token
            stateless: true
            json_login:
                check_path: /%api_route%/get_token
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure

        api_1:
            pattern:   ^/%api_route%/api_1
            stateless: true
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
        api_2:
            pattern:   ^/%api_route%/api_2
            stateless: true
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
    access_control:
        - { path: ^/%api_route%/get_token, roles: PUBLIC_ACCESS }
        - { path: ^/%api_route%/api_1, roles: PUBLIC_ACCESS }
        - { path: ^/%api_route%/api_2, roles: PUBLIC_ACCESS }

api_routeの設定

.envファイル

API_ROUTE="your_api_route"

app/config/eccube/packages/eccube.yaml

    # APIルート
    api_route: '%env(API_ROUTE)%'

認証の仕組み

今回はECCUBEで設定したApiUserのusernameとpasswordをget_tokenの認証に使用しtokenを発行します。
get_tokenで発行したtokenを利用しECCUBE上での処理を行います。

ApiControllerの設定

get_token

json形式でapi_routeに設定したパスに{“username”:“hoge”,“password”:“huga”}をPOSTします。ApiUserと照合し、usernameとpasswordの組み合わせが正しければトークンを発行します。

ApiController

<?php

namespace Eccube\Controller;

use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Eccube\Repository\ApiUserRepository;
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;



class ApiController extends AbstractController
{
    /**
     * @var JWTTokenManagerInterface
     */
    private $JWTManager;
    /**
     * @var ApiUserRepository
     */
    protected $apiUserRepository;
    /**
     * @var JWTEncoderInterface
     */
    private $JWTEncoder;

    public function __construct(
        ApiUserRepository $apiUserRepository,
        JWTTokenManagerInterface $JWTManager,
        JWTEncoderInterface $JWTEncoder
    ) {
        $this->apiUserRepository = $apiUserRepository;
        $this->JWTManager = $JWTManager;
        $this->JWTEncoder = $JWTEncoder;
    }


    /**
     * @Route("/%api_route%/get_token", name="get_token", methods={"POST"})
     */
    public function getToken(Request $request)
    {
        $data = json_decode($request->getContent(), true);
        $username = $data['username'] ?? null;
        $password = $data['password'] ?? null;

        if(!$username || !$password){
            $massage = !$username ? 'username is required' : 'password is required';

            return $this->json([
                'statusCode' => JsonResponse::HTTP_BAD_REQUEST,
                'error' => 'Bad Request',
                'message' => $massage,
            ], JsonResponse::HTTP_BAD_REQUEST);
        }

        $user = $this->apiUserRepository->findOneBy(['username' => $username]);
        if (!$user || $password !== $user->getPassword()) {
            return $this->json([
                'statusCode' => JsonResponse::HTTP_UNAUTHORIZED,
                'error' => 'Unauthorized',
                'message' => 'username or password is incorrect',
            ], JsonResponse::HTTP_UNAUTHORIZED);
        }

        $token = $this->JWTManager->create($user);

        return $this->json([
            'token' => $token,
        ]);
    }

リクエスト例

curl -k -X POST https://your_domain/your_api_route/get_token -H "Content-Type: application/json" -d '{"username":"hoge","password":"huga"}'

トークンを検証しapiを実行

ヘッダーのBearer tokenを検証します。認証できた場合、リクエストボディのパラメータを利用して処理を行います。

ApiController

    /**
     * @Route("/%api_route%/api_1", name="api_1", methods={"POST"})
     */
    public function api_1(Request $request)
    {
        $token = $request->headers->get('Authorization');
        if (!$token) {
            return $this->json([
                'statusCode' => JsonResponse::HTTP_UNAUTHORIZED,
                'error' => 'Unauthorized',
                'message' => 'Token not found',
            ], JsonResponse::HTTP_UNAUTHORIZED);
        }

        $token = str_replace('Bearer ', '', $token);
        try {
            $this->JWTEncoder->decode($token);
        } catch (JWTDecodeFailureException $e) {
            return $this->json([
                'statusCode' => JsonResponse::HTTP_UNAUTHORIZED,
                'error' => 'Unauthorized',
                'message' => $e->getMessage(),
            ], JsonResponse::HTTP_UNAUTHORIZED);

        }

        // トークンが有効な場合の処理
        $data = json_decode($request->getContent());

        //$dataを利用した処理

        // 成功の処理
        $message = 'message';
        return new JsonResponse(['message' =>  $message]);
    }

    /**
     * @Route("/%api_route%/api_2", name="api_1", methods={"POST"})
     */
    public function api_2(Request $request)
    {
    //api_2の処理
    ...

リクエスト例

curl -k -X POST https://your_domain/your_api_route/api_1 -H "Authorization: Bearer ey......" -H "Content-Type: application/json" -d '{"hoge":"huga"}'
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0