#Security, Authentication, Authorization und sichere Migration

Security-Inventar, Rollen, Principal, Session, CSRF, SOAP Security und sichere Plattformmigration.

#Security, AuthN/AuthZ, JAAS, Rollen, Session, CSRF, SOAP Security und sichere Migration

Ziel dieses Abschnitts: Security im Legacy-Java-Enterprise-System nicht nebenbei migrieren, sondern als eigenes Modernisierungs-Slice behandeln. Besonders kritisch sind Container-Security, JAAS, Rollen, SessionContext, JSP-Session-Logik, CSRF-Schutz, SOAP/WS-Security, Service-to-Service-Identitäten und Rollen-Mapping bei Migration nach Spring Boot, Jakarta EE oder Quarkus.


#Warum Security ein eigenes Modernisierungs-Slice ist

Security ist in alten Java-Enterprise-Anwendungen oft über viele Schichten verteilt:

text
web.xml
  ↓
login-config / security-constraint
  ↓
JAAS Realm / App-Server Realm
  ↓
Servlet Filter
  ↓
JSP Session Checks
  ↓
EJB @RolesAllowed
  ↓
SessionContext.isCallerInRole(...)
  ↓
SOAP WS-Security Handler
  ↓
Datenbank-Rechte / Mandantenfilter

Deshalb darf Security nicht unbewusst durch Framework-Migration verändert werden.

Typische Fehler bei Migration:

text
- web.xml security-constraint wird vergessen.
- @RolesAllowed wird nicht nach Spring/Quarkus/Jakarta übertragen.
- SessionContext.getCallerPrincipal() verschwindet.
- JSP hatte versteckte Rollenchecks.
- CSRF-Schutz fehlt nach REST-Migration.
- SOAP-Signaturen oder Zertifikate werden nicht übernommen.
- Systemuser und Batchuser werden gleich behandelt wie Enduser.
- Mandantenprüfung hängt an HTTP Session und geht bei API-Migration verloren.

Grundsatz:

text
Security-Verhalten zuerst inventarisieren,
dann testen,
dann migrieren.

#Security-Inventar erstellen

Lege im Projekt an:

text
docs/security-map.md

Vorlage:

markdown
# Security Map

## Authentication

| Einstieg | Mechanismus | Quelle | Bemerkung |
|---|---|---|---|
| JSP Login | FORM Login | web.xml / Realm | Legacy UI |
| SOAP Endpoint | WS-Security UsernameToken | SOAP Handler | Partner-System |
| Batch Job | technischer User | App Server Config | nächtliche Jobs |
| JMS Consumer | Container Identity | MDB Runtime | systemintern |

## Authorization

| Funktion | Rolle | Prüfung | Ort | Risiko |
|---|---|---|---|---|
| Bestellung anzeigen | USER | security-constraint | web.xml | mittel |
| Bestellung freigeben | MANAGER | @RolesAllowed | EJB | hoch |
| Admin User ändern | ADMIN | JSP if-Abfrage | JSP | hoch |
| Payment auslösen | PAYMENT_OPERATOR | SessionContext | EJB | hoch |

## Security Context Usage

| Klasse | API | Zweck | Migration |
|---|---|---|---|
| OrderServiceBean | SessionContext.getCallerPrincipal | Audit User | SecurityContext Adapter |
| AdminFilter | request.isUserInRole | UI AuthZ | Spring/Jakarta/Quarkus Filter |
| PaymentEndpoint | SOAP Header | Partner Auth | WS-Security Adapter |

#Suchliste für Security-Code

Suche im Legacy-Projekt nach:

text
@RolesAllowed
@PermitAll
@DenyAll
@RunAs
@DeclareRoles
isUserInRole
isCallerInRole
getCallerPrincipal
SessionContext
Principal
Subject
AccessController
web.xml
security-constraint
login-config
auth-method
form-login-page
form-error-page
jaas
realm
LDAP
Role
Group
csrf
token
XSRF
session.getAttribute
session.setAttribute
invalidate
JSESSIONID
HttpSession
@WebFilter
FilterChain
SOAPHeader
UsernameToken
BinarySecurityToken
WSS4J
Signature
X509
keystore
truststore

Klassifiziere Treffer in:

text
1. Authentifizierung
2. Autorisierung
3. Session Management
4. CSRF / Form Protection
5. SOAP Security
6. Service-to-Service Security
7. Mandantenprüfung
8. Audit / Principal Logging
9. Secrets / Zertifikate
10. Technische User / Batch User

#Authentication im Legacy-System verstehen

Typisches web.xml-Beispiel:

xml
<login-config>
    <auth-method>FORM</auth-method>
    <form-login-config>
        <form-login-page>/login.jsp</form-login-page>
        <form-error-page>/login-error.jsp</form-error-page>
    </form-login-config>
</login-config>

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Admin</web-resource-name>
        <url-pattern>/admin/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>ADMIN</role-name>
    </auth-constraint>
</security-constraint>

Dokumentiere:

markdown
| URL Pattern | Auth Methode | Rollen | Bemerkung |
|---|---|---|---|
| /admin/* | FORM | ADMIN | web.xml |
| /orders/* | FORM | USER, MANAGER | web.xml |
| /soap/payment | WS-Security | PARTNER_PAYMENT | SOAP Handler |

Wichtig:

text
web.xml ist oft der einzige Ort,
an dem URL-Level-Security sichtbar ist.

Wenn du später nach REST Controller migrierst, musst du diese Regeln explizit nachbauen.


#EJB-Rollen und Method Security

Legacy EJB:

java
@Stateless
@RolesAllowed("MANAGER")
public class OrderApprovalBean {

    public void approveOrder(Long orderId) {
        // Nur MANAGER darf freigeben
    }
}

Oder methodenspezifisch:

java
@Stateless
public class OrderServiceBean {

    @RolesAllowed({"USER", "MANAGER"})
    public OrderDto findOrder(Long orderId) {
        ...
    }

    @RolesAllowed("MANAGER")
    public void approveOrder(Long orderId) {
        ...
    }

    @PermitAll
    public List<ProductDto> listProducts() {
        ...
    }
}

Migrationstabelle:

markdown
| Legacy | Spring Boot | Jakarta EE | Quarkus |
|---|---|---|---|
| @RolesAllowed | @PreAuthorize oder @Secured | @RolesAllowed | @RolesAllowed |
| SessionContext.getCallerPrincipal | SecurityContextHolder | SecurityContext | SecurityIdentity |
| web.xml security-constraint | SecurityFilterChain | Jakarta Security / web.xml / annotations | HTTP permissions / annotations |
| JAAS Realm | AuthenticationProvider / OIDC / LDAP | Jakarta Security / server realm | OIDC / Elytron / custom IdentityProvider |

Wichtig:

text
Rollenannotation migrieren reicht nicht.
Du musst auch sicherstellen, dass Rollen aus dem neuen Identity Provider korrekt gemappt werden.

#Principal und Audit sauber kapseln

Legacy-Code:

java
@Resource
private SessionContext sessionContext;

public void createOrder(CreateOrderRequest request) {
    String username = sessionContext.getCallerPrincipal().getName();
    auditService.write("ORDER_CREATED", username);
}

Problem:

text
Fachlogik hängt direkt an EJB SessionContext.

Besserer Port:

java
public interface CurrentUserPort {
    CurrentUser currentUser();
}
java
public class CurrentUser {

    private final String username;
    private final Set<String> roles;

    public CurrentUser(String username, Set<String> roles) {
        this.username = username;
        this.roles = roles;
    }

    public String username() {
        return username;
    }

    public boolean hasRole(String role) {
        return roles.contains(role);
    }
}

EJB Adapter:

java
public class EjbCurrentUserAdapter implements CurrentUserPort {

    private final SessionContext sessionContext;

    public EjbCurrentUserAdapter(SessionContext sessionContext) {
        this.sessionContext = sessionContext;
    }

    @Override
    public CurrentUser currentUser() {
        String username = sessionContext.getCallerPrincipal().getName();

        Set<String> roles = new HashSet<>();
        for (String role : List.of("USER", "MANAGER", "ADMIN", "PAYMENT_OPERATOR")) {
            if (sessionContext.isCallerInRole(role)) {
                roles.add(role);
            }
        }

        return new CurrentUser(username, roles);
    }
}

Use Case:

java
public class ApproveOrderUseCase {

    private final CurrentUserPort currentUserPort;
    private final OrderRepository orderRepository;

    public void approve(OrderId orderId) {
        CurrentUser user = currentUserPort.currentUser();

        if (!user.hasRole("MANAGER")) {
            throw new AccessDeniedException("Only MANAGER can approve orders");
        }

        Order order = orderRepository.findById(orderId).orElseThrow();
        order.approveBy(user.username());
        orderRepository.update(order);
    }
}

Damit bleibt die Fachregel testbar und unabhängig vom Container.


#Autorisierung fachlich vs technisch trennen

Nicht jede Rollenprüfung gehört in denselben Layer.

Technische Zugriffskontrolle:

text
Darf der Benutzer diesen Endpoint / diese Methode überhaupt aufrufen?

Fachliche Autorisierung:

text
Darf dieser konkrete Benutzer diese konkrete Bestellung bearbeiten?

Beispiel:

java
@RolesAllowed({"MANAGER", "SUPPORT"})
public void cancelOrder(String orderId) {
    cancelOrderUseCase.cancel(OrderId.of(orderId));
}

Im Use Case:

java
public void cancel(OrderId orderId) {
    CurrentUser user = currentUserPort.currentUser();
    Order order = orderRepository.findById(orderId).orElseThrow();

    if (!order.canBeCancelledBy(user)) {
        throw new AccessDeniedException("User cannot cancel this order");
    }

    order.cancel();
    orderRepository.update(order);
}

Merksatz:

text
Rollen schützen grob.
Fachliche Berechtigungen schützen konkret.

#JSP Session Security analysieren

Alte JSPs enthalten oft Security-Checks:

jsp
<%
String role = (String) session.getAttribute("role");
if (!"ADMIN".equals(role)) {
    response.sendRedirect("/access-denied.jsp");
    return;
}
%>

Oder gefährlicher:

jsp
<%
Boolean canApprove = (Boolean) session.getAttribute("canApprove");
if (Boolean.TRUE.equals(canApprove)) {
%>
    <button>Approve</button>
<%
}
%>

Problem:

text
UI versteckt Button,
aber Backend schützt Operation vielleicht nicht.

Analyse-Tabelle:

markdown
| JSP | Session Attribute | Sicherheitsrelevanz | Backend-Prüfung vorhanden? | Risiko |
|---|---|---|---|---|
| adminUser.jsp | role=ADMIN | hoch | unklar | hoch |
| approveOrder.jsp | canApprove | hoch | ja/nein prüfen | hoch |
| orderList.jsp | customerId | Mandant | unklar | hoch |

Regel:

text
JSP darf UI-Elemente anzeigen/verstecken.
Backend muss jede sicherheitsrelevante Aktion selbst prüfen.

#Session Management modernisieren

Dokumentiere zuerst:

markdown
| Thema | Ist-Zustand | Ziel |
|---|---|---|
| Session Timeout | web.xml / Server Default | explizit dokumentiert |
| Absolute Timeout | unbekannt | definieren |
| Session Fixation Schutz | unbekannt | nach Login Session erneuern |
| Logout | session.invalidate() | zentral |
| Sensitive Attribute | viele Werte | minimieren |
| JSESSIONID Cookie | Server Default | Secure, HttpOnly, SameSite prüfen |

Typische Legacy-Probleme:

text
- Benutzerrechte werden einmal beim Login in Session geschrieben und nie aktualisiert.
- Mandant/customerId liegt manipulierbar in Request oder Session.
- Logout invalidiert nicht alle serverseitigen Zustände.
- Session Timeout ist nur implizit im App Server konfiguriert.
- Session wird für fachliche Workflows missbraucht.

Modernisierungsziel:

text
Session enthält nur UI-Zustand und Identität,
keine kritischen fachlichen Entscheidungen ohne Backend-Prüfung.

#CSRF bei JSP, Servlet und REST

CSRF ist vor allem relevant, wenn Browser automatisch Cookies mitsenden und der Benutzer bereits authentifiziert ist.

Legacy-Risiko:

jsp
<form action="/orders/approve" method="post">
    <input type="hidden" name="orderId" value="${order.id}" />
    <button>Approve</button>
</form>

Ohne CSRF-Token kann eine fremde Webseite den Browser des eingeloggten Benutzers zu einem unerwünschten POST bringen.

Minimaler Legacy-Schutz:

java
public class CsrfTokenFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest http = (HttpServletRequest) request;
        HttpSession session = http.getSession();

        if ("GET".equalsIgnoreCase(http.getMethod())) {
            session.setAttribute("CSRF_TOKEN", UUID.randomUUID().toString());
            chain.doFilter(request, response);
            return;
        }

        if ("POST".equalsIgnoreCase(http.getMethod())) {
            String expected = (String) session.getAttribute("CSRF_TOKEN");
            String actual = http.getParameter("csrfToken");

            if (expected == null || !expected.equals(actual)) {
                ((HttpServletResponse) response).sendError(403);
                return;
            }
        }

        chain.doFilter(request, response);
    }
}

JSP:

jsp
<input type="hidden" name="csrfToken" value="${sessionScope.CSRF_TOKEN}" />

Bei Migration:

text
Spring Boot:
- CSRF für browserbasierte Form-Logins aktiv lassen.
- CSRF für stateless APIs nur bewusst deaktivieren, wenn keine Cookie-basierte Browser-Session verwendet wird.

Jakarta EE:
- CSRF über Framework/Filter/JSF/Jakarta MVC oder eigenen Filter sicherstellen.

Quarkus:
- Bei Cookie-basierten Browser-Flows CSRF-Schutz bewusst planen.
- Bei reinen Bearer-Token APIs andere Schutzannahmen dokumentieren.

#SOAP Security und WS-Security analysieren

SOAP-Security ist oft produktionskritisch.

Suche nach:

text
WSS4J
UsernameToken
PasswordDigest
Timestamp
Nonce
Signature
Encrypt
BinarySecurityToken
X509
keystore
truststore
ws-security
SOAPHandler
handler-chain
policy.xml
sp:SignedParts
sp:EncryptedParts

SOAP Security Map:

markdown
| SOAP Service | Richtung | Mechanismus | Zertifikat/User | Signatur | Verschlüsselung | Risiko |
|---|---|---|---|---|---|---|
| PaymentProvider | Outbound | X.509 + Signature | client-cert | ja | nein | hoch |
| CustomerEndpoint | Inbound | UsernameToken | partner-user | nein | nein | mittel |
| TaxService | Outbound | Basic Auth over TLS | tax-user | nein | nein | mittel |

Wichtig:

text
SOAP-Vertrag ist nicht nur WSDL/XSD.
Security Policy, Zertifikate, Header und Handler gehören zum Vertrag.

#SOAP Security Adapter bauen

Legacy-Code:

java
@WebServiceRef
private PaymentProviderService paymentProviderService;

public PaymentResponse charge(...) {
    PaymentPort port = paymentProviderService.getPaymentPort();
    return port.charge(...);
}

Besserer Adapter:

java
public class SoapPaymentGateway implements PaymentGateway {

    private final PaymentProviderService paymentProviderService;
    private final SoapSecurityConfigurer securityConfigurer;

    public SoapPaymentGateway(
            PaymentProviderService paymentProviderService,
            SoapSecurityConfigurer securityConfigurer
    ) {
        this.paymentProviderService = paymentProviderService;
        this.securityConfigurer = securityConfigurer;
    }

    @Override
    public PaymentResult charge(PaymentCommand command) {
        PaymentPort port = paymentProviderService.getPaymentPort();
        securityConfigurer.configure(port);

        try {
            PaymentResponse response = port.charge(
                    command.idempotencyKey(),
                    command.customerId(),
                    command.amount()
            );

            return map(response);
        } catch (SOAPFaultException e) {
            throw new PaymentProviderException("SOAP fault", e);
        }
    }
}

Security-Konfiguration bleibt gekapselt:

java
public interface SoapSecurityConfigurer {
    void configure(Object port);
}

Damit kann der Use Case unverändert bleiben, wenn SOAP-Security technisch migriert wird.


#Secrets, Keystores und Truststores inventarisieren

Lege an:

text
docs/secrets-and-certificates.md

Vorlage:

markdown
# Secrets and Certificates

| Name | Typ | Ort | Genutzt von | Rotation | Risiko |
|---|---|---|---|---|---|
| payment-client.jks | Keystore | App Server Config | Payment SOAP | manuell | hoch |
| truststore.jks | Truststore | JVM arg | alle SOAP Clients | unbekannt | hoch |
| db-password | Passwort | datasource config | OrderDS | manuell | hoch |
| partner-soap-user | Username/Password | encrypted property? | CustomerEndpoint | unbekannt | mittel |

Nicht in Git:

text
- Passwörter
- Private Keys
- Keystores mit privaten Schlüsseln
- produktive Zertifikate
- Service Account Tokens

Modernisierungsziel:

text
Secrets aus Code und Artefakten entfernen,
zentral über Secret Store, Vault, Kubernetes Secret oder Plattform-Mechanismus bereitstellen.

#Mandantenfähigkeit und Datenzugriff prüfen

Legacy-Systeme haben oft implizite Mandantenlogik:

java
String tenantId = (String) session.getAttribute("tenantId");
query.setParameter("tenantId", tenantId);

Oder gefährlich:

java
String sql = "select * from orders where tenant_id = '" + request.getParameter("tenant") + "'";

Dokumentiere:

markdown
| Datenobjekt | Mandantenfeld | Quelle | Prüfung | Risiko |
|---|---|---|---|---|
| ORDER | tenant_id | HTTP Session | DAO Query | hoch |
| CUSTOMER | company_id | Principal Mapping | Repository | mittel |
| INVOICE | tenant_id | Order Aggregate | Domain | niedrig |

Modernisierungsregel:

text
Mandant nicht aus frei manipulierbarem Request übernehmen.
Mandant aus authentifizierter Identität oder serverseitiger Zuordnung ableiten.

Besser:

java
public interface TenantContextPort {
    TenantId currentTenant();
}

Use Case:

java
public void findOrders() {
    TenantId tenantId = tenantContextPort.currentTenant();
    orderRepository.findByTenant(tenantId);
}

#Security-Tests für Legacy-Verhalten

Für jeden kritischen Use Case brauchst du mindestens diese Tests:

markdown
| Test | Zweck |
|---|---|
| unauthenticated user cannot access protected URL | AuthN |
| USER cannot approve order | AuthZ |
| MANAGER can approve order | AuthZ |
| user cannot access another tenant's order | Mandantenschutz |
| POST without CSRF token fails | CSRF |
| SOAP request without required security header fails | SOAP Security |
| audit contains correct principal | Audit |

Beispiel Plain Java Test:

java
@Test
void userWithoutManagerRole_cannotApproveOrder() {
    FakeCurrentUserPort currentUser = new FakeCurrentUserPort(
            new CurrentUser("alice", Set.of("USER"))
    );

    ApproveOrderUseCase useCase = new ApproveOrderUseCase(
            currentUser,
            orderRepository
    );

    assertThrows(
            AccessDeniedException.class,
            () -> useCase.approve(OrderId.of("order-1"))
    );
}

Spring Security Testidee:

java
@Test
@WithMockUser(username = "alice", roles = "USER")
void userCannotApproveOrder() throws Exception {
    mockMvc.perform(post("/orders/123/approve")
            .with(csrf()))
            .andExpect(status().isForbidden());
}

#Spring-Boot-Security-Migration

Zielstruktur:

text
security/
  SecurityConfig.java
  CurrentUserSpringAdapter.java
  TenantContextSpringAdapter.java

Beispiel SecurityConfig:

java
@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/actuator/health").permitAll()
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        .requestMatchers("/orders/*/approve").hasRole("MANAGER")
                        .anyRequest().authenticated()
                )
                .csrf(Customizer.withDefaults())
                .build();
    }
}

Current User Adapter:

java
@Component
public class CurrentUserSpringAdapter implements CurrentUserPort {

    @Override
    public CurrentUser currentUser() {
        Authentication authentication = SecurityContextHolder
                .getContext()
                .getAuthentication();

        String username = authentication.getName();

        Set<String> roles = authentication.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .map(role -> role.replace("ROLE_", ""))
                .collect(Collectors.toSet());

        return new CurrentUser(username, roles);
    }
}

Mapping prüfen:

markdown
| Legacy Rolle | Spring Authority | Bemerkung |
|---|---|---|
| ADMIN | ROLE_ADMIN | Prefix beachten |
| MANAGER | ROLE_MANAGER | Prefix beachten |
| USER | ROLE_USER | Prefix beachten |
| PAYMENT_OPERATOR | ROLE_PAYMENT_OPERATOR | Prefix beachten |

#Jakarta-EE-Security-Migration

Jakarta EE Ziel:

java
@Path("/orders")
@ApplicationScoped
public class OrderResource {

    @Inject
    private ApproveOrderApplicationService approveOrderApplicationService;

    @POST
    @Path("/{id}/approve")
    @RolesAllowed("MANAGER")
    public Response approve(@PathParam("id") String id) {
        approveOrderApplicationService.approve(OrderId.of(id));
        return Response.noContent().build();
    }
}

Current User Adapter:

java
@ApplicationScoped
public class JakartaCurrentUserAdapter implements CurrentUserPort {

    @Inject
    private SecurityContext securityContext;

    @Override
    public CurrentUser currentUser() {
        String username = securityContext.getCallerPrincipal().getName();

        Set<String> roles = new HashSet<>();
        for (String role : List.of("USER", "MANAGER", "ADMIN", "PAYMENT_OPERATOR")) {
            if (securityContext.isUserInRole(role)) {
                roles.add(role);
            }
        }

        return new CurrentUser(username, roles);
    }
}

Wichtig:

text
Jakarta Security / Container Security muss so konfiguriert werden,
dass Rollen und Principal dem Legacy-Verhalten entsprechen.

#Quarkus-Security-Migration

Quarkus Resource:

java
@Path("/orders")
@ApplicationScoped
public class OrderResource {

    @Inject
    ApproveOrderApplicationService approveOrderApplicationService;

    @POST
    @Path("/{id}/approve")
    @RolesAllowed("MANAGER")
    public Response approve(@PathParam("id") String id) {
        approveOrderApplicationService.approve(OrderId.of(id));
        return Response.noContent().build();
    }
}

Current User Adapter:

java
@ApplicationScoped
public class QuarkusCurrentUserAdapter implements CurrentUserPort {

    @Inject
    SecurityIdentity securityIdentity;

    @Override
    public CurrentUser currentUser() {
        return new CurrentUser(
                securityIdentity.getPrincipal().getName(),
                securityIdentity.getRoles()
        );
    }
}

Beispiel application.properties:

properties
quarkus.http.auth.permission.admin.paths=/admin/*
quarkus.http.auth.permission.admin.policy=admin-policy
quarkus.http.auth.policy.admin-policy.roles-allowed=ADMIN

quarkus.http.auth.permission.orders.paths=/orders/*
quarkus.http.auth.permission.orders.policy=authenticated
quarkus.http.auth.policy.authenticated.roles-allowed=USER,MANAGER,ADMIN

Bei OIDC/JWT:

properties
quarkus.oidc.auth-server-url=https://issuer.example.com/realms/company
quarkus.oidc.client-id=order-service

Rollen-Mapping mit dem Identity Provider muss explizit getestet werden.


#Security-Cutover-Strategie

Security-Cutover niemals als Big Bang.

Empfohlener Ablauf:

text
1. Security Map erstellen.
2. Legacy-Regeln in Tests ausdrücken.
3. CurrentUserPort und TenantContextPort einführen.
4. JSP/EJB/SOAP Security Checks dokumentieren.
5. Neue Plattform mit denselben Rollenregeln im Shadow Mode betreiben.
6. AuthN/AuthZ-Testmatrix gegen Legacy und neues System laufen lassen.
7. Audit-Logs vergleichen.
8. Einzelnen Endpoint cutovern.
9. Monitoring auf 401/403-Anstieg aktivieren.
10. Rollback-Pfad bereithalten.

AuthZ-Vergleichsmatrix:

markdown
| Benutzer | Rollen | Aktion | Legacy Ergebnis | Neues Ergebnis | OK? |
|---|---|---|---|---|---|
| alice | USER | order anzeigen | 200 | 200 | ja |
| alice | USER | order approve | 403 | 403 | ja |
| bob | MANAGER | order approve | 204 | 204 | ja |
| carol | ADMIN | admin öffnen | 200 | 200 | ja |
| guest | none | order anzeigen | 302/401 | 401 | fachlich prüfen |

Bei Unterschieden:

text
Nicht automatisch neues Verhalten akzeptieren.
Erst klären, ob Legacy-Verhalten fachlich gewollt war.

#Security-Plan für Complex Slice 001

Für unseren Order/Payment/Invoice-Slice ergibt sich dieser Security-Plan:

markdown
# Security Plan: Complex Slice 001

## Betroffene Entry Points

| Entry Point | Schutz |
|---|---|
| create-order.jsp | USER |
| OrderServiceBean#createOrder | USER |
| PaymentRequestedWorker | SYSTEM_PAYMENT_WORKER |
| Payment SOAP Client | Client Certificate / WS-Security |
| OrderPaidOutboxPublisher | SYSTEM_OUTBOX |
| InvoiceMDB | SYSTEM_MESSAGING |
| Admin Reprocess Outbox | ADMIN / OPERATOR |

## Rollen

| Rolle | Zweck |
|---|---|
| USER | Bestellung anlegen |
| MANAGER | Bestellung freigeben / Sonderaktionen |
| PAYMENT_OPERATOR | Payment manuell prüfen |
| ADMIN | technische Administration |
| SYSTEM_PAYMENT_WORKER | Payment Worker |
| SYSTEM_OUTBOX | Outbox Publisher |
| SYSTEM_MESSAGING | JMS Consumer |

## Kritische Prüfungen

| Prüfung | Ort | Test nötig |
|---|---|---|
| Nur USER darf Order erstellen | JSP/REST/EJB | ja |
| Payment Worker läuft als Systemrolle | Worker | ja |
| Manuelles Reprocessing nur OPERATOR/ADMIN | Admin Endpoint | ja |
| Invoice-Erzeugung nicht durch Enduser direkt | MDB/Service | ja |
| SOAP Payment nutzt korrektes Zertifikat | SOAP Adapter | ja |
| Audit enthält realen Enduser oder Systemuser | AuditPort | ja |
| Tenant wird aus Identität abgeleitet | TenantContextPort | ja |

## Modernisierungsreihenfolge

1. Security Map für Order/Payment/Invoice erstellen.
2. CurrentUserPort einführen.
3. AuditPort um Principal erweitern.
4. Payment Worker als Systemuser modellieren.
5. SOAP Security in SoapPaymentGateway kapseln.
6. Admin-Reprocessing-Endpunkte separat schützen.
7. Security-Tests für USER, MANAGER, ADMIN, SYSTEM schreiben.
8. Rollen-Mapping bei Spring/Jakarta/Quarkus Migration verifizieren.
9. 401/403, Login-Fehler und SOAP-Security-Fehler beobachten.
10. Cutover erst nach bestandener AuthZ-Matrix.

## Wichtigster Merksatz

Security wird nicht automatisch modernisiert, nur weil das Framework moderner ist.
Security muss explizit gemappt, getestet und beobachtet werden.

#Referenzpunkte für diesen Abschnitt

  • OWASP Cheat Sheet Series: CSRF Prevention Cheat Sheet
  • OWASP Cheat Sheet Series: Session Management Cheat Sheet
  • Jakarta Security 4.0 Specification
  • Spring Security Reference: CSRF and Method Security
  • Quarkus Security Guides
  • MicroProfile JWT 2.1 Specification
  • OASIS WS-Security SOAP Message Security
⌂ Cockpit