İtalyan Dijital Kimliğinin Manzarası

İtalyan dijital kimlik ekosistemi Avrupa'nın en karmaşık ekosistemlerinden biridir; Bir arada var olan ve birleşen ana sistemler: SPID (Kamu Kimlik Sistemi Dijital), CIE (Elektronik Kimlik Kartı) ve ödemeler için, ödemePA. 2023'ten itibaren geçiş OpenID Bağlantısı protokol olarak unified, geliştiriciler için entegrasyonu önemli ölçüde basitleştirdi.

Devlet kimlik doğrulamasını bir uygulamaya entegre etmesi gereken bir geliştirici veya mimar olarak aşağıdaki sorunlarla karşı karşıya kalırsınız: önemli etkileri olan çeşitli teknik seçenekler. Bu makale AGID'e katılım konusunda size rehberlik eder SAML 2.0'ı (tarihsel SPID protokolü) OpenID Connect ile karşılaştırarak somut uygulamaya aktarın Federasyon (hem SPID hem de CIE'nin geleceği) ve ödemeler için pagoPA'nın nasıl entegre edileceğini gösteriyor.

Ne Öğreneceksiniz

  • SPID mimarisi: Kimlik Sağlayıcı, Servis Sağlayıcı, SAML 2.0 ve güvenlik düzeyleri
  • CIE mimarisi: NFC çipi, PIN, CIE-ID arka ucu ve kimlik doğrulama modu
  • SPID ve CIE için OpenID Connect: federasyon, belirteçler, talepler ve kapsamlar
  • AGID katılım süreci: teknik ve prosedürle ilgili gereksinimler
  • Resmi SDK'lar: 5 programlama dili için kütüphaneler
  • Pratik uygulama: Tam örneklerle Python ve TypeScript
  • pagoPA: PA hizmetlerine ödeme entegrasyonu
  • Geliştirme ve doğrulama için test etme ve hazırlama ortamları

SPID: Genel Dijital Kimlik Sistemi

SPID, İtalya'nın ulusal dijital kimlik sistemidir. Kanun Hükmünde Kararname 82/2005 (CAD) ve AgID tarafından düzenlenir. Vatandaşların herhangi bir PA hizmetinde (ve birçok (özel) tek bir kimlik bilgi çifti ile, şunlardan biri tarafından yönetilir: Kimlik Sağlayıcı (IdP) akredite edilmiştir (Aruba, Infocert, Namirial, Poste, Register, Sielte, SpidItalia, Tim, Intesa).

SPID tahmin ediyor 3 seviyeli güvenlik:

  • Seviye 1: Kullanıcı adı ve şifre ile kimlik doğrulama. Düşük riskli hizmetler için uygundur.
  • Seviye 2: kullanıcı adı/şifre + OTP (SMS veya uygulama). PA hizmetleri için en çok kullanılan düzey. İkinci bir kimlik doğrulama faktörü (2FA) gerektirir.
  • Seviye 3: dijital sertifika veya akıllı kartla kimlik doğrulama. Yüksek performanslı hizmetler için risk (noter işlemleri, nitelikli dijital imza).

CIE: Elektronik Kimlik Kartı

Devlet Matbaa ve Darphane Enstitüsü tarafından verilen CIE 3.0, sertifikalara sahip bir NFC çipi içeriyor Güçlü kimlik doğrulamayı mümkün kılan X.509 dijital cihazlar. CIE-ID sistemi çevrimiçi kimlik doğrulamasına olanak tanır aracılığıyla:

  • NFC'li akıllı telefon: Kullanıcı CIE'yi akıllı telefonuna yaklaştırır ve PIN'i girer. CIE ID uygulaması dijital olarak imzalanmış bir kimlik doğrulama onayı oluşturur.
  • NFC okuyuculu masaüstü: İçişleri Bakanlığı'nın CIE ID yazılımı aracılığıyla.
  • NFC'siz masaüstü: CIE ID uygulamasıyla taranan QR kodu aracılığıyla kimlik doğrulama.

Geliştirici açısından bakıldığında, CIE ve SPID artık aynı OpenID Connect (OIDC) protokolünü paylaşıyor. Temel olarak Kimlik Sağlayıcının kaydı ve meta verilerinde farklılıklar vardır.

SPID ve CIE için OpenID Connect: Birleşik Protokol

AgID yayınladı SPID ve CIE için OpenID Connect Teknik Kuralları, docs.italia.it adresinde mevcuttur. Bu kurallar bir tanımlar OpenID Bağlantı Federasyonu OIDC Federasyonu 1.0 standardını (IETF taslağı) temel alır; burada:

  • AgID orada Güven Çapası: Federasyonun kök düğümü, tüm katılımcılar arasındaki güven için gerçeğin kaynağıdır.
  • Kimlik Sağlayıcılar (SPID ile aynı) Yaprak Düğümleri kayıt ol AgID'de.
  • I Güvenen Taraf (SPID/CIE kullanmak isteyen hizmetler) yayınlayarak kaydolun bir Varlık Yapılandırması (genel anahtarlarınızla imzalanmış bir JWT).
# Implementazione OpenID Connect per SPID/CIE in Python
# Usa la libreria spid-cie-oidc-django o implementazione custom

import httpx
import jwt
import json
import secrets
import hashlib
import base64
from datetime import datetime, timedelta
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

class SPIDCIEOIDCClient:
    """
    Client OIDC per integrazione SPID/CIE.
    Implementa il flow Authorization Code con PKCE (obbligatorio per SPID/CIE OIDC).
    """

    def __init__(
        self,
        client_id: str,       # URI del Relying Party (il tuo servizio)
        redirect_uri: str,    # URI di callback
        private_key_path: str, # Chiave privata RSA/EC per firma JWT
        trust_anchor: str = "https://registry.agid.gov.it"
    ):
        self.client_id = client_id
        self.redirect_uri = redirect_uri
        self.trust_anchor = trust_anchor

        # Carica chiave privata per firma
        with open(private_key_path, "rb") as f:
            self.private_key = serialization.load_pem_private_key(
                f.read(), password=None, backend=default_backend()
            )

    def generate_pkce(self) -> tuple[str, str]:
        """
        Genera code_verifier e code_challenge per PKCE.
        PKCE è OBBLIGATORIO nelle specifiche SPID/CIE OIDC.
        """
        # code_verifier: stringa casuale di 43-128 caratteri
        code_verifier = secrets.token_urlsafe(64)

        # code_challenge = BASE64URL(SHA256(code_verifier))
        code_challenge = base64.urlsafe_b64encode(
            hashlib.sha256(code_verifier.encode()).digest()
        ).rstrip(b"=").decode()

        return code_verifier, code_challenge

    def build_authorization_url(
        self,
        idp_authorization_endpoint: str,
        scope: list[str] = None,
        acr_values: str = "https://www.spid.gov.it/SpidL2",  # Livello 2 default
        state: str = None,
        nonce: str = None,
        ui_locales: str = "it",
        claims: dict = None
    ) -> tuple[str, dict]:
        """
        Costruisce l'URL di autorizzazione per SPID/CIE OIDC.
        Ritorna (authorization_url, session_data) dove session_data va salvato in sessione.
        """
        if scope is None:
            scope = ["openid", "profile"]

        if state is None:
            state = secrets.token_urlsafe(32)

        if nonce is None:
            nonce = secrets.token_urlsafe(32)

        code_verifier, code_challenge = self.generate_pkce()

        # Request Object: JWT firmato con claims della request
        # Obbligatorio in SPID/CIE OIDC per sicurezza end-to-end
        request_object_claims = {
            "iss": self.client_id,
            "aud": idp_authorization_endpoint,
            "iat": int(datetime.utcnow().timestamp()),
            "exp": int((datetime.utcnow() + timedelta(minutes=5)).timestamp()),
            "jti": secrets.token_urlsafe(16),
            "response_type": "code",
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
            "scope": " ".join(scope),
            "state": state,
            "nonce": nonce,
            "code_challenge": code_challenge,
            "code_challenge_method": "S256",
            "acr_values": acr_values,
            "ui_locales": ui_locales,
        }

        if claims:
            request_object_claims["claims"] = claims

        # Firma il Request Object con la chiave privata del RP
        request_object = jwt.encode(
            request_object_claims,
            self.private_key,
            algorithm="RS256",
            headers={"kid": "rp-signing-key-2024"}
        )

        # Costruisci URL autorizzazione
        import urllib.parse
        params = {
            "client_id": self.client_id,
            "response_type": "code",
            "scope": " ".join(scope),
            "redirect_uri": self.redirect_uri,
            "state": state,
            "code_challenge": code_challenge,
            "code_challenge_method": "S256",
            "request": request_object,  # Request Object firmato
        }

        auth_url = f"{idp_authorization_endpoint}?{urllib.parse.urlencode(params)}"

        session_data = {
            "state": state,
            "nonce": nonce,
            "code_verifier": code_verifier,
        }

        return auth_url, session_data

    async def exchange_code_for_tokens(
        self,
        authorization_code: str,
        code_verifier: str,
        idp_token_endpoint: str
    ) -> dict:
        """
        Scambia il codice di autorizzazione per i token (access_token, id_token).
        Usa Client Authentication con private_key_jwt (obbligatorio in SPID/CIE).
        """
        now = int(datetime.utcnow().timestamp())

        # client_assertion: JWT firmato per autenticare il RP al token endpoint
        client_assertion = jwt.encode(
            {
                "iss": self.client_id,
                "sub": self.client_id,
                "aud": idp_token_endpoint,
                "iat": now,
                "exp": now + 300,
                "jti": secrets.token_urlsafe(16),
            },
            self.private_key,
            algorithm="RS256",
            headers={"kid": "rp-signing-key-2024"}
        )

        async with httpx.AsyncClient() as client:
            response = await client.post(
                idp_token_endpoint,
                data={
                    "grant_type": "authorization_code",
                    "code": authorization_code,
                    "redirect_uri": self.redirect_uri,
                    "code_verifier": code_verifier,
                    "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
                    "client_assertion": client_assertion,
                    "client_id": self.client_id,
                }
            )
            response.raise_for_status()
            return response.json()

    def validate_id_token(
        self,
        id_token: str,
        idp_jwks_uri: str,
        nonce: str,
        expected_acr: str = None
    ) -> dict:
        """
        Valida l'ID Token ricevuto dall'IdP.
        Verifica firma, nonce, audience, e livello di autenticazione (acr).
        """
        # Recupera le chiavi pubbliche dell'IdP
        import httpx
        jwks = httpx.get(idp_jwks_uri).json()

        # Decodifica e valida il JWT
        claims = jwt.decode(
            id_token,
            jwks,
            algorithms=["RS256", "ES256"],
            audience=self.client_id,
            options={"require": ["nonce", "acr"]}
        )

        # Verifica nonce (anti-replay)
        if claims.get("nonce") != nonce:
            raise ValueError("Invalid nonce in ID Token")

        # Verifica livello di autenticazione se richiesto
        if expected_acr and claims.get("acr") != expected_acr:
            raise ValueError(f"ACR mismatch: expected {expected_acr}, got {claims.get('acr')}")

        return claims

SPID ve CIE Talepleri: Kullanıcı Nitelikleri

SPID ile tipik özel OIDC sağlayıcıları arasındaki önemli bir fark, iddialar (nitelikler kullanıcı) mevcut. SPID/CIE, İtalyan hükümeti tarafından onaylanmış bir dizi özellik sağlar:

OIDC'yi talep edin SPID özelliği CIE'de mevcut Notlar
sub IdP'deki benzersiz kimlik Si CF değil; farklı IdP'ler arasındaki değişiklikler
fiscal_number Vergi kimlik kodu Si Biçim: TINIT-XXXXXXXXXXXXXX
given_name İsim Si OIDC standartları
family_name Soyadı Si OIDC standartları
birthdate Doğum tarihi Si Biçim: YYYY-AA-GG
place_of_birth Doğum yeri Si Belediyenin kadastro kodu
gender Seks Si E/K
email E-posta (sertifikalı değil) No Yalnızca kullanıcı tarafından bildirilen SPID
mobile_phone Cep telefonu No Yalnızca kullanıcı tarafından bildirilen SPID
document_details Belge verileri Si Yalnızca CIE: numara. CIE, geçerlilik sonu, ortak sorun

Uyarı: alt ve mali_numarası

Il sub SPID'deki talep kullanıcı tanımlayıcısıdır belirli bir IdP'de, küresel bir tanımlayıcı değil. Bir kullanıcı IdP'yi değiştirirse (ör. Aruba'dan Poste'ye), sub değiştirmek. Vergi Kodu (fiscal_number) bir vatandaş için tek istikrarlı ve küresel tanımlayıcıdır İtalyan. Şunu kullanın: fiscal_number veritabanınızdaki birincil anahtar olarak değil, sub.

AGID'e Katılım: Hizmet Sağlayıcı Olmak

SPID veya CIE OIDC'yi hizmetinize entegre etmek için akreditasyon sürecini şu şekilde tamamlamanız gerekir: Güvenen Taraf (RP) AgID'de. Süreç şu şekilde ayrılmıştır:

  1. Developers.italia.it'e kayıt: bir hesap oluşturun ve platforma giriş yapın SPID/CIE'nin katılımı.
  2. Teknik hazırlık: SP'nizi resmi SDK'lardan biriyle veya bir uygulamayla uygulayın özel. Bağlı Taraf meta verilerini (Varlık Yapılandırması JWT) yapılandırın.
  3. Aşama ortamı: AGID, bir test IdP'si sağlar (SAML için spid-test.agid.gov.it, OIDC için demo-oidc.agid.gov.it) önceden tanımlanmış test kullanıcılarıyla.
  4. Teknik doğrulama: SP'niz resmi AGID doğrulama aracıyla test edilmiştir. Tüm zorunlu test senaryolarını geçmek zorundadırlar.
  5. Yasal anlaşma: AgID (veya seçilen toplayıcı ile) üyelik sözleşmesinin imzalanması, toplayıcı aracılığıyla üyelik durumunda).
  6. Üretme: Onaylandıktan sonra SP'niz üretime ve kullanıcılara kaydedilir. gerçek insanlar kimlik doğrulaması yapabilir.
