Introduktion til Spring Security ACL

1. Introduktion

Adgangskontroliste (ACL) er en liste over tilladelser, der er knyttet til et objekt. En ACL angiver, hvilke identiteter der tildeles, hvilke operationer på et givet objekt.

Forårssikkerhed Adgangskontrolisteer -en Forår komponent, der understøtter Sikkerhed for domæneobjekter. Kort sagt, Spring ACL hjælper med at definere tilladelser til specifik bruger / rolle på et enkelt domæneobjekt - i stedet for over hele linjen på det typiske niveau for operation.

For eksempel en bruger med rollen Admin kan se (LÆS) og rediger (SKRIVE) alle beskeder på en Central meddelelsesboks, men den normale bruger kan kun se meddelelser, forholde sig til dem og kan ikke redigere. I mellemtiden bruger andre rollen Redaktør kan se og redigere nogle specifikke meddelelser.

Derfor har forskellige brugere / roller forskellige tilladelser til hvert specifikt objekt. I dette tilfælde, Forår ACL er i stand til at nå opgaven. Vi undersøger, hvordan du opretter grundlæggende tilladelseskontrol med Forår ACL i denne artikel.

2. Konfiguration

2.1. ACL-database

At bruge Spring Security ACL, skal vi oprette fire obligatoriske tabeller i vores database.

Den første tabel er ACL_CLASS, som gemmer klassenavn på domæneobjektet, indeholder kolonner:

  • ID
  • KLASSE: klassenavnet på sikre domæneobjekter, for eksempel:com.baeldung.acl.persistence.entity.NoticeMessage

For det andet har vi brug for ACL_SID tabel, der giver os mulighed for universelt at identificere ethvert princip eller autoritet i systemet. Bordet har brug for:

  • ID
  • SID: hvilket er brugernavnet eller rollenavnet. SID står for Sikkerhedsidentitet
  • REKTOR: 0 eller 1, for at angive, at det tilsvarende SID er en hovedstol (bruger, f.eks mary, mike, jack ...) eller en autoritet (rolle, såsom ROLE_ADMIN, ROLE_USER, ROLE_EDITOR ...)

Næste tabel er ACL_OBJECT_IDENTITY, der gemmer information for hvert unikt domæneobjekt:

  • ID
  • OBJECT_ID_CLASS: definere domæneobjektklassen,links til ACL_CLASS bord
  • OBJECT_ID_IDENTITY: domæneobjekter kan gemmes i mange tabeller afhængigt af klassen. Derfor gemmer dette felt målobjektets primære nøgle
  • PARENT_OBJECT: angiv forælder til dette Objektidentitet inden for denne tabel
  • OWNER_SID: ID af objektsejeren, links til ACL_SID bord
  • ENTRIES_INHERITTING: om ACL-poster af dette objekt arver fra det overordnede objekt (ACL-poster er defineret i ACL_ENTRY bord)

Endelig blev ACL_ENTRY gem individuel tilladelse tildeles hver SID på en Objektidentitet:

  • ID
  • ACL_OBJECT_IDENTITY: angiv objektets identitet, links til ACL_OBJECT_IDENTITY bord
  • ACE_ORDER: rækkefølgen af ​​den aktuelle indtastning i ACL-poster liste over tilsvarende Objektidentitet
  • SID: målet SID som tilladelsen gives eller nægtes fra, links til ACL_SID bord
  • MASKE: heltal bitmasken, der repræsenterer den faktiske tilladelse, der gives eller nægtes
  • TILDELING: værdi 1 betyder tildeling, værdi 0 betyder benægtelse
  • AUDIT_SUCCESS og AUDIT_FAILURE: til revisionsformål

2.2. Afhængighed

At kunne bruge Forår ACL Lad os i vores projekt først definere vores afhængigheder:

 org.springframework.security spring-security-acl org.springframework.security spring-security-config org.springframework spring-context-support net.sf.ehcache ehcache-core 2.6.11 

Forår ACL kræver en cache for at gemme Objektidentitet og ACL-poster, så vi bruger Ehcache her. Og for at støtte Ehcache i Forår, vi har også brug for spring-context-support.

