#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:
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:
- 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:
Security-Verhalten zuerst inventarisieren,
dann testen,
dann migrieren.
#Security-Inventar erstellen
Lege im Projekt an:
docs/security-map.md
Vorlage:
# 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:
@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:
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:
<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:
| 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:
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:
@Stateless
@RolesAllowed("MANAGER")
public class OrderApprovalBean {
public void approveOrder(Long orderId) {
// Nur MANAGER darf freigeben
}
}
Oder methodenspezifisch:
@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:
| 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:
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:
@Resource
private SessionContext sessionContext;
public void createOrder(CreateOrderRequest request) {
String username = sessionContext.getCallerPrincipal().getName();
auditService.write("ORDER_CREATED", username);
}
Problem:
Fachlogik hängt direkt an EJB SessionContext.
Besserer Port:
public interface CurrentUserPort {
CurrentUser currentUser();
}
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:
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:
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:
Darf der Benutzer diesen Endpoint / diese Methode überhaupt aufrufen?
Fachliche Autorisierung:
Darf dieser konkrete Benutzer diese konkrete Bestellung bearbeiten?
Beispiel:
@RolesAllowed({"MANAGER", "SUPPORT"})
public void cancelOrder(String orderId) {
cancelOrderUseCase.cancel(OrderId.of(orderId));
}
Im Use Case:
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:
Rollen schützen grob.
Fachliche Berechtigungen schützen konkret.
#JSP Session Security analysieren
Alte JSPs enthalten oft Security-Checks:
<%
String role = (String) session.getAttribute("role");
if (!"ADMIN".equals(role)) {
response.sendRedirect("/access-denied.jsp");
return;
}
%>
Oder gefährlicher:
<%
Boolean canApprove = (Boolean) session.getAttribute("canApprove");
if (Boolean.TRUE.equals(canApprove)) {
%>
<button>Approve</button>
<%
}
%>
Problem:
UI versteckt Button,
aber Backend schützt Operation vielleicht nicht.
Analyse-Tabelle:
| 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:
JSP darf UI-Elemente anzeigen/verstecken.
Backend muss jede sicherheitsrelevante Aktion selbst prüfen.
#Session Management modernisieren
Dokumentiere zuerst:
| 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:
- 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:
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:
<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:
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:
<input type="hidden" name="csrfToken" value="${sessionScope.CSRF_TOKEN}" />
Bei Migration:
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:
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:
| 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:
SOAP-Vertrag ist nicht nur WSDL/XSD.
Security Policy, Zertifikate, Header und Handler gehören zum Vertrag.
#SOAP Security Adapter bauen
Legacy-Code:
@WebServiceRef
private PaymentProviderService paymentProviderService;
public PaymentResponse charge(...) {
PaymentPort port = paymentProviderService.getPaymentPort();
return port.charge(...);
}
Besserer Adapter:
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:
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:
docs/secrets-and-certificates.md
Vorlage:
# 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:
- Passwörter
- Private Keys
- Keystores mit privaten Schlüsseln
- produktive Zertifikate
- Service Account Tokens
Modernisierungsziel:
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:
String tenantId = (String) session.getAttribute("tenantId");
query.setParameter("tenantId", tenantId);
Oder gefährlich:
String sql = "select * from orders where tenant_id = '" + request.getParameter("tenant") + "'";
Dokumentiere:
| 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:
Mandant nicht aus frei manipulierbarem Request übernehmen.
Mandant aus authentifizierter Identität oder serverseitiger Zuordnung ableiten.
Besser:
public interface TenantContextPort {
TenantId currentTenant();
}
Use Case:
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:
| 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:
@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:
@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:
security/
SecurityConfig.java
CurrentUserSpringAdapter.java
TenantContextSpringAdapter.java
Beispiel SecurityConfig:
@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:
@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:
| 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:
@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:
@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:
Jakarta Security / Container Security muss so konfiguriert werden,
dass Rollen und Principal dem Legacy-Verhalten entsprechen.
#Quarkus-Security-Migration
Quarkus Resource:
@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:
@ApplicationScoped
public class QuarkusCurrentUserAdapter implements CurrentUserPort {
@Inject
SecurityIdentity securityIdentity;
@Override
public CurrentUser currentUser() {
return new CurrentUser(
securityIdentity.getPrincipal().getName(),
securityIdentity.getRoles()
);
}
}
Beispiel application.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:
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:
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:
| 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:
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:
# 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