Freigeben über


Szenario: Überprüfen eines Autorisierungsheaders

Validieren Sie eingehende Bezeichnertoken, indem Sie sie an den Microsoft Entra SDK von AgentID für den Endpunkt /Validate weiterleiten. Extrahieren Sie dann die zurückgegebenen Claims, um Autorisierungsentscheidungen zu treffen. In diesem Leitfaden erfahren Sie, wie Sie Middleware für die Tokenüberprüfung implementieren und Autorisierungsentscheidungen basierend auf Bereichen oder Rollen treffen.

Voraussetzungen

  • Ein Azure-Konto mit einem aktiven Abonnement. Kostenlos ein Konto erstellen.
  • Das Microsoft Entra SDK für AgentID wird mit Netzwerkzugriff von Ihrer Anwendung bereitgestellt und ausgeführt. Anweisungen zur Einrichtung finden Sie im Installationshandbuch .
  • Registrierte Anwendung in Microsoft Entra ID – Registrieren Sie eine neue App im Microsoft Entra Admin Center, die nur für Konten in diesem Organisationsverzeichnis konfiguriert ist. Weitere Informationen finden Sie unter Registrieren einer Anwendung . Notieren Sie die folgenden Werte auf der Seite "Übersicht" der Anwendung:
    • Anwendungs-ID (Client)
    • Verzeichnis-ID (Mandant)
    • Konfigurieren eines App-ID-URI im Abschnitt "Bereitstellen einer API " (wird als Zielgruppe für die Tokenüberprüfung verwendet)
  • Bearer-Token von authentifizierten Clients – Ihre Anwendung muss Token von Clientanwendungen über OAuth 2.0 Flows empfangen.
  • Geeignete Berechtigungen in der Microsoft Entra-ID – Ihr Konto muss über Berechtigungen zum Registrieren von Anwendungen verfügen und Authentifizierungseinstellungen konfigurieren.

Konfiguration

Um Token für Ihre API zu überprüfen, konfigurieren Sie das Microsoft Entra SDK für AgentID mit Ihren Microsoft Entra ID-Mandanteninformationen.

env:
- name: AzureAd__Instance
  value: "https://login.microsoftonline.com/"
- name: AzureAd__TenantId
  value: "your-tenant-id"
- name: AzureAd__ClientId
  value: "your-api-client-id"
- name: AzureAd__Audience
  value: "api://your-api-id"

TypeScript/Node.js

Die folgende Implementierung zeigt, wie Sie eine Tokenüberprüfungs-Middleware erstellen, die in das Microsoft Entra SDK für AgentID mit TypeScript oder JavaScript integriert wird. Diese Middleware überprüft jede eingehende Anforderung auf ein gültiges Bearertoken und extrahiert Ansprüche für die Verwendung in Ihren Routenhandlern:

import fetch from 'node-fetch';

interface ValidateResponse {
  protocol: string;
  token: string;
  claims: {
    aud: string;
    iss: string;
    oid: string;
    sub: string;
    tid: string;
    upn?: string;
    scp?: string;
    roles?: string[];
    [key: string]: any;
  };
}

async function validateToken(authorizationHeader: string): Promise<ValidateResponse> {
  const sidecarUrl = process.env.SIDECAR_URL || 'http://localhost:5000';
  
  const response = await fetch(`${sidecarUrl}/Validate`, {
    headers: {
      'Authorization': authorizationHeader
    }
  });
  
  if (!response.ok) {
    throw new Error(`Token validation failed: ${response.statusText}`);
  }
  
  return await response.json() as ValidateResponse;
}

Der folgende Codeausschnitt veranschaulicht die Verwendung der validateToken Funktion in einer Express.js Middleware zum Schutz von API-Endpunkten:

// Express.js middleware example
import express from 'express';

const app = express();

