Home  >  Article  >  PHP Framework  >  Sharing on the pit: Laravel integration process of phpCAS

Sharing on the pit: Laravel integration process of phpCAS

藏色散人
藏色散人forward
2021-09-19 16:53:491827browse

The following tutorial column of Laravel will share with you a pitfall of Laravel integrated phpCAS. I hope it will be helpful to friends who need it!

Laravel integrates phpCAS. Notes on pitfalls

CAS is currently a popular single sign-on protocol. The official provides the php version of client-side phpCAS. So far, So far, its coding style has remained in the PEAR era, without even using namespaces. Fortunately, phpCAS supports the introduction of composer, and I have done several Laravel project introductions without any problems. However, in the past two days, a project needs to be deployed from a single machine to a multi-machine deployment. I never expected to step on some pitfalls here. I will record them here. one time.

Callback pit

When jumping to the CAS Server for authentication, it was found that port 8080 was added to the incoming callback address. Because it is a multi-machine deployment, the access request will first pass through the load balancer (Alibaba Cloud SLB) and then reach the web server, and this 8080 is the listening port of the web server.

So I traced the logic of phpCAS to generate the callback address and found this piece of code:

if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
    $server_port = $_SERVER['SERVER_PORT'];
} else {
    $ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']);
    $server_port = $ports[0];
}

And Alibaba Cloud's SLB will not be passed to the back-end serverX-FORWARDED-PORT This http header, so phpCAS will get $_SERVER['SERVER_PORT'] which is the port 8080 of nginx.

Fortunately, phpCAS provides the setFixedServiceURL function, which allows us to manually set the callback address:

phpCAS::setFixedServiceURL($request->url());

The callback address is normal now, but the callback address is returned from the CAS Server. The client is informed that the ticket is invalid.

Continuing to check the logs and codes, I found that I was negligent here. When the CAS Server returned to the client, the url of the page was http://client/login?ticket=xxxxx, and When the client uses a ticket to exchange user information with the server, it also needs to bring the callback address (service) when applying for the ticket. The server will verify whether the ticket and service are consistent, and the service when applying for the ticket should be http:/ /client/login, so we need to remove the ticket parameter in the url.

phpCAS::setFixedServiceURL($this->getUrlWithoutTicket($request));

getUrlWithoutTicket The function is as follows:

private function getUrlWithoutTicket(Request $request)
{
    $query = parse_query($request->getQueryString());
    unset($query['ticket']);
    $question = $request->getBaseUrl().$request->getPathInfo() == '/' ? '/?' : '?';

    return $query ? $request->url().$question.http_build_query($query) : $request->url();
}

Session pit

This is a combination pit of phpCAS Laravel, which makes you lose your temper.

PHP defaults to session storage as a file, so a very important point when converting a single machine into multiple machines is to handle session sharing. The solution is also very simple, which is to change the Session storage method from file to redis/memecache/database, etc.

Laravel provides these drivers by default, so I excitedly changed the .env file and changed SESSION_DRIVER to redis. I tried it online and found that it didn't work. The changes made by phpCAS to the $_SESSION variable were not written to redis. What's going on!

So I followed Laravel's Session implementation and found that it was not the imagined use of session_set_save_handler to register the Session read and write logic. In other words, Laravel's Session did not actually modify php's ## For the read and write logic of #$_SESSION, directly operate $_SESSION or follow the default behavior (read and write local files).

Well, fortunately, several SessionDrivers in Laravel have implemented the

SessionHandlerInterface interface. We can call it ourselves session_set_save_handler

session_set_save_handler(app(StartSession::class)->getSession($request)->getHandler());
万I never expected an error!

session_write_close(): Session callback expects true/false return value
After chasing the Laravel code, I found that the

write method of the redis driver's parent class Illuminate\Session\CacheBasedSessionHandler returns void . So I submitted a PR to fix it, but I didn't expect it to be rejected. It turned out that someone had fixed it before and then reverted it, saying that it would cause the server to get stuck. However, I didn't find the specific issue.

Well, memcache and redis both inherit this parent class, so I will have to try database instead.

This time

session_write_close No error is reported, but there is still a problem with CAS login, and it keeps jumping between the CAS server and the callback url. So I chased all the logs and codes and found that the destroy method of the database driver class Illuminate\Session\DatabaseSessionHandler did not remove $this->exists## after destroying the Session. # The attribute is marked as false, and phpCAS has a logic that is renameSession<pre class="brush:php;toolbar:false">$old_session = $_SESSION; session_destroy(); $session_id = preg_replace('/[^a-zA-Z0-9\-]/', '', $ticket); session_id($session_id); session_start(); $_SESSION = $old_session;</pre>The consequence is

$_SESSION = $old_session;

The corresponding operation session The sql of the table executes update instead of insert, which means that the session data cannot be written to the session table! There is really no other way but to write a Session Wrapper yourself to handle it.

From the above two situations, the redis driver is easier to handle, as long as it can return true when calling the write method. So the code is as follows

namespace App\Services;

use SessionHandlerInterface;

class MySession implements SessionHandlerInterface
{
    /**
     * @var SessionHandlerInterface
     */
    protected $realHdl;

    /**
     * Session constructor.
     * @param SessionHandlerInterface $realHdl
     */
    public function __construct(SessionHandlerInterface $realHdl)
    {
        $this->realHdl = $realHdl;
    }

    public function close()
    {
        return $this->realHdl->close();
    }

    public function destroy($session_id)
    {
        return $this->realHdl->destroy($session_id);
    }

    public function gc($maxlifetime)
    {
        return $this->realHdl->gc($maxlifetime);
    }

    public function open($save_path, $name)
    {
        return $this->realHdl->open($save_path, $name);
    }

    public function read($session_id)
    {
        return $this->realHdl->read($session_id) ?: '';
    }

    public function write($session_id, $session_data)
    {
        $this->realHdl->write($session_id, $session_data);

        return true; // 这里
    }
}

and then calls

session_set_save_handler

to become <pre class="brush:php;toolbar:false">session_set_save_handler(new MySession(app(StartSession::class)-&gt;getSession($request)-&gt;getHandler()));</pre>Done!

The above is the detailed content of Sharing on the pit: Laravel integration process of phpCAS. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete