

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

# Configura un autorizzatore AWS Lambda per l'autenticazione OIDC
<a name="dicomweb-oidc-requirements"></a>

Questa guida presuppone che tu abbia già configurato il tuo Identity Provider (IdP) preferito per fornire token di accesso compatibili con i requisiti della funzionalità di HealthImaging autenticazione OIDC.

## 1. Configura i ruoli IAM per l'accesso alle API DICOMWeb
<a name="dicomweb-oidc-iam-roles"></a>

Prima di configurare l'autorizzatore Lambda, crea ruoli IAM da assumere durante HealthImaging DICOMWeb l'elaborazione delle richieste API. La funzione authorizer Lambda restituisce uno di questi ruoli ARN dopo una corretta verifica del token, HealthImaging permettendo di eseguire le richieste con le autorizzazioni appropriate.

1. Crea politiche IAM che definiscono i privilegi API desiderati. DICOMWeb Consulta la sezione "[Utilizzo DICOMweb](https://docs.aws.amazon.com/healthimaging/latest/devguide/using-dicomweb.html)" della HealthImaging documentazione per le autorizzazioni disponibili.

1. Crea ruoli IAM che:
   + Allega queste politiche
   + Includi una relazione di fiducia che consenta al HealthImaging service principal (`medical-imaging.amazonaws.com`) di assumere questi ruoli.

Ecco un esempio di policy che consente ai ruoli associati di accedere a un'API di HealthImaging DICOMWeb sola lettura:

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "MedicalImagingDicomWebOperations",
            "Effect": "Allow",
            "Action": [
                "medical-imaging:SearchDICOMInstances",
                "medical-imaging:GetImageSetMetadata",
                "medical-imaging:GetDICOMSeriesMetadata",
                "medical-imaging:SearchDICOMStudies",
                "medical-imaging:GetDICOMBulkdata",
                "medical-imaging:SearchDICOMSeries",
                "medical-imaging:GetDICOMInstanceMetadata",
                "medical-imaging:GetDICOMInstance",
                "medical-imaging:GetDICOMInstanceFrames"
            ],
            "Resource": "arn:aws:medical-imaging:us-east-1:123456789012:datastore/datastore-123"
        }
    ]
}
```

------

Ecco un esempio della politica di relazione di fiducia che dovrebbe essere associata al/ai ruolo/i:

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "OIDCRoleFederation",
            "Effect": "Allow",
            "Principal": {
                "Service": "medical-imaging.amazonaws.com"
        },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

L'autorizzatore Lambda che creerai nel passaggio successivo può valutare le attestazioni del token e restituire l'ARN del ruolo appropriato. AWS HealthImaging impersonerà quindi questo ruolo per eseguire la richiesta DICOMWeb API con le autorizzazioni corrispondenti.

Esempio:
+ Un token con attestazioni «admin» potrebbe restituire un ARN per un ruolo con accesso completo
+ Un token con affermazioni «reader» potrebbe restituire un ARN per un ruolo con accesso in sola lettura
+ Un token con attestazioni «Department\$1a» potrebbe restituire un ARN per un ruolo specifico del livello di accesso di quel reparto

Questo meccanismo ti consente di mappare il modello di autorizzazione del tuo IdP a specifiche HealthImaging autorizzazioni AWS tramite ruoli IAM.

## 2. Creazione e configurazione della funzione Lambda Authorizer
<a name="dicomweb-oidc-configure-lambda"></a>

Crea una funzione Lambda che verifichi il token JWT e restituisca l'ARN del ruolo IAM appropriato in base alla valutazione delle dichiarazioni del token. Questa funzione viene richiamata dal servizio di imaging sanitario e trasmette un evento che contiene l'ID del HealthImaging datastore, l' DICOMWeb operazione e il token di accesso trovati nella richiesta HTTP:

```
{
  "datastoreId": "{datastore id}",
  "operation": "{Healthimaging API name e.g. GetDICOMInstance}",
  "bearerToken": "{access token}"
}
```

La funzione di autorizzazione Lambda deve restituire una risposta JSON con la seguente struttura:

```
{
  "isTokenValid": {true or false},
  "roleArn": "{role arn or empty string meaning to deny the request explicitly}"
}
```

Puoi fare riferimento all'esempio di implementazione per ulteriori informazioni.

**Nota**  
Poiché alla DICOMWeb richiesta viene data risposta solo dopo la verifica del token di accesso da parte dell'autorizzatore lambda, è importante che l'esecuzione di questa funzione sia la più rapida possibile per fornire il miglior tempo di risposta dell' DICOMWeb API.

Affinché il HealthImaging servizio sia autorizzato a richiamare la funzione di autorizzazione lambda, deve disporre di una politica delle risorse che HealthImaging consenta al servizio di richiamarla. Questa politica delle risorse può essere creata nel menu di autorizzazione della scheda di configurazione lambda o utilizzando: AWS CLI

```
aws lambda add-permission \
    --function-name YourAuthorizerFunctionName \
    --statement-id HealthImagingInvoke \
    --action lambda:InvokeFunction \
    --principal medical-imaging.amazonaws.com
```

Questa politica delle risorse consente al HealthImaging servizio di richiamare l'autorizzatore Lambda durante DICOMWeb l'autenticazione delle richieste API.

**Nota**  
La politica delle risorse lambda può essere aggiornata in seguito con una condizione "ArnLike" corrispondente all'ARN di un HealthImaging datastore specifico.

Ecco un esempio di politica delle risorse lambda:

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Id": "default",
  "Statement": [
    {
      "Sid": "LambaAuthorizer-HealthImagingInvokePermission",
      "Effect": "Allow",
      "Principal": {
        "Service": "medical-imaging.amazonaws.com"
      },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:us-east-1:123456789012::function:{LambdaAuthorizerFunctionName}",
      "Condition": {
        "ArnLike": {
          "AWS:SourceArn": "arn:aws:medical-imaging:us-east-1:123456789012:datastore/datastore-123"
        }
      }
    }
  ]
}
```

------

## 3. Crea un nuovo datastore con l'autenticazione OIDC
<a name="dicomweb-oidc-datastore"></a>

Per abilitare l'autenticazione OIDC, è necessario creare un nuovo datastore utilizzando il parametro "». AWS CLI lambda-authorizer-arn L'autenticazione OIDC non può essere abilitata su datastore esistenti senza contattare l'assistenza. AWS 

Ecco un esempio di come creare un nuovo datastore con l'autenticazione OIDC abilitata:

```
aws medical-imaging create-datastore \
    --datastore-name YourDatastoreName \
    --lambda-authorizer-arn YourAuthorizerFunctionArn
```

Puoi verificare se uno specifico datastore ha la funzionalità di autenticazione OIDC abilitata utilizzando il comando AWS CLI get-datastore e verificando se l'attributo "" è presente: lambdaAuthorizerArn

```
aws medical-imaging get-datastore --datastore-id YourDatastoreId
```

```
{
    "datastoreProperties": {
        "datastoreId": YourdatastoreId,
        "datastoreName": YourDatastoreName,
        "datastoreStatus": "ACTIVE",
        "lambdaAuthorizerArn": YourAuthorizerFunctionArn,
        "datastoreArn": YourDatastoreArn,
        "createdAt": "2025-09-30T14:16:04.015000-05:00",
        "updatedAt": "2025-09-30T14:16:04.015000-05:00"
    }
}
```

**Nota**  
Il ruolo di esecuzione per il comando di creazione del AWS CLI datastore deve disporre delle autorizzazioni appropriate per richiamare la funzione di autorizzazione Lambda. Ciò mitiga gli attacchi di escalation dei privilegi in cui utenti malintenzionati potrebbero eseguire funzioni Lambda non autorizzate tramite la configurazione dell'autorizzazione del datastore.

## Codici di eccezione
<a name="dicomweb-oidc-exceptions"></a>

In caso di errore di autenticazione HealthImaging restituisce i seguenti codici di risposta di errore HTTP e messaggi del corpo:


