Úvod do jarnej metódy zabezpečenia

1. Úvod

Jednoducho povedané, Spring Security podporuje sémantiku autorizácie na úrovni metódy.

Spravidla by sme mohli zabezpečiť našu vrstvu služieb napríklad obmedzením, ktoré roly sú schopné vykonať konkrétnu metódu, a otestovať ju pomocou vyhradenej podpory testov zabezpečenia na úrovni metód.

V tomto článku najskôr preskúmame použitie niektorých bezpečnostných anotácií. Potom sa zameriame na testovanie zabezpečenia našej metódy pomocou rôznych stratégií.

2. Povolenie zabezpečenia metódy

Najskôr, aby sme mohli používať Spring Method Security, musíme pridať spring-security-config závislosť:

 org.springframework.security spring-security-config 

Jeho najnovšiu verziu nájdeme na Maven Central.

Ak chceme použiť Spring Boot, môžeme použiť zabezpečenie jarného štartéra-štartéra závislosť, ktorá zahŕňa spring-security-config:

 org.springframework.boot spring-boot-starter-security 

Najnovšiu verziu opäť nájdete na serveri Maven Central.

Ďalej musíme povoliť globálne zabezpečenie metód:

@Configuration @EnableGlobalMethodSecurity (prePostEnabled = true, secureEnabled = true, jsr250Enabled = true) verejná trieda MethodSecurityConfig rozširuje GlobalMethodSecurityConfiguration {}
  • The prePostEnabled Táto vlastnosť umožňuje pre / post anotácie Spring Security
  • The secureEnabled vlastnosť určuje, či @ Zabezpečené anotácia by mala byť povolená
  • The jsr250Povolené majetok nám umožňuje používať @RoleAllowed anotácia

Viac o týchto anotáciách sa dozvieme v nasledujúcej časti.

3. Aplikácia zabezpečenia metódy

3.1. Použitím @ Zabezpečené Anotácia

The @ Zabezpečené anotácia sa používa na určenie zoznamu rolí v metóde. Používateľ teda môže k tejto metóde pristupovať, iba ak má aspoň jednu zo zadaných rolí.

Definujme a getUsername metóda:

@Secured ("ROLE_VIEWER") verejný reťazec getUsername () {SecurityContext securityContext = SecurityContextHolder.getContext (); vrátiť securityContext.getAuthentication (). getName (); }

Tu je @Secured („ROLE_VIEWER“) anotácia definuje, že iba používatelia, ktorí majú danú rolu ROLE_VIEWER sú schopní vykonať getUsername metóda.

Okrem toho môžeme definovať zoznam rolí v a @ Zabezpečené anotácia:

@Secured ({"ROLE_VIEWER", "ROLE_EDITOR"}) verejný boolean isValidUsername (reťazec používateľské meno) {návrat userRoleRepository.isValidUsername (používateľské meno); }

V tomto prípade konfigurácia uvádza, že ak má používateľ buď ROLE_VIEWER alebo ROLE_EDITOR, ktorý môže užívateľ vyvolať isValidUsername metóda.

The @ Zabezpečené anotácia nepodporuje Spring Expression Language (SpEL).

3.2. Použitím @RoleAllowed Anotácia

The @RoleAllowed anotácia je ekvivalentná anotácia JSR-250 k @ Zabezpečené anotácia.

V zásade môžeme použiť @RoleAllowed anotácia podobným spôsobom ako @ Zabezpečené. Takto by sme mohli nanovo definovať getUsername a isValidUsername metódy:

@RolesAllowed ("ROLE_VIEWER") verejný reťazec getUsername2 () {// ...} @RolesAllowed ({"ROLE_VIEWER", "ROLE_EDITOR"}) verejný boolean isValidUsername2 (meno používateľa reťazca) {// ...}

Podobne iba používateľ, ktorý má rolu ROLE_VIEWER môže vykonať getUsername2.

Užívateľ je opäť schopný vyvolať isValidUsername2 iba ak má aspoň jednu z ROLE_VIEWER alebo ROLER_EDITOR role.

3.3. Použitím @PreAuthorize a @PostAuthorize Anotácie

Oboje @PreAuthorize a @PostAuthorize anotácie poskytujú riadenie prístupu na základe výrazu. Preto je možné predikáty zapisovať pomocou jazyka SpEL (Spring Expression Language).

The @PreAuthorize anotácia pred zadaním metódy skontroluje daný výrazkeďže the @PostAuthorize anotácia to overí po vykonaní metódy a mohla by zmeniť výsledok.

Teraz vyhlásime a getUsernameInUpperCase metóda uvedená nižšie:

@PreAuthorize ("hasRole ('ROLE_VIEWER')") public String getUsernameInUpperCase () {return getUsername (). ToUpperCase (); }

The @PreAuthorize (“hasRole ('ROLE_VIEWER')“) má rovnaký význam ako @Secured („ROLE_VIEWER“) ktoré sme použili v predchádzajúcej časti. Ďalšie podrobnosti o bezpečnostných výrazoch nájdete v predchádzajúcich článkoch.

V dôsledku toho anotácia @Secured ({„ROLE_VIEWER“, „ROLE_EDITOR“}) možno nahradiť @PreAuthorize (“hasRole ('ROLE_VIEWER') alebo hasRole ('ROLE_EDITOR')"):

@PreAuthorize ("hasRole ('ROLE_VIEWER') alebo hasRole ('ROLE_EDITOR')") verejná logická hodnota isValidUsername3 (meno používateľa v reťazci) {// ...}

Navyše, môžeme vlastne použiť argument metódy ako súčasť výrazu:

@PreAuthorize ("# username == authentication.principal.username") verejný reťazec getMyRoles (používateľské meno reťazca) {// ...}

Tu môže užívateľ vyvolať getMyRoles metóda, iba ak je hodnota argumentu používateľské meno je rovnaké ako používateľské meno aktuálneho príkazcu.

Je potrebné si to uvedomiť @PreAuthorize výrazy môžu byť nahradené @PostAuthorize tie.

Prepíšme getMyRoles:

@PostAuthorize ("# username == authentication.principal.username") verejný reťazec getMyRoles2 (používateľské meno reťazca) {// ...}

V predchádzajúcom príklade by sa však autorizácia po vykonaní cieľovej metódy oneskorila.

Navyše, the @PostAuthorize anotácia poskytuje možnosť prístupu k výsledku metódy:

@PostAuthorize ("returnObject.username == authentication.principal.nickName") public CustomUser loadUserDetail (reťazec používateľské meno) {návrat userRoleRepository.loadUserByUserName (meno používateľa); }

V tomto príklade loadUserDetail metóda by sa úspešne vykonala, iba ak by používateľské meno vrátených CustomUser sa rovná aktuálnemu principálu autentifikácie prezývka.

V tejto časti väčšinou používame jednoduché jarné výrazy. Pre zložitejšie scenáre by sme mohli vytvárať vlastné bezpečnostné výrazy.

3.4. Použitím @PreFilter a @PostFilter Anotácie

Jarná bezpečnosť poskytuje @PreFilter anotácia na filtrovanie argumentu kolekcie pred vykonaním metódy:

@PreFilter ("filterObject! = Authentication.principal.username") public String joinUsernames (zoznam používateľských mien) {return usernames.stream (). Collect (Collectors.joining (";")); }

V tomto príklade spájame všetky používateľské mená okrem toho, ktorý je autentizovaný.

Tu, v našom prejave používame meno filterObject reprezentovať aktuálny objekt v zbierke.

Ak má však metóda viac ako jeden argument, ktorý je typom kolekcie, musíme použiť filterTarget vlastnosť určiť, ktorý argument chceme filtrovať:

@PreFilter (value = "filterObject! = Authentication.principal.username", filterTarget = "usernames") public String joinUsernamesAndRoles (zoznam používateľských mien, zoznam rolí) {return usernames.stream (). Collect (Collectors.joining (";") ) + ":" + role.stream (). collect (Collectors.joining (";")); }

Navyše, môžeme tiež filtrovať vrátenú kolekciu metódy pomocou @PostFilter anotácia:

@PostFilter ("filterObject! = Authentication.principal.username") verejný zoznam getAllUsernamesExceptCurrent () {návrat userRoleRepository.getAllUsernames (); }

V tomto prípade meno filterObject odkazuje na aktuálny objekt vo vrátenej kolekcii.

S touto konfiguráciou bude Spring Security prechádzať vráteným zoznamom a odstráni všetky hodnoty zodpovedajúce používateľskému menu principála.

Jarné zabezpečenie - článok @PreFilter a @PostFilter podrobnejšie popisuje obe anotácie.

3.5. Meta-anotácia metódy Zabezpečenie

Spravidla sa nachádzame v situácii, keď chránime rôzne metódy pomocou rovnakej konfigurácie zabezpečenia.

V tomto prípade môžeme definovať metaanotáciu zabezpečenia:

@Target (ElementType.METHOD) @Retention (RetentionPolicy.RUNTIME) @PreAuthorize ("hasRole ('VIEWER')") public @interface IsViewer {}

Ďalej môžeme na zabezpečenie našej metódy priamo použiť anotáciu @IsViewer:

@IsViewer verejný reťazec getUsername4 () {// ...}

Bezpečnostné metanotácie sú skvelým nápadom, pretože pridávajú viac sémantiky a oddeľujú našu obchodnú logiku od bezpečnostného rámca.

3.6. Bezpečnostná anotácia na úrovni triedy

Ak zistíme, že používame rovnakú bezpečnostnú anotáciu pre každú metódu v jednej triede, môžeme zvážiť uvedenie tejto anotácie na úrovni triedy:

@Service @PreAuthorize ("hasRole ('ROLE_ADMIN')") verejná trieda SystemService {public String getSystemYear () {// ...} public String getSystemDate () {// ...}}

Vo vyššie uvedenom príklade je to pravidlo zabezpečenia hasRole („ROLE_ADMIN“) sa bude vzťahovať na obidve getSystemYear a getSystemDate metódy.

3.7. Viacero bezpečnostných anotácií k metóde

Na jednu metódu môžeme použiť aj viac bezpečnostných anotácií:

@PreAuthorize ("# username == authentication.principal.username") @PostAuthorize ("returnObject.username == authentication.principal.nickName") verejný CustomUser secureLoadUserDetail (meno používateľa v reťazci) {návrat userRoleRepository.loadUserByUserName (meno používateľa); }

Preto spoločnosť Spring overí autorizáciu pred aj po vykonaní protokolu secureLoadUserDetail metóda.

4. Dôležité úvahy

Pokiaľ ide o zabezpečenie metódy, chceli by sme vám pripomenúť dva body:

  • V predvolenom nastavení sa na použitie zabezpečenia metódy používa jarné proxy AOP - ak je zabezpečená metóda A volaná inou metódou v tej istej triede, je bezpečnosť v A úplne ignorovaná. To znamená, že metóda A sa vykoná bez akejkoľvek bezpečnostnej kontroly. To isté platí pre súkromné ​​metódy
  • Jar SecurityContext je viazaný na vlákna - v predvolenom nastavení sa kontext zabezpečenia neprenáša na podradené vlákna. Viac informácií nájdete v článku Spring Security Context Propagation

5. Zabezpečenie testovacej metódy

5.1. Konfigurácia

Na otestovanie Spring Security s JUnit potrebujeme bezpečnostný test pružiny závislosť:

 org.springframework.security spring-security-test 

Nemusíme určovať verziu závislosti, pretože používame doplnok Spring Boot. Najnovšiu verziu tejto závislosti nájdeme na serveri Maven Central.

Ďalej nakonfigurujme jednoduchý test jarnej integrácie zadaním bežca a ApplicationContext konfigurácia:

@RunWith (SpringRunner.class) @ContextConfiguration verejná trieda MethodSecurityIntegrationTest {// ...}

5.2. Testovanie používateľského mena a rolí

Teraz, keď je naša konfigurácia pripravená, skúsime vyskúšať našu getUsername metóda, ktorú sme zabezpečili pomocou @Secured („ROLE_VIEWER“) anotácia:

@Secured ("ROLE_VIEWER") verejný reťazec getUsername () {SecurityContext securityContext = SecurityContextHolder.getContext (); vrátiť securityContext.getAuthentication (). getName (); }

Keďže používame @ Zabezpečené anotácia tu, na vyvolanie metódy sa vyžaduje autentifikácia používateľa. V opačnom prípade dostaneme AuthenticationCredentialsNotFoundException.

Teda potrebujeme poskytnúť používateľovi, aby otestoval našu zabezpečenú metódu. Aby sme to dosiahli, ozdobíme skúšobnú metódu @WithMockUser a poskytnúť používateľa a roly:

@Test @WithMockUser (username = "john", role = {"VIEWER"}) public void givenRoleViewer_whenCallGetUsername_thenReturnUsername () {String userName = userRoleService.getUsername (); assertEquals ("john", meno používateľa); }

Poskytli sme autentizovaného používateľa, ktorého používateľské meno je john a ktorej rola je ROLE_VIEWER. Ak neurčíme používateľské meno alebo úlohu, predvolené používateľské meno je používateľ a predvolené úlohu je ROLE_USER.

Upozorňujeme, že nie je potrebné pridávať ROLE_ predpona tu, Spring Security pridá túto predponu automaticky.

Ak nechceme mať túto predponu, môžeme zvážiť použitie orgánu namiesto úlohu.

Vyhlásime napríklad a getUsernameInLowerCase metóda:

@PreAuthorize ("hasAuthority ('SYS_ADMIN')") public String getUsernameLC () {return getUsername (). ToLowerCase (); }

Mohli by sme to otestovať pomocou orgánov:

@Test @WithMockUser (username = "JOHN", autority = {"SYS_ADMIN"}) public void givenAuthoritySysAdmin_whenCallGetUsernameLC_thenReturnUsername () {String username = userRoleService.getUsernameInLowerCase (); assertEquals ("john", meno používateľa); }

Pohodlne ak chceme použiť rovnakého používateľa pre mnoho testovacích prípadov, môžeme deklarovať @WithMockUser anotácia na testovacej hodine:

@RunWith (SpringRunner.class) @ContextConfiguration @WithMockUser (username = "john", role = {"VIEWER"}) verejná trieda MockUserAtClassLevelIntegrationTest {// ...}

Ak by sme chceli spustiť náš test ako anonymný užívateľ, mohli by sme použiť @WithAnonymousUser anotácia:

@Test (očakáva sa = AccessDeniedException.class) @WithAnonymousUser verejné neplatné danéAnomynousUser_whenCallGetUsername_thenAccessDenied () {userRoleService.getUsername (); }

Vo vyššie uvedenom príklade očakávame znak AccessDeniedException pretože anonymný užívateľ nemá túto rolu ROLE_VIEWER alebo orgán SYS_ADMIN.

5.3. Testovanie s vlastným UserDetailsService

Pre väčšinu aplikácií je bežné používať vlastnú triedu ako principál autentifikácie. V tomto prípade musí vlastná trieda implementovať org.springframework.security.core.userdetails.UserDetails rozhranie.

V tomto článku deklarujeme a CustomUser triedy, ktorá rozširuje existujúcu implementáciu UserDetails, ktorý je org.springframework.security.core.userdetails.Užívateľ:

verejná trieda CustomUser rozširuje používateľa {private String nickName; // getter a setter}

Vráťme si príklad s @PostAuthorize anotácia v časti 3:

@PostAuthorize ("returnObject.username == authentication.principal.nickName") public CustomUser loadUserDetail (reťazec používateľské meno) {návrat userRoleRepository.loadUserByUserName (meno používateľa); }

V takom prípade by sa metóda úspešne spustila, iba ak by používateľské meno vrátených CustomUser sa rovná aktuálnemu principálu autentifikácie prezývka.

Keby sme chceli túto metódu otestovať, mohli by sme zabezpečiť implementáciu UserDetailsService ktoré by mohli načítať naše CustomUser na základe užívateľského mena:

@Test @WithUserDetails (value = "john", userDetailsServiceBeanName = "userDetailService") public void whenJohn_callLoadUserDetail_thenOK () {CustomUser user = userService.loadUserDetail ("jane"); assertEquals ("jane", user.getNickName ()); }

Tu je @WithUserDetails anotácia uvádza, že použijeme a UserDetailsService inicializovať nášho overeného používateľa. Na službu sa odvoláva userDetailsServiceBeanName nehnuteľnosť. Toto UserDetailsService môže ísť o skutočnú implementáciu alebo faloš na testovacie účely.

Okrem toho bude služba využívať hodnotu majetku hodnotu ako užívateľské meno na načítanie UserDetails.

Pohodlne môžeme ozdobiť aj a @WithUserDetails anotácie na úrovni triedy, podobne ako sme to robili s dokumentom @WithMockUser anotácia.

5.4. Testovanie s metaznačkami

Často sa pri rôznych testoch opakovane stretávame s tým istým používateľom / rolami.

Pre tieto situácie je vhodné vytvoriť a meta-anotácia.

Beriem späť predchádzajúci príklad @WithMockUser (používateľské meno = „john“, role = {„VIEWER“}), môžeme deklarovať metaanotáciu ako:

@Retention (RetentionPolicy.RUNTIME) @WithMockUser (value = "john", role = "VIEWER") public @interface WithMockJohnViewer {}

Potom môžeme jednoducho použiť @WithMockJohnViewer v našom teste:

@Test @WithMockJohnViewer public void givenMockedJohnViewer_whenCallGetUsername_thenReturnUsername () {String userName = userRoleService.getUsername (); assertEquals ("john", meno používateľa); }

Rovnako môžeme použiť metanotácie na vytvorenie používateľov špecifických pre doménu pomocou @WithUserDetails.

6. Záver

V tomto tutoriáli sme preskúmali rôzne možnosti použitia metódy Zabezpečenie na jar v Zabezpečení.

Tiež sme prešli niekoľkými technikami, ako ľahko testovať zabezpečenie metód, a naučili sme sa, ako opakovane používať vysmievaných používateľov v rôznych testoch.

Všetky príklady tohto tutoriálu nájdete na Githube.


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