Spring Cloud - Services beveiligen

1. Overzicht

In het vorige artikel, Spring Cloud - Bootstrapping, hebben we een basic Lente Cloud toepassing. Dit artikel laat zien hoe u het kunt beveiligen.

We zullen natuurlijk gebruiken Lente beveiliging om sessies te delen met Lentesessie en Redis. Deze methode is eenvoudig in te stellen en gemakkelijk uit te breiden naar veel bedrijfsscenario's. Als u niet bekend bent met Lentesessie, lees dan dit artikel.

Door sessies te delen, kunnen we gebruikers aanmelden bij onze gateway-service en die authenticatie doorgeven aan elke andere service van ons systeem.

Als u er niet bekend mee bent Redis ofLente beveiliging, is het een goed idee om deze onderwerpen nu kort te bespreken. Hoewel een groot deel van het artikel klaar is voor kopiëren en plakken voor een toepassing, is er geen vervanging om te begrijpen wat er onder de motorkap gebeurt.

Voor een kennismaking met Redis lees deze tutorial. Voor een kennismaking met Lente beveiliging lees spring-security-login, role-and-privilege-for-spring-security-registration en spring-security-session. Om een ​​volledig begrip te krijgen van Spring Security, neem een ​​kijkje in de leer-lente-beveiliging-de-masterclass.

2. Maven-instellingen

Laten we beginnen met het toevoegen van de spring-boot-starter-security-afhankelijkheid aan elke module in het systeem:

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

Omdat we gebruiken Voorjaar afhankelijkheidsbeheer waarvoor we de versies kunnen weglaten spring-boot-starter afhankelijkheden.

Laten we als tweede stap het pom.xml van elke applicatie met spring-session, spring-boot-starter-data-redis afhankelijkheden:

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

Slechts vier van onze applicaties zullen aansluiten op Lentesessie: ontdekking, poort, boekenservice, en rating-service.

Voeg vervolgens een sessieconfiguratieklasse toe aan alle drie de services in dezelfde map als het hoofdtoepassingsbestand:

@EnableRedisHttpSession openbare klasse SessionConfig breidt AbstractHttpSessionApplicationInitializer {}

Voeg als laatste deze eigenschappen toe aan de drie *.eigendommen bestanden in onze git-repository:

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

Laten we nu eens kijken naar de servicespecifieke configuratie.

3. Beveiliging van de configuratieservice

De configuratieservice bevat gevoelige informatie die vaak verband houdt met databaseverbindingen en API-sleutels. We kunnen deze informatie niet compromitteren, dus laten we meteen naar binnen duiken en deze service beveiligen.

Laten we beveiligingseigenschappen toevoegen aan het application.properties bestand in src / main / resources van de configuratieservice:

eureka.client.serviceUrl.defaultZone = // discUser: [email protected]: 8082 / eureka / security.user.name = configUser security.user.password = configPassword security.user.role = SYSTEEM

Hierdoor wordt onze service ingesteld om in te loggen met discovery. Bovendien configureren we onze beveiliging met de application.properties het dossier.

Laten we nu onze zoekservice configureren.

4. Beveiliging van zoekservice

Onze zoekservice bevat gevoelige informatie over de locatie van alle services in de applicatie. Het registreert ook nieuwe exemplaren van die services.

Als kwaadwillende clients toegang krijgen, leren ze de netwerklocatie van alle services in ons systeem en kunnen ze hun eigen schadelijke services in onze applicatie registreren. Het is van cruciaal belang dat de zoekservice is beveiligd.

4.1. Beveiligingsconfiguratie

Laten we een beveiligingsfilter toevoegen om de eindpunten te beschermen die de andere services zullen gebruiken:

@Configuration @EnableWebSecurity @Order (1) public class SecurityConfig breidt WebSecurityConfigurerAdapter uit {@Autowired public void configureGlobal (AuthenticationManagerBuilder auth) {auth.inMemoryAuthentication (). WithUser ("discUser"). "SYSTEM" ("discUser") .password ("discPassword"). ); } @Override protected void configure (HttpSecurity http) {http.sessionManagement () .sessionCreationPolicy (SessionCreationPolicy.ALWAYS) .and (). RequestMatchers (). AntMatchers ("/ eureka / **") .and (). AuthorizeRequests () .antMatchers ("/ eureka / **") .hasRole ("SYSTEEM"). anyRequest (). denyAll (). en () .httpBasic (). en (). csrf (). disable (); }}