// Token validation middleware
async function requireAuth(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader) {
    return res.status(401).json({ error: 'No authorization token provided' });
  }
  
  try {
    const validation = await validateToken(authHeader);
    
    // Attach claims to request object
    req.user = {
      id: validation.claims.oid,
      upn: validation.claims.upn,
      tenantId: validation.claims.tid,
      scopes: validation.claims.scp?.split(' ') || [],
      roles: validation.claims.roles || [],
      claims: validation.claims
    };
    
    next();
  } catch (error) {
    console.error('Token validation failed:', error);
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// Protected endpoint
app.get('/api/protected', requireAuth, (req, res) => {
  res.json({
    message: 'Access granted',
    user: {
      id: req.user.id,
      upn: req.user.upn
    }
  });
});

// Scope-based authorization
app.get('/api/admin', requireAuth, (req, res) => {
  if (!req.user.roles.includes('Admin')) {
    return res.status(403).json({ error: 'Insufficient permissions' });
  }
  
  res.json({ message: 'Admin access granted' });
});

app.listen(8080);

Python

Der folgende Python-Codeausschnitt verwendet Flask-Dekoratoren zur Einbettung von Routing-Handlern mit Token-Überprüfung. Dieser Decorator extrahiert das Bearer-Token aus dem Autorisierungs-Header, überprüft es mit dem Microsoft Entra SDK für AgentID und stellt die Claims in Ihrer Route zur Verfügung.

import os
import requests
from flask import Flask, request, jsonify
from functools import wraps

app = Flask(__name__)

def validate_token(authorization_header: str) -> dict:
    """Validate token using the SDK."""
    sidecar_url = os.getenv('SIDECAR_URL', 'http://localhost:5000')
    
    response = requests.get(
        f"{sidecar_url}/Validate",
        headers={'Authorization': authorization_header}
    )
    
    if not response.ok:
        raise Exception(f"Token validation failed: {response.text}")
    
    return response.json()

# Token validation decorator
def require_auth(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        auth_header = request.headers.get('Authorization')
        
        if not auth_header:
            return jsonify({'error': 'No authorization token provided'}), 401
        
        try:
            validation = validate_token(auth_header)
            
            # Attach user info to Flask's g object
            from flask import g
            g.user = {
                'id': validation['claims']['oid'],
                'upn': validation['claims'].get('upn'),
                'tenant_id': validation['claims']['tid'],
                'scopes': validation['claims'].get('scp', '').split(' '),
                'roles': validation['claims'].get('roles', []),
                'claims': validation['claims']
            }
            
            return f(*args, **kwargs)
        except Exception as e:
            print(f"Token validation failed: {e}")
            return jsonify({'error': 'Invalid token'}), 401
    
    return decorated_function

# Protected endpoint
@app.route('/api/protected')
@require_auth
def protected():
    from flask import g
    return jsonify({
        'message': 'Access granted',
        'user': {
            'id': g.user['id'],
            'upn': g.user['upn']
        }
    })

# Role-based authorization
@app.route('/api/admin')
@require_auth
def admin():
    from flask import g
    if 'Admin' not in g.user['roles']:
        return jsonify({'error': 'Insufficient permissions'}), 403
    
    return jsonify({'message': 'Admin access granted'})

if __name__ == '__main__':
    app.run(port=8080)

Go

Die folgende Go-Implementierung veranschaulicht die Tokenüberprüfung mithilfe des standardmäßigen HTTP-Handlermusters. Dieser Middleware-Ansatz extrahiert Bearertoken aus dem Autorisierungsheader, überprüft sie mit dem Microsoft Entra SDK für AgentID und speichert Benutzerinformationen in Anforderungsheadern für die Verwendung in downstream Handlern:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
)

type ValidateResponse struct {
    Protocol string                 `json:"protocol"`
    Token    string                 `json:"token"`
    Claims   map[string]interface{} `json:"claims"`
}

type User struct {
    ID       string
    UPN      string
    TenantID string
    Scopes   []string
    Roles    []string
    Claims   map[string]interface{}
}

func validateToken(authHeader string) (*ValidateResponse, error) {
    sidecarURL := os.Getenv("SIDECAR_URL")
    if sidecarURL == "" {
        sidecarURL = "http://localhost:5000"
    }
    
    req, err := http.NewRequest("GET", fmt.Sprintf("%s/Validate", sidecarURL), nil)
    if err != nil {
        return nil, err
    }
    
    req.Header.Set("Authorization", authHeader)
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("token validation failed: %s", resp.Status)
    }
    
    var validation ValidateResponse
    if err := json.NewDecoder(resp.Body).Decode(&validation); err != nil {
        return nil, err
    }
    
    return &validation, nil
}

// Middleware for token validation
func requireAuth(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        authHeader := r.Header.Get("Authorization")
        
        if authHeader == "" {
            http.Error(w, "No authorization token provided", http.StatusUnauthorized)
            return
        }
        
        validation, err := validateToken(authHeader)
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }
        
        // Extract user information from claims
        user := &User{
            ID:       validation.Claims["oid"].(string),
            TenantID: validation.Claims["tid"].(string),
            Claims:   validation.Claims,
        }
        
        if upn, ok := validation.Claims["upn"].(string); ok {
            user.UPN = upn
        }
        
        if scp, ok := validation.Claims["scp"].(string); ok {
            user.Scopes = strings.Split(scp, " ")
        }
        
        if roles, ok := validation.Claims["roles"].([]interface{}); ok {
            for _, role := range roles {
                user.Roles = append(user.Roles, role.(string))
            }
        }
        
        // Store user in context (simplified - use context.Context in production)
        r.Header.Set("X-User-ID", user.ID)
        r.Header.Set("X-User-UPN", user.UPN)
        
        next(w, r)
    }
}

func protectedHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "message": "Access granted",
        "user": map[string]string{
            "id":  r.Header.Get("X-User-ID"),
            "upn": r.Header.Get("X-User-UPN"),
        },
    })
}

func main() {
    http.HandleFunc("/api/protected", requireAuth(protectedHandler))
    
    fmt.Println("Server starting on :8080")
    http.ListenAndServe(":8080", nil)
}

C#

Die folgende C#-Implementierung veranschaulicht die Tokenüberprüfung mithilfe ASP.NET Core Middleware. Bei diesem Ansatz wird abhängigkeitsinjektion verwendet, um auf den Tokenüberprüfungsdienst zuzugreifen, Bearertoken aus dem Autorisierungsheader zu extrahieren, sie mit dem Microsoft Entra SDK für AgentID zu überprüfen und Benutzeransprüche im HttpContext für die Verwendung in Controllern zu speichern:

using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;

public class ValidateResponse
{
    public string Protocol { get; set; }
    public string Token { get; set; }
    public JsonElement Claims { get; set; }
}

public class TokenValidationService
{
    private readonly HttpClient _httpClient;
    private readonly string _sidecarUrl;
    
    public TokenValidationService(IHttpClientFactory httpClientFactory, IConfiguration config)
    {
        _httpClient = httpClientFactory.CreateClient();
        _sidecarUrl = config["SIDECAR_URL"] ?? "http://localhost:5000";
    }
    
    public async Task<ValidateResponse> ValidateTokenAsync(string authorizationHeader)
    {
        var request = new HttpRequestMessage(HttpMethod.Get, $"{_sidecarUrl}/Validate");
        request.Headers.Add("Authorization", authorizationHeader);
        
        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();
        
        return await response.Content.ReadFromJsonAsync<ValidateResponse>();
    }
}

// Middleware example
public class TokenValidationMiddleware
{
    private readonly RequestDelegate _next;
    private readonly TokenValidationService _validationService;
    
    public TokenValidationMiddleware(RequestDelegate next, TokenValidationService validationService)
    {
        _next = next;
        _validationService = validationService;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        var authHeader = context.Request.Headers["Authorization"].ToString();
        
        if (string.IsNullOrEmpty(authHeader))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsJsonAsync(new { error = "No authorization token" });
            return;
        }
        
        try
        {
            var validation = await _validationService.ValidateTokenAsync(authHeader);
            
            // Store claims in HttpContext.Items for use in controllers
            context.Items["UserClaims"] = validation.Claims;
            context.Items["UserId"] = validation.Claims.GetProperty("oid").GetString();
            
            await _next(context);
        }
        catch (Exception ex)
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsJsonAsync(new { error = "Invalid token" });
        }
    }
}

// Controller example
[ApiController]
[Route("api")]
public class ProtectedController : ControllerBase
{
    [HttpGet("protected")]
    public IActionResult GetProtected()
    {
        var userId = HttpContext.Items["UserId"] as string;
        
        return Ok(new
        {
            message = "Access granted",
            user = new { id = userId }
        });
    }
}

Extrahieren bestimmter Ansprüche

Nach der Überprüfung eines Tokens können Sie die Ansprüche extrahieren, um Autorisierungsentscheidungen in Ihrer Anwendung zu treffen. Der /Validate Endpunkt gibt ein Anspruchsobjekt mit den folgenden Informationen zurück:

{
  "protocol": "Bearer",
  "claims": {
    "oid": "user-object-id",
    "upn": "user@contoso.com",
    "tid": "tenant-id",
    "scp": "User.Read Mail.Read",
    "roles": ["Admin"]
  }
}

Zu den allgemeinen Ansprüchen gehören:

  • oid: Objektkennung (eindeutige Benutzer-ID) in Ihrem Microsoft Entra ID-Mandanten
  • upn: Benutzerprinzipalname (in der Regel E-Mail-Format)
  • tid: Mandanten-ID, zu der der Benutzer gehört
  • scp: Delegierte Bereiche, die der Benutzer Ihrer Anwendung gewährt hat
  • roles: Dem Benutzer zugewiesene Anwendungsrollen

Die folgenden Beispiele zeigen, wie bestimmte Ansprüche aus der Überprüfungsantwort extrahiert werden:

Benutzeridentität:

// Extract user identity
const userId = validation.claims.oid;  // Object ID
const userPrincipalName = validation.claims.upn;  // User Principal Name
const tenantId = validation.claims.tid;  // Tenant ID

