Uvod u sigurnost metoda Spring

1. Uvod

Jednostavno rečeno, Spring Security podržava semantiku autorizacije na razini metode.

Tipično bismo mogli zaštititi svoj servisni sloj, na primjer, ograničavajući koje su uloge sposobne izvršiti određenu metodu - i testirati je pomoću posebne podrške za sigurnosni test na razini metode.

U ovom ćemo članku najprije pregledati upotrebu nekih sigurnosnih napomena. Zatim ćemo se usredotočiti na testiranje sigurnosti naše metode s različitim strategijama.

2. Omogućavanje zaštite metoda

Prije svega, da bismo koristili Spring Method Security, moramo dodati opruga-sigurnost-konfiguracija ovisnost:

 org.springframework.security spring-security-config 

Njegovu najnoviju verziju možemo pronaći na Maven Central.

Ako želimo koristiti Spring Boot, možemo koristiti spring-boot-starter-sigurnost ovisnost koja uključuje opruga-sigurnost-konfiguracija:

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

Opet, najnoviju verziju možete pronaći na Maven Central.

Dalje, moramo omogućiti globalnu sigurnost metode:

@Configuration @EnableGlobalMethodSecurity (prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) javna klasa MethodSecurityConfig proširuje GlobalMethodSecurityConfiguration {}
  • The prePostEnabled svojstvo omogućava bilješke Spring / post bilješke Spring Security
  • The osiguranOmogućen svojstvo određuje da li @Secured treba omogućiti napomenu
  • The jsr250Omogućeno svojstvo nam omogućuje upotrebu @RoleAllowed bilješka

Više o tim bilješkama istražit ćemo u sljedećem odjeljku.

3. Primjena metode zaštite

3.1. Koristeći @Secured Bilješka

The @Secured napomena se koristi za određivanje popisa uloga u metodi. Dakle, korisnik može pristupiti toj metodi samo ako ima barem jednu od navedenih uloga.

Definirajmo a getUsername metoda:

@Secured ("ROLE_VIEWER") javni niz getUsername () {SecurityContext securityContext = SecurityContextHolder.getContext (); vratiti securityContext.getAuthentication (). getName (); }

Evo, @Secured (“ROLE_VIEWER”) napomena definira da samo korisnici koji imaju ulogu ROLE_VIEWER mogu izvršiti getUsername metoda.

Osim toga, možemo definirati popis uloga u a @Secured napomena:

@Secured ({"ROLE_VIEWER", "ROLE_EDITOR"}) javno boolean isValidUsername (String korisničko ime) {return userRoleRepository.isValidUsername (korisničko ime); }

U ovom slučaju, konfiguracija navodi da ako korisnik ima bilo koji ROLE_VIEWER ili RELE_EDITOR, taj korisnik može pozvati isValidUsername metoda.

The @Secured napomena ne podržava Spring Expression Language (SpEL).

3.2. Koristeći @RoleAllowed Bilješka

The @RoleAllowed napomena je ekvivalentna napomena JSR-250 za @Secured bilješka.

U osnovi možemo koristiti @RoleAllowed bilješka na sličan način kao @Secured. Stoga bismo mogli redefinirati getUsername i isValidUsername metode:

@RolesAllowed ("ROLE_VIEWER") javni niz getUsername2 () {// ...} @RolesAllowed ({"ROLE_VIEWER", "ROLE_EDITOR"}) javni boolean isValidUsername2 (korisničko ime niza) {// ...}

Slično tome, samo korisnik koji ima ulogu ROLE_VIEWER može izvršiti getUsername2.

Opet, korisnik se može pozvati isValidUsername2 samo ako ima barem jedan od ROLE_VIEWER ili UREDNIK ROLERA uloge.

3.3. Koristeći @PreAuthorize i @PostAuthorize Bilješke

Oba @PreAuthorize i @PostAuthorize napomene pružaju kontrolu pristupa zasnovanu na izrazima. Stoga se predikati mogu pisati pomoću SpEL (Spring Expression Language).

The @PreAuthorize anotacija provjerava zadani izraz prije unosa metode, dok, the @PostAuthorize anotacija ga provjerava nakon izvršenja metode i može promijeniti rezultat.

Sada, proglasimo a getUsernameInUpperCase metoda kao u nastavku:

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

The @PreAuthorize (“hasRole (‘ ROLE_VIEWER ’)”) ima isto značenje kao @Secured (“ROLE_VIEWER”) koje smo koristili u prethodnom odjeljku. Slobodno otkrijte više detalja o sigurnosnim izrazima u prethodnim člancima.

Slijedom toga, bilješka @Secured ({"ROLE_VIEWER", "ROLE_EDITOR"}) može se zamijeniti sa @PreAuthorize (“hasRole (‘ ROLE_VIEWER ’) ili hasRole (‘ ROLE_EDITOR ’)”):

@PreAuthorize ("hasRole ('ROLE_VIEWER') ili hasRole ('ROLE_EDITOR')") javni boolean isValidUsername3 (korisničko ime niza) {// ...}

Štoviše, zapravo možemo koristiti argument metode kao dio izraza:

@PreAuthorize ("# username == authentication.principal.username") javni String getMyRoles (Korisničko ime niza) {// ...}

Ovdje se korisnik može pozvati na getMyRoles metoda samo ako je vrijednost argumenta Korisničko ime je isto kao trenutno korisničko ime korisnika.

Vrijedno je to napomenuti @PreAuthorize izrazi se mogu zamijeniti sa @PostAuthorize one.

Prepišimo getMyRoles:

@PostAuthorize ("# username == authentication.principal.username") javni String getMyRoles2 (Korisničko ime niza) {// ...}

Međutim, u prethodnom primjeru autorizacija će se odgoditi nakon izvršavanja ciljne metode.

Dodatno, the @PostAuthorize napomena pruža mogućnost pristupa rezultatu metode:

@PostAuthorize ("returnObject.username == authentication.principal.nickName") javni CustomUser loadUserDetail (String korisničko ime) {return userRoleRepository.loadUserByUserName (korisničko ime); }

U ovom primjeru, loadUserDetail metoda uspješno bi se izvršila samo ako Korisničko ime vraćenih CustomUser jednak je trenutnom principu autentifikacije nadimak.

U ovom odjeljku uglavnom koristimo jednostavne proljetne izraze. Za složenije scenarije mogli bismo stvoriti prilagođene sigurnosne izraze.

3.4. Koristeći @PreFilter i @PostFilter Bilješke

Spring Security pruža @PreFilter napomena za filtriranje argumenta kolekcije prije izvođenja metode:

@PreFilter ("filterObject! = Authentication.principal.username") javni String joinUsernames (Popis korisničkih imena) {return usernames.stream (). Collect (Collectors.joining (";")); }

U ovom primjeru pridružujemo se svim korisničkim imenima, osim onoga koji je ovjeren.

Ovdje, u našem izrazu koristimo ime filterObject za predstavljanje trenutnog objekta u zbirci.

Međutim, ako metoda ima više argumenata koji je vrsta zbirke, trebamo koristiti filterTarget svojstvo za specificiranje argumenta koji želimo filtrirati:

@PreFilter (value = "filterObject! = Authentication.principal.username", filterTarget = "usernames") javni String joinUsernamesAndRoles (Popis korisničkih imena, Popis uloga) {return usernames.stream (). Collect (Collectors.joining (";") ) + ":" + role.stream (). collect (Collectors.joining (";")); }

Dodatno, također možemo filtrirati vraćenu zbirku metode pomoću @PostFilter bilješka:

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

U ovom slučaju, ime filterObject odnosi se na trenutni objekt u vraćenoj zbirci.

S tom konfiguracijom, Spring Security će prelistavati vraćeni popis i ukloniti svaku vrijednost koja se podudara s korisnikovim korisničkim imenom.

Spring Security - Članak @PreFilter i @PostFilter detaljnije opisuje obje napomene.

3.5. Meta-bilješka o sigurnosti metode

Obično smo se našli u situaciji u kojoj štitimo različite metode koristeći istu sigurnosnu konfiguraciju.

U ovom slučaju možemo definirati sigurnosnu meta-napomenu:

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

Dalje, možemo izravno upotrijebiti bilješku @IsViewer kako bismo osigurali našu metodu:

@IsViewer javni niz getUsername4 () {// ...}

Sigurnosne meta-bilješke izvrsna su ideja jer dodaju više semantike i razdvajaju našu poslovnu logiku od sigurnosnog okvira.

3.6. Sigurnosna napomena na razini predavanja

Ako se utvrdimo da koristimo istu sigurnosnu bilješku za svaku metodu unutar jedne klase, možemo razmisliti o stavljanju te bilješke na razinu klase:

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

U gornjem primjeru, pravilo sigurnosti hasRole ('ROLE_ADMIN') primjenjivat će se na oboje getSystemYear i getSystemDate metode.

3.7. Više sigurnosnih bilješki o metodi

Također možemo koristiti više sigurnosnih bilješki na jednoj metodi:

@PreAuthorize ("# username == authentication.principal.username") @PostAuthorize ("returnObject.username == authentication.principal.nickName") javni CustomUser securedLoadUserDetail (String username) {return userRoleRepository.loadUserByUserName; }

Stoga će Spring provjeriti autorizaciju i prije i nakon izvršavanja securedLoadUserDetail metoda.

4. Važna razmatranja

Dvije su točke koje bismo podsjetili u vezi sa sigurnošću metode:

  • Prema zadanim se postavkama proljetni AOP proxy koristi za primjenu sigurnosti metode - ako se zaštićena metoda A pozove drugom metodom unutar iste klase, sigurnost u A se uopće zanemaruje. To znači da će se metoda A izvršiti bez ikakve sigurnosne provjere. Isto se odnosi i na privatne metode
  • Proljeće SecurityContext je vezan za nit - prema zadanim postavkama, sigurnosni kontekst se ne prenosi na podređene niti. Za više informacija možemo se obratiti članku Proširivanje proljetnog sigurnosnog konteksta

5. Sigurnost metode ispitivanja

5.1. Konfiguracija

Da bismo testirali Spring Security s JUnit-om, potreban nam je test opruge-sigurnost ovisnost:

 org.springframework.security proljeće-test sigurnosti 

Ne trebamo navesti verziju ovisnosti jer koristimo dodatak Spring Boot. Najnoviju verziju ove ovisnosti možemo pronaći na Maven Central.

Dalje, konfigurirajmo jednostavan test integracije proljeća navodeći trkač i ApplicationContext konfiguracija:

@RunWith (SpringRunner.class) @ContextConfiguration javna klasa MethodSecurityIntegrationTest {// ...}

5.2. Testiranje korisničkog imena i uloga

Sada kada je naša konfiguracija spremna, pokušajmo testirati našu getUsername metodu koju smo osigurali pomoću @Secured (“ROLE_VIEWER”) bilješka:

@Secured ("ROLE_VIEWER") javni niz getUsername () {SecurityContext securityContext = SecurityContextHolder.getContext (); vratiti securityContext.getAuthentication (). getName (); }

Budući da koristimo @Secured ovdje, ona zahtijeva autentifikaciju korisnika za pozivanje metode. Inače ćemo dobiti AuthenticationCredentialsNotFoundException.

Stoga, moramo pružiti korisniku da testira našu sigurnu metodu. Da bismo to postigli, testnu metodu ukrašavamo s @WithMockUser i pružiti korisniku i ulogama:

@Test @WithMockUser (username = "john", role = {"VIEWER"}) javna praznina givenRoleViewer_whenCallGetUsername_thenReturnUsername () {String userName = userRoleService.getUsername (); assertEquals ("john", korisničko ime); }

Dali smo autentificiranog korisnika čije je korisničko ime Ivan a čija je uloga ROLE_VIEWER. Ako ne odredimo Korisničko ime ili uloga, zadani Korisničko ime je korisnik i zadani uloga je ROLE_USER.

Imajte na umu da nije potrebno dodavati ULOGA_ prefiksa ovdje, Spring Security automatski će ga dodati.

Ako ne želimo imati taj prefiks, možemo razmotriti upotrebu autoritet umjesto uloga.

Na primjer, proglasimo a getUsernameInLowerCase metoda:

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

To bismo mogli testirati koristeći ovlaštenja:

@Test @WithMockUser (korisničko ime = "JOHN", powers = {"SYS_ADMIN"}) javna praznina givenAuthoritySysAdmin_whenCallGetUsernameLC_thenReturnUsername () {String username = userRoleService.getUsernameInLowerCase (); assertEquals ("john", korisničko ime); }

Prikladno, ako želimo koristiti istog korisnika za mnoge test slučajeve, možemo proglasiti @WithMockUser bilješka na ispitnom satu:

@RunWith (SpringRunner.class) @ContextConfiguration @WithMockUser (korisničko ime = "john", role = {"VIEWER"}) javna klasa MockUserAtClassLevelIntegrationTest {// ...}

Ako bismo htjeli pokrenuti test kao anonimni korisnik, mogli bismo koristiti @WithAnonymousUser napomena:

@Test (očekuje se = AccessDeniedException.class) @WithAnonymousUser javna praznina givenAnomynousUser_whenCallGetUsername_thenAccessDenied () {userRoleService.getUsername (); }

U gornjem primjeru očekujemo AccessDeniedException jer anonimnom korisniku nije dodijeljena uloga ROLE_VIEWER ili autoritet SYS_ADMIN.

5.3. Testiranje prilagođenim UserDetailsService

Za većinu aplikacija uobičajeno je koristiti prilagođenu klasu kao principa provjere autentičnosti. U ovom slučaju, prilagođena klasa mora implementirati org.springframework.security.core.userdetails.Pojedinosti o korisniku sučelje.

U ovom članku izjavljujemo a CustomUser klasa koja proširuje postojeću implementaciju Pojedinosti o korisniku, koji je org.springframework.security.core.userdetails.Korisnik:

javna klasa CustomUser proširuje User {private String nickName; // dobivač i postavljač}

Vratimo primjer s @PostAuthorize napomena u odjeljku 3:

@PostAuthorize ("returnObject.username == authentication.principal.nickName") javni CustomUser loadUserDetail (korisničko ime niza) {return userRoleRepository.loadUserByUserName (korisničko ime); }

U ovom slučaju, metoda bi se uspješno izvršila samo ako Korisničko ime vraćenih CustomUser jednak je trenutnom principu autentifikacije nadimak.

Kad bismo htjeli isprobati tu metodu, mogli bismo osigurati provedbu UserDetailsService koji bi mogao učitati naš CustomUser na osnovu korisničkog imena:

@Test @WithUserDetails (value = "john", userDetailsServiceBeanName = "userDetailService") javna praznina kadaJohn_callLoadUserDetail_thenOK () {CustomUser user = userService.loadUserDetail ("jane"); assertEquals ("jane", user.getNickName ()); }

Evo, @WithUserDetails u napomeni stoji da ćemo koristiti UserDetailsService za inicijalizaciju našeg autentificiranog korisnika. Uslugu upućuje userDetailsServiceBeanName imovine. Ovaj UserDetailsService može biti stvarna implementacija ili lažna za potrebe testiranja.

Uz to, usluga će koristiti vrijednost imovine vrijednost kao korisničko ime za učitavanje Pojedinosti o korisniku.

Prikladno, možemo ukrasiti i a @WithUserDetails napomena na razini razreda, slično onome što smo radili s @WithMockUser bilješka.

5.4. Testiranje s meta bilješkama

Često se zateknemo da iznova i iznova koristimo iste korisnike / uloge u raznim testovima.

U tim je situacijama prikladno stvoriti a meta-anotacija.

Vraćajući prethodni primjer @WithMockUser (korisničko ime = ”john”, uloge = {“VIEWER”}), možemo metanotaciju proglasiti kao:

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

Tada možemo jednostavno koristiti @WithMockJohnViewer u našem testu:

@Test @WithMockJohnViewer javna praznina givenMockedJohnViewer_whenCallGetUsername_thenReturnUsername () {String userName = userRoleService.getUsername (); assertEquals ("john", korisničko ime); }

Isto tako, meta-bilješke možemo koristiti za stvaranje korisnika specifičnih za domenu koji koriste @WithUserDetails.

6. Zaključak

U ovom smo uputstvu istražili razne mogućnosti korištenja sigurnosti metoda u Spring Security.

Također smo prošli nekoliko tehnika za lako testiranje sigurnosti metoda i naučili kako ponovno koristiti izrugivane korisnike u različitim testovima.

Svi primjeri ovog vodiča mogu se naći na Githubu.