Custom Evaluators
Custom evaluators breiden het beveiligingssysteem van webforJ uit met gespecialiseerde toegangscodes die verder gaan dan de basisverificatie en rolchecks. Gebruik ze wanneer je dynamische voorwaarden moet verifiëren die afhankelijk zijn van de context van het verzoek, niet alleen van de gebruikersmachtigingen.
Deze gids behandelt aangepaste evaluators voor Spring Security. Als je Spring Boot niet gebruikt, zie dan de Evaluators Chain-gids om te begrijpen hoe evaluators werken en Compleet Implementatie voor een werkend voorbeeld.
Wat zijn aangepaste evaluators
Een evaluator bepaalt of een gebruiker toegang kan krijgen tot een specifieke route op basis van aangepaste logica. Evaluators worden tijdens de navigatie gecontroleerd voordat een component wordt gerenderd, waardoor je toegang dynamisch kunt onderscheppen en regelen.
webforJ bevat ingebouwde evaluators voor standaard Jakarta-annotaties:
AnonymousAccessEvaluator- Behandelt@AnonymousAccessPermitAllEvaluator- Behandelt@PermitAllRolesAllowedEvaluator- Behandelt@RolesAllowedDenyAllEvaluator- Behandelt@DenyAll
Aangepaste evaluators volgen hetzelfde patroon, waardoor je je eigen annotaties en toegangscodes kunt maken.
Voor meer details over @AnonymousAccess, @PermitAll, @RolesAllowed en @DenyAll, zie de Beveiligingsannotaties-gids.
Gebruikscase: Eigendom verificatie
Een veelvoorkomende vereiste is dat gebruikers alleen toegang hebben tot hun eigen middelen. Bijvoorbeeld, gebruikers mogen alleen hun eigen profiel bewerken, niet dat van iemand anders.
Het probleem: @RolesAllowed("USER") verleent toegang aan alle geauthenticeerde gebruikers, maar verifieert niet of de gebruiker hun eigen middel gebruikt. Je moet de ingelogde gebruikers-ID vergelijken met de middelen-ID in de URL.
Voorbeeldscenario:
- Gebruikers-ID
123is ingelogd - Ze navigeren naar
/users/456/edit - Mogen ze deze pagina openen? NEE - ze kunnen alleen
/users/123/editbewerken
Je kunt dit niet oplossen met rollen, omdat het afhangt van de routeparameter :userId, die voor elk verzoek verandert.
Een aangepaste annotatie maken
Definieer een annotatie om routes te markeren die eigendom verificatie vereisen:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireOwnership {
/**
* De naam van de routeparameter die de gebruikers-ID bevat.
*/
String value() default "userId";
}
Gebruik het op routes die eigendom checks vereisen:
@Route(value = "/users/:userId/edit", outlet = MainLayout.class)
@RequireOwnership("userId")
public class EditProfileView extends Composite<Div> {
private final Div self = getBoundComponent();
public EditProfileView() {
self.setText("Bewerk Profiel Pagina");
}
}
De evaluator implementeren
Maak een door Spring beheerde evaluator die de ingelogde gebruikers-ID vergelijkt met de routeparameter:
@RegisteredEvaluator(priority = 10)
public class OwnershipEvaluator implements RouteSecurityEvaluator {
@Override
public boolean supports(Class<?> routeClass) {
return routeClass.isAnnotationPresent(RequireOwnership.class);
}
@Override
public RouteAccessDecision evaluate(Class<?> routeClass, NavigationContext context,
RouteSecurityContext securityContext, SecurityEvaluatorChain chain) {
// Controleer eerst de authenticatie
if (!securityContext.isAuthenticated()) {
return RouteAccessDecision.denyAuthentication();
}
// Haal de annotatie op
RequireOwnership annotation = routeClass.getAnnotation(RequireOwnership.class);
String paramName = annotation.value();
// Haal de ingelogde gebruikers-ID op uit de beveiligingscontext
String currentUserId = securityContext.getPrincipal()
.filter(p -> p instanceof UserDetails)
.map(p -> ((UserDetails) p).getUsername())
.orElse(null);
// Haal :userId op uit de routeparameters
String requestedUserId = context.getRouteParameters()
.get(paramName)
.orElse(null);
// Controleer of ze overeenkomen
if (currentUserId != null && currentUserId.equals(requestedUserId)) {
// Eigendom geverifieerd - ga door met de keten om andere evaluators toe te staan
return chain.evaluate(routeClass, context, securityContext);
}
return RouteAccessDecision.deny("Je kunt alleen toegang krijgen tot je eigen middelen");
}
}
Spring ontdekt en registreert automatisch evaluators die zijn gemarkeerd met @RegisteredEvaluator.
Hoe het werkt
De implementatie van de evaluator heeft twee belangrijke methoden:
supports(Class<?> routeClass)
- Retourneert
trueals deze evaluator de route moet afhandelen - Alleen evaluators die
trueretourneren zullen voor de route worden aangeroepen - Filtert routes door te controleren op de annotatie
@RequireOwnership
evaluate(...)
- Controleert eerst of de gebruiker geauthenticeerd is
- Haalt de ingelogde gebruikers-ID op uit
securityContext.getPrincipal() - Haalt de waarde van de routeparameter op uit
context.getRouteParameters().get(paramName) - Vergelijkt de twee ID's
- Als ze overeenkomen, geeft het de controle door aan
chain.evaluate()om andere evaluators uit te voeren - Als ze niet overeenkomen, retourneert het
deny()met een reden
Stroomschema voorbeeld
Wanneer de eigendomcheck faalt:
- Gebruiker
123logt in en navigeert naar/users/456/edit OwnershipEvaluator.supports()retourneerttrue(route heeft@RequireOwnership)OwnershipEvaluator.evaluate()wordt uitgevoerd:currentUserId = "123"(uit beveiligingscontext)requestedUserId = "456"(uit routeparameter:userId)"123".equals("456")→false- Retourneert
RouteAccessDecision.deny("Je kunt alleen toegang krijgen tot je eigen middelen")
- Gebruiker wordt doorgestuurd naar de toegang geweigerd pagina
Wanneer de eigendomcheck slaagt:
- Gebruiker
123logt in en navigeert naar/users/123/edit OwnershipEvaluator.evaluate()wordt uitgevoerd:currentUserId = "123",requestedUserId = "123"- ID's komen overeen → roept
chain.evaluate()aan om door te gaan
- Als geen andere evaluators toegang weigeren, krijgt de gebruiker toegang
Begrijpen van de evaluatorketen
Het beveiligingssysteem gebruikt een keten van verantwoordelijkheid patroon waarin evaluators in prioriteitsvolgorde worden verwerkt. Evaluators kunnen ofwel terminale beslissingen nemen of delegeren aan de keten om meerdere controle uit te voeren.
Hoe de keten werkt
- Evaluators worden gesorteerd op prioriteit (lagere nummers eerst)
- Voor elke evaluator wordt
supports(routeClass)aangeroepen om te controleren of deze van toepassing is - Als
supports()trueretourneert, wordt deevaluate()-methode van de evaluator aangeroepen - De evaluator kan:
- Een terminale beslissing retourneren (
grant()ofdeny()) - stop de keten - Delegeer aan de keten door
chain.evaluate()aan te roepen - laat andere evaluators uitvoeren
- Een terminale beslissing retourneren (
- Als de keten eindigt zonder een beslissing en secure-by-default is ingeschakeld, worden niet-geauthenticeerde gebruikers geweigerd
Terminale besluiten
Stop de keten onmiddellijk:
RouteAccessDecision.grant()
- Verleent toegang en stopt verdere evaluatie
- Gebruikt door
@AnonymousAccessen@PermitAll- dit zijn complete autorisaties die niet combineren met andere controles
RouteAccessDecision.deny(reason)
- Weigert toegang en stopt verdere evaluatie
- Gebruikt door
@DenyAllen wanneer aangepaste controles falen - Voorbeeld:
RouteAccessDecision.deny("Je kunt alleen toegang krijgen tot je eigen middelen")
RouteAccessDecision.denyAuthentication()
- Stuur door naar inlogpagina
- Gebruikt wanneer authenticatie vereist is maar ontbreekt
Ketendelegatie
Maakt het combineren van controles mogelijk:
chain.evaluate(routeClass, context, securityContext)
- Geeft de controle door aan de volgende evaluator in de keten
- Maakt het combineren van meerdere autorisatiecontroles mogelijk
- Gebruikt door
@RolesAlloweden@RouteAccessnadat hun controles zijn geslaagd - Aangepaste evaluators moeten dit patroon gebruiken wanneer controles slagen om samenstelling mogelijk te maken
Evaluator prioriteit
Evaluators worden gecontroleerd in prioriteitsvolgorde (lagere nummers eerst). Framework-evaluators gebruiken prioriteiten 1-9, aangepaste evaluators moeten 10 of hoger gebruiken.
Ingebouwde evaluators zijn in deze volgorde geregistreerd:
// Prioriteit 1: @DenyAll - blokkeert alles
// Prioriteit 2: @AnonymousAccess - staat niet-geauthenticeerde toegang toe
// Prioriteit 3: AuthenticationRequiredEvaluator - zorgt voor auth voor @PermitAll/@RolesAllowed
// Prioriteit 4: @PermitAll - vereist alleen authenticatie
// Prioriteit 5: @RolesAllowed - vereist specifieke rollen
// Prioriteit 6: @RouteAccess - SpEL-expressies (alleen Spring Security)
// Prioriteit 10+: Aangepaste evaluators (zoals @RequireOwnership)
Hoe prioriteit de evaluatie beïnvloedt
- Evaluators met een lagere prioriteit worden eerst uitgevoerd en kunnen de keten "kortsluiten"
@DenyAll(prioriteit 1) wordt als eerste uitgevoerd - als deze aanwezig is, wordt de toegang altijd geweigerd@AnonymousAccess(prioriteit 2) wordt vervolgens uitgevoerd - als deze aanwezig is, wordt de toegang altijd verleend (zelfs zonder auth)AuthenticationRequiredEvaluator(prioriteit 3) controleert of de route auth nodig heeft en of de gebruiker geauthenticeerd is- Als geen enkele evaluator de route afhandelt, geldt de secure-by-default-logica
Prioriteit instellen
Stel de prioriteit in met de annotatie @RegisteredEvaluator:
@RegisteredEvaluator(priority = 10) // Draaft na ingebouwde evaluators
public class OwnershipEvaluator implements RouteSecurityEvaluator {
// ...
}
Aangepaste evaluators moeten prioriteit 10 of hoger gebruiken. Prioriteiten 1-9 zijn gereserveerd voor framework-evaluators. Als je een prioriteit in het gereserveerde bereik gebruikt, ontvang je een waarschuwing in de logs.
Evaluators combineren
Evaluators die delegeerden aan de keten kunnen worden gecombineerd om complexe autorisatielogica te creëren. Routes kunnen meerdere beveiligingsannotaties hebben:
Combineren van rolchecks met aangepaste logica
@Route("/users/:userId/settings")
@RolesAllowed("USER")
@RequireOwnership("userId")
public class UserSettingsView extends Composite<Div> {
// Moet de USER-rol hebben EN hun eigen instellingen raadplegen
}
Hoe het werkt:
RolesAllowedEvaluator(prioriteit 5) controleert of de gebruiker de rol "USER" heeft- Als dit het geval is, roept het
chain.evaluate()aan om door te gaan OwnershipEvaluator(prioriteit 10) controleert ofuserIdovereenkomt met de ingelogde gebruiker- Als dit het geval is, roept het
chain.evaluate()aan om door te gaan - Ketting eindigt → toegang verleend
Combineren van SpEL-expressies met aangepaste logica
@Route("/admin/users/:userId/edit")
@RouteAccess("hasRole('ADMIN')")
@RequireOwnership("userId")
public class AdminEditUserView extends Composite<Div> {
// Moet admin zijn EN hun eigen account raadplegen
}
Wat kan niet worden gecombineerd
@AnonymousAccess en @PermitAll maken terminal beslissingen - ze verlenen onmiddellijk toegang zonder de keten aan te roepen. Je kunt ze niet combineren met aangepaste evaluators:
// @PermitAll verleent onmiddellijk toegang, @RequireOwnership wordt nooit uitgevoerd
@Route("/users/:userId/profile")
@PermitAll
@RequireOwnership("userId")
public class ProfileView extends Composite<Div> {
// ...
}
Voor middelen waartoe alle geauthenticeerde gebruikers toegang hebben, gebruik je @RolesAllowed met een gemeenschappelijke rol in plaats daarvan:
// @RolesAllowed delegeert naar keten
@Route("/users/:userId/profile")
@RolesAllowed("USER")
@RequireOwnership("userId")
public class ProfileView extends Composite<Div> {
// Moet een geauthenticeerde gebruiker zijn EN hun eigen profiel raadplegen
}