Bereiche und Rollen:

// Extract scopes (delegated permissions)
const scopes = validation.claims.scp?.split(' ') || [];

// Check for specific scope
if (scopes.includes('User.Read')) {
  // Allow access
}

// Extract roles (application permissions)
const roles = validation.claims.roles || [];

// Check for specific role
if (roles.includes('Admin')) {
  // Allow admin access
}

Autorisierungsmuster

Nach der Überprüfung von Token können Sie die Autorisierung basierend auf delegierten Bereichen (berechtigungen, die vom Benutzer erteilt werden) oder Anwendungsrollen (zugewiesen von Ihrem Mandantenadministrator) erzwingen. Wählen Sie das Muster aus, das Ihrem Autorisierungsmodell entspricht:

Bereichsbasierte Autorisierung

Überprüfen Sie, ob das Benutzertoken erforderliche Bereiche enthält, bevor Sie Zugriff gewähren:

function requireScopes(requiredScopes: string[]) {
  return async (req, res, next) => {
    const validation = await validateToken(req.headers.authorization);
    const userScopes = validation.claims.scp?.split(' ') || [];
    const hasAllScopes = requiredScopes.every(s => userScopes.includes(s));
    
    if (!hasAllScopes) {
      return res.status(403).json({ error: 'Insufficient scopes' });
    }
    next();
  };
}

app.get('/api/mail', requireScopes(['Mail.Read']), (req, res) => {
  res.json({ message: 'Mail access granted' });
});

Rollenbasierte Autorisierung

Überprüfen Sie, ob der Benutzer über erforderliche Anwendungsrollen verfügt:

function requireRoles(requiredRoles: string[]) {
  return async (req, res, next) => {
    const validation = await validateToken(req.headers.authorization);
    const userRoles = validation.claims.roles || [];
    const hasRole = requiredRoles.some(r => userRoles.includes(r));
    
    if (!hasRole) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
}

app.delete('/api/resource', requireRoles(['Admin']), (req, res) => {
  res.json({ message: 'Resource deleted' });
});

Fehlerbehandlung

Die Tokenüberprüfung kann aus mehreren Gründen fehlschlagen: Das Token kann abgelaufen oder ungültig sein oder es können erforderliche Berechtigungen fehlen. Implementieren Sie die Fehlerbehandlung, die zwischen verschiedenen Fehlerszenarien unterscheidet, damit Sie entsprechend reagieren können:

async function validateTokenSafely(authHeader: string): Promise<ValidateResponse | null> {
  try {
    return await validateToken(authHeader);
  } catch (error) {
    if (error.message.includes('401')) {
      console.error('Token is invalid or expired');
    } else if (error.message.includes('403')) {
      console.error('Token missing required scopes');
    } else {
      console.error('Token validation error:', error.message);
    }
    return null;
  }
}

Häufige Überprüfungsfehler

Fehler Ursache Lösung
401 Nicht autorisiert Ungültiges oder abgelaufenes Token Anfordern eines neuen Tokens vom Client
403 Verboten Fehlende erforderliche Bereiche Aktualisieren der Bereichskonfiguration oder Tokenanforderung
400 Fehlerhafte Anfrage Falsch formatierter Autorisierungsheader Kopfzeilenformat überprüfen: ******

Antwortstruktur

Der /Validate Endpunkt gibt Folgendes zurück:

{
  "protocol": "Bearer",
  "token": "******",
  "claims": {
    "aud": "api://your-api-id",
    "iss": "https://sts.windows.net/tenant-id/",
    "iat": 1234567890,
    "nbf": 1234567890,
    "exp": 1234571490,
    "oid": "user-object-id",
    "sub": "subject",
    "tid": "tenant-id",
    "upn": "user@contoso.com",
    "scp": "User.Read Mail.Read",
    "roles": ["Admin"]
  }
}

Bewährte Methoden

  1. Frühzeitig überprüfen: Überprüfen von Token am API-Gateway oder Einstiegspunkt
  2. Berechtigungen überprüfen: Immer sicherstellen, dass das Token die erforderlichen Berechtigungen für den Vorgang hat
  3. Protokollfehler: Protokollüberprüfungsfehler für die Sicherheitsüberwachung
  4. Behandeln von Fehlern: Bereitstellen klarer Fehlermeldungen für das Debuggen
  5. Verwenden von Middleware: Implementieren der Validierung als Middleware zur Konsistenz
  6. Secure SDK: Stellen Sie sicher, dass auf das SDK nur über Ihre Anwendung zugegriffen werden kann

Nächste Schritte

Nach der Überprüfung von Token müssen Sie möglicherweise Folgendes ausführen: