Uvod u proljetni sigurnosni ACL

1. Uvod

Popis kontrole pristupa (ACL) je popis dozvola priloženih uz objekt. An ACL specificira koji se identiteti dodjeljuju koje operacije na danom objektu.

Proljetna sigurnost Popis kontrole pristupaje a Proljeće komponenta koja podržava Sigurnost objekta domene. Jednostavno rečeno, Spring ACL pomaže u definiranju dozvola za određenog korisnika / ulogu na objektu s jednom domenom - umjesto na ploči, na tipičnoj razini po operaciji.

Na primjer, korisnik s ulogom Admin Možete vidjeti (ČITATI) i uredi (PISATI) sve poruke na Središnji okvir s obavijestima, ali normalan korisnik može samo vidjeti poruke, povezati se s njima i ne može ih uređivati. U međuvremenu, drugi koriste ulogu Urednik mogu vidjeti i urediti neke određene poruke.

Stoga različiti korisnik / uloga ima različita dopuštenja za svaki određeni objekt. U ovom slučaju, Proljetni ACL sposoban je ostvariti zadatak. Istražit ćemo kako postaviti osnovnu provjeru dozvola pomoću Proljetni ACL u ovom članku.

2. Konfiguracija

2.1. ACL baza podataka

Koristiti Proljetni sigurnosni ACL, u našoj bazi podataka moramo stvoriti četiri obvezne tablice.

Prva tablica je ACL_CLASS, koji pohranjuju ime klase objekta domene, stupci uključuju:

  • iskaznica
  • RAZRED: naziv klase osiguranih objekata domene, na primjer:com.baeldung.acl.persistence.entity.NoticeMessage

Drugo, trebamo ACL_SID tablica koja nam omogućava da univerzalno identificiramo bilo koje načelo ili autoritet u sustavu. Tablica treba:

  • iskaznica
  • ŠID: što je korisničko ime ili ime uloge. ŠID stoji za Sigurnosni identitet
  • GLAVNI: 0 ili 1, da označi da odgovarajući ŠID je glavni (korisnik, kao što je Mary, Mike, Jack ...) ili autoritet (uloga, kao što je ROLE_ADMIN, ROLE_USER, ROLE_EDITOR…)

Sljedeća je tablica ACL_OBJECT_IDENTITY, koji pohranjuje informacije za svaki jedinstveni objekt domene:

  • iskaznica
  • OBJECT_ID_CLASS: definirati klasu objekta domene,poveznice na ACL_CLASS stol
  • OBJECT_ID_IDENTITY: objekti domene mogu se pohraniti u mnoge tablice, ovisno o klasi. Stoga ovo polje sprema primarni ključ ciljnog objekta
  • PARENT_OBJECT: navedite roditelja ovog Identitet objekta unutar ove tablice
  • OWNER_SID: iskaznica vlasnika objekta, poveznice na ACL_SID stol
  • ENTRIES_INHERITTING: da li ACL unosi ovog objekta nasljeđuje od nadređenog objekta (ACL unosi definirani su u ACL_ENTRY stol)

Napokon, ACL_ENTRY pohraniti pojedinačno dopuštenje dodijeljeno svakom ŠID na an Identitet objekta:

  • iskaznica
  • ACL_OBJECT_IDENTITY: odredite identitet objekta, poveznice na ACL_OBJECT_IDENTITY stol
  • ACE_ORDER: redoslijed trenutnog unosa u ACL unosi popis odgovarajućih Identitet objekta
  • ŠID: meta ŠID za koje je odobrenje dodijeljeno ili odbijeno, veze do ACL_SID stol
  • MASKA: cjelobrojna bitna maska ​​koja predstavlja stvarno odobrenje koje se dodjeljuje ili odbija
  • DODJELJIVANJE: vrijednost 1 znači davanje, vrijednost 0 znači poricati
  • AUDIT_USCCESS i AUDIT_FAILURE: u svrhu revizije

2.2. Ovisnost

Da bi mogao koristiti Proljetni ACL u našem projektu, prvo definirajmo svoje ovisnosti:

 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 

Proljetni ACL zahtijeva predmemoriju za pohranu Identitet objekta i ACL unosi, pa ćemo iskoristiti Ehcache ovdje. I, podržati Ehcache u Proljeće, trebamo i proljeće-kontekst-podrška.

Kada ne radimo s Spring Boot-om, trebamo eksplicitno dodati verzije. Oni se mogu provjeriti na Maven Central: spring-security-acl, spring-security-config, spring-context-support, ehcache-core.

2.3. Konfiguracija povezana s ACL-om

Moramo omogućiti sve metode koje vraćaju zaštićene objekte domene ili unose izmjene u objekt omogućavanjem Globalna sigurnost metode:

@Configuration @EnableGlobalMethodSecurity (prePostEnabled = true, securedEnabled = true) javna klasa AclMethodSecurityConfiguration proširuje GlobalMethodSecurityConfiguration {@Autowired MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler; @Override protected MethodSecurityExpressionHandler createExpressionHandler () {return defaultMethodSecurityExpressionHandler; }}

Omogućimo i mi Kontrola pristupa zasnovana na izrazu postavljanjem prePostEnabled do pravi koristiti Proljetni jezik izražavanja (SpEL). Štoviše, trebamo obrađivač izraza s ACL podrška:

@Bean public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler () {DefaultMethodSecurityExpressionHandler expressionHandler = novi DefaultMethodSecurityExpressionHandler (); AclPermissionEvaluator dozvolaEvaluator = novi AclPermissionEvaluator (aclService ()); expressionHandler.setPermissionEvaluator (dozvolaEvaluator); return expressionHandler; }

Stoga, dodjeljujemo AclPermissionEvaluator prema DefaultMethodSecurityExpressionHandler. Ocjenjivač treba a MutableAclService za učitavanje postavki dozvola i definicija objekta domene iz baze podataka.

Radi jednostavnosti koristimo priloženo JdbcMutableAclService:

@Bean public JdbcMutableAclService aclService () {return new JdbcMutableAclService (dataSource, lookupStrategy (), aclCache ()); }

Kao što mu je ime, JdbcMutableAclService koristi JDBCTemplate radi pojednostavljenja pristupa bazi podataka. Treba Izvor podataka (za JDBCTemplate), LookupStrategy (pruža optimizirano pretraživanje prilikom postavljanja upita prema bazi podataka) i AclCache (predmemoriranje ACLUnosi i Identitet objekta).

Opet, radi jednostavnosti koristimo osigurano BasicLookupStrategy i EhCacheBasedAclCache.

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

Evo, AclAuthorizationStrategy zadužen je za utvrđivanje posjeduje li trenutni korisnik sva potrebna dopuštenja za određene objekte ili ne.

Treba podršku PermissionGrantingStrategy, koja definira logiku za određivanje da li se dozvola daje određenom ŠID.

3. Sigurnost metode s proljetnim ACL-om

Do sada smo obavili sve potrebne konfiguracije. Sada na svoje osigurane metode možemo staviti potrebno pravilo provjere.

Prema zadanim postavkama, Proljetni ACL odnosi se na BasePermission klasa za sva dostupna dopuštenja. U osnovi, imamo PROČITAJTE, PIŠITE, STVARAJTE, BRIŠITE i UPRAVA dopuštenje.

Pokušajmo definirati neka sigurnosna pravila:

@PostFilter ("hasPermission (filterObject, 'READ')") Popis findAll (); @PostAuthorize ("hasPermission (returnObject, 'READ')") NoticeMessage findById (Integer id); @PreAuthorize ("hasPermission (#noticeMessage, 'WRITE')") NoticeMessage save (@Param ("noticeMessage") NoticeMessage noticeMessage);

Nakon izvršenja findAll () metoda, @PostFilter će se pokrenuti. Potrebno pravilo hasPermission (filterObject, ‘READ’), znači vraćanje samo onih ObavijestPoruka koje trenutni korisnik ima ČITATI dozvola za.

Slično tome, @PostAuthorize pokreće se nakon izvršenja findById () metodu, pripazite da vratite samo ObavijestPoruka objekt ako ga ima trenutni korisnik ČITATI dopuštenje za to. Ako nije, sustav će izbaciti AccessDeniedException.

S druge strane, sustav pokreće @PreAuthorize napomena prije poziva na uštedjeti() metoda. Odlučit će gdje se odgovarajuća metoda smije izvršiti ili ne. Ako ne, AccessDeniedException bit će bačen.

4. Na djelu

Sad ćemo testirati sve te konfiguracije koristeći JUNIT. Koristit ćemo H2 baza podataka kako bi konfiguracija bila što jednostavnija.

Morat ćemo dodati:

 com.h2database h2 test probnog ispitivanja org.springframework org.springframework.security test probnog testiranja 

4.1. Scenarij

U ovom ćemo scenariju imati dva korisnika (upravitelj, hr) i jedna korisnička uloga (ROLE_EDITOR), tako naša acl_sid bit će:

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

Zatim, moramo se izjasniti ObavijestPoruka razred u acl_class. I tri slučaja ObavijestPoruka klasa će biti umetnuta u poruka_ sustava.

Štoviše, odgovarajući zapisi za ta 3 slučaja moraju se prijaviti u acl_object_identity:

INSERT INTO acl_class (id, class) VRIJEDNOSTI (1, 'com.baeldung.acl.persistence.entity.NoticeMessage'); INSERT INTO system_message (id, content) VRIJEDNOSTI (1, 'Poruka prve razine'), (2, 'Poruka druge razine'), (3, 'Poruka treće razine'); INSERT INTO acl_object_identity (id, object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) VRIJEDNOSTI (1, 1, 1, NULL, 3, 0), (2, 1, 2, NULL, 3, 0), (3, 1, 3, NULL, 3, 0);

U početku odobravamo ČITATI i PISATI dozvole za prvi objekt (id = 1) korisniku menadžer. U međuvremenu, svaki korisnik s RELE_EDITOR imat će ČITATI dozvolu za sva tri objekta, ali samo posjeduju PISATI dozvola za treći objekt (id = 3). Osim toga, korisnik hr imat će samo ČITATI dozvola za drugi objekt.

Ovdje, jer koristimo zadani Proljetni ACLBasePermission klasa za provjeru dopuštenja, vrijednost maske ČITATI dopuštenje će biti 1, a vrijednost maske PISATI dopuštenje će biti 2. Naši podaci u acl_entry bit će:

INSERT INTO acl_entry (id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) VRIJEDNOSTI (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 slučaj

Prije svega, pokušavamo nazvati pronađi sve metoda.

Kao naša konfiguracija, metoda vraća samo one ObavijestPoruka na kojem korisnik ima ČITATI dopuštenje.

Stoga očekujemo da popis rezultata sadrži samo prvu poruku:

@Test @WithMockUser (username = "manager") javna praznina givenUserManager_whenFindAllMessage_thenReturnFirstMessage () {Pojedinosti popisa = repo.findAll (); assertNotNull (detalji); assertEquals (1, details.size ()); assertEquals (FIRST_MESSAGE_ID, details.get (0) .getId ()); }

Tada pokušavamo nazvati istu metodu sa bilo kojim korisnikom koji ima ulogu - RELE_EDITOR. Imajte na umu da u ovom slučaju ovi korisnici imaju ČITATI dozvola za sva tri objekta.

Stoga očekujemo da će popis rezultata sadržavati sve tri poruke:

@Test @WithMockUser (role = {"EDITOR"}) javna praznina givenRoleEditor_whenFindAllMessage_thenReturn3Message () {Pojedinosti popisa = repo.findAll (); assertNotNull (detalji); assertEquals (3, details.size ()); }

Dalje, pomoću menadžer korisnik, pokušat ćemo prvu poruku dobiti po id-u i ažurirati njezin sadržaj - što bi sve trebalo raditi u redu:

@Test @WithMockUser (username = "manager") javna praznina givenUserManager_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 (editedFirstMessage); assertEquals (FIRST_MESSAGE_ID, editedFirstMessage.getId ()); assertEquals (EDITTED_CONTENT, editedFirstMessage.getContent ()); }

Ali ako bilo koji korisnik s RELE_EDITOR uloga ažurira sadržaj prve poruke - naš sustav će poslati AccessDeniedException:

@Test (očekuje se = AccessDeniedException.class) @WithMockUser (role = {"EDITOR"}) javna praznina givenRoleEditor_whenFind1stMessageByIdAndUpdateContent_thenFail () {NoticeMessage firstMessage = repo.findByIDAGE (FIDBYIDAGE; FIDBYIDAGE; FindByIDAGE; assertNotNull (firstMessage); assertEquals (FIRST_MESSAGE_ID, firstMessage.getId ()); firstMessage.setContent (EDITTED_CONTENT); repo.save (firstMessage); }

Slično tome, hr korisnik može pronaći drugu poruku po id-u, ali je neće uspjeti ažurirati:

@Test @WithMockUser (username = "hr") javna praznina givenUsernameHr_whenFindMessageById2_thenOK () {NoticeMessage secondMessage = repo.findById (SECOND_MESSAGE_ID); assertNotNull (secondMessage); assertEquals (SECOND_MESSAGE_ID, secondMessage.getId ()); } @Test (očekuje se = AccessDeniedException.class) @WithMockUser (korisničko ime = "hr") javna praznina givenUsernameHr_whenUpdateMessageWithId2_thenFail () {NoticeMessage secondMessage = new NoticeMessage (); secondMessage.setId (SECOND_MESSAGE_ID); secondMessage.setContent (EDITTED_CONTENT); repo.save (secondMessage); }

5. Zaključak

Prošli smo kroz osnovnu konfiguraciju i upotrebu Proljetni ACL u ovom članku.

Kao što znamo, Proljetni ACL potrebne specifične tablice za upravljanje objektom, principom / ovlaštenjem i postavljanjem dozvola. Sve interakcije s tim tablicama, posebno akcije ažuriranja, moraju proći AclService. Istražit ćemo ovu uslugu kao osnovno CRUD akcije u budućem članku.

Prema zadanim postavkama ograničeni smo na unaprijed definirano dopuštenje u BasePermission razred.

Konačno, provedba ovog vodiča može se naći na Githubu.