Introduksjon til Spring Method Security

1. Introduksjon

Enkelt sagt, Spring Security støtter autorisasjonssemantikk på metodenivå.

Vanligvis kan vi sikre tjenestelaget vårt ved for eksempel å begrense hvilke roller som er i stand til å utføre en bestemt metode - og teste det ved hjelp av dedikert støtte for sikkerhetstest på metodenivå.

I denne artikkelen skal vi først gjennomgå bruken av noen sikkerhetsanmerkninger. Deretter vil vi fokusere på å teste metodesikkerheten vår med forskjellige strategier.

2. Aktivere metodesikkerhet

Først og fremst, for å bruke Spring Method Security, må vi legge til vår-sikkerhet-config avhengighet:

 org.springframework.security spring-security-config 

Vi finner den nyeste versjonen på Maven Central.

Hvis vi vil bruke Spring Boot, kan vi bruke vår-boot-starter-sikkerhet avhengighet som inkluderer vår-sikkerhet-config:

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

Igjen, den siste versjonen finner du på Maven Central.

Deretter må vi aktivere global metodesikkerhet:

@Configuration @EnableGlobalMethodSecurity (prePostEnabled = true, secureEnabled = true, jsr250Enabled = true) offentlig klasse MethodSecurityConfig utvider GlobalMethodSecurityConfiguration {}
  • De prePostEnabled egenskap muliggjør Spring Security-kommentarer før / etter innlegg
  • De secureEnabled eiendommen avgjør om @Sikret merknader bør være aktivert
  • De jsr250 Aktivert eiendommen tillater oss å bruke @RoleAllowed kommentar

Vi vil utforske mer om disse kommentarene i neste avsnitt.

3. Bruke metodesikkerhet

3.1. Ved hjelp av @Sikret Kommentar

De @Sikret kommentar brukes til å spesifisere en liste over roller på en metode. Derfor kan en bruker bare få tilgang til den metoden hvis hun har minst en av de angitte rollene.

La oss definere en getUsername metode:

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

Her, den @Secured (“ROLE_VIEWER”) kommentar definerer at bare brukere som har rollen ROLE_VIEWER er i stand til å utføre getUsername metode.

Dessuten kan vi definere en liste over roller i en @Sikret kommentar:

@Secured ({"ROLE_VIEWER", "ROLE_EDITOR"}) offentlig boolsk isValidUsername (String brukernavn) {return userRoleRepository.isValidUsername (brukernavn); }

I dette tilfellet angir konfigurasjonen at hvis en bruker har en av dem ROLE_VIEWER eller ROLE_EDITOR, kan brukeren påberope seg isValidUsername metode.

De @Sikret kommentar støtter ikke Spring Expressionsspråk (SpEL).

3.2. Ved hjelp av @RoleAllowed Kommentar

De @RoleAllowed merknad er JSR-250s tilsvarende merknad av @Sikret kommentar.

I utgangspunktet kan vi bruke @RoleAllowed kommentar på en lignende måte som @Sikret. Dermed kunne vi definere på nytt getUsername og isValidUsername metoder:

@RolesAllowed ("ROLE_VIEWER") offentlig streng getUsername2 () {// ...} @RolesAllowed ({"ROLE_VIEWER", "ROLE_EDITOR"}) offentlig boolsk isValidUsername2 (streng brukernavn) {// ...}

Tilsvarende bare brukeren som har rolle ROLE_VIEWER kan utføre getUsername2.

Igjen kan en bruker påberope seg isValidUsername2 bare hvis hun har minst en av ROLE_VIEWER eller ROLER_EDITOR roller.

3.3. Ved hjelp av @PreAuthorize og @PostAuthorize Kommentarer

Både @PreAuthorize og @PostAuthorize merknader gir uttrykksbasert tilgangskontroll. Derfor kan predikater skrives med SpEL (Spring Expression Language).

De @PreAuthorize merknad sjekker det gitte uttrykket før du går inn i metoden, mens, de @PostAuthorize kommentar verifiserer det etter utførelsen av metoden og kan endre resultatet.

La oss nå erklære et getUsernameInUpperCase metoden som nedenfor:

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

De @PreAuthorize (“hasRole (‘ ROLE_VIEWER ') ”) har samme betydning som @Secured (“ROLE_VIEWER”) som vi brukte i forrige avsnitt. Oppdag gjerne flere sikkerhetsuttrykk detaljer i tidligere artikler.

Følgelig merknaden @Secured ({“ROLE_VIEWER”, “ROLE_EDITOR”}) kan erstattes med @PreAuthorize (“hasRole (‘ ROLE_VIEWER ') eller hasRole (‘ROLE_EDITOR')”):

@PreAuthorize ("hasRole ('ROLE_VIEWER') eller hasRole ('ROLE_EDITOR')") offentlig boolsk isValidUsername3 (String brukernavn) {// ...}

Videre vi kan faktisk bruke metodeargumentet som en del av uttrykket:

@PreAuthorize ("# brukernavn == autentisering.principal.username") offentlig streng getMyRoles (streng brukernavn) {// ...}

Her kan en bruker påberope seg getMyRoles metode bare hvis verdien av argumentet brukernavn er det samme som gjeldende rektor brukernavn.

Det er verdt å merke seg det @PreAuthorize uttrykk kan erstattes av @PostAuthorize de.

La oss skrive om getMyRoles:

@PostAuthorize ("# brukernavn == autentisering.principal.username") offentlig streng getMyRoles2 (streng brukernavn) {// ...}

I forrige eksempel vil autorisasjonen imidlertid bli forsinket etter utførelsen av målmetoden.

I tillegg de @PostAuthorize kommentar gir muligheten til å få tilgang til metoderesultatet:

@PostAuthorize ("returnObject.username == authentication.principal.nickName") offentlig CustomUser loadUserDetail (streng brukernavn) {return userRoleRepository.loadUserByUserName (brukernavn); }

I dette eksemplet er loadUserDetail metoden ville bare kjøres vellykket hvis brukernavn av de returnerte CustomUser er lik gjeldende autentiseringshoved kallenavn.

I denne delen bruker vi stort sett enkle våruttrykk. For mer komplekse scenarier kan vi lage tilpassede sikkerhetsuttrykk.

3.4. Ved hjelp av @FreFilter og @PostFilter Kommentarer

Spring Security gir @FreFilter kommentar for å filtrere et samlingsargument før metoden kjøres:

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

I dette eksemplet blir vi med i alle brukernavn bortsett fra den som er autentisert.

Her, i vårt uttrykk bruker vi navnet filterObject for å representere det nåværende objektet i samlingen.

Imidlertid, hvis metoden har mer enn ett argument som er en samlingstype, må vi bruke filterTarget egenskap for å spesifisere hvilket argument vi vil filtrere:

@PreFilter (value = "filterObject! = Authentication.principal.username", filterTarget = "usernames") public String joinUsernamesAndRoles (List usernames, Listroller) {return usernames.stream (). Collect (Collectors.joining (";") ) + ":" + roller.stream (). samle (Collectors.joining (";")); }

I tillegg Vi kan også filtrere den returnerte samlingen av en metode ved hjelp av @PostFilter kommentar:

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

I dette tilfellet navnet filterObject refererer til gjeldende objekt i den returnerte samlingen.

Med den konfigurasjonen vil Spring Security gjenta gjennom den returnerte listen og fjerne enhver verdi som samsvarer med rektorens brukernavn.

Spring Security - @PreFilter og @PostFilter artikkelen beskriver begge kommentarene mer detaljert.

3.5. Metodesikkerhet Meta-kommentar

Vi befinner oss vanligvis i en situasjon der vi beskytter forskjellige metoder ved hjelp av samme sikkerhetskonfigurasjon.

I dette tilfellet kan vi definere en sikkerhetsmetanotering:

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

Deretter kan vi bruke @IsViewer-merknaden direkte for å sikre metoden vår:

@IsViewer public String getUsername4 () {// ...}

Sikkerhetsmetanoteringer er en god idé fordi de legger til mer semantikk og kobler fra vår forretningslogikk fra sikkerhetsrammen.

3.6. Sikkerhetsnotering på klassenivå

Hvis vi bruker den samme sikkerhetsanmerkningen for hver metode innen en klasse, kan vi vurdere å sette den merknaden på klassenivå:

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

I eksemplet ovenfor, sikkerhetsregelen hasRole (‘ROLE_ADMIN’) vil bli brukt på begge deler getSystemYear og getSystemDate metoder.

3.7. Flere sikkerhetsmerknader om en metode

Vi kan også bruke flere sikkerhetsanmerkninger på en metode:

@PreAuthorize ("# brukernavn == autentisering.principal.brukernavn") @ PostAuthorize ("returnObject.username == autentisering.principal.nicknavn") offentlig CustomUser secureLoadUserDetail (String brukernavn) {return userRoleRepository.loadUserByUserName (brukernavn); }

Derfor vil Spring verifisere autorisasjon både før og etter gjennomføringen av secureLoadUserDetail metode.

4. Viktige hensyn

Det er to punkter vi vil minne om metodesikkerhet:

  • Som standard brukes Spring AOP-proxying for å bruke metodesikkerhet - hvis en sikret metode A kalles av en annen metode innen samme klasse, ignoreres sikkerheten i A helt. Dette betyr at metode A vil kjøre uten sikkerhetskontroll. Det samme gjelder private metoder
  • Vår SecurityContext er trådbundet - som standard blir ikke sikkerhetskonteksten spredt til barnetråder. For mer informasjon kan vi referere til Spring Security Context Propagation artikkel

5. Testmetodesikkerhet

5.1. Konfigurasjon

For å teste vårsikkerhet med JUnit, trenger vi vår-sikkerhetstest avhengighet:

 org.springframework.security spring-security-test 

Vi trenger ikke å spesifisere avhengighetsversjonen fordi vi bruker Spring Boot-plugin. Vi finner den siste versjonen av denne avhengigheten av Maven Central.

Deretter la oss konfigurere en enkel vårintegrasjonstest ved å spesifisere løperen og ApplicationContext konfigurasjon:

@RunWith (SpringRunner.class) @ContextConfiguration offentlig klasse MethodSecurityIntegrationTest {// ...}

5.2. Testing av brukernavn og roller

Nå som konfigurasjonen vår er klar, la oss prøve å teste vår getUsername metoden som vi sikret med @Secured (“ROLE_VIEWER”) kommentar:

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

Siden vi bruker @Sikret merknad her, krever det at en bruker blir godkjent for å påkalle metoden. Ellers får vi en AuthenticationCredentialsNotFoundException.

Derfor, vi må gi en bruker å teste den sikrede metoden vår. For å oppnå dette dekorerer vi testmetoden med @WithMockUser og gi en bruker og roller:

@Test @WithMockUser (brukernavn = "john", roller = {"VIEWER"}) offentlig ugyldig givenRoleViewer_whenCallGetUsername_thenReturnUsername () {String userName = userRoleService.getUsername (); assertEquals ("john", brukernavn); }

Vi har gitt en autentisert bruker som har brukernavnet sitt john og hvis rolle er ROLE_VIEWER. Hvis vi ikke spesifiserer brukernavn eller rolle, Standaren brukernavn er bruker og standard rolle er ROLE_USER.

Merk at det ikke er nødvendig å legge til ROLE_ prefikset her, vil Spring Security legge til prefikset automatisk.

Hvis vi ikke vil ha det prefikset, kan vi vurdere å bruke autoritet i stedet for rolle.

La oss for eksempel erklære en getUsernameInLowerCase metode:

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

Vi kan teste det ved bruk av myndigheter:

@Test @WithMockUser (brukernavn = "JOHN", autoriteter = {"SYS_ADMIN"}) offentlig ugyldig givenAuthoritySysAdmin_whenCallGetUsernameLC_thenReturnUsername () {String brukernavn = userRoleService.getUsernameInLowerCase (); assertEquals ("john", brukernavn); }

Beleilig, Hvis vi ønsker å bruke samme bruker i mange testsaker, kan vi erklære @WithMockUser kommentar i testklassen:

@RunWith (SpringRunner.class) @ContextConfiguration @WithMockUser (brukernavn = "john", roller = {"VIEWER"}) offentlig klasse MockUserAtClassLevelIntegrationTest {// ...}

Hvis vi ønsket å kjøre testen vår som en anonym bruker, kunne vi bruke @WithAnonymousUser kommentar:

@Test (forventet = AccessDeniedException.class) @WithAnonymousUser offentlig ugyldig givenAnomynousUser_whenCallGetUsername_thenAccessDenied () {userRoleService.getUsername (); }

I eksemplet ovenfor forventer vi en AccessDeniedException fordi den anonyme brukeren ikke får rollen ROLE_VIEWER eller autoriteten SYS_ADMIN.

5.3. Testing med en egendefinert UserDetailsService

For de fleste applikasjoner er det vanlig å bruke en egendefinert klasse som autentiseringshoved. I dette tilfellet må den tilpassede klassen implementere org.springframework.security.core.userdetails.UserDetails grensesnitt.

I denne artikkelen erklærer vi a CustomUser klasse som utvider den eksisterende implementeringen av UserDetails, som er org.springframework.security.core.userdetails.Bruker:

offentlig klasse CustomUser utvider bruker {private String nickname; // getter og setter}

La oss ta tilbake eksemplet med @PostAuthorize kommentar i avsnitt 3:

@PostAuthorize ("returnObject.username == authentication.principal.nickName") offentlig CustomUser loadUserDetail (streng brukernavn) {return userRoleRepository.loadUserByUserName (brukernavn); }

I dette tilfellet vil metoden bare utføres vellykket hvis brukernavn av de returnerte CustomUser er lik gjeldende autentiseringshoved kallenavn.

Hvis vi ønsket å teste den metoden, kunne vi gi en implementering av UserDetailsService som kan laste vår CustomUser basert på brukernavnet:

@Test @WithUserDetails (verdi = "john", userDetailsServiceBeanName = "userDetailService") offentlig ugyldig nårJohn_callLoadUserDetail_thenOK () {CustomUser user = userService.loadUserDetail ("jane"); assertEquals ("jane", user.getNickName ()); }

Her, den @WithUserDetails kommentar sier at vi vil bruke en UserDetailsService for å initialisere den autentiserte brukeren. Tjenesten henvises av userDetailsServiceBeanName eiendom. Dette UserDetailsService kan være en reell implementering eller en falsk for testformål.

I tillegg vil tjenesten bruke eiendommens verdi verdi som brukernavnet du skal laste inn UserDetails.

Beleilig kan vi også pynte med en @WithUserDetails kommentar på klassenivå, på samme måte som det vi gjorde med @WithMockUser kommentar.

5.4. Testing With Meta Annotations

Vi bruker ofte de samme brukerne / rollene igjen og igjen i forskjellige tester.

For disse situasjonene er det praktisk å lage en meta-kommentar.

Tar vi tilbake det forrige eksemplet @WithMockUser (brukernavn = ”john”, roller = {“VIEWER”}), kan vi erklære en metaanmerking som:

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

Da kan vi rett og slett bruke @WithMockJohnViewer i vår test:

@Test @WithMockJohnViewer offentlig ugyldig givenMockedJohnViewer_whenCallGetUsername_thenReturnUsername () {String brukernavn = userRoleService.getUsername (); assertEquals ("john", brukernavn); }

På samme måte kan vi bruke metaanmerkninger for å opprette domenespesifikke brukere ved hjelp av @WithUserDetails.

6. Konklusjon

I denne opplæringen har vi utforsket ulike alternativer for bruk av Method Security i Spring Security.

Vi har også gått gjennom noen få teknikker for enkelt å teste metodesikkerhet og lært hvordan vi kan gjenbrukte spottede brukere i forskjellige tester.

Alle eksempler på denne veiledningen finner du på Github.


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