BlogThủ thuật Website

Cách sử dụng WordPress làm nhà cung cấp xác thực cho Laravel

Giống như nhiều nhà phát triển PHP và JavaScript, tôi bắt đầu phát triển web bằng cách học đủ PHP và sau đó là jQuery để tùy chỉnh một chủ đề WordPress và sau đó bắt đầu từ đó. Trên đường đi, tôi đã biết được WordPress là một giải pháp tốt và những gì nó không phù hợp. Điều này đã dẫn đến việc học Angular, Vue, React và Laravel.

Ngoài giờ, ngày càng nhiều công việc của tôi là xây dựng các ứng dụng liền kề với WordPress trong Laravel và / hoặc Node. Điều này đã dẫn tôi đến nhiều giải pháp khác nhau để chia sẻ dữ liệu và xác thực người dùng giữa hai bên. Bài viết này hướng dẫn cách cho phép người dùng của một trang WordPress hiện có đăng nhập vào một trang Laravel với cùng tên người dùng và mật khẩu. Tôi đã làm điều này mà không bị trùng lặp dữ liệu – ngoài bộ nhớ cache – hoặc lưu trữ mật khẩu ở bất kỳ đâu.

Nếu các ứng dụng WordPress và Laravel của bạn có thể truy cập vào cùng một cơ sở dữ liệu, có một giải pháp thay thế mà tôi sẽ thảo luận sau. Giải pháp mà tôi sẽ chia sẻ trong bài đăng này, sử dụng API REST của WordPress để kiểm tra thông tin đăng nhập và cấp mã thông báo JWT cho người dùng.

Thiết lập trang web WordPress

Giải pháp này yêu cầu một trang web WordPress có plugin xác thực JWT . Đây là một plugin miễn phí mà bạn có thể cài đặt thông qua wp-admin.

Khi plugin đó được kích hoạt, hãy đảm bảo đặt một chuỗi dài, ngẫu nhiên trong hằng số JWT_AUTH_SECRET_KEY. Ngoài ra, hãy đảm bảo rằng máy chủ của bạn không chặn tiêu đề HTTP Authorization.

Plugin này sử dụng tên người dùng và mật khẩu để phát hành mã thông báo. Chúng không được lưu trữ, vì vậy quá trình này tương đối an toàn miễn là nó được chạy trên HTTPS.

Khi plugin này được cài đặt, bạn có thể thực hiện yêu cầu ĐĂNG tới điểm cuối “/ jwt-auth / v1 / token” với tên người dùng và mật khẩu trong nội dung để nhận mã thông báo JWT. Sau đó, mã thông báo đó có thể được đặt trong tiêu đề Ủy quyền để ủy quyền các yêu cầu tới API REST của WordPress. Nếu chúng tôi cung cấp tiêu đề đó khi thực hiện yêu cầu GET tới “/ wp / v2 / users / me”, chúng tôi sẽ nhận được thông tin chi tiết về người dùng hiện tại.

WordPress 5.6 sẽ thêm tính năng “mật khẩu ứng dụng” . Đây là một hệ thống xác thực cơ bản sử dụng mật khẩu khác sau đó là mật khẩu để đăng nhập. Trong tương lai, đó có thể là một giải pháp tốt hơn đối với các mã thông báo JWT.

Tôi đã làm theo hướng dẫn về cách tạo nhà cung cấp người dùng khách hàng. Nó lấy tên người dùng và mật khẩu được cung cấp và cố gắng lấy mã thông báo JWT từ trang WordPress. Nếu điều đó hoạt động, một mô hình Người dùng sẽ được tạo – trong bộ nhớ và bộ đệm. Mật khẩu không được lưu. Một phiên Laravel tiêu chuẩn được tạo. Phiên đó sẽ được duy trì miễn là người dùng vẫn ở trong bộ nhớ cache hoặc người dùng đăng xuất.

Trong bài đăng này, tôi sẽ cho bạn thấy nó hoạt động như thế nào. Tôi cũng đặt mã nguồn cho ứng dụng tôi đã xây dựng để kiểm tra điều này trên Githubs . Hãy thoải mái để phân nhánh nó. Đó là một trang web Laravel 8 cơ bản với Breeze .

Tạo ứng dụng khách API

Bước đầu tiên trong ứng dụng Laravel sẽ là tạo một ứng dụng khách API. Ứng dụng khách này, sẽ sử dụng mặt tiền HTTP của Laravel sẽ cần có khả năng thực hiện các yêu cầu có và không có mã thông báo xác thực.

Ứng dụng khách này sẽ cần các phương thức cho các yêu cầu GET và POST, tùy chọn thêm tiêu đề. Tôi cũng đã thêm một phương pháp để trao đổi tên người dùng và mật khẩu cho mã thông báo:

<?php

namespace App\Http\Clients;


use Illuminate\Support\Facades\Http;

/**
 * API client for WordPress REST API
 *
 * Must have the JWT auth plugin
 * https://wordpress.org/plugins/jwt-auth/
 */
class WordPressApiClient
{

    public string $apiUrl;
    protected $token;

    protected array $options;

    public function __construct(string $apiUrl, ?string $token = null)
    {
        $this->apiUrl = $apiUrl;
        if ($token) {
            $this->token = $token;
        }
        //In production require TLS on both ends.
        //Else do not verify.
        $this->options = [
            'verify' => 'production' === app('ENV')
        ];

    }

    public function setToken(string $token) : WordPressApiClient
    {
        $this->token = $token;
        return $this;
    }

    public function hasToken(): bool
    {
        return isset($this->token) && is_string($this->token);
    }

    public function get(string $endpoint, array $query = [])
    {
        $url = $this->apiUrl . $endpoint;
        if( $this->hasToken() ){
            $request = Http::withToken($this->token)
                ->withOptions($this->options);
        }else{
            $request = Http::withOptions($this->options);
        }

        return $request
            ->get(
                $url,
                $query
            );

    }

    public function post(string $endpoint, array $body)
    {
        $url = $this->apiUrl . $endpoint;
        if( $this->hasToken() ){
            $request = Http::withToken($this->token)
                ->withOptions($this->options);
        }else{
            $request = Http::withOptions($this->options);

        }
        return $request
            ->post(
                $url,
                $body
            );

    }


    public function getToken(string $username, string $password)
    {
        return $this->post('/jwt-auth/v1/token', [
            'username' => $username,
            'password' => $password
        ]);

    }

}

Tạo nhà cung cấp người dùng

Cho đến nay, chúng tôi có một ứng dụng khách HTTP có thể được sử dụng cho một nhà cung cấp người dùng. Nó cũng có thể được sử dụng để lấy hoặc chỉnh sửa bài đăng hoặc dữ liệu khác từ trang WordPress. Bây giờ, chúng ta sẽ xem xét nhà cung cấp người dùng.

Tài liệu để tạo nhà cung cấp người dùng tùy chỉnh khá đầy đủ và đáng để đọc qua.

Phương pháp quan trọng nhất ở đây là retrieveById(). Trả về mô hình người dùng ở đây đặt người dùng đã đăng nhập hiện tại. Tôi muốn tránh phải kiểm tra mã thông báo dựa trên API REST của WordPress theo mọi yêu cầu.

Để tránh điều này, tôi đã tạo một nhà máy cho các mô hình Người dùng lưu kết quả vào bộ nhớ cache:

{
    protected function userFactory(array $data) : User
    {
       $user =  new User;
       Cache::put(
           $this->cacheKey(
               $user->getAuthIdentifier()
           ),
           $user->toArray(),
           9000
       );
       return  $user;
    }
}

Đây là phương pháp tìm người dùng theo id, sử dụng nhà máy đó:


    public function retrieveById($identifier)
    {
        //Has user in cache?
        $user = Cache::get(
            $this->cacheKey($identifier)
        );
        //Yes? Make a user model and return it
        if( $user ){
            return (new User())
                ->forceFill($user);
        }

        //No user? Do a login.
        //Could trigger a redirect to wp-logint that comes back with a JWT...
    }

Chức năng này, có thể không trả về một mô hình người dùng. Điều này xảy ra khi không có người dùng đã đăng nhập. NẾU điều đó xảy ra, hai phương thức thụt lùi của chúng sẽ được gọi, theo thứ tự bạn thấy ở đây. Nếu kiểm tra mã thông báo đưa ra một ngoại lệ, mà ứng dụng khách API sẽ thực hiện nếu nó không hợp lệ, điều đó sẽ gây ra lỗi đăng nhập:

public function retrieveByCredentials(array $credentials)
    {
        $r = $this->wordpressClient
            ->getToken(
                $credentials['email'], $credentials['password']
            );
        return $this
            ->userFactory($r);
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        Auth::setUser($user);
        return $this
            ->retrieveById(
                $user->id
            );
    }

Tôi đã sửa đổi mô hình Người dùng để đơn giản hóa nó và làm cho nó hoạt động theo nhu cầu của tôi. Bạn không cần phải sử dụng mô hình Người dùng, bạn có thể sử dụng bất kỳ mô hình nào. Điều đó có thể hữu ích nếu bạn muốn có người dùng trong cơ sở dữ liệu Laravel và người dùng trong cơ sở dữ liệu WordPress. Đây là mô hình người dùng của tôi:

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'token'
    ];

}

Tôi cũng đã xóa di chuyển người dùng. Trong trang web thử nghiệm của tôi, tôi đã sử dụng sqlite cho bảng phiên và bộ nhớ cache. Nếu tôi triển khai điều này với Vapor, tôi sẽ sử dụng DynamoDB và / hoặc Redis để thay thế. Điều đó có nghĩa là ứng dụng Laravel có thể không cần cơ sở dữ liệu MySQL, điều này sẽ rất tốt cho việc mở rộng quy mô và kiểm soát chi phí.