Dit zal onze service opzetten met een ‘SYSTEEM‘Gebruiker. Dit is een basis Lente beveiliging configuratie met een paar wendingen. Laten we die wendingen eens bekijken:

  • @Bestellen (1) - vertelt Voorjaar om dit beveiligingsfilter eerst te bedraden, zodat het eerder wordt geprobeerd dan andere
  • .sessionCreationPolicy - vertelt Voorjaar om altijd een sessie te maken wanneer een gebruiker inlogt op dit filter
  • .requestMatchers - beperkt op welke eindpunten dit filter van toepassing is

Het beveiligingsfilter, dat we zojuist hebben ingesteld, configureert een geïsoleerde authenticatieomgeving die alleen betrekking heeft op de zoekservice.

4.2. Eureka Dashboard beveiligen

Omdat onze detectietoepassing een mooie gebruikersinterface heeft om momenteel geregistreerde services te bekijken, laten we dat laten zien met behulp van een tweede beveiligingsfilter en deze koppelen aan de authenticatie voor de rest van onze applicatie. Houd er rekening mee dat nee @Bestellen() tag betekent dat dit het laatste beveiligingsfilter is dat moet worden geëvalueerd:

@Configuration openbare statische klasse AdminSecurityConfig breidt WebSecurityConfigurerAdapter uit {@Override protected void configure (HttpSecurity http) {http.sessionManagement (). SessionCreationPolicy (SessionCreationPolicy.NEVER) .en (). HttpBasic (). Disable (). HttpMethod.GET, "/").hasRole("ADMIN") .antMatchers ("/ info", "/ health"). Geverifieerd (). AnyRequest () .denyAll (). En (). Csrf (). Uitschakelen (); }}

Voeg deze configuratieklasse toe binnen het SecurityConfig klasse. Hierdoor wordt een tweede beveiligingsfilter gemaakt dat de toegang tot onze gebruikersinterface controleert. Dit filter heeft een paar ongebruikelijke kenmerken, laten we die eens bekijken:

  • httpBasic (). uitschakelen () - vertelt Spring Security om alle authenticatieprocedures voor dit filter uit te schakelen
  • sessionCreationPolicy - we hebben dit ingesteld op NOOIT om aan te geven dat we vereisen dat de gebruiker zich al heeft geverifieerd voordat hij toegang krijgt tot bronnen die door dit filter worden beschermd

Dit filter zal nooit een gebruikerssessie instellen en vertrouwt op Redis om een ​​gedeelde beveiligingscontext te vullen. Als zodanig is het afhankelijk van een andere service, de gateway, om authenticatie te bieden.

4.3. Verifiëren met Config Service

Laten we in het discovery-project twee eigenschappen aan het bootstrap.properties in src / main / resources:

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

Met deze eigenschappen kan de detectieservice bij het opstarten verifiëren met de configuratieservice.

Laten we onze discovery.properties in onze Git-opslagplaats

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

We hebben basisverificatiegegevens toegevoegd aan onze ontdekking service zodat deze kan communiceren met de config onderhoud. Daarnaast configureren we Eureka om in stand-alone modus te werken door onze service te vertellen zich niet bij zichzelf te registreren.

Laten we het bestand vastleggen in het git repository. Anders worden de wijzigingen niet gedetecteerd.

5. Gatewayservice beveiligen

Onze gateway-service is het enige onderdeel van onze applicatie dat we aan de wereld willen tonen. Als zodanig heeft het beveiliging nodig om ervoor te zorgen dat alleen geauthenticeerde gebruikers toegang hebben tot gevoelige informatie.

5.1. Beveiligingsconfiguratie

Laten we een SecurityConfig klasse zoals onze zoekservice en overschrijf de methoden met deze inhoud:

@Autowired public void configureGlobal (AuthenticationManagerBuilder auth) {auth.inMemoryAuthentication (). WithUser ("user"). Password ("password") .roles ("USER"). And (). WithUser ("admin"). Password ( "admin") .roles ("ADMIN"); } @Override protected void configure (HttpSecurity http) {http.authorizeRequests (). AntMatchers ("/ book-service / books") .permitAll (). AntMatchers ("/ eureka / **"). HasRole ("ADMIN") .anyRequest (). geverifieerd (). en (). formLogin (). en () .logout (). allowAll (). logoutSuccessUrl ("/ book-service / books") .permitAll (). en (). csrf (). uitschakelen (); }

Deze configuratie is vrij eenvoudig. We verklaren een beveiligingsfilter met aanmeldingsformulier dat verschillende eindpunten beveiligt.

De beveiliging op / eureka / ** is om enkele statische bronnen te beschermen die we vanuit onze gateway-service voor het Eureka statuspagina. Als u het project met het artikel samenstelt, kopieer dan het resource / statisch map van het gateway-project op Github naar uw project.

Nu wijzigen we het @EnableRedisHttpSession annotatie op onze configuratieklasse:

@EnableRedisHttpSession (redisFlushMode = RedisFlushMode.IMMEDIATE)

We zetten de spoelmodus op direct om eventuele wijzigingen in de sessie onmiddellijk vast te houden. Dit helpt bij het voorbereiden van het authenticatietoken voor omleiding.

Laten we tot slot een ZuulFilter dat ons authenticatietoken zal doorsturen na inloggen:

@Component openbare klasse SessionSavingZuulPreFilter breidt ZuulFilter {@Autowired private SessionRepository repository uit; @Override openbare boolean shouldFilter () {return true; } @Override public Object run () {RequestContext context = RequestContext.getCurrentContext (); HttpSession httpSession = context.getRequest (). GetSession (); Sessiesessie = repository.getSession (httpSession.getId ()); context.addZuulRequestHeader ("Cookie", "SESSION =" + httpSession.getId ()); null teruggeven; } @Override public String filterType () {retourneer "pre"; } @Override public int filterOrder () {return 0; }}

Dit filter pakt het verzoek terwijl het wordt omgeleid na het inloggen en voegt de sessiesleutel toe als een cookie in de koptekst. Dit zal authenticatie doorgeven aan elke ondersteunende service na inloggen.

5.2. Authenticatie met configuratie- en zoekservice

Laten we de volgende authenticatie-eigenschappen toevoegen aan het bootstrap.properties bestand in src / main / resources van de gateway-service:

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

Laten we vervolgens onze gateway.properties in onze Git-opslagplaats

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

We hebben sessiebeheer toegevoegd om altijd sessies te genereren, omdat we maar één beveiligingsfilter hebben dat we kunnen instellen in het eigenschappenbestand. Vervolgens voegen we onze Redis host- en servereigenschappen.

Daarnaast hebben we een route toegevoegd die verzoeken omleidt naar onze zoekservice. Aangezien een zelfstandige detectieservice zich niet bij zichzelf registreert, moeten we die service lokaliseren met een URL-schema.

We kunnen het serviceUrl.defaultZone eigendom van de gateway.properties bestand in onze configuratie git repository. Deze waarde wordt gedupliceerd in het bootstrap het dossier.

Laten we het bestand vastleggen in de Git-repository, anders worden de wijzigingen niet gedetecteerd.

6. Boekservice beveiligen

De boekserviceserver bevat gevoelige informatie die door verschillende gebruikers wordt beheerd. Deze dienst moet worden beveiligd om lekken van beschermde informatie in ons systeem te voorkomen.

6.1. Beveiligingsconfiguratie

Om onze boekenservice te beveiligen, zullen we het SecurityConfig class van de gateway en overschrijf de methode met deze inhoud:

@Override protected void configure (HttpSecurity http) {http.httpBasic (). Disable (). AuthorizeRequests () .antMatchers ("/ books"). AllowAll () .antMatchers ("/ books / *"). HasAnyRole ("USER "," ADMIN ") .authenticated (). En (). Csrf (). Disable (); }

6.2. Eigendommen

Voeg deze eigenschappen toe aan het bootstrap.properties bestand in src / main / resources van de boekenservice:

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

Laten we eigenschappen toevoegen aan onze book-service.properties bestand in onze git-repository:

management.security.sessions = nooit

We kunnen het serviceUrl.defaultZone eigendom van de book-service.properties bestand in onze configuratie git repository. Deze waarde wordt gedupliceerd in de bootstrap het dossier.

Vergeet niet om deze wijzigingen door te voeren, zodat de boekenservice ze ophaalt.

7. Beveiligen van beoordelingsservice

De beoordelingsservice moet ook worden beveiligd.

7.1. Beveiligingsconfiguratie

Om onze beoordelingsservice te beveiligen, zullen we het SecurityConfig class van de gateway en overschrijf de methode met deze inhoud:

@Override protected void configure (HttpSecurity http) {http.httpBasic (). Disable (). AuthorizeRequests () .antMatchers ("/ ratings"). HasRole ("USER") .antMatchers ("/ ratings / all"). HasAnyRole ("USER", "ADMIN"). AnyRequest () .authenticated (). En (). Csrf (). Disable (); }

We kunnen het configurerenGlobal () methode van de poort onderhoud.

7.2. Eigendommen

Voeg deze eigenschappen toe aan het bootstrap.properties bestand in src / main / resources van de beoordelingsservice:

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

