Bevezetés a Spring Method Security-be

1. Bemutatkozás

Egyszerűen fogalmazva: a Spring Security metódus szinten támogatja az engedélyezési szemantikát.

Jellemzően úgy biztosíthatjuk a szolgáltatási réteget, hogy például korlátozzuk, hogy mely szerepkörök képesek végrehajtani az adott módszert - és ezt dedikált módszer-szintű biztonsági teszt támogatással teszteljük.

Ebben a cikkben először áttekintjük néhány biztonsági kommentár használatát. Ezután a módszer biztonságának tesztelésére fogunk összpontosítani különböző stratégiákkal.

2. A Method Security engedélyezése

Először is, a Spring Method Security használatához hozzá kell adnunk a spring-security-config függőség:

 org.springframework.security spring-security-config 

A legújabb verziót a Maven Central oldalon találjuk.

Ha a Spring Boot-ot akarjuk használni, használhatjuk a spring-boot-starter-security függőség, amely magában foglalja spring-security-config:

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

Ismét a legújabb verzió megtalálható a Maven Central oldalon.

Ezután engedélyeznünk kell a globális Method Security-t:

@Configuration @EnableGlobalMethodSecurity (prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) public class MethodSecurityConfig kiterjeszti a GlobalMethodSecurityConfiguration {}
  • A prePostEnabled tulajdonság lehetővé teszi a Spring Security előre / után kommentárokat
  • A securedEnabled tulajdonság meghatározza, hogy a @ Biztonságos a jelölést engedélyezni kell
  • A jsr250Enabled tulajdonság lehetővé teszi számunkra a @RoleAllowed annotáció

A következő szakaszban többet fogunk megtudni ezekről a kommentárokról.

3. A Method Security alkalmazása

3.1. Használata @ Biztonságos Megjegyzés

A @ Biztonságos az annotációval megadható a szerepkörök listája a metóduson. Ennélfogva a felhasználó csak akkor férhet hozzá ehhez a módszerhez, ha rendelkezik a megadott szerepek legalább egyikével.

Határozzuk meg a getUsername módszer:

@Secured ("ROLE_VIEWER") public String getUsername () {SecurityContext securityContext = SecurityContextHolder.getContext (); return securityContext.getAuthentication (). getName (); }

Itt a @Secured („ROLE_VIEWER”) az annotáció csak azokat a felhasználókat határozza meg, akiknek szerepük van ROLE_VIEWER képesek végrehajtani a getUsername módszer.

Ezenkívül meghatározhatunk egy szereplistát a @ Biztonságos kommentár:

@Secured ({"ROLE_VIEWER", "ROLE_EDITOR"}) nyilvános logikai érték isValidUsername (String felhasználónév) {return userRoleRepository.isValidUsername (felhasználónév); }

Ebben az esetben a konfiguráció kimondja, hogy ha a felhasználónak van valamelyik ROLE_VIEWER vagy ROLE_EDITOR, az a felhasználó meghívhatja a isValidUsername módszer.

A @ Biztonságos az annotáció nem támogatja a Spring Expression Language (SpEL) nyelvet.

3.2. Használata @RoleAllowed Megjegyzés

A @RoleAllowed annotáció a JSR-250 megfelelő megjegyzései a @ Biztonságos annotáció.

Alapvetően használhatjuk a @RoleAllowed annotáció hasonló módon, mint @ Biztonságos. Így újra meghatározhatnánk getUsername és isValidUsername mód:

@RolesAllowed ("ROLE_VIEWER") nyilvános karakterlánc getUsername2 () {// ...} @RolesAllowed ({"ROLE_VIEWER", "ROLE_EDITOR"}) nyilvános logikai isValidUsername2 (karakterlánc felhasználónév) {// ...}

Hasonlóképpen csak az a felhasználó, akinek szerepe van ROLE_VIEWER végre tudja hajtani getUsername2.

A felhasználó ismét képes meghívni isValidUsername2 csak akkor, ha legalább az egyik ROLE_VIEWER vagy ROLER_EDITOR szerepek.

3.3. Használata @PreAuthorize és @PostAuthorize Megjegyzések

Mindkét @PreAuthorize és @PostAuthorize az annotációk kifejezésalapú hozzáférés-vezérlést biztosítanak. Ezért a predikátumokat a SpEL (Spring Expression Language) segítségével írhatjuk.

A @PreAuthorize az annotáció a metódus beírása előtt ellenőrzi az adott kifejezést, mivel a @PostAuthorize a kommentár a módszer végrehajtása után ellenőrzi, és megváltoztathatja az eredményt.

Most nyilvánítsuk ki a getUsernameInUpperCase módszer az alábbiak szerint:

@PreAuthorize ("hasRole ('ROLE_VIEWER')") nyilvános karakterlánc getUsernameInUpperCase () {return getUsername (). ToUpperCase (); }

A @PreAuthorize (“hasRole (’ ROLE_VIEWER ’)”) jelentése azonos @Secured („ROLE_VIEWER”) amelyet az előző szakaszban használtunk. Nyugodtan fedezhet fel további biztonsági kifejezéseket az előző cikkekben.

Következésképpen az annotáció @Secured ({"ROLE_VIEWER", "ROLE_EDITOR"}) helyettesíthető @PreAuthorize (“hasRole (’ ROLE_VIEWER ’) vagy hasRole (’ ROLE_EDITOR ’)”:

@PreAuthorize ("hasRole ('ROLE_VIEWER') vagy hasRole ('ROLE_EDITOR')") nyilvános logikai érték isValidUsername3 (karakterlánc felhasználónév) {// ...}

Ráadásul, valójában a metódus argumentumot használhatjuk a kifejezés részeként:

@PreAuthorize ("# username == authentication.principal.username") public String getMyRoles (String felhasználónév) {// ...}

Itt a felhasználó meghívhatja a getMyRoles metódus csak akkor, ha az argumentum értéke felhasználónév megegyezik a jelenlegi megbízó felhasználónevével.

Érdemes ezt megjegyezni @PreAuthorize kifejezéseket helyettesíthetjük @PostAuthorize azok.

Írjuk át getMyRoles:

@PostAuthorize ("# felhasználónév == hitelesítés.principal.username") nyilvános karakterlánc getMyRoles2 (karakterlánc felhasználónév) {// ...}

Az előző példában azonban az engedélyezés késleltetésre kerül a cél módszer végrehajtása után.

Ezenkívül a @PostAuthorize az annotáció lehetővé teszi a módszer eredményének elérését:

@PostAuthorize ("returnObject.username == authentication.principal.nickName") public CustomUser loadUserDetail (String felhasználónév) {return userRoleRepository.loadUserByUserName (felhasználónév); }

Ebben a példában a loadUserDetail metódus csak akkor hajtható végre sikeresen, ha a felhasználónév a visszaküldöttek CustomUser egyenlő a jelenlegi hitelesítési megbízóéval becenév.

Ebben a részben leginkább egyszerű tavaszi kifejezéseket használunk. Bonyolultabb forgatókönyvek esetén egyedi biztonsági kifejezéseket hozhatunk létre.

3.4. Használata @Előszűrő és @PostFilter Megjegyzések

A Spring Security biztosítja a @Előszűrő annotáció a argumentum szűrésére a metódus végrehajtása előtt:

@PreFilter ("filterObject! = Authentication.principal.username") public String joinUsernames (List usernames) {return usernames.stream (). Collect (Collectors.joining (";")); }

Ebben a példában az összes felhasználónevet összekapcsoljuk, kivéve azt, aki hitelesített.

Itt, kifejezésünkben a nevet használjuk filterObject hogy a gyűjtemény aktuális tárgyát ábrázolja.

Ha azonban a metódusnak több argumentuma van, amely gyűjteménytípus, akkor a filterTarget tulajdonság annak megadásához, hogy melyik argumentumot akarjuk szűrni:

@PreFilter (value = "filterObject! = Authentication.principal.username", filterTarget = "felhasználónév") public String joinUsernamesAndRoles (List felhasználónév, List szerepek) {return usernames.stream (). Collect (Collectors.joining (";") ) + ":" + szerepek.folyam (). gyűjt (Gyűjtők.csatlakozás (";")); }

Ezenkívül segítségével visszaszűrhetjük egy módszer visszaküldött gyűjteményét is @PostFilter annotáció:

@PostFilter ("filterObject! = Authentication.principal.username") public list getAllUsernamesExceptCurrent () {return userRoleRepository.getAllUsernames (); }

Ebben az esetben a név filterObject a visszaküldött gyűjtemény aktuális objektumára utal.

Ezzel a konfigurációval a Spring Security a visszaküldött listán végigvezetve eltávolítja a megbízó felhasználónevének megfelelő értékeket.

Spring Security - A @PreFilter és a @PostFilter cikk részletesebben leírja mindkét feliratozást.

3.5. Módszer Biztonsági metajegyzet

Jellemzően olyan helyzetbe kerülünk, amikor különböző módszereket védünk ugyanazon biztonsági konfigurációval.

Ebben az esetben definiálhatunk egy biztonsági meta-annotációt:

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

Ezután közvetlenül felhasználhatjuk az @IsViewer kommentárokat a módszerünk biztosításához:

@IsViewer nyilvános karakterlánc getUsername4 () {// ...}

A biztonsági meta-kommentárok remek ötlet, mert több szemantikát adnak hozzá, és elválasztják üzleti logikánkat a biztonsági kerettől.

3.6. Biztonsági kommentár osztály szinten

Ha azt találjuk, hogy egy osztályon belül minden módszerhez ugyanazt a biztonsági megjegyzést használjuk, akkor fontolóra vehetjük, hogy ezt a kommentárt osztályszintre tesszük:

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

A fenti példában a biztonsági szabályt hasRole (‘ROLE_ADMIN’) mindkettőre alkalmazni fogják getSystemYear és getSystemDate mód.

3.7. Több biztonsági kommentár egy módszerhez

Több módszerrel is használhatunk több biztonsági megjegyzést:

@PreAuthorize ("# felhasználónév == hitelesítés.principal.username") @PostAuthorize ("returnObject.username == hitelesítés.principal.nickName") nyilvános CustomUser securedLoadUserDetail (String felhasználónév) {return userRoleRepository.loadUserByUserName (felhasználónév) }

Ezért Spring ellenőrzi a jogosultságot a program végrehajtása előtt és után is securedLoadUserDetail módszer.

4. Fontos szempontok

Két szempontot szeretnénk emlékeztetni a módszer biztonságára:

  • Alapértelmezés szerint a tavaszi AOP proxy-t használják a módszer biztonságának alkalmazására - ha egy biztonságos A módszert ugyanazon osztályon belül más módszerrel hívnak meg, az A biztonságát teljesen figyelmen kívül hagyják. Ez azt jelenti, hogy az A módszer biztonsági ellenőrzés nélkül fog végrehajtani. Ugyanez vonatkozik a magán módszerekre is
  • Tavaszi SecurityContext szálkötésű - alapértelmezés szerint a biztonsági kontextus nem terjed át gyermekszálakra. További információ a Spring Security Context Propagation cikkében található

5. Vizsgálati módszer biztonsága

5.1. Konfiguráció

A Spring Security JUnit alkalmazással történő teszteléséhez szükségünk van a tavaszi biztonsági teszt függőség:

 org.springframework.security tavaszi-biztonsági teszt 

Nem kell meghatároznunk a függőségi verziót, mert a Spring Boot plugint használjuk. Megtalálhatjuk ennek a függőségnek a legújabb verzióját a Maven Central-tól.

Ezután állítsunk be egy egyszerű tavaszi integrációs tesztet a futó és a ApplicationContext konfiguráció:

@RunWith (SpringRunner.class) @ContextConfiguration nyilvános osztály MethodSecurityIntegrationTest {// ...}

5.2. Felhasználónév és szerepkörök tesztelése

Most, hogy konfigurációnk készen áll, próbáljuk meg kipróbálni getUsername módszer, amelyet a @Secured („ROLE_VIEWER”) annotáció:

@Secured ("ROLE_VIEWER") public String getUsername () {SecurityContext securityContext = SecurityContextHolder.getContext (); return securityContext.getAuthentication (). getName (); }

Mivel használjuk a @ Biztonságos annotáció itt megköveteli a felhasználó hitelesítését a módszer meghívásához. Ellenkező esetben kapunk egy AuthenticationCredentialsNotFoundException.

Ennélfogva, biztosítanunk kell egy felhasználót a biztonságos módszer teszteléséhez. Ennek elérése érdekében a vizsgálati módszert díszítjük @WithMockUser és adja meg a felhasználót és a szerepeket:

@Test @WithMockUser (felhasználónév = "john", szerepek = {"VIEWER"}) public void givenRoleViewer_whenCallGetUsername_thenReturnUsername () {String felhasználónév = userRoleService.getUsername (); assertEquals ("john", felhasználónév); }

Megadtunk egy hitelesített felhasználót, akinek a felhasználóneve János és akinek a szerepe ROLE_VIEWER. Ha nem adjuk meg a felhasználónév vagy szerep, az alapértelmezett felhasználónév van felhasználó és alapértelmezett szerep van ROLE_USER.

Ne feledje, hogy nem szükséges hozzáadni a SZEREP_ előtag, itt a Spring Security automatikusan hozzáadja ezt az előtagot.

Ha nem akarjuk, hogy ez az előtag legyen, akkor fontolóra vehetjük a használatát hatóság ahelyett szerep.

Nyilatkozjuk például a getUsernameInLowerCase módszer:

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

Kipróbálhatnánk, hogy a hatóságok segítségével:

@Test @WithMockUser (felhasználónév = "JOHN", hatóságok = {"SYS_ADMIN"}) public void givenAuthoritySysAdmin_whenCallGetUsernameLC_thenReturnUsername () {String felhasználónév = userRoleService.getUsernameInLowerCase (); assertEquals ("john", felhasználónév); }

Kényelmesen, ha ugyanazt a felhasználót akarjuk használni sok tesztesethez, kijelenthetjük a @WithMockUser jegyzet a teszt órán:

@RunWith (SpringRunner.class) @ContextConfiguration @WithMockUser (felhasználónév = "john", szerepek = {"VIEWER"}) nyilvános osztály MockUserAtClassLevelIntegrationTest {// ...}

Ha névtelen felhasználóként szeretnénk futtatni a tesztünket, használhatnánk a @WithAnonymousUser kommentár:

@Test (várható = AccessDeniedException.class) @WithAnonymousUser public void givenAnomynousUser_whenCallGetUsername_thenAccessDenied () {userRoleService.getUsername (); }

A fenti példában várunk egy AccessDeniedException mert a névtelen felhasználó nem kap szerepet ROLE_VIEWER vagy a hatóság SYS_ADMIN.

5.3. Tesztelés testreszabással UserDetailsService

A legtöbb alkalmazásnál szokás egy egyedi osztályt használni hitelesítési főként. Ebben az esetben az egyéni osztálynak végre kell hajtania a org.springframework.security.core.userdetails.UserDetails felület.

Ebben a cikkben kijelentjük a CustomUser osztály, amely kiterjeszti a UserDetails, ami org.springframework.security.core.userdetails.Felhasználó:

public class A CustomUser kiterjeszti a {private String nickName felhasználót; // getter és setter}

Vegyük vissza a példát a @PostAuthorize kommentár a 3. szakaszban:

@PostAuthorize ("returnObject.username == authentication.principal.nickName") public CustomUser loadUserDetail (String felhasználónév) {return userRoleRepository.loadUserByUserName (felhasználónév); }

Ebben az esetben a módszer csak akkor hajtható végre sikeresen, ha a felhasználónév a visszaküldöttek CustomUser egyenlő a jelenlegi hitelesítési megbízóéval becenév.

Ha kipróbálnánk ezt a módszert, megvalósítását nyújthatnánk UserDetailsService ami betöltheti a mi CustomUser felhasználónév alapján:

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

Itt a @WithUserDetails a felirat szerint az a-t fogjuk használni UserDetailsService a hitelesített felhasználó inicializálásához. A szolgáltatást a userDetailsServiceBeanName ingatlan. Ez UserDetailsService tesztelés céljából valódi megvalósítás vagy hamisítvány lehet.

Ezenkívül a szolgáltatás felhasználja az ingatlan értékét érték mint a betöltendő felhasználónév UserDetails.

Kényelmesen díszíthetünk a-val is @WithUserDetails osztályszintű kommentár, hasonlóan ahhoz, mint amit a @WithMockUser annotáció.

5.4. Tesztelés metajegyzetekkel

Gyakran előfordul, hogy ugyanazon felhasználót / szerepeket újra és újra felhasználjuk a különféle tesztekben.

Ezekben a helyzetekben kényelmes létrehozni a meta-annotáció.

Visszatérve az előző példára @WithMockUser (felhasználónév = "john", szerepek = {"VIEWER"}), kijelölhetjük a meta-annotációt:

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

Akkor egyszerűen használhatjuk @WithMockJohnViewer tesztünkben:

@Test @WithMockJohnViewer public void givenMockedJohnViewer_whenCallGetUsername_thenReturnUsername () {String felhasználónév = userRoleService.getUsername (); assertEquals ("john", felhasználónév); }

Hasonlóképpen használhatunk meta-annotációkat domainspecifikus felhasználók létrehozására @WithUserDetails.

6. Következtetés

Ebben az oktatóanyagban megvizsgáltuk a Method Security Spring Spring alkalmazásának különféle lehetőségeit.

Néhány technikán át is mentünk a módszer biztonságának egyszerű tesztelésén, és megtanultuk, hogyan lehet a csúfolt felhasználókat újból felhasználni különböző tesztekben.

Az oktatóanyag összes példája megtalálható a Github oldalon.