Đây là nhà cung cấp người dùng đầy đủ:

<?php

namespace App\Providers;

use App\DTO\UserResponse;
use App\Http\Clients\WordPressApiClient;
use App\Models\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;

class WordPressUserProvider  implements  UserProvider
{

    protected WordPressApiClient $wordpressClient;

    public function __construct()
    {
        $this->wordpressClient = new WordPressApiClient(
            env('WP_API_URL')
        );
    }

    public function getModel()
    {
        return User::class;
    }

    protected function cacheKey($identifier):string {
        return __CLASS__ . $identifier;
    }

    public function retrieveById($identifier)
    {
        //Has user in cache?
        $user = Cache::get(
            $this->cacheKey($identifier)
        );
        //Yes? Make a user model and return it
        if( $user ){
            return (new User())
                ->forceFill($user);
        }

        //If nothing is returned here, login redirect will be triggered
        //That is normal for unauthorized users.
    }

    public function retrieveByToken($identifier, $token)
    {
        //Try to find by id
        $user = $this
            ->retrieveById($identifier);

        //Not found? Exchange token for user details again.
        if( ! $user ){
            $r = $this
                ->wordpressClient
                ->get('/wp-json/wp/v2/me');
            return $this
                ->userFactory($r);
        }
        return $user;
    }

    public function updateRememberToken(Authenticatable $user, $token)
    {
        //Not actually needed
    }

    /**
     * @param array $credentials
     * @return User|Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials)
    {
        $r = $this->wordpressClient
            ->getToken(
                $credentials['email'], $credentials['password']
            );
        return $this
            ->userFactory($r);
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        //Recheck token?
        //Auth::setUser($user);
       return $this
           ->retrieveById(
               $user->id
           );
    }

    protected function userFactory(array $data) : User
    {
        $user =  ( new UserResponse(
            Arr::only($data,[
                'token',
                'ID',
                'user_email'
            ])
        ) )
            ->toModel();
       Cache::put(
           $this->cacheKey(
               $user->getAuthIdentifier()
           ),
           $user->toArray(),
           9000
       );
       return  $user;
    }

}

{
    protected function userFactory(array $data) : User
    {
        $user =  new User;
        $user->forceFill(
          [
              'id' => $data['ID'],
              'name' => $data['user_email'],
              'token' => $data['token']
          ]
        );

        Cache::put(
            $this->cacheKey(
                $user->getAuthIdentifier()
            ),
            $user->toArray(),
            9000
        );
        return  $user;
    }
}

Định cấu hình mọi thứ

Về cơ bản là vậy. Bước cuối cùng là lên dây cót mọi thứ. Các bước này nằm ngay trong tài liệu .

Đầu tiên trong AuthProvidernhà cung cấp dịch vụ, hãy đăng ký nhà cung cấp người dùng:

 Auth::provider('wordpress', function ($app, array $config) {
            return new WordPressUserProvider();
        });

Sau khi hoàn tất, bạn có thể yêu cầu Laravel sử dụng trình cung cấp đó bằng cách sửa đổi config/auth.php.

'providers' => [
    'users' => [
        'driver' => 'wordpress',
    ],
],

Trong nhà cung cấp người dùng, tôi đã sử dụng env varibale WP_API_URLcho URL của API REST của trang web. Bạn sẽ đặt một cái gì đó giống như WP_API_URL=https://hiroy.club/wp-jsontrong tệp .env của bạn.

Sự kết luận

Để thay thế cho cách tiếp cận mà tôi đã trình bày trong bài đăng này, bạn có thể muốn xem gói này . Gói đó yêu cầu quyền truy cập vào cơ sở dữ liệu MySQL của WordPress. Điều đó không bắt buộc theo cách tiếp cận mà tôi đã trình bày ở đây.

Mục tiêu của tổ chức tôi là tách trang web WordPress lớn của chúng tôi khỏi các ứng dụng nội bộ khác của chúng tôi, vì vậy vị trí chung không có ý nghĩa. Ngoài ra, tôi muốn sử dụng các nhà cung cấp dịch vụ lưu trữ WordPress được quản lý cho WordPress và Vapor cho Laravel. Việc trộn chúng trên cùng một máy chủ giống như một sự hồi quy.

Tôi hy vọng bạn thấy điều này hữu ích. Vui lòng sao chép ứng dụng mẫu trên Github . Tôi tò mò muốn xem liệu có ai thấy điều này hữu ích không và nếu bạn có giải pháp tốt hơn. Tôi biết một điều tôi đang xem xét là sử dụng chuyển hướng đến màn hình đăng nhập WordPress.

Related Articles

Trả lời

Email của bạn sẽ không được hiển thị công khai.

Back to top button