Laten we eigenschappen toevoegen aan onze beoordelingsservice.eigendommen bestand in onze git-repository:

management.security.sessions = nooit

We kunnen het serviceUrl.defaultZone eigendom van de beoordelingsservice.eigendommen bestand in onze configuratie git repository. Deze waarde wordt gedupliceerd in de bootstrap het dossier.

Vergeet niet om deze wijzigingen door te voeren, zodat de beoordelingsservice ze oppikt.

8. Hardlopen en testen

Begin Redis en alle services voor de applicatie: config, ontdekking, gateway, boekenservice, en rating-service. Laten we nu testen!

Laten we eerst een testles maken in ons poort project en maak een methode voor onze test:

openbare klasse GatewayApplicationLiveTest {@Test openbare ongeldige testAccess () {...}}

Laten we vervolgens onze test opzetten en valideren dat we toegang hebben tot onze onbeschermde / boekenservice / boeken bron door dit codefragment toe te voegen aan onze testmethode:

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

Voer deze test uit en controleer de resultaten. Als we fouten zien, bevestig dan dat de hele applicatie met succes is gestart en dat configuraties zijn geladen vanuit onze configuratie-git-repository.

Laten we nu testen of onze gebruikers worden omgeleid om in te loggen wanneer ze een beschermde bron bezoeken als een niet-geverifieerde gebruiker door deze code toe te voegen aan het einde van de testmethode:

response = testRestTemplate .getForEntity (testUrl + "/ book-service / books / 1", String.class); Assert.assertEquals (HttpStatus.FOUND, response.getStatusCode ()); Assert.assertEquals ("// localhost: 8080 / login", response.getHeaders () .get ("Locatie"). Get (0));

Voer de test opnieuw uit en bevestig dat deze slaagt.

Laten we vervolgens inloggen en vervolgens onze sessie gebruiken om toegang te krijgen tot het door de gebruiker beschermde resultaat:

MultiValueMap-formulier = nieuwe LinkedMultiValueMap (); form.add ("gebruikersnaam", "gebruiker"); form.add ("wachtwoord", "wachtwoord"); response = testRestTemplate .postForEntity (testUrl + "/ login", formulier, String.class); 

Laten we nu de sessie uit de cookie halen en deze doorgeven aan het volgende verzoek:

String sessionCookie = response.getHeaders (). Get ("Set-Cookie") .get (0) .split (";") [0]; HttpHeaders headers = nieuwe HttpHeaders (); headers.add ("Cookie", sessionCookie); HttpEntity httpEntity = nieuwe HttpEntity (headers); 

en vraag de beschermde bron aan:

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

Voer de test opnieuw uit om de resultaten te bevestigen.

Laten we nu proberen toegang te krijgen tot de admin-sectie met dezelfde sessie:

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

Voer de test opnieuw uit en zoals verwacht hebben we geen toegang tot admin-gebieden als een gewone oude gebruiker.

De volgende test zal valideren dat we kunnen inloggen als de admin en toegang krijgen tot de door de beheerder beschermde bron:

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

Onze test wordt groot! Maar we kunnen zien wanneer we het uitvoeren dat we door in te loggen als de admin toegang krijgen tot de admin-bron.

Onze laatste test is om toegang te krijgen tot onze detectieserver via onze gateway. Om dit te doen, voegt u deze code toe aan het einde van onze test:

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

Voer deze test nog een laatste keer uit om te bevestigen dat alles werkt. Succes!!!

Heb je dat gemist? Omdat we ons hebben aangemeld bij onze gateway-service en inhoud van onze boek-, beoordelings- en zoekservices hebben bekeken zonder dat we op vier afzonderlijke servers hoeven in te loggen!

Door gebruik te maken van Lentesessie om ons authenticatieobject tussen servers te verspreiden, kunnen we eenmaal inloggen op de gateway en die authenticatie gebruiken om toegang te krijgen tot controllers op een willekeurig aantal ondersteuningsservices.

9. Conclusie

Beveiliging in de cloud wordt zeker ingewikkelder. Maar met de hulp van Lente beveiliging en Lentesessiekunnen we dit kritieke probleem gemakkelijk oplossen.

We hebben nu een cloudapplicatie met beveiliging rondom onze diensten. Gebruik makend van Zuul en Lentesessie we kunnen gebruikers in slechts één service aanmelden en die authenticatie doorgeven aan onze hele applicatie. Dit betekent dat we onze applicatie gemakkelijk kunnen opsplitsen in de juiste domeinen en ze allemaal naar eigen inzicht kunnen beveiligen.

Zoals altijd kun je de broncode vinden op GitHub.