Cachowanie stron z formularzem logowania

Marcin Kaciuba 09.09.2018

Najczęsciej gdy robi się strony, które mają być dostępne dla określonego grona odbiorców (posiadających login i hasło) nie pozwala się na to żeby serwer proxujący cachował je. Cachowanie takiej strony mogłoby spowodować, że użytkownicy, którzy nie powinni mieć dostępu do zawartości, taki dostęp uzyskają.

W tym poście pokażę, że jednak da się przygotować taką konfigurację serwera proxującego, żeby cachować stronę per użytkownik (naszczęście w tym przypadku mamy tylko 2 grupy: zalogowany, niezalogowany. Przy większej ilości grupy takie podejście nie miałoby sensu.)

Jako serwer proxujący został użyty NGINX.

Cachowanie w NGINX

Na początku informacja jak działa cachowanie w NGINX. Posiada on dyrektywę proxy_cache_key, która domyślnie złożona jest z $scheme$proxy_host$request_uri;

Czyli kluczem do cache jest protokołu host i url. Przykładowo strona https://mkaciuba.pl/uri Bedzie zapisać przy użyciu klucza httpsmkaciuba.pluri. Takie podejście nie daje dużo możliwości. Ale dzięki temu, że w NGINX można użyć języka LUA znika problem ze statyczna konfiguracja, zmienne i prawie całą konfigurację można modyfikować dynamicznie.

 

NGINX i LUA

Zwykle konfiguracja NGINX zawiera się w statycznym pliku z konfiguracja, jednak dzięki modułuowi nginx-lua możemy rozszerzyć obsługę zapytać przez pisanie skryptów  operujących na requestach. LUA w NGINX może uruchamiać się na kilku fazach.

Nas będzie interesować faza rewrite by lua.

Użycie LUA w NGINX daje bardzo dużo możliwości obsługi zapytań. Dzięki niej nie potrzebujemy już statycznego podejścia (wszystko zdefiniowane w konfiguracji) lecz możemy wykonywać zapytania HTTP, łaczyć się do redis itd.

 

Modyfikacja klucza do cache

Wiemy już jak NGINX generuje klucz do cacha oraz że można użyć LUA do modyfikacji zmiennych NGINX. Pora zebrać to wszystko w całość i przygotować konfigurację, które bedzię modyfikowało klucz do cache w zależności od tego czy użytkownik powinien mieć dostęp czy nie.

Kilka początkowych założeń:

  1. Użytkownik zalogowany będzie posiadał ciastko w którym będziemy przechowywać dane potrzebne do weryfikacji czy ma dostęp do strony

  2. Nginx będzie komunikował się z CacheAPI w celu wyliczenia klucza do cacha. Klucz będzie modyfikowany jeśli użytkownik ma dostęp do strony

 

Poniższy diagram przedstawia proponowany przepływ sterowania dla aplikacji backendowej.

W przypadku pobrania strony z cache

A gdy nie mamy strony w cachu nginxa

 

 

Konfiguracja NGINX

Na początku powinniśmy zdefiniować kilka zmiennych w nginx dla dzięki którym będziemy mogli w razie błędu z CacheAPI nie cachowac odpowiedzi

 

  set $no_cache 0;   
  set $bypass_cache 0;


   # Zmienna cache_version w niej zapiszemy wersje cacha    
   set $cache_version "";


   # Użycie zmiennych do pomienięcia cacha oraz nie cachowania opdowiedzi
   proxy_cache_bypass $bypass_cache;
   proxy_no_cache $no_cache;

  # Nasz głowny skryt do obsługi requestów jest zbyt  duzy żeby go zamieszczać w konfiguracji nginx. Wygodniej edytować go w osobnym pliku
   rewrite_by_lua_file /var/lib/nginx/lua/rewrite.lua;


   # Nazwa naszego bucketu z cachem
   proxy_cache dynamic;


   # Nasz klucz do cache
   set $cache_key "$method!$host!$uri!$is_args$args!$content_encoding!$cache_version";
   proxy_cache_key $cache_key;

Sam skrypt wygląda następująco

ngx.var.no_cache = '0'
local user_id = ngx.var.cookie_session_cookie
if user_id == nil then
   return
end

local path = ngx.var.uri
-- dziala na okreslonych sciezkac
if not re.match(path, '^/(path1|path)/(.*)', 'ijo') then
   return
end

local http = require "resty.http"
local httpc = http.new()

-- timeouts do api
httpc:set_timeouts(50, 450, 450)
httpc:connect('unix:/var/run/nginx.sock')

local res, err = httpc:request({
   path = '/api/v1/lambda/?userid=' .. user_id .. '&path=' .. path,
   headers = {
       ["Host"] = "mkaciuba.com",
       ['x-cache-api-user-id'] = user_id,
       ['x-cache-api-path'] = path
   },
})

if not res then
-- in case of error no cache response
   ngx.log(ngx.ERR, 'request to lambda failed ' .. err )
   ngx.var.no_cache = '1'
   return
end

if res.status ~= 200 then
   ngx.log(ngx.ERR, 'request to lambda invalid status ' .. res.status)
   ngx.var.no_cache = '1'
   return
end

local info = res.headers['x-cache-api-info']
if info == 'no-cache' then
   ngx.var.no_cache = '1'
   return
end

if res.headers['x-cache-api-cache-param'] == nil then
   return
end

if info == 'no-change-cache-key' then
   return
end


-- update cache key
local cache_key = ngx.var.cache_key
ngx.var.cache_key = cache_key .. '!' .. res.headers['x-cache-api-cache-param']

 

Podsumowanie

Cachowanie stron per segment użytkownika nie zadaniem prostym, ale gdy się je już wykona można bardzo przyspieszyć działanie strony w przypadku wolnego backendu.

PS Kod backendu i konfiguracja nginx wykorzystująca opisaną logikę można zobaczyć tutaj

Share: