淺談外網通過反向代理訪問內網資源時的權限保護

標題可能有些繞口,意思就是我在外網要訪問內網一資源(比如網站)時需要身份驗證,只有通過身份驗證才能訪問,這個該如何實現?
其實這是一朋友問我的一個問題,需求就是:

領導在外出差,要通過企業微信訪問內網資源,而這個資源又是高度保密的,不能隨便讓別人看到,公司又不提供VPN等工具,要如何實現?

我覺得這個需求蠻有意思的,就去深入思考了一下。我們知道,微信開發很多情況下是需要綁定域名的,那麼綁定域名的情況下又要訪問內網資源,很容易讓人想到反向代理。所以,要實現這個需求,通過反向代理是必須的,問題就是,如何進行身份驗證?今天會介紹兩種 方式,一種使用Apache服務器+驗證模塊(這裏利用Java Web的Servlet,實現的語言不唯一)實現,另外一種使用內網穿透工具,frp實現。

Apache實現

測試環境

Apache 2.2.31
JDK 1.8
IntelliJ IDEA 15

實現思路

首先,Apache服務器要架設在一臺內外網都可訪問的計算機上。
這個問題其實我想了蠻久的,最早想的是通過Apache服務器自帶的驗證模塊,也就是記錄用戶名和密碼到.htaccess文件,但是發現,這個只適用於架設在本機上的網站項目,通過反向代理就沒用了。後來想到可以利圖片防盜鏈的思路,我們在做防盜鏈時,常會判斷請求HTTP頭裏是否有諸如HTTP_REFERER之類的參數,然後根據這個參數進行重定向。我們在實現訪問權限控制時不妨也效仿它,整體思路都一樣,不一樣的地方就是在訪問頁面前進行授權,授權通過之後加上某個HTTP頭,表示授權成功。

需求

假設我所在的公司開發了一套新的API,還沒有開發完畢,不能隨便公開,現在領導外出開會,需要對客戶展示。(但是公司又不提供VPN)

實現步驟

Apache的httpd.conf文件配置

需要開啓Apache的mod_rewrite模塊。
Apache支持檢測的HTTP頭如下:

我這次就使用HTTP_REFERER頭吧
Apache的反向代理設置如下:

<VirtualHost *:80>  
    ServerName  api.example.com
    ServerAlias api.example.com
    RewriteEngine On
    RewriteCond %{HTTP_REFERER} !^http://api.example.com/
    RewriteRule ^(.*)$ http://www.baidu.com/ [R=401]   
    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass / http://127.0.0.1:8080/api/sdk/
    ProxyPassReverse / http://127.0.0.1:8080/api/sdk/
</VirtualHost>

這裏解釋一下意思:
當對api.example.com發起請求時,Apache會檢測請求頭中是否有HTTP_REFERER,如果有,值是否以http://api.example.com/開頭,如果不是,則401強制外部重定向到百度,其實你根本看不到百度的頁面,只會看到401 Forbidden。
那爲什麼要判斷HTTP_REFERER是否以http://api.example.com/呢?其實,當我們每次對api.example.com發起請求時,包括對子頁面,Apache都會進行攔截,它會對每次的請求頭進行檢查,如果不符合就會直接返回401。所以,這個要觀察子站點,看一下子站點是不是在頁面見跳轉時都會帶HTTP_REFERER頭,如果有的話就要按照它裏面的規則來,驗證成功後的入口頁面的請求頭要設置成一樣的。否則我們永遠只能看到一個頁面了,頁面間一旦跳轉,就會401。

Apache的重寫規則可以自行百度一下,這裏推薦一篇簡單的Apache的Rewrite規則詳細介紹,裏面關於重寫標誌的總結還是蠻清楚的。

Java端的編寫

首先是Servlet

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by Martin Huang on 2018/5/16.
 */
public class ProxyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //在這裏進行驗證授權
        resp.addHeader("REFERER","http://api.example.com/");
        resp.sendRedirect("http://api.example.com/");
    }
}

然後是HTML頁面,這裏比較簡單,我直接使用一個a標籤進行代替,主要是爲了演示效果

<html>
<body>
<h2>Hello World!</h2>
<br/>
<a href="proxyServlet">Jump</a>
</body>
</html>

最後是web.xml中Servlet的配置

  <servlet>
    <servlet-name>proxyServlet</servlet-name>
    <servlet-class>com.example.servlet.ProxyServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>proxyServlet</servlet-name>
    <url-pattern>/proxyServlet</url-pattern>
  </servlet-mapping>

效果

未授權情況下:

可以發現子頁面也是無法訪問的

授權後:


子頁面也是可以訪問的

存在的問題

  • 頁面直接只能通過點擊鏈接的方式跳轉,不能直接輸入地址欄,否則依然401
  • HTTP_REFERER的值一旦被他人知道的話就容易使用第三方工具破解了

frp的實現(推薦)

對於訪問網站的權限控制,我還是比較推薦frp的,畢竟這是一個開源項目,已經經過多個版本的迭代,安全性也好。
frp 是一個可用於內網穿透的高性能的反向代理應用,支持 tcp, udp, http, https 協議。
frp的文檔點這裏

實現步驟

搭建、配置可以見這篇文章LINUX | frp內網穿透穿透服務器
這裏我們只關心網站訪問的授權
frpc.ini配置文件如下:

[common]
server_addr = x.x.x.x #frp服務器IP
server_port = 7000    #與服務器進行通信的端口

[web01]
type = http
local_ip = 127.0.0.1
local_port = 80
http_user = abc  #用於授權的用戶名
http_pwd = abc   #用於授權的密碼
custom_domains = api.example.com

注:

我在服務器上設置了5520爲web的訪問端口,所以我利用api.example.com:5520進行訪問

效果


訪問時會讓你輸入用戶名和密碼進行驗證,子頁面也會。它這個驗證是session級別的,也就是說如果瀏覽器關閉後要重新進行驗證。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章