先後端分離部署時如何保護前端代碼不被匿名訪問

背景

如今不少項目早就採用先後端分離的方式開發和部署了。前端代碼部署在nginx服務器上,由nginx直接對外提供靜態文件的服務,後端接口則由nginx作反向代理。html

這原本是極爲合理的部署方式,但對於一些須要登陸才能進行訪問的系統,負責安全的同事就會提出以下的疑慮:前端

index.html容許匿名訪問,別有用心之人豈不是能夠根據index裏的<script>標籤,拿到你全部的前端代碼了?

看來要解決這個問題。java

思路

爲了保護前端首頁代碼,一次請求的流程應該是下面這樣:nginx

用戶發起首頁的請求,服務端發現用戶沒有登陸,跳轉到登陸頁;
用戶發起首頁的請求,服務端發現用戶已經登陸了,正常輸出首頁的內容。後端

注意,這裏是服務端判斷,而不是客戶端判斷。api

判斷有沒有登陸,毫無疑問是是咱們的java後端才能作到的事情,可是首頁是html文件,在nginx下面,用戶請求它的時候還沒到後端這裏呢,怎麼判斷?tomcat

固然,你能夠把前端文件移到後端tomcat下,由tomcat提供服務,但這樣又走回老路了,這不是一個好方法,不推薦。安全

其實,在不改變部署架構的前提下,咱們簡單的經過nginx的配置和後端接口的配合,就能夠達到目的。服務器

簡單來講,利用nginx的rewrite + error_page指令來實現。cookie

  1. 首先,利用nginx的rewrite指令,把對index的請求,rewrite到後端某個接口上
  2. 後端這個接口裏判斷當前用戶是否已經登陸,若是沒有登陸,返回302跳轉,跳轉到受權頁去登陸
  3. 若是後端接口判斷當前用戶已經登陸,則返回一個錯誤碼給nginx(例如404),nginx利用error_page,指定404時,輸出index.html的文件內容。

nginx示例配置以下:

server {
       listen       80;
       server_name www.abc.com;
        recursive_error_pages on; #這個選項要開啓
        location / {   
            root /path/to/front-end;
        }
        location /api #交由tomcat處理
        {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto  $scheme;
            proxy_set_header   Cookie $http_cookie;
            proxy_pass http://localhost:9000;
        }

        location ~* ^(/|(/index\.html))$ {
            #未登陸的狀況下,不容許訪問首頁,注意這裏rewrite到一個location,而不是直接proxy_pass到後端接口。由於我要在@fallback裏利用queryString
            rewrite ^/(.*) /abcdefg?res=$request_uri; 
        }
        
        #
        location /abcdefg {
            proxy_pass http://localhost:9000/api/home/check-user?res=$request_uri;
            proxy_redirect off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_intercept_errors on;
            error_page  404  = @fallback;
        }
        
        
        location @fallback {
            if ($query_string ~* ^res=([^&]*)) {
                    set $path $1;
                    rewrite ^ /local/scripts$path;
            }
        }
        location /local/scripts/ {
            internal; #nginx內部纔有效的location,外部沒法經過/local/scripts/這個路徑訪問
            alias /path/to/front-end/; #注意,最後有個/符號
            error_page  404  =200 /local/scripts/index.html;
        }
}

後端check-user接口示例以下:

@GetMapping("check-user")
public void checkUser(String res, HttpSession session, HttpServletRequest request, HttpServletResponse response) throws IOException {
    if(session.getAttribute("User") == null){
        response.sendRedirect("login?returnUrl=" + URLEncoder.encode(res, "UTF-8"));
        return;
    }
    response.setStatus(HttpStatus.SC_NOT_FOUND);
}
相關文章
相關標籤/搜索