# Entity Configuration del Relying Party (JWT firmato)
# Questo documento deve essere pubblicato all'URL: {client_id}/.well-known/openid-federation

import jwt
import json
from datetime import datetime, timedelta

def generate_entity_configuration(
    client_id: str,  # URI del tuo servizio, es: https://servizi.miocomune.it
    private_key,
    public_key_jwk: dict,
    redirect_uris: list,
    organization_name: str,
    contacts: list
) -> str:
    """
    Genera l'Entity Configuration JWT per la registrazione OIDC Federation.
    Deve essere publicata a: {client_id}/.well-known/openid-federation
    """
    now = int(datetime.utcnow().timestamp())

    payload = {
        # Claims standard OIDC Federation
        "iss": client_id,
        "sub": client_id,
        "iat": now,
        "exp": now + 86400 * 365,  # Valida 1 anno (da aggiornare)
        "jwks": {"keys": [public_key_jwk]},  # Chiave pubblica del RP

        # Metadata del Relying Party
        "metadata": {
            "openid_relying_party": {
                "application_type": "web",
                "client_id": client_id,
                "client_registration_types": ["automatic"],
                "redirect_uris": redirect_uris,
                "response_types": ["code"],
                "grant_types": ["authorization_code"],
                "id_token_signed_response_alg": "RS256",
                "userinfo_signed_response_alg": "RS256",
                "token_endpoint_auth_method": "private_key_jwt",
                "token_endpoint_auth_signing_alg": "RS256",
                "scope": ["openid", "profile", "email", "offline_access"],
                "client_name": organization_name,
                "contacts": contacts,
                # Parametri obbligatori per SPID/CIE
                "policy_uri": f"{client_id}/privacy",
                "logo_uri": f"{client_id}/logo.png",
                "subject_type": "pairwise",
                "request_object_signing_alg": "RS256",
            }
        },

        # Trust chain verso la Trust Anchor di AgID
        "authority_hints": ["https://registry.agid.gov.it"],
    }

    return jwt.encode(payload, private_key, algorithm="RS256", headers={"kid": "rp-signing-key-2024"})

# Endpoint FastAPI per esporre l'Entity Configuration
from fastapi import FastAPI
from fastapi.responses import Response

app = FastAPI()

@app.get("/.well-known/openid-federation")
async def entity_configuration():
    ec_jwt = generate_entity_configuration(
        client_id="https://servizi.miocomune.it",
        private_key=private_key,  # Caricata da HSM o file sicuro
        public_key_jwk=public_key_jwk,
        redirect_uris=["https://servizi.miocomune.it/auth/callback"],
        organization_name="Comune di Esempio",
        contacts=["tech@miocomune.it"]
    )

    return Response(
        content=ec_jwt,
        media_type="application/entity-statement+jwt"
    )

pagoPA: Ödeme Entegrasyonu

ödemePA PagoPA S.p.A tarafından yönetilen, KA'lara yapılan ödemelere yönelik ulusal platformdur. Vatandaşların vergileri, cezaları, ücretleri ve diğer KA hizmetlerini geniş bir ağ üzerinden ödemesine olanak tanır. kanallar (bankalar, postaneler, ödeme uygulamaları, Satispay vb.).

Teknik açıdan bakıldığında, Alacaklı Kuruluş (EC) için pagoPA entegrasyonu şunları sağlar:

  • PagoPA düğümüne üyelik: PagoPA üyelik portalı aracılığıyla. Organizasyonlar katılabilir doğrudan veya yetkili teknolojik aracılar aracılığıyla.
  • IUV üretimi (Benzersiz Ödeme Tanımlayıcı): benzersiz şekilde tanımlayan kod beklenen herhangi bir ödeme. Format, Alacaklı Kuruluş tarafından tanımlanır ancak pagoPA kurallarına uygun olmalıdır.
  • Borç Pozisyonu: Vatandaşın EC'ye olan her borcu node'a kaydedilir ilişkili bir IUV ile birlikte bir "borç pozisyonu" olarak pagoPA.
  • Doğrulama ve kapatma: pagoPA bir ödeme tamamlandığında AK'ye bildirimde bulunur, AK Borç pozisyonunu doğrular ve kapatır.