| Condizione | Risposta AHI | 
| --- | --- | 
| Lambda Authorizer non esiste o non è valido | 424 Authorizer: configurazione errata | 
| Autorizzatore terminato a causa di un errore di esecuzione | Autorizzazione 424 non riuscita | 
| Qualsiasi altro errore di autorizzazione non mappato | Autorizzazione 424 non riuscita | 
| L'autorizzatore ha restituito una risposta non valida/mal formata | 424 Errore di configurazione dell'autorizzatore | 
| Authorizer ha funzionato per più di 1 secondo | 408 Authorizer Timeout | 
| Il token è scaduto o comunque non valido | 403 Token non valido o scaduto | 
| AHI non può federare il ruolo IAM restituito a causa di un'errata configurazione dell'autorizzatore | 424 Errore di configurazione dell'autorizzatore | 
| L'autorizzatore ha restituito un ruolo vuoto | 403 Accesso negato | 
| Il ruolo restituito non è richiamabile (assume-role/trust misconfig) | 424 Autorizzatore errato | 
| La frequenza delle richieste supera i limiti del gateway DICOMweb  | 429 Troppe richieste | 
| Datastore, Return Role o Authorizer Cross Region Account/Cross  | 424 Autorizzatore di accesso interregionale Account/Cross  | 

## Esempio di implementazione
<a name="dicomweb-oidc-implementation"></a>

Questo esempio in Python dimostra una funzione di autorizzazione lambda che verifica i token di accesso a AWS Cognito dagli eventi HealthImaging e restituisce un ARN del ruolo IAM con i privilegi appropriati. DICOMWeb 

L'autorizzatore Lambda implementa due meccanismi di memorizzazione nella cache per ridurre le chiamate esterne e la latenza di risposta. Il JWKS (JSON Web Key Set) viene recuperato una volta ogni ora e archiviato nella cartella temporanea della funzione, per consentire alle successive chiamate di funzione di leggerlo localmente anziché recuperarlo dalla rete pubblica. Noterai anche che un oggetto dizionario token\$1cache viene istanziato nel contesto globale di questa funzione Lambda. Le variabili globali sono condivise da tutte le chiamate che riutilizzano lo stesso contesto Lambda riscaldato. Grazie a ciò, i token verificati con successo possono essere archiviati in questo dizionario e consultati rapidamente durante la successiva esecuzione della stessa funzione Lambda. Il metodo di memorizzazione nella cache rappresenta un approccio generalista che potrebbe adattarsi ai token di accesso emessi dalla maggior parte dei provider di identità. [Per un'opzione di caching specifica per AWS Cognito, consulta la sezione [Gestione del pool di utenti](https://docs.aws.amazon.com/cognito/latest/developerguide/managing-users.html) e la [sezione caching della documentazione di](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-caching-tokens.html) Cognito.AWS](https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html)

```
import json
import os
import time
import logging
from jose import jwk, jwt
from jose.exceptions import ExpiredSignatureError, JWTClaimsError, JWTError
import requests
import tempfile

# Configure logging
logger = logging.getLogger()
log_level = os.environ.get('LOG_LEVEL', 'WARNING').upper()
logger.setLevel(getattr(logging, log_level, logging.WARNING))

# Global token cache with TTL
token_cache = {}

# JWKS cache file path
JWKS_CACHE_FILE = os.path.join(tempfile.gettempdir(), 'jwks.json')
JWKS_CACHE_TTL = 3600  # 1 hour

# Load environment variables once
USER_POOL_ID = os.environ['USER_POOL_ID']
CLIENT_ID = os.environ['CLIENT_ID']
ROLE_ARN = os.environ.get('AHIDICOMWEB_READONLY_ROLE_ARN', '')

def cleanup_expired_tokens():
    """Remove expired tokens from cache"""
    now = int(time.time())
    expired_keys = [token for token, data in token_cache.items() if now > data['cache_expiry']]
    for token in expired_keys:
        del token_cache[token]

def get_cached_jwks():
    """Get JWKS from cache file if valid, otherwise return None """
    try:
        if os.path.exists(JWKS_CACHE_FILE):
            # Check if cache file is still valid
            cache_age = time.time() - os.path.getmtime(JWKS_CACHE_FILE)
            if cache_age < JWKS_CACHE_TTL:
                with open(JWKS_CACHE_FILE, 'r') as f:
                    jwks = json.load(f)
                    logger.debug(f'Using cached JWKS (age: {int(cache_age)}s)')
                    return jwks
            else:
                logger.debug(f'JWKS cache expired (age: {int(cache_age)}s)')
    except Exception as e:
        logger.debug(f'Error reading JWKS cache: {e}')
    
    return None

def cache_jwks(jwks):
    """Cache JWKS to file"""
    try:
        with open(JWKS_CACHE_FILE, 'w') as f:
            json.dump(jwks, f)
        logger.debug('JWKS cached successfully')
    except Exception as e:
        logger.debug(f'Error caching JWKS: {e}')

def fetch_jwks(jwks_url):
    """Fetch JWKS from URL and cache it"""
    logger.debug('Fetching JWKS from URL')
    jwks = requests.get(jwks_url, timeout=10).json()
    # Convert to dict for faster lookups
    jwks['keys_by_kid'] = {key['kid']: key for key in jwks['keys']}
    cache_jwks(jwks)
    return jwks

def is_token_cached(token):
    if token not in token_cache:
        return None
    
    cached = token_cache[token]
    now = int(time.time())
    
    if now > cached['cache_expiry']:
        del token_cache[token]
        return None
    
    return cached

def cache_token(token, payload):
    now = int(time.time())
    token_exp = payload.get('exp')
    cache_expiry = min(now + 60, token_exp)  # 1 minute or token expiry, whichever is sooner
    
    token_cache[token] = {
        'payload': payload,
        'cache_expiry': cache_expiry,
        'role_arn': ROLE_ARN
    }

def handler(event, context):
    cleanup_expired_tokens() # start be removing expired tokens from the cache
    try:
        # Extract token from bearerToken or authorizationToken field
        token = event.get('bearerToken')
        if not token:
            raise Exception('No token provided')
        
        # Check cache first
        cached = is_token_cached(token)
        if cached:
            logger.debug('Token found in cache, skipping verification')
            return {
                'isTokenValid': True,
                'roleArn': cached['role_arn']
            }
        
        # Get Cognito configuration
        region = context.invoked_function_arn.split(':')[3]
        
        # Get JWKS (cached or fresh)
        jwks_url = f'https://cognito-idp.{region}.amazonaws.com/{USER_POOL_ID}/.well-known/jwks.json'
        jwks = get_cached_jwks()
        if not jwks:
            jwks = fetch_jwks(jwks_url)
        
        # Decode token header to get kid
        headers = jwt.get_unverified_headers(token)
        kid = headers['kid']
        
        # Find the correct key
        key = None
        for jwk_key in jwks['keys']:
            if jwk_key['kid'] == kid:
                key = jwk_key
                break
        
        if not key:
            # Key not found - try refreshing JWKS in case of key rotation
            logger.debug('Key not found in cached JWKS, fetching fresh JWKS')
            jwks = fetch_jwks(jwks_url)
            for jwk_key in jwks['keys']:
                if jwk_key['kid'] == kid:
                    key = jwk_key
                    break
        
        if not key:
            raise Exception('Public key not found')
        
        # Construct the public key
        public_key = jwk.construct(key)
        
        # Verify and decode the token (includes expiry validation)
        payload = jwt.decode(
            token,
            public_key,
            algorithms=['RS256'],
            audience=CLIENT_ID,
            issuer=f'https://cognito-idp.{region}.amazonaws.com/{USER_POOL_ID}'
        )
        
        logger.debug('Token validated successfully')
        logger.debug('User: %s', payload.get('username', 'unknown'))
        
        # Cache the validated token
        cache_token(token, payload)
        
        # Return authorization response
        return {
            'isTokenValid': True,
            'roleArn': ROLE_ARN
        }
        
    except ExpiredSignatureError:
        logger.debug('Token expired')
        return {
            'isTokenValid': False,
            'roleArn': ''
        }
    except JWTClaimsError:
        logger.debug('Invalid token claims')
        return {
            'isTokenValid': False,
            'roleArn': ''
        }
    except JWTError as e:
        logger.debug('JWT validation error: %s', e)
        return {
            'isTokenValid': False,
            'roleArn': ''
        }
    except Exception as e:
        logger.debug('Authorization failed: %s', e)
        return {
            'isTokenValid': False,
            'roleArn': ''
        }
```