Spring Cloud - Sikringstjenester

1. Oversikt

I forrige artikkel, Spring Cloud - Bootstrapping, har vi bygget en grunnleggende Vårsky applikasjon. Denne artikkelen viser hvordan du kan sikre den.

Vi bruker det naturlig Vårsikkerhet å dele økter ved hjelp av Vårsesjon og Redis. Denne metoden er enkel å sette opp og lett å utvide til mange forretningsscenarier. Hvis du ikke er kjent med Vårsesjon, sjekk ut denne artikkelen.

Deling av økter gir oss muligheten til å logge brukere i gateway-tjenesten vår og overføre denne autentiseringen til andre tjenester i systemet vårt.

Hvis du ikke er kjent med Redis ellerVårsikkerhet, er det lurt å gjøre en rask gjennomgang av disse emnene på dette tidspunktet. Mens mye av artikkelen er kopi-lim klar for en applikasjon, er det ingen erstatning for å forstå hva som skjer under panseret.

For en introduksjon til Redis les denne veiledningen. For en introduksjon til Vårsikkerhet les vår-sikkerhet-pålogging, rolle-og-privilegium-for-vår-sikkerhet-registrering, og vår-sikkerhets-økt. For å få en fullstendig forståelse av Vårsikkerhet, ta en titt på lær-våren-sikkerhet-mesterklassen.

2. Maven-oppsett

La oss begynne med å legge til vår-boot-starter-sikkerhetsavhengighet til hver modul i systemet:

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

Fordi vi bruker Vår avhengighetsstyring vi kan utelate versjonene for spring-boot-starter avhengigheter.

Som et andre trinn, la oss endre pom.xml av hver applikasjon med vår-økt, vår-boot-starter-data-redis avhengighet:

 org.springframework.session spring-session org.springframework.boot spring-boot-starter-data-redis 

Bare fire av søknadene våre er knyttet til Vårsesjon: oppdagelse, inngangsport, boktjeneste, og vurderingstjeneste.

Deretter legger du til en øktkonfigurasjonsklasse i alle tre tjenestene i samme katalog som hovedprogramfilen:

@EnableRedisHttpSession offentlig klasse SessionConfig utvider AbstractHttpSessionApplicationInitializer {}

Til slutt, legg til disse egenskapene til de tre *.eiendommer filer i vårt git-arkiv:

spring.redis.host = localhost spring.redis.port = 6379

La oss nå hoppe inn i tjenestespesifikk konfigurasjon.

3. Sikre konfigurasjonstjeneste

Konfigurasjonstjenesten inneholder sensitiv informasjon som ofte er relatert til databaseforbindelser og API-nøkler. Vi kan ikke kompromittere denne informasjonen, så la oss dykke rett inn og sikre denne tjenesten.

La oss legge til sikkerhetsegenskaper til application.properties fil i src / main / resources av konfigurasjonstjenesten:

eureka.client.serviceUrl.defaultZone = // discUser: [e-postbeskyttet]: 8082 / eureka / security.user.name = configUser security.user.password = configPassword security.user.role = SYSTEM

Dette vil sette opp tjenesten vår for å logge inn med discovery. I tillegg konfigurerer vi sikkerheten vår med application.properties fil.

La oss nå konfigurere oppdagelsestjenesten vår.

4. Sikre Discovery Service

Vår oppdagelsestjeneste inneholder sensitiv informasjon om plasseringen av alle tjenestene i applikasjonen. Det registrerer også nye forekomster av disse tjenestene.

Hvis ondsinnede klienter får tilgang, vil de lære nettverksplasseringen til alle tjenestene i systemet vårt og kunne registrere sine egne ondsinnede tjenester i applikasjonen vår. Det er viktig at oppdagelsestjenesten er sikret.

4.1. Sikkerhetskonfigurasjon

La oss legge til et sikkerhetsfilter for å beskytte sluttpunktene de andre tjenestene vil bruke:

@Configuration @EnableWebSecurity @Order (1) offentlig klasse SecurityConfig utvider WebSecurityConfigurerAdapter {@Autowired public void configureGlobal (AuthenticationManagerBuilder auth) {auth.inMemoryAuthentication (). WithUser ("discUser") .assword (") .password (" ); } @ Override beskyttet tomrom konfigurere (HttpSecurity http) {http.sessionManagement () .sessionCreationPolicy (SessionCreationPolicy.ALWAYS) .and (). RequestMatchers (). AntMatchers ("/ eureka / **"). Og (). AuthorizeRequests () .antMatchers ("/ eureka / **") .hasRole ("SYSTEM"). anyRequest (). denyAll (). og () .httpBasic (). og (). csrf (). deaktiver (); }}