# Integrazione pagoPA - Generazione posizione debitoria
# API SOAP/REST verso il Nodo pagoPA

import httpx
import uuid
from datetime import datetime, timedelta
from dataclasses import dataclass

@dataclass
class PaymentPosition:
    iuv: str               # Identificativo Univoco Versamento
    amount_cents: int      # Importo in centesimi di euro
    description: str       # Causale del pagamento
    citizen_fiscal_code: str
    due_date: datetime
    company_name: str      # Denominazione dell'Ente Creditore

class PagoPAClient:
    """
    Client per le API del Nodo pagoPA (versione REST/JSON).
    Supporta le API GPD (Gestione Posizioni Debitorie).
    """

    def __init__(self, organization_fiscal_code: str, api_key: str, base_url: str):
        self.org_fc = organization_fiscal_code
        self.api_key = api_key
        self.base_url = base_url

    def generate_iuv(self) -> str:
        """
        Genera un IUV conforme alle specifiche pagoPA.
        Struttura per Enti con aux digit 3 (applicativo gestionale):
        - 17 caratteri numerici
        - Deve essere univoco per l'Ente Creditore
        """
        # Componente temporale: YYMMDDHHMM (10 cifre)
        time_component = datetime.utcnow().strftime("%y%m%d%H%M")
        # Componente random: 7 cifre
        random_component = str(uuid.uuid4().int)[:7]
        iuv = f"{time_component}{random_component}"
        return iuv[:17]  # Tronca a 17 caratteri

    async def create_payment_position(self, position: PaymentPosition) -> dict:
        """
        Crea una posizione debitoria sul nodo pagoPA.
        """
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.base_url}/organizations/{self.org_fc}/debtpositions",
                headers={
                    "Ocp-Apim-Subscription-Key": self.api_key,
                    "Content-Type": "application/json"
                },
                json={
                    "iupd": f"{self.org_fc}-{position.iuv}",  # Identificativo Univoco Posizione Debitoria
                    "type": "F",                          # F = Persona fisica
                    "fiscalCode": position.citizen_fiscal_code,
                    "companyName": position.company_name,
                    "validityDate": position.due_date.isoformat(),
                    "paymentOption": [
                        {
                            "iuv": position.iuv,
                            "amount": position.amount_cents,
                            "description": position.description,
                            "isPartialPayment": False,
                            "dueDate": (position.due_date + timedelta(days=30)).isoformat(),
                            "fee": 100,  # Commissione in centesimi (1 euro)
                            "transfer": [
                                {
                                    "idTransfer": "1",
                                    "amount": position.amount_cents,
                                    "organizationFiscalCode": self.org_fc,
                                    "remittanceInformation": position.description,
                                    "category": "0201102IM",  # Codice tassonomia
                                }
                            ]
                        }
                    ]
                }
            )
            response.raise_for_status()
            return response.json()

    def generate_payment_notice_url(self, iuv: str) -> str:
        """
        Genera il link di pagamento che il cittadino può usare.
        Formato standard: https://checkout.pagopa.it/pay?...
        """
        notice_number = f"3{self.org_fc}{iuv}"  # Numero Avviso pagoPA
        return (
            f"https://checkout.pagopa.it/pay"
            f"?rptId={self.org_fc}{notice_number}"
            f"&amount={100}"  # Amount in centesimi
        )

Test ve Geliştirme Ortamları

AgID ve PagoPA, geliştirme ve doğrulama için özel test ortamları sağlar:

Sistem Çevre URL'si Kimlik Bilgisi Testi
SPID OIDC'si Demo/Test demo.spid.gov.it Developers.italia.it sitesinde kullanıcıları test edin
CIE OIDC Test preprod.cie.gov.it MinInterno portalı üzerinden talep
SPID SAML IdP testi spidtest.agid.gov.it test/test (seviye 1, 2, 3)
payPA GPD'si UAT api.uat.platform.pagopa.it DevOps portalında API Anahtarı istendi
pagoPA Ödeme UAT uat.checkout.pagopa.it Test kartı: 4242 4242 4242 4242

Resmi SDK'lar ve Önerilen Kitaplıklar

proje Geliştiriciler İtalya (developers.italia.it) entegrasyon için resmi SDK'ları korur 5 programlama dili için SPID ve CIE'nin:

  • Python: spid-cie-oidc-django (Django'ya dayanarak), pyspid (SAML)
  • Java: spid-spring-integration (Bahar Çizmesi)
  • .AÇIK: spid-dotnet-sdk (ASP.NET Çekirdeği)
  • PHP: spid-php
  • Yakut: spid-ruby

Resmi SDK'ların kapsamına girmeyen bağlamlardaki entegrasyonlar için tüm teknik özellikler mevcuttur docs.italia.it adresinde ("SPID CIE OIDC" veya "SPID Teknik Kuralları" ifadesini arayın). OIDC federasyonu olabilir herhangi bir standart OIDC kitaplığıyla uygulanarak SPID/CIE'ye özgü talepler için destek eklenir.

Sonuçlar ve Sonraki Adımlar

SPID, CIE ve pagoPA'nın İtalyan dijital hizmetlerine entegrasyonu, herhangi bir PA için bir gerekliliktir. CAD uyumlu çevrimiçi hizmetler sunmak istiyorum. OpenID Connect'e geçiş önemli ölçüde basitleşiyor uygulama, geçmiş SAML 2.0 ile karşılaştırıldığında ve Developers Italia'nın resmi SDK'ları daha da düşük giriş bariyeri.

Bu serinin bir sonraki ve son makalesinde, GovStack Yapı Taşı: çerçeve Yeniden kullanılabilir dijital devlet hizmetleri oluşturmaya yönelik uluslararası modüler sistem, 2025 yılında 20'den fazla ülke tarafından benimsenmiştir.

Bu Serideki İlgili Makaleler

  • Devlet Teknolojisi #00: Dijital Kamu Altyapısı - yapı taşları ve küresel mimari
  • Devlet Teknolojisi #01: eIDAS 2.0 ve EUDI Cüzdan - Avrupa dijital kimliği
  • Devlet Teknolojisi #02: Devlet Kimliği için OpenID Connect - tam kullanıma sunma
  • Devlet Teknolojisi #04: Tasarım Yoluyla GDPR - PA hizmetlerinde kullanıcı verileri ve gizlilik