Custom Evaluators
Custom evaluators laajentavat webforJ:n turvallisuusjärjestelmää erikoistuneella pääsynhallintalogikalla, joka ylittää perusautentikoinnin ja roolien tarkistukset. Käytä niitä silloin, kun sinun on vahvistettava dynaamisia olosuhteita, jotka riippuvat pyyntökontekstista, eivät vain käyttäjäoikeuksista.
Tässä oppaassa käsitellään mukautettuja arvioijia Spring Securitylle. Jos et käytä Spring Bootia, katso Evaluator Chain -opas ymmärtääksesi, miten arvioijat toimivat, sekä Complete Implementation toimivasta esimerkistä.
Mitkä ovat mukautetut arvioijat
Arvioija määrittää, voiko käyttäjä käyttää tiettyä reittiä mukautetun logiikan perusteella. Arvioijat tarkistetaan navigoinnin aikana ennen kuin mitään komponenttia renderöidään, jolloin voit keskeyttää ja hallita pääsyä dynaamisesti.
webforJ sisältää sisäänrakennetut arvioijat standardeille Jakarta-annotaatioille:
AnonymousAccessEvaluator- Käsittelee@AnonymousAccessPermitAllEvaluator- Käsittelee@PermitAllRolesAllowedEvaluator- Käsittelee@RolesAllowedDenyAllEvaluator- Käsittelee@DenyAll
Mukautetut arvioijat seuraavat samaa kaavaa, mikä mahdollistaa omien annotaatioiden ja pääsynhallintalogikan luomisen.
Lisätietoja @AnonymousAccess, @PermitAll, @RolesAllowed ja @DenyAll -annotaatioista, katso Security Annotations -opas.
Käyttötapa: Omistajuuden vahvistaminen
Yleinen vaatimus on sallia käyttäjien käyttää vain omia resurssejaan. Esimerkiksi käyttäjien tulisi voida muokata vain omaa profiiliaan, ei muiden profiilia.
Ongelma: @RolesAllowed("USER") myöntää pääsyn kaikille todennetuille käyttäjille, mutta ei vahvista, onko käyttäjä käyttämässä omaa resurssiaan. Sinun on verrattava kirjautuneen käyttäjän ID:tä URL:in resurssi ID:hen.
Esimerkkitilanne:
- Käyttäjä-ID
123on kirjautuneena - He navigoivat osoitteeseen
/users/456/edit - Saako he käyttää tätä sivua? EI - he voivat muokata vain
/users/123/edit
Et voi ratkaista tätä rooleilla, koska se riippuu reittiparametrista :userId, joka muuttuu jokaiselle pyyntöä varten.
Mukautetun annotaation luominen
Määrittele annotaatio merkitsemään reittejä, jotka vaativat omistajuuden vahvistamista:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireOwnership {
/**
* Reittiparametrin nimi, joka sisältää käyttäjä-ID:n.
*/
String value() default "userId";
}
Käytä sitä reiteillä, jotka vaativat omistajuuden tarkistuksia:
@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("Muokkaa profiilisivua");
}
}
Arvioijan toteuttaminen
Luo Springin hallinnoima arvioija, joka vertaa kirjautuneen käyttäjän ID:tä reittiparametriin:
@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) {
// Ensiksi tarkista todennus
if (!securityContext.isAuthenticated()) {
return RouteAccessDecision.denyAuthentication();
}
// Hanki annotaatio
RequireOwnership annotation = routeClass.getAnnotation(RequireOwnership.class);
String paramName = annotation.value();
// Hanki kirjautuneen käyttäjän ID turvallisuuskontextista
String currentUserId = securityContext.getPrincipal()
.filter(p -> p instanceof UserDetails)
.map(p -> ((UserDetails) p).getUsername())
.orElse(null);
// Hanki :userId reittiparametreista
String requestedUserId = context.getRouteParameters()
.get(paramName)
.orElse(null);
// Tarkista, vastaavatko ne toisiaan
if (currentUserId != null && currentUserId.equals(requestedUserId)) {
// Omistajuus vahvistettu - jatka ketjua salliaksesi muiden arvioijien toimia
return chain.evaluate(routeClass, context, securityContext);
}
return RouteAccessDecision.deny("Voit käyttää vain omia resurssejasi");
}
}
Spring havaitsee ja rekisteröi automaattisesti arvioijat, joilla on @RegisteredEvaluator -annotation.
Miten se toimii
Arvioijan toteutuksessa on kaksi keskeistä metodia:
supports(Class<?> routeClass)
- Palauttaa
true, jos tämän arvioijan tulisi käsitellä reitti - Vain arvioijat, jotka palauttavat
true, kutsutaan reitille - Suodattaa reittejä tarkistamalla, onko
@RequireOwnershipannotaatio
evaluate(...)
- Tarkistaa ensin, onko käyttäjä todennettu
- Hakee kirjautuneen käyttäjän ID:n
securityContext.getPrincipal()-metodista - Hakee reittiparametrin arvon
context.getRouteParameters().get(paramName)-metodista - Vertaa kahta ID:tä
- Jos ne vastaavat, delegoi
chain.evaluate():lle salliakseen muiden arvioijien toimia - Jos ne eivät vastaa, palauttaa
deny()syyn kanssa
Virtaesimerkki
Kun omistajuuden tarkistus epäonnistuu:
- Käyttäjä
123kirjautuu sisään ja navigoi osoitteeseen/users/456/edit OwnershipEvaluator.supports()palauttaatrue(reittillä on@RequireOwnership)OwnershipEvaluator.evaluate()aktiivinen:currentUserId = "123"(turvallisuuskontextista)requestedUserId = "456"(reittiparametrista:userId)"123".equals("456")→false- Palauttaa
RouteAccessDecision.deny("Voit käyttää vain omia resurssejasi")
- Käyttäjä ohjataan pääsyn estämissivulle
Kun omistajuuden tarkistus onnistuu:
- Käyttäjä
123kirjautuu sisään ja navigoi osoitteeseen/users/123/edit OwnershipEvaluator.evaluate()aktivoituu:currentUserId = "123",requestedUserId = "123"- ID:t vastaavat → kutsuu
chain.evaluate()jatkaakseen
- Jos mikään muu arvioija ei estä pääsyä, käyttäjälle myönnetään pääsy
Arvioijaketjun ymmärtäminen
Turvallisuusjärjestelmä käyttää vastuun siirtoketjun mallia, jossa arvioijat käsitellään prioriteetti järjestyksessä. Arvioijat voivat joko tehdä päättävissä päätöksiä tai delegoida ketjulle useiden tarkistusten yhdistämiseksi.
Miten ketju toimii
- Arvioijat lajitellaan prioriteetin mukaan (matalammat numerot ensin)
- Jokaiselle arvioijalle kutsutaan
supports(routeClass)tarkistamaan, onko se sovellettavissa - Jos
supports()palauttaatrue, arvioijanevaluate()-metodia kutsutaan - Arvioija voi joko:
- Palauttaa päättävän päätöksen (
grant()taideny()) - pysäyttää ketjun - Delegoida ketjulle kutsumalla
chain.evaluate()- sallii muiden arvioijien toimia
- Palauttaa päättävän päätöksen (
- Jos ketju päättyy ilman päätöstä ja oletus "turvallinen" pääsy on aktivoitu, todennetut käyttäjät estetään
Päättävät päätökset
Pysäyttää ketjun välittömästi:
RouteAccessDecision.grant()
- Myöntää pääsyn ja pysäyttää lisäarvioinnit
- Käytetään
@AnonymousAccessja@PermitAll-annotaatioilla - nämä ovat täydellisiä valtuutuksia, jotka eivät yhdisty muihin tarkastuksiin
RouteAccessDecision.deny(reason)
- Estää pääsyn ja pysäyttää lisäarvioinnit
- Käytetään
@DenyAllja kun mukautetut tarkastukset epäonnistuvat - Esimerkki:
RouteAccessDecision.deny("Voit käyttää vain omia resurssejasi")
RouteAccessDecision.denyAuthentication()
- Ohjaa kirjautumissivulle
- Käytetään, kun todennus on vaadittu mutta puuttuu
Ketjun delegointi
Mahdollistaa tarkistusten yhdistämisen:
chain.evaluate(routeClass, context, securityContext)
- Siirtää ohjauksen seuraavalle arvioijalle ketjussa
- Mahdollistaa useiden valtuutustarkistusten yhdistämisen
- Käytetään
@RolesAllowedja@RouteAccessjälkeen, kun heidän tarkistuksensa ovat onnistuneet - Mukautettujen arvioijien tulisi käyttää tätä kaavaa, kun tarkistukset onnistuvat mahdollistamaan yhdistämisen
Arvioijien prioriteetti
Arvioijat tarkistetaan prioriteetti järjestyksessä (matalammat numerot ensin). Kehyksen arvioijat käyttävät prioriteetteja 1-9, mukautetut arvioijat pitäisi käyttää 10 tai korkeampia.
Sisäänrakennetut arvioijat rekisteröidään tässä järjestyksessä:
// Prioriteetti 1: @DenyAll - estää kaiken
// Prioriteetti 2: @AnonymousAccess - sallii todennuksettomat pääsyt
// Prioriteetti 3: AuthenticationRequiredEvaluator - varmistaa todennuksen @PermitAll/@RolesAllowed
// Prioriteetti 4: @PermitAll - vaatii vain todennusta
// Prioriteetti 5: @RolesAllowed - vaatii erityisiä rooleja
// Prioriteetti 6: @RouteAccess - SpEL-lausekkeet (vain Spring Security)
// Prioriteetti 10+: Mukautetut arvioijat (kuten @RequireOwnership)
Miten prioriteetti vaikuttaa arviointiin
- Alempiprioriteettiset arvioijat toimivat ensin ja voivat "lyhyttää" ketjun
@DenyAll(prioriteetti 1) toimii ensin - jos se on läsnä, pääsy on aina estetty@AnonymousAccess(prioriteetti 2) toimii seuraavaksi - jos se on läsnä, pääsy on aina myönnetty (ilman todennusta)AuthenticationRequiredEvaluator(prioriteetti 3) tarkistaa, tarvitsee reitti todennuksen ja onko käyttäjä todennettu- Jos mikään arvioija ei käsittele reittiä, turvallinen oletuslogiikka sovelletaan
Prioriteetin asettaminen
Aseta prioriteetti @RegisteredEvaluator -annotaatiolla:
@RegisteredEvaluator(priority = 10) // Toimii sisäänrakennettujen arvioijien jälkeen
public class OwnershipEvaluator implements RouteSecurityEvaluator {
// ...
}
Mukauttujen arvioijien tulisi käyttää prioriteettia 10 tai korkeampia. Prioriteetti 1-9 on varattu kehyksen arvioijille. Jos käytät varattua aluetta, saat varoituksen lokiin.
Arvioijien yhdistäminen
Arvioijat, jotka delegoivat ketjulle, voidaan yhdistää luomaan monimutkaista valtuutuslogiikkaa. Reiteillä voi olla useita turvallisuusannotaatioita:
Roolitarkistusten yhdistäminen mukautettuun logiikkaan
@Route("/users/:userId/settings")
@RolesAllowed("USER")
@RequireOwnership("userId")
public class UserSettingsView extends Composite<Div> {
// Käyttäjällä on oltava USER-rooli JA päästävä omiin asetuksiinsa
}
Miten se toimii:
RolesAllowedEvaluator(prioriteetti 5) tarkistaa, onko käyttäjällä "USER" -rooli- Jos on, kutsuu
chain.evaluate()jatkaakseen OwnershipEvaluator(prioriteetti 10) tarkistaa, vastaakouserIdkirjautunutta käyttäjää- Jos kyllä, kutsuu
chain.evaluate()jatkaakseen - Ketju päättyy → pääsy myönnetään
SpEL-lausekkeiden yhdistäminen mukautettuun logiikkaan
@Route("/admin/users/:userId/edit")
@RouteAccess("hasRole('ADMIN')")
@RequireOwnership("userId")
public class AdminEditUserView extends Composite<Div> {
// Pitää olla ylläpitäjä JA päästä omaan tiliinsä
}
Mitä ei voi yhdistää
@AnonymousAccess ja @PermitAll tekevät päättävät päätökset - ne myöntävät pääsyn välittömästi ilman ketjun kutsumista. Et voi yhdistää niitä mukautettuihin arvioijiin:
// @PermitAll myöntää pääsyn välittömästi, @RequireOwnership ei koskaan toimi
@Route("/users/:userId/profile")
@PermitAll
@RequireOwnership("userId")
public class ProfileView extends Composite<Div> {
// ...
}
Resursseille, joihin kaikki todennetut käyttäjät voivat päästä, käytä @RolesAllowed yleisellä roolilla:
// @RolesAllowed delegoi ketjulle
@Route("/users/:userId/profile")
@RolesAllowed("USER")
@RequireOwnership("userId")
public class ProfileView extends Composite<Div> {
// Käyttäjän on oltava todennettu JA päästävä omaan profiiliinsa
}