Dette vil sette opp tjenesten vår med enSYSTEM‘Bruker. Dette er en grunnleggende Vårsikkerhet konfigurasjon med noen få vendinger. La oss ta en titt på disse vendingene:

  • @Bestill (1) - forteller Vår å koble dette sikkerhetsfilteret først slik at det blir forsøkt før noen andre
  • .sessionCreationPolicy - forteller Vår å alltid opprette en økt når en bruker logger på dette filteret
  • .requestMatchers - begrenser hvilke endepunkter dette filteret gjelder for

Sikkerhetsfilteret, som vi nettopp har satt opp, konfigurerer et isolert autentiseringsmiljø som bare gjelder oppdagelsestjenesten.

4.2. Sikre Eureka Dashboard

Siden oppdagelsesapplikasjonen vår har et hyggelig brukergrensesnitt for å se for øyeblikket registrerte tjenester, la oss utsette det ved å bruke et andre sikkerhetsfilter og knytte dette til autentiseringen for resten av applikasjonen. Husk at nei @Rekkefølge() tag betyr at dette er det siste sikkerhetsfilteret som skal evalueres:

@Configuration offentlig statisk klasse AdminSecurityConfig utvider WebSecurityConfigurerAdapter {@Override beskyttet ugyldig konfigurasjon (HttpSecurity http) {http.sessionManagement (). SessionCreationPolicy (SessionCreationPolicy.NEVER). Og (). HttpBasic (). Disable (). Author. HttpMethod.GET, "/").hasRole("ADMIN") .antMatchers ("/ info", "/ health"). Autentisert (). AnyRequest () .denyAll (). Og (). Csrf (). Deaktiver (); }}

Legg til denne konfigurasjonsklassen i SecurityConfig klasse. Dette vil opprette et andre sikkerhetsfilter som vil kontrollere tilgangen til brukergrensesnittet vårt. Dette filteret har noen uvanlige egenskaper, la oss se på disse:

  • httpBasic (). deaktiver () - forteller vårsikkerhet om å deaktivere alle autentiseringsprosedyrer for dette filteret
  • sessionCreationPolicy - vi setter dette til ALDRI for å indikere at vi krever at brukeren allerede har autentisert før tilgang til ressurser beskyttet av dette filteret

Dette filteret vil aldri angi en brukersesjon og er avhengig av Redis for å fylle ut en delt sikkerhetskontekst. Som sådan er det avhengig av en annen tjeneste, gatewayen, for å gi autentisering.

4.3. Autentisering med konfigurasjonstjeneste

I oppdagelsesprosjektet, la oss legge til to egenskaper til bootstrap.properties i src / hoved / ressurser:

spring.cloud.config.username = config Bruker spring.cloud.config.password = configPassword

Disse egenskapene lar oppdagelsestjenesten autentisere seg med konfigurasjonstjenesten ved oppstart.

La oss oppdatere vår discovery.properties i vårt Git-depot

eureka.client.serviceUrl.defaultZone = // discUser: [email protected]: 8082 / eureka / eureka.client.register-with-eureka = false eureka.client.fetch-registry = false

Vi har lagt til grunnleggende autentiseringsinformasjon til vår oppdagelse tjenesten for å la den kommunisere med config service. I tillegg konfigurerer vi Eureka å kjøre i frittstående modus ved å fortelle tjenesten vår om ikke å registrere seg med seg selv.

La oss forplikte filen til git oppbevaringssted. Ellers vil ikke endringene bli oppdaget.

5. Sikre gateway-tjenesten

Gateway-tjenesten vår er den eneste delen av applikasjonen vi vil eksponere for verden. Som sådan vil det trenge sikkerhet for å sikre at bare autentiserte brukere har tilgang til sensitiv informasjon.

5.1. Sikkerhetskonfigurasjon

La oss lage en SecurityConfig klasse som vår oppdagelsestjeneste og overskriv metodene med dette innholdet:

@Autowired public void configureGlobal (AuthenticationManagerBuilder auth) {auth.inMemoryAuthentication (). WithUser ("user"). Password ("password") .roles ("USER"). And (). WithUser ("admin"). Password ( "admin") .roles ("ADMIN"); } @ Override-beskyttet ugyldig konfigurering (HttpSecurity http) {http.authorizeRequests (). AntMatchers ("/ book-service / books") .permitAll (). AntMatchers ("/ eureka / **"). HasRole ("ADMIN") .anyRequest (). autentisert (). og (). formLogin (). og () .logout (). permitAll (). logoutSuccessUrl ("/ book-service / books") .permitAll (). og (). csrf () .disable (); }

Denne konfigurasjonen er ganske grei. Vi erklærer et sikkerhetsfilter med skjemainnlogging som sikrer en rekke endepunkter.

Sikkerheten på / eureka / ** er å beskytte noen statiske ressurser vi vil betjene fra vår gateway-tjeneste for Eureka statusside. Hvis du bygger prosjektet med artikkelen, kopier du ressurs / statisk mappe fra gateway-prosjektet på Github til prosjektet ditt.

Nå endrer vi @EnableRedisHttpSession kommentar på vår konfigurasjonsklasse:

@EnableRedisHttpSession (redisFlushMode = RedisFlushMode.IMMEDIATE)

Vi setter skyllemodus til øyeblikkelig for å vedvare alle endringer på økten umiddelbart. Dette hjelper deg med å forberede godkjenningstokenet for omdirigering.

Til slutt, la oss legge til en ZuulFilter som vil videresende godkjenningstokenet vårt etter pålogging:

@Component public class SessionSavingZuulPreFilter utvider ZuulFilter {@Autowired private SessionRepository repository; @Override offentlig boolsk shouldFilter () {return true; } @Override public Object run () {RequestContext context = RequestContext.getCurrentContext (); HttpSession httpSession = context.getRequest (). GetSession (); Sessionsøkt = repository.getSession (httpSession.getId ()); context.addZuulRequestHeader ("Cookie", "SESSION =" + httpSession.getId ()); return null; } @ Override public String filterType () {return "pre"; } @ Override public int filterOrder () {return 0; }}

Dette filteret vil hente forespørselen når den blir omdirigert etter pålogging og legge til øktnøkkelen som en informasjonskapsel i overskriften. Dette vil spre autentisering til hvilken som helst sikkerhetstjeneste etter pålogging.

5.2. Autentisering med Config and Discovery Service

La oss legge til følgende autentiseringsegenskaper til bootstrap.properties fil i src / main / resources av gateway-tjenesten:

spring.cloud.config.username = configUser spring.cloud.config.password = configPassword eureka.client.serviceUrl.defaultZone = // discUser: [email protected]: 8082 / eureka /

Neste, la oss oppdatere vår gateway.properties i vårt Git-depot

management.security.sessions = alltid zuul.routes.book-service.path = / book-service / ** zuul.routes.book-service.sensitive-headers = Set-Cookie, Authorization hystrix.command.book-service.execution .isolation.thread .timeoutInMilliseconds = 600000 zuul.routes.rating-service.path = / rating-service / ** zuul.routes.rating-service.sensitive-headers = Set-Cookie, Authorization hystrix.command.rating-service. execution.isolation.thread .timeoutInMilliseconds = 600000 zuul.routes.discovery.path = / discovery / ** zuul.routes.discovery.sensitive-headers = Set-Cookie, Authorization zuul.routes.discovery.url = // localhost: 8082 hystrix.command.discovery.execution.isolation.thread .timeoutInMilliseconds = 600000

Vi har lagt til øktadministrasjon for å alltid generere økter fordi vi bare har ett sikkerhetsfilter vi kan sette det i egenskapsfilen. Deretter legger vi til vår Redis verts- og serveregenskaper.

I tillegg la vi til en rute som vil omdirigere forespørsler til oppdagelsestjenesten vår. Siden en frittstående oppdagelsestjeneste ikke vil registrere seg selv, må vi finne den tjenesten med et URL-skjema.

Vi kan fjerne serviceUrl.defaultZone eiendom fra gateway.properties filen i vårt konfigurasjonsgit-arkiv. Denne verdien dupliseres i Støvelhempe fil.

La oss forplikte filen til Git-depotet, ellers vil ikke endringene bli oppdaget.

6. Sikre boktjeneste

Boktjenesteserveren vil inneholde sensitiv informasjon kontrollert av forskjellige brukere. Denne tjenesten må sikres for å forhindre lekkasjer av beskyttet informasjon i systemet vårt.

6.1. Sikkerhetskonfigurasjon

For å sikre vår boktjeneste kopierer vi SecurityConfig klasse fra gatewayen og overskriv metoden med dette innholdet:

@ Override beskyttet ugyldig konfigurasjon (HttpSecurity http) {http.httpBasic (). Deaktiver (). AuthorizeRequests () .antMatchers ("/ books"). PermitAll () .antMatchers ("/ books / *"). HasAnyRole ("USER "," ADMIN "). Godkjent (). Og (). Csrf (). Deaktiver (); }

6.2. Eiendommer

Legg til disse egenskapene til bootstrap.properties fil i src / main / resources av boktjenesten:

spring.cloud.config.username = configUser spring.cloud.config.password = configPassword eureka.client.serviceUrl.defaultZone = // discUser: [email protected]: 8082 / eureka /

La oss legge til egenskaper i vår book-service.properties filen i vårt git-arkiv:

management.security.sessions = aldri

Vi kan fjerne serviceUrl.defaultZone eiendom fra book-service.properties filen i vårt konfigurasjonsgit-arkiv. Denne verdien dupliseres i Støvelhempe fil.

Husk å foreta disse endringene slik at boktjenesten henter dem.

7. Sikring av rangeringstjeneste

Rangeringstjenesten må også sikres.

7.1. Sikkerhetskonfigurasjon

For å sikre vår rangeringstjeneste vil vi kopiere SecurityConfig klasse fra gatewayen og overskriv metoden med dette innholdet:

@ Override beskyttet ugyldig konfigurering (HttpSecurity http) {http.httpBasic (). Deaktiver (). AuthorizeRequests () .antMatchers ("/ ratings"). HasRole ("USER") .antMatchers ("/ ratings / all"). HasAnyRole ("BRUKER", "ADMIN"). AnyRequest (). Godkjent (). Og (). Csrf (). Deaktiver (); }

Vi kan slette configureGlobal () metoden fra inngangsport service.

7.2. Eiendommer

Legg til disse egenskapene til bootstrap.properties fil i src / main / resources av rangeringstjenesten:

spring.cloud.config.username = configUser spring.cloud.config.password = configPassword eureka.client.serviceUrl.defaultZone = // discUser: [email protected]: 8082 / eureka /

La oss legge til egenskaper i vår rangeringstjeneste.eiendommer fil i vårt git-arkiv:

management.security.sessions = aldri

Vi kan fjerne serviceUrl.defaultZone eiendom fra rangeringstjenesten.eiendommer filen i vårt konfigurasjonsgit-arkiv. Denne verdien dupliseres i Støvelhempe fil.

Husk å foreta disse endringene slik at rangeringstjenesten henter dem.

8. Løping og testing

Start Redis og alle tjenestene for applikasjonen: config, oppdagelse, gateway, book-service, og vurderingstjeneste. La oss nå teste!

La oss først lage en testklasse i vår inngangsport prosjekt og lage en metode for testen vår:

offentlig klasse GatewayApplicationLiveTest {@Test offentlig ugyldig testAccess () {...}}

Deretter la oss sette opp testen og validere at vi kan få tilgang til vår ubeskyttede / book-service / bøker ressurs ved å legge til denne kodebiten i testmetoden vår:

TestRestTemplate testRestTemplate = ny TestRestTemplate (); Streng testUrl = "// localhost: 8080"; ResponseEntity response = testRestTemplate .getForEntity (testUrl + "/ book-service / books", String.class); Assert.assertEquals (HttpStatus.OK, response.getStatusCode ()); Assert.assertNotNull (respons.getBody ());

Kjør denne testen og verifiser resultatene. Hvis vi ser feil, bekreft at hele applikasjonen startet vellykket, og at konfigurasjoner ble lastet fra vårt konfigurasjonsgit-arkiv.

La oss nå teste at brukerne våre blir omdirigert til å logge på når de besøker en beskyttet ressurs som en uautentisert bruker ved å legge denne koden til slutten av testmetoden:

respons = testRestTemplate .getForEntity (testUrl + "/ boktjeneste / bøker / 1", String.class); Assert.assertEquals (HttpStatus.FOUND, response.getStatusCode ()); Assert.assertEquals ("// localhost: 8080 / login", response.getHeaders () .get ("Location"). Get (0));

Kjør testen på nytt og bekreft at den lykkes.

Deretter, la oss faktisk logge på og deretter bruke økten vår for å få tilgang til det brukerbeskyttede resultatet:

MultiValueMap form = new LinkedMultiValueMap (); form.add ("brukernavn", "bruker"); form.add ("passord", "passord"); respons = testRestTemplate .postForEntity (testUrl + "/ login", skjema, String.class); 

La oss nå trekke ut økten fra informasjonskapselen og overføre den til følgende forespørsel:

Streng sessionCookie = respons.getHeaders (). Get ("Set-Cookie") .get (0) .split (";") [0]; HttpHeaders headers = nye HttpHeaders (); headers.add ("Cookie", sessionCookie); HttpEntity httpEntity = ny HttpEntity (overskrifter); 

og be om den beskyttede ressursen:

respons = testRestTemplate.exchange (testUrl + "/ book-service / books / 1", HttpMethod.GET, httpEntity, String.class); Assert.assertEquals (HttpStatus.OK, response.getStatusCode ()); Assert.assertNotNull (respons.getBody ());

Kjør testen på nytt for å bekrefte resultatene.

La oss nå prøve å få tilgang til administrasjonsseksjonen med samme økt:

respons = testRestTemplate.exchange (testUrl + "/ rating-service / ratings / all", HttpMethod.GET, httpEntity, String.class); Assert.assertEquals (HttpStatus.FORBIDDEN, respons.getStatusCode ());

Kjør testen igjen, og som forventet er vi begrenset fra å få tilgang til adminområder som en ren gammel bruker.

Den neste testen vil validere at vi kan logge på som administrator og få tilgang til den administratorbeskyttede ressursen:

form.clear (); form.add ("brukernavn", "admin"); form.add ("passord", "admin"); respons = testRestTemplate .postForEntity (testUrl + "/ login", skjema, String.class); sessionCookie = respons.getHeaders (). get ("Set-Cookie"). get (0) .split (";") [0]; headers = nye HttpHeaders (); headers.add ("Cookie", sessionCookie); httpEntity = ny HttpEntity (overskrifter); respons = testRestTemplate.exchange (testUrl + "/ rating-service / ratings / all", HttpMethod.GET, httpEntity, String.class); Assert.assertEquals (HttpStatus.OK, response.getStatusCode ()); Assert.assertNotNull (respons.getBody ());

Testen vår blir stor! Men vi kan se når vi kjører det at ved å logge inn som admin får vi tilgang til admin-ressursen.

Den siste testen vår er å få tilgang til oppdagelsesserveren vår via gatewayen. For å gjøre dette, legg til denne koden til slutten av testen:

respons = testRestTemplate.exchange (testUrl + "/ discovery", HttpMethod.GET, httpEntity, String.class); Assert.assertEquals (HttpStatus.OK, response.getStatusCode ());

Kjør denne testen en siste gang for å bekrefte at alt fungerer. Suksess!!!

Savnet du det? Fordi vi logget på gateway-tjenesten vår og så på innhold i bok-, rangering- og oppdagelsestjenestene våre uten å måtte logge på fire separate servere!

Ved å bruke Vårsesjon for å overføre autentiseringsobjektet vårt mellom servere, kan vi logge inn en gang på gatewayen og bruke denne autentiseringen for å få tilgang til kontrollere på et hvilket som helst antall backingtjenester.

9. Konklusjon

Sikkerhet i skyen blir absolutt mer komplisert. Men ved hjelp av Vårsikkerhet og Vårsesjon, kan vi enkelt løse dette kritiske problemet.

Vi har nå en skyapplikasjon med sikkerhet rundt tjenestene våre. Ved hjelp av Zuul og Vårsesjon vi kan bare logge brukere i én tjeneste og overføre autentiseringen til hele applikasjonen. Dette betyr at vi enkelt kan dele søknaden vår inn i riktige domener og sikre hver av dem etter eget ønske.

Som alltid kan du finne kildekoden på GitHub.


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