EC-CUBE4でLineログインを実装する方法です。
細かい説明はしていませんのでご了承ください。
ファイル設置場所やファイル名はネームスペースやクラス名をご確認ください。
必要なライブラリをComposerでインストール
bin/console eccube:composer:require knpuniversity/oauth2-client-bundle
bin/console eccube:composer:require firebase/php-jwt
bundles.phpにライブラリを追加
<?php
// app/config/eccube/bundles.php
/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* http://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
// ...
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true]
];
LineとOauth2通信するためのプロバイダーを作成
<?php
namespace Customize\Security\OAuth2\Client\Provider\Line;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
class Line extends AbstractProvider
{
use BearerAuthorizationTrait;
/**
* @inheritDoc
*/
public function getBaseAuthorizationUrl()
{
return 'https://access.line.me/oauth2/v2.1/authorize';
}
/**
* @inheritDoc
*/
public function getBaseAccessTokenUrl(array $params)
{
return 'https://api.line.me/oauth2/v2.1/token';
}
/**
* @inheritDoc
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
return 'https://api.line.me/v2/profile';
}
/**
* @inheritDoc
*/
protected function getDefaultScopes()
{
return [
'openid'
];
}
/**
* @inheritDoc
*/
protected function checkResponse(ResponseInterface $response, $data)
{
if(!empty($data['error'])) {
$code = 0;
$error = $data['error'];
if(is_array($error)) {
$code = $error['code'];
$message = $error['message'];
}
throw new IdentityProviderException($message, $response->getStatusCode(), $data);
}
}
/**
* @inheritDoc
*/
protected function createResourceOwner(array $response, AccessToken $token)
{
return new LineResourceOwner($response);
}
}
ユーザー情報を取得するためのLineResourceOwnerを用意
<?php
namespace Customize\Security\OAuth2\Client\Provider\Line;
use Firebase\JWT\JWT;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
use League\OAuth2\Client\Token\AccessToken;
class LineResourceOwner implements ResourceOwnerInterface
{
/**
* @var array
*/
protected $response;
/**
* @var AccessToken
*/
protected $token;
public function __construct(array $response)
{
$this->response = $response;
}
public function __call($name, $arguments)
{
$get = substr($name,0, 3);
if ($get !== 'get') {
return null;
}
$parameter = function($name) {
return ltrim(strtolower(preg_replace('/[A-Z]/', '_\0', str_replace("get", "", $name))), '_');
};
return $this->getResource($parameter($name));
}
/**
* @inheritDoc
*/
public function getId()
{
return $this->getResource("userId");
}
public function getEmail() {
$id_token = $this->token->getValues()["id_token"];
$tokens = explode(".", $id_token);
$payload = JWT::urlsafeB64Decode($tokens[1]);
return isset(json_decode($payload)->email) ? json_decode($payload)->email : null;
}
/**
* @inheritDoc
*/
public function toArray()
{
return $this->response;
}
protected function getResource(string $name)
{
return isset($this->response[$name]) ? $this->response[$name] : null;
}
}
knpu_oauth2_client.yamlにLine用の設定を追加
line_client:
type: generic
provider_class: Customize\Security\OAuth2\Client\Provider\Line\Line
client_id: '***************************************'
client_secret: '***********************************'
redirect_route: line_callback
Customerエンティティにline_user_idプロパティを追加
<?php
namespace Customize\Entity;
use Customize\Entity\Master\CustomerType;
use Doctrine\ORM\Mapping as ORM;
use Eccube\Annotation\EntityExtension;
/**
* @EntityExtension("Eccube\Entity\Customer")
*/
trait CustomerTrait
{
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $line_user_id;
public function getLineUserId(): ?string
{
return $this->line_user_id;
}
public function setLineUserId(?string $line_user_id): self
{
$this->line_user_id = $line_user_id;
return $this;
}
}
LineAuthenticatorを用意
<?php
namespace Customize\Security\Authenticator;
use Customize\Security\OAuth2\Client\Provider\Line\Line;
use Doctrine\ORM\EntityManagerInterface;
use Eccube\Entity\Customer;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use KnpU\OAuth2ClientBundle\Security\Exception\FinishRegistrationException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class LineAuthenticator extends SocialAuthenticator
{
const OAUTH2_PROVIDER = "line";
/**
* @var ClientRegistry
*/
private $clientRegistry;
/**
* @var EntityManagerInterface
*/
private $entityManager;
/**
* @var RouterInterface
*/
private $router;
public function __construct(
ClientRegistry $clientRegistry,
EntityManagerInterface $entityManager,
RouterInterface $router
) {
$this->clientRegistry = $clientRegistry;
$this->entityManager = $entityManager;
$this->router = $router;
}
public function supports(Request $request)
{
return $request->attributes->get('_route') === 'line_callback';
}
/**
* @inheritDoc
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse(
$this->router->generate("line"),
Response::HTTP_TEMPORARY_REDIRECT
);
}
/**
* @inheritDoc
*/
public function getCredentials(Request $request)
{
try {
return $this->fetchAccessToken($this->getLineClient());
} catch (AuthenticationException $e) {
throw new $e;
}
}
/**
* @inheritDoc
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$userInfo = $this->getLineClient()
->fetchUserFromToken($credentials);
// Line連携済みの場合
$Customer = $this->entityManager->getRepository(Customer::class)
->findOneBy(['line_user_id' => $userInfo->getId()]);
if($Customer) {
return $Customer;
}
$Customer = $this->entityManager->getRepository(Customer::class)
->findOneBy(['email' => $userInfo->getEmail()]);
// 会員登録していない場合、会員登録ページへ
if(!$Customer) {
throw new FinishRegistrationException(array_merge($userInfo->toArray(), ["email" => $userInfo->getEmail(), "provider" => self::OAUTH2_PROVIDER]));
}
// 通常の会員登録済みの場合はユーザー識別子を保存
$Customer->setLineUserId($userInfo->getId());
$this->entityManager->persist($Customer);
$this->entityManager->flush();
return $Customer;
}
/**
* @inheritDoc
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// 会員登録していない場合
if ($exception instanceof FinishRegistrationException) {
$this->saveUserInfoToSession($request, $exception);
return new RedirectResponse($this->router->generate("entry"));
} else {
$this->saveAuthenticationErrorToSession($request, $exception);
return new RedirectResponse($this->router->generate("mypage_login"));
}
}
/**
* @inheritDoc
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$targetUrl = $this->router->generate("mypage");
return new RedirectResponse($targetUrl);
}
/**
* EC-CUBEがUsernamePasswordTokenなので合わせる
*
* @param UserInterface $user
* @param string $providerKey
* @return UsernamePasswordToken|\Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken
*/
public function createAuthenticatedToken(UserInterface $user, $providerKey)
{
if ($user instanceof Customer && $providerKey === 'customer') {
return new UsernamePasswordToken($user, null, $providerKey, ['ROLE_USER']);
}
return parent::createAuthenticatedToken($user, $providerKey);
}
private function getLineClient()
{
return $this->clientRegistry
->getClient('line_client');
}
}
security.yamlを修正
guardを追加しています。
security:
encoders:
# Our user class and the algorithm we'll use to encode passwords
# https://symfony.com/doc/current/security.html#c-encoding-the-user-s-password
Eccube\Entity\Member:
id: Eccube\Security\Core\Encoder\PasswordEncoder
Eccube\Entity\Customer:
id: Eccube\Security\Core\Encoder\PasswordEncoder
providers:
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
# In this example, users are stored via Doctrine in the database
# To see the users at src/App/DataFixtures/ORM/LoadFixtures.php
# To load users from somewhere else: https://symfony.com/doc/current/security/custom_provider.html
member_provider:
id: Eccube\Security\Core\User\MemberProvider
customer_provider:
id: Eccube\Security\Core\User\CustomerProvider
# https://symfony.com/doc/current/security.html#initial-security-yml-setup-authentication
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: '^/%eccube_admin_route%/'
anonymous: true
provider: member_provider
form_login:
check_path: admin_login
login_path: admin_login
csrf_token_generator: security.csrf.token_manager
default_target_path: admin_homepage
username_parameter: 'login_id'
password_parameter: 'password'
use_forward: true
success_handler: eccube.security.success_handler
failure_handler: eccube.security.failure_handler
logout:
path: admin_logout
target: admin_login
customer:
pattern: ^/
anonymous: true
provider: customer_provider
remember_me:
secret: '%kernel.secret%'
lifetime: 3600
name: eccube_remember_me
remember_me_parameter: 'login_memory'
form_login:
check_path: mypage_login
login_path: mypage_login
csrf_token_generator: security.csrf.token_manager
default_target_path: homepage
username_parameter: 'login_email'
password_parameter: 'login_pass'
use_forward: true
success_handler: eccube.security.success_handler
failure_handler: eccube.security.failure_handler
logout:
path: logout
target: homepage
guard:
authenticators:
- Customize\Security\Authenticator\LineAuthenticator
access_decision_manager:
strategy: unanimous
allow_if_all_abstain: false
Lineログイン用のコントローラーを用意
<?php
namespace Customize\Controller;
use Eccube\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
/**
* @Route("/line")
*
* Class LineController
* @package Customize\Controller
*/
class LineController extends AbstractController
{
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(
TokenStorageInterface $tokenStorage
) {
$this->tokenStorage = $tokenStorage;
}
/**
* @Route("/", name="line")
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function index()
{
return $this->get('oauth2.registry')
->getClient('line_client')
->redirect([
"scope" => "openid profile email"
]);
}
/**
* @Route("/callback", name="line_callback")
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function callback()
{
if($this->isGranted("IS_AUTHENTICATED_FULLY")) {
return $this->redirectToRoute("mypage");
}else{
return $this->redirectToRoute("line");
}
}
}
EntryTypeを拡張
Line連携経由で会員登録する場合はline_user_idを登録するためEntryTypeを拡張します。
<?php
namespace Customize\Form\Extension;
use Customize\Security\OAuth2\Client\Provider\Line\Line;
use Eccube\Entity\Customer;
use Eccube\Form\Type\AddressType;
use Eccube\Form\Type\Front\EntryType;
use Eccube\Form\Type\NameType;
use Eccube\Form\Type\PostalType;
use Eccube\Form\Type\RepeatedEmailType;
use Eccube\Repository\Master\PrefRepository;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
class EntryTypeExtension extends AbstractTypeExtension
{
use FinishRegistrationBehavior;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$userInfo = $this->getUserInfoFromSession($this->requestStack->getCurrentRequest());
if($userInfo) {
// メールアドレスをセット
$builder
->addEventListener(FormEvents::POST_SET_DATA, function(FormEvent $event) use ($userInfo) {
$form = $event->getForm();
$form['email']->setData($userInfo["email"]);
});
// ユーザー識別子をCustomerにセット
$builder
->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) use ($userInfo) {
$Customer = $event->getData();
if($Customer instanceof Customer) {
$Customer->setLineUserId($userInfo["userId"]);
}
});
}
}
/**
* {@inheritdoc}
*/
public function getExtendedType()
{
return EntryType::class;
}
}
ソーシャルログインのときは会員登録ページでメールアドレスを編集不可にする
Entry/index.twigを編集してください。
{% if app.session.get('guard.finish_registration.user_information') %}
<dl>
<dt>
{{ form_label(form.email, 'common.mail_address', {'label_attr': {'class': 'ec-label'}}) }}
</dt>
<dd>
{{ form.email.vars.data }}
{{ form_widget(form.email.first, { type : 'hidden' }) }}
{{ form_widget(form.email.second, { type : 'hidden' }) }}
</dd>
</dl>
{% else %}
<dl>
<dt>
{{ form_label(form.email, 'common.mail_address', { 'label_attr': { 'class': 'ec-label' }}) }}
</dt>
<dd>
<div class="ec-input{{ has_errors(form.email.first) ? ' error' }}">
{{ form_widget(form.email.first, { 'attr': { 'placeholder': 'common.mail_address_sample' }}) }}
{{ form_errors(form.email.first) }}
</div>
<div class="ec-input{{ has_errors(form.email.second) ? ' error' }}">
{{ form_widget(form.email.second, { 'attr': { 'placeholder': 'common.repeated_confirm' }}) }}
{{ form_errors(form.email.second) }}
</div>
</dd>
</dl>
{% endif %}
以上で完成です。
bundles.phpにKnpUOAuth2ClientBundle登録した時点でエラーが出てしまいます。
Fatal error: Uncaught Error: Class ‘KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle’ not found in /var/www/shachihata-id/src/Eccube/Kernel.php:74 Stack trace: #0 /var/www/shachihata-id/vendor/symfony/http-kernel/Kernel.php(488): Eccube\Kernel->registerBundles() #1 /var/www/shachihata-id/vendor/symfony/http-kernel/Kernel.php(132): Symfony\Component\HttpKernel\Kernel->initializeBundles() #2 /var/www/shachihata-id/src/Eccube/Kernel.php(89): Symfony\Component\HttpKernel\Kernel->boot() #3 /var/www/shachihata-id/vendor/symfony/http-kernel/Kernel.php(195): Eccube\Kernel->boot() #4 /var/www/shachihata-id/index.php(78): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request)) #5 {main} thrown in /var/www/shachihata-id/src/Eccube/Kernel.php on line 74
composerで以下のライブラリはインストールしましたか?
knpuniversity/oauth2-client-bundle