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.