Når vi ikke arbejder med Spring Boot, er vi nødt til at tilføje versioner eksplicit. Disse kan kontrolleres på Maven Central: spring-security-acl, spring-security-config, spring-context-support, ehcache-core.

2.3. ACL-relateret konfiguration

Vi er nødt til at sikre alle metoder, der returnerer sikrede domæneobjekter eller foretage ændringer i objektet ved at aktivere det Global metodesikkerhed:

@Configuration @EnableGlobalMethodSecurity (prePostEnabled = true, secureEnabled = true) offentlig klasse AclMethodSecurityConfiguration udvider GlobalMethodSecurityConfiguration {@Autowired MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler; @ Override beskyttet MethodSecurityExpressionHandler createExpressionHandler () {return defaultMethodSecurityExpressionHandler; }}

Lad os også aktivere Ekspressionsbaseret adgangskontrol ved at indstille prePostEnabled til rigtigt at bruge Spring Expression Language (SpEL). i øvrigt, vi har brug for en ekspressionshåndterer med ACL support:

@Bean public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler () {DefaultMethodSecurityExpressionHandler expressionHandler = ny DefaultMethodSecurityExpressionHandler (); AclPermissionEvaluator permissionEvaluator = ny AclPermissionEvaluator (aclService ()); expressionHandler.setPermissionEvaluator (tilladelseEvaluator); return expressionHandler; }

Derfor, vi tildeler AclPermissionEvaluator til DefaultMethodSecurityExpressionHandler. Evaluatoren har brug for en MutableAclService at indlæse tilladelsesindstillinger og definitioner af domæneobjekter fra databasen.

For enkelheds skyld bruger vi den medfølgende JdbcMutableAclService:

@Bean public JdbcMutableAclService aclService () {returner ny JdbcMutableAclService (dataSource, lookupStrategy (), aclCache ()); }

Som sit navn er JdbcMutableAclService anvendelser JDBCTemplate for at forenkle databaseadgang. Det har brug for en Datakilde (til JDBCTemplate), Opslagstrategi (giver et optimeret opslag, når du spørger til databasen), og en AclCache (caching ACLIndlæg og Objektidentitet).

Igen, for enkelhedens skyld, bruger vi forudsat BasicLookupStrategy og EhCacheBasedAclCache.

@Autowired DataSource dataSource; @Bean public AclAuthorizationStrategy aclAuthorizationStrategy () {return new AclAuthorizationStrategyImpl (new SimpleGrantedAuthority ("ROLE_ADMIN")); } @Bean public PermissionGrantingStrategy permissionGrantingStrategy () {returner nyt DefaultPermissionGrantingStrategy (ny ConsoleAuditLogger ()); } @Bean public EhCacheBasedAclCache aclCache () {returner ny EhCacheBasedAclCache (aclEhCacheFactoryBean (). GetObject (), permitGrantingStrategy (), aclAuthorizationStrategy ()); } @Bean public EhCacheFactoryBean aclEhCacheFactoryBean () {EhCacheFactoryBean ehCacheFactoryBean = ny EhCacheFactoryBean (); ehCacheFactoryBean.setCacheManager (aclCacheManager (). getObject ()); ehCacheFactoryBean.setCacheName ("aclCache"); returner ehCacheFactoryBean; } @Bean public EhCacheManagerFactoryBean aclCacheManager () {returner ny EhCacheManagerFactoryBean (); } @Bean offentlig LookupStrategy lookupStrategy () {returner ny BasicLookupStrategy (dataSource, aclCache (), aclAuthorizationStrategy (), ny ConsoleAuditLogger ()); } 

Her, den AclAuthorizationStrategy har ansvaret for at konkludere, om en nuværende bruger har alle nødvendige tilladelser til bestemte objekter eller ej.

Det har brug for støtte fra Tilladelse tildelingstrategi, der definerer logikken til bestemmelse af, om en tilladelse gives til et bestemt SID.

3. Metodesikkerhed med ACL i foråret

Indtil videre har vi foretaget al nødvendig konfiguration. Nu kan vi sætte den krævede kontrolregel på vores sikrede metoder.

Som standard, Forår ACL refererer til BasePermission klasse for alle tilgængelige tilladelser. Dybest set har vi en LÆS, SKRIV, OPRET, SLET og ADMINISTRATION tilladelse.

Lad os prøve at definere nogle sikkerhedsregler:

@PostFilter ("hasPermission (filterObject, 'LÆS')") List findAll (); @PostAuthorize ("hasPermission (returnObject, 'LÆS')") NoticeMessage findById (Heltal-id); @PreAuthorize ("hasPermission (#noticeMessage, 'WRITE')") NoticeMessage save (@Param ("noticeMessage") NoticeMessage noticeMessage);

Efter udførelsen af findAll () metode, @PostFilter udløses. Den krævede regel hasPermission (filterObject, 'LÆS'), betyder kun at returnere dem Bemærk meddelelse hvilken nuværende bruger har LÆS tilladelse til.

Tilsvarende @PostAuthorize udløses efter udførelsen af findById () metode, skal du sørge for kun at returnere Bemærk meddelelse objekt, hvis den aktuelle bruger har LÆS tilladelse til det. Hvis ikke, kaster systemet en AccessDeniedException.

På den anden side udløser systemet @PreAuthorize anmærkning inden påberåbelse af Gemme() metode. Det bestemmer, hvor den tilsvarende metode får lov til at udføre eller ej. Hvis ikke, AccessDeniedException vil blive kastet.

4. I aktion

Nu skal vi teste alle disse konfigurationer ved hjælp af JUnit. Vi bruger H2 database for at holde konfigurationen så enkel som muligt.

Vi bliver nødt til at tilføje:

 com.h2database h2 org.springframework spring-test test org.springframework.security spring-security-test test 

4.1. Scenariet

I dette scenarie har vi to brugere (manager, hr) og en brugerrolle (ROLE_EDITOR), så vores acl_sid vil være:

INSERT INTO acl_sid (id, principal, sid) VALUES (1, 1, 'manager'), (2, 1, 'hr'), (3, 0, 'ROLE_EDITOR');

Så skal vi erklære Bemærk meddelelse klasse i acl_class. Og tre tilfælde af Bemærk meddelelse klasse indsættes i systemmeddelelse.

Desuden skal tilsvarende poster for disse 3 forekomster deklareres i acl_object_identity:

INDSÆT I acl_class (id, class) VALUES (1, 'com.baeldung.acl.persistence.entity.NoticeMessage'); INSERT INTO system_message (id, content) VALUES (1, 'First Level Message'), (2, 'Second Level Message'), (3, 'Third Level Message'); INDSÆT I acl_object_identity (id, object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) VALUES (1, 1, 1, NULL, 3, 0), (2, 1, 2, NULL, 3, 0), (3, 1, 3, NULL, 3, 0);

Oprindeligt giver vi LÆS og SKRIVE tilladelser til det første objekt (id = 1) til brugeren Manager. I mellemtiden er enhver bruger med ROLE_EDITOR vil have LÆS tilladelse til alle tre objekter, men kun besidde SKRIVE tilladelse til det tredje objekt (id = 3). Desuden bruger hr vil kun have LÆS tilladelse til det andet objekt.

Her, fordi vi bruger standard Forår ACLBasePermission klasse til tilladelseskontrol, maskeværdien af LÆS tilladelse er 1, og maskeværdien af SKRIVE tilladelse vil være 2. Vores data i acl_entry vil være:

INSERT INTO acl_entry (id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) VALUES (1, 1, 1, 1, 1, 1, 1, 1, 1), (2, 1, 2, 1, 2, 1, 1, 1), (3, 1, 3, 3, 1, 1, 1, 1), (4, 2, 1, 2, 1, 1, 1, 1), (5, 2, 2, 3, 1, 1, 1, 1), (6, 3, 1, 3, 1, 1, 1, 1), (7, 3, 2, 3, 2, 1, 1, 1);

4.2. Test sag

Først og fremmest forsøger vi at kalde findAlle metode.

Som vores konfiguration returnerer metoden kun dem Bemærk meddelelse som brugeren har LÆS tilladelse.

Derfor forventer vi, at resultatlisten kun indeholder den første besked:

@Test @WithMockUser (brugernavn = "manager") offentligt ugyldigt givetUserManager_whenFindAllMessage_thenReturnFirstMessage () {Listedetaljer = repo.findAll (); assertNotNull (detaljer); assertEquals (1, details.size ()); assertEquals (FIRST_MESSAGE_ID, details.get (0) .getId ()); }

Derefter prøver vi at kalde den samme metode med enhver bruger, der har rollen - ROLE_EDITOR. Bemærk, at i dette tilfælde har disse brugere LÆS tilladelse til alle tre objekter.

Derfor forventer vi, at resultatlisten indeholder alle tre meddelelser:

@Test @WithMockUser (roller = {"EDITOR"}) ugyldigt offentligt givenRoleEditor_whenFindAllMessage_thenReturn3Message () {Listedetaljer = repo.findAll (); assertNotNull (detaljer); assertEquals (3, details.size ()); }

Brug derefter Manager bruger, vi prøver at få den første besked efter id og opdatere dens indhold - som alle skal fungere fint:

@Test @WithMockUser (brugernavn = "manager") offentligt ugyldigt givetUserManager_whenFind1stMessageByIdAndUpdateItsContent_thenOK () {NoticeMessage firstMessage = repo.findById (FIRST_MESSAGE_ID); assertNotNull (firstMessage); assertEquals (FIRST_MESSAGE_ID, firstMessage.getId ()); firstMessage.setContent (EDITTED_CONTENT); repo.save (firstMessage); NoticeMessage editedFirstMessage = repo.findById (FIRST_MESSAGE_ID); assertNotNull (redigeretFirstMessage); assertEquals (FIRST_MESSAGE_ID, editedFirstMessage.getId ()); assertEquals (EDITTED_CONTENT, editedFirstMessage.getContent ()); }

Men hvis nogen bruger med ROLE_EDITOR rolle opdaterer indholdet af den første besked - vores system kaster en AccessDeniedException:

@Test (forventet = AccessDeniedException.class) @WithMockUser (roller = {"EDITOR"}) offentlig ugyldighed givenRoleEditor_whenFind1stMessageByIdAndUpdateContent_thenFail () {NoticeMessage firstMessage = repo.findByIdAGE (FIRST_M) assertNotNull (firstMessage); assertEquals (FIRST_MESSAGE_ID, firstMessage.getId ()); firstMessage.setContent (EDITTED_CONTENT); repo.save (firstMessage); }

Tilsvarende er hr brugeren kan finde den anden meddelelse efter id, men opdaterer den ikke:

@Test @WithMockUser (brugernavn = "hr") offentligt ugyldigt givetUsernameHr_whenFindMessageById2_thenOK () {NoticeMessage secondMessage = repo.findById (SECOND_MESSAGE_ID); assertNotNull (secondMessage); assertEquals (SECOND_MESSAGE_ID, secondMessage.getId ()); } @Test (forventet = AccessDeniedException.class) @WithMockUser (brugernavn = "hr") offentligt ugyldigt givetUsernameHr_whenUpdateMessageWithId2_thenFail () {NoticeMessage secondMessage = new NoticeMessage (); secondMessage.setId (SECOND_MESSAGE_ID); secondMessage.setContent (EDITTED_CONTENT); repo.save (secondMessage); }

5. Konklusion

Vi har gennemgået grundlæggende konfiguration og brug af Forår ACL i denne artikel.

Som vi ved, Forår ACL krævede specifikke tabeller til styring af objekt, princip / autoritet og tilladelsesindstilling. Alle interaktioner med disse tabeller, især opdateringshandlinger, skal gå igennem AclService. Vi undersøger denne service grundlæggende CRUD handlinger i en fremtidig artikel.

Som standard er vi begrænset til foruddefineret tilladelse i BasePermission klasse.

Endelig kan implementeringen af ​​denne vejledning findes på Github.


$config[zx-auto] not found$config[zx-overlay] not found