1. Configurando openresty con TSL
Veamos el fichero de configuración de openresty (/usr/local/openresty/nginx/conf/nginx.conf)
Las líneas importantes son:
39: Le indicamos el puerto ssl 8449
46-47: Le damos la ruta del certificado de servidor
50-51: Protocolos
54-57: Propiedades del proxy para pasar información al servidor
62-101: Se indican las posibles rutas a utiizar y el tratamiento de las mismas. Para ello se hace includes de ficheros que están en las mismas carpetas. Estos ficheros ya se vieron en una entrada anterior.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | worker_processes auto; error_log /var/log/openresty/error.log notice; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; error_log /usr/local/openresty/nginx/logs/error.log info; access_log /var/log/openresty/access.log main; sendfile on; keepalive_timeout 65; # Enable Lua shared dictionary for sessions lua_shared_dict sessions 10m; lua_package_path '/usr/local/share/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/openresty/nginx/conf/?.lua;;'; lua_package_cpath '/usr/local/lib/lua/5.1/?.so;;'; # Creamos una bateria de servidores para softprop con un solo servidor upstream softpropsrv { server 0.0.0.0:5001; #server 127.0.0.1:5001; #server el_que_sea.com; } server { #->listen 8001; listen 8449 ssl; #->New server_name localhost; #server_name edu.tavernes.es; #-> Begin new # SSL certificate configuration ssl_certificate /usr/local/openresty/nginx/conf/wildcard2023Nginx.crt; ssl_certificate_key /usr/local/openresty/nginx/conf/wildcard2023Nginx.rsa; # Optional SSL settings for security and performance ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Common proxy settings (optional) proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; # Location of static files location /static/ { root /home/eduard/MyPython/11.softprop-01; # Path where static files are stored expires max; access_log off; } location /softprop { include nginx.conf.softprop; } # Redirect root to login page location = / { return 301 /login; } # Serve the login form location = /login { include nginx.conf.login; } location = /initial { include nginx.conf.initial; } # Protected proxy location location /protected { include nginx.conf.protected; } #location = /success { # include nginx.conf.success; #} location = /error { include nginx.conf.error; } location = /logout { include nginx.conf.logout; } } } |
2. Manejo de sesiones en Python
En cualquier ruta, validamos la sesión con "xmsession.validate(tab_id=tab_id, req=req)"
veamos un ejemplo del tratamiento de un ruta
@app.get("/softprop/tree") def geto2(tab_id:str | None= None, req=None):#request:Request): ''' Execute the main view that is tree menu''' # 1. Validem que haja tab_id i que l'usuari estiga en loa sessió de la BD my_response,myerror=xmsession.validate(tab_id=tab_id, req=req) if (len(myerror.strip())>1): print ("redirecting from /softprop/tree" ) return my_response
Y el sistema te rebota a la pantalla de login si considera que no se ha validado convenientemente la sesión.
Veamos que es lo que se valida, para ello veamos el código de xmsession.py y en concreto lo marcado en amarillo claro.
Primero se verifica que haya un tab_id y después que exista en la base de datos una sessión referenciada por el tab_id. SI no se cumplen las condiciones te envia a la pantalla de "login" para que te identifiques
El tab_id nos sirve para poder separar las sesiones que hay no solo entre diferentes navegadores o ventanas del navegador, sino también entre diferentes pestañas.
Hay una función "test_session" en rosa claro, que verifica que el tab_id se encuentra en una cookie y que además está guardada en la BD y que el navegador y la IP coinciden (esto puede dar lugar a fallos en dispositivos móviles si cambian la conexión).
from datetime import datetime, timedelta, timezone import uuid import orjson as json from sqlalchemy.orm import Session from fasthtml import common as fh import os #------Imprescindible para poder importar de otras carpetas (de basicutils) import sys from pathlib import Path path_root1 = Path(__file__).parents[1] # Damos un salto de directorio hacia arriba-> ximoutilsmod sys.path.append(str(path_root1)) path_root0 = Path(__file__).parents[0] # El mismo directorio sys.path.append(str(path_root0)) from basicutils import xmjwt, xmother, xmldap, xmdb from session.xmsessionmodels import SessionStore, SessionStoreAbst, SessionStoreHst, SessionExistence #from basicutils.xmdb import dbSingleton # ------Fin imprescindible def delete_old_sessions(db: Session, days: int=1): '''Delete old tokens from the db''' #1. Get the date to delete date_to_delete=datetime.now(timezone.utc) - timedelta(days=days) #2. Delete the old tokens db.query(SessionStore).filter(SessionStore.creation_date < date_to_delete).delete() db.commit() #------------------------------------------------------------------- def get_is_nginx(req)->bool: isnginx=False try: headers = req.headers isnginx = ("x-forwSessionarded-for" in headers or "x-forwarded-proto" in headers) except Exception: pass return isnginx def get_user_agent(req)->str: useragent=None try: useragent=req.headers.get("User-Agent",'') except Exception: pass return useragent def get_user_ip(req)->str: user_ip=None if get_is_nginx(req): x_forwarded_for = req.headers.get('X-Forwarded-For') if x_forwarded_for: # If there is more than one IP, the first is the client IP try: user_ip = x_forwarded_for.split(',')[0] except Exception: user_ip=req.headers.get("X-Real-IP",'') else: # Fallback to the direct client IP user_ip = req.client.host return user_ip def get_req_info(req): ''' Get client ip, user agent, is_nginx .... from request ''' user_agent=user_ip="" is_nginx=False is_nginx=get_is_nginx(req) user_agent=get_user_agent(req) user_ip=get_user_ip(req) return user_ip, user_agent, is_nginx def set_session(req, db: Session, user: str, tab_id:str)->tuple: ''' creates the session in a DB after the login ''' user_ip,user_agent,is_nginx=get_req_info(req) mysession=SessionStoreAbst(description=tab_id, user=user, user_agent=user_agent, user_ip=user_ip, is_nginx=is_nginx) mysession=xmdb.merge_to_main_and_histo(db, mysession, SessionStore, SessionStoreHst) if not mysession: return "", "Cannot create session" return user,'' def test_session(req, db: Session, tab_id:str)->tuple: '''Tests if the session in the DB and the cookie (tab_id) has the same values''' # 1. Get the user from the tab_id cookie cookie_value= req.cookies.get(tab_id) if not cookie_value: return "","No tab_id cookie found" my_dict= json.loads(cookie_value) user=my_dict.get("username",'') user_ip,user_agent,is_nginx=get_req_info(req) # 2. Get the session from the DB mysession=db.query(SessionStore).filter_by(description=tab_id).first() # 3. If is nginx and has no session let's create a session if not mysession and is_nginx: mysession=SessionStoreAbst(description=tab_id, user=user, user_agent=user_agent, user_ip=user_ip, is_nginx=is_nginx) mysession=xmdb.merge_to_main_and_histo(db, mysession, SessionStore, SessionStoreHst) # 4. If it has no session there is a problem ! if not mysession: return "","Session not existing." # 3. Compare values. if mysession.user != user: return "","Users are different: "+user + "!=" + mysession.user if mysession.user_agent != user_agent: return "","Users agents are different: "+user_agent + "!=" + mysession.user_agent if mysession.is_nginx != is_nginx: return "","Nginx problem: session is_nginx is different from is_nginx " if mysession.user_ip != user_ip: return "","User IPs are different: "+user_agent + "!=" + mysession.user_agent return user,'' def validate(tab_id:str, req)->tuple: # 1. Si es la primera vegada, no tenim tab_id i envia un script per a arreplegar-lo if tab_id is None: redirection='/softprop/login' a=fh.RedirectResponse(redirection, status_code=303) print('enviem a /softprop/login perque NO hi ha TAB_ID' ) return a, "no hi ha tab_id" #2. Si tenim tab_id with xmdb.session_local() as db: user, myerror=test_session(req, db, tab_id) if (len(user.strip())==0 or (len(myerror.strip())>0)): redirection='/softprop/login' a=fh.RedirectResponse(redirection, status_code=303) print('enviem a /softprop/login perque:' + myerror) return a, myerror return '','' def read_session_info(tab_id:str, db: Session)->dict: '''Get the field info from the table "x_session_store" with id=tab_id''' # 1. Get the session from the DB mysession=db.query(SessionStore).filter_by(description=tab_id).first() if not mysession : return {} info=mysession.info try: info_dict=json.loads(info) except json.JSONDecodeError: info_dict = {} return info_dict def write_session_info(tab_id:str, db: Session, info:dict): '''Writes the field info from the table "x_session_store" with id=tab_id''' # 1. Get the session from the DB mysession=db.query(SessionStore).filter_by(description=tab_id).first() if mysession : info_json=json.dumps(info).decode("utf-8") mysession.info=info_json mysession=xmdb.merge_to_main_and_histo(db, mysession, SessionStore, SessionStoreHst)
No hay comentarios :
Publicar un comentario