简体   繁体   中英

Mimic Apache's ProxyPassReverse with PHP

Problem Summary

具有经过身份验证的用户查看来自localhost应用程序的内容的流程图。

Let's say you run a server and want to serve content from a localhost app only for users who have logged in with a username and password (authenticated users).

The app runs on your server, but it is not exposed to the public. It is only available via http://localhost:3838 on your server.

For simplicity, let's say this is how you run the localhost app:

cd my-private-folder
python3 -m http.server 3838

Authenticated users should be able to go to http://example.com/app to view the content served by the localhost app.

Unauthenticated users should be denied access and redirected to a login page.

Questions

  • Apache has a directive ProxyPass that modifies HTTP headers in the request before it is sent to the destination. It also has a directive ProxyPassReverse that modifies the headers in the response before it arrives back to the user.

  • Is there a way to mimic Apache's behavior in PHP?

  • Is there a simple way to do this with the Laravel PHP framework?

For reference, there is a package called oxy that achieves this goal in Go. I don't know of an equivalent package for PHP.


Failed attempt 1: Using routes

Here's what I tried with Laravel 5.5:

# /var/www/example.com/routes/web.php
Route::get('/app', function() {
    return Response::make(
        file_get_contents("http://localhost:3838/")
    );
});
# This does not work, because the HTTP header is not modified.
Route::get('/app/{any}', function($any) {
    return Response::make(
        file_get_contents("http://localhost:3838/$any")
    );
})->where('any', '.*');

This successfully passes the content from localhost:3838 to example.com/app , but it fails to pass any of the resources requested by the app, because the HTTP headers are not modified.

For example:

To understand why this fails, see the correctly working Apache solution below. Apache has a directive called ProxyPassReverse that modifies the HTTP headers, so the localhost app requests resources from the correct URL.


Using Apache

Apache works perfectly! However, adding new users requires running the htpasswd command on the server.

With Laravel, new users should be able to register by themselves on the website.

Without Laravel, you could use Apache to secure the app like this:

# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
  ServerName example.com
  Redirect /app /app/
  ProxyPass /app/ http://localhost:3838/
  ProxyPassReverse /app/ http://localhost:3838/
  ProxyPreserveHost On
  <Location /app/>
      AuthType Basic
      AuthName "Restricted Access - Please Authenticate"
      AuthUserFile /etc/httpd/htpasswd.users
      Require user myusername 
      # To add a new user: htpasswd /etc/httpd/htpasswd.users newuser 
  </Location>
</VirtualHost>

This related answer helped me to better understand how the ProxyPassReverse directive works:

If the problem is only with the assets, you can route them as well to the localhost:3838 application:

# /var/www/example.com/routes/web.php
Route::get('/app/{any}', function($any) {
    return Response::make(
        file_get_contents("http://localhost:3838/$any")
    );
})->where('any', '.*');

Route::get('/{assets}', function($assets) {
    return Response::make(
        file_get_contents("http://localhost:3838/$assets")
    );
})->where('assets', '.*(png|jpe?g|js)');

I think Jens Segers has a package that can enable PHP to mimic the ProxyPass directive: https://github.com/jenssegers/php-proxy

Here's an example from the GitHub README:

use Proxy\Proxy;
use Proxy\Adapter\Guzzle\GuzzleAdapter;
use Proxy\Filter\RemoveEncodingFilter;
use Zend\Diactoros\ServerRequestFactory;

// Create a PSR7 request based on the current browser request.
$request = ServerRequestFactory::fromGlobals();

// Create a guzzle client
$guzzle = new GuzzleHttp\Client();

// Create the proxy instance
$proxy = new Proxy(new GuzzleAdapter($guzzle));

// Add a response filter that removes the encoding headers.
$proxy->filter(new RemoveEncodingFilter());

// Forward the request and get the response.
$response = $proxy->forward($request)->to('http://example.com');

// Output response to the browser.
(new Zend\Diactoros\Response\SapiEmitter)->emit($response);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM