OAuth 2.0 ressource server med Spring Security 5
1. Oversigt
I denne vejledning lærer vi hvordan man opretter en OAuth 2.0 ressource server ved hjælp af Spring Security 5.
Vi gør dette ved hjælp af JWT såvel som uigennemsigtige tokens, de to slags bærer-tokens understøttet af Spring Security.
Inden vi springer videre til implementerings- og kodeeksemplerne, etablerer vi noget baggrund.
2. En lille baggrund
2.1. Hvad er JWT'er og uigennemsigtige tokens?
JWT eller JSON Web Token er en måde at overføre følsomme oplysninger sikkert i det bredt accepterede JSON-format. De indeholdte oplysninger kan være om brugeren eller om selve tokenet, såsom dets udløb og udsteder.
På den anden side er et uigennemsigtigt symbol, som navnet antyder, uigennemsigtigt med hensyn til de oplysninger, det bærer. Tokenet er bare en identifikator, der peger på de oplysninger, der er gemt på autorisationsserveren - det bliver valideret via introspektion i slutningen af serveren.
2.2. Hvad er en ressource server?
I forbindelse med OAuth 2.0, a ressource server er et program, der beskytter ressourcer via OAuth tokens. Disse tokens udstedes af en autorisationsserver, typisk til en klientapplikation. Ressursserverens opgave er at validere tokenet, før det serveres en ressource til klienten.
Et tokens gyldighed bestemmes af flere ting:
- Kom dette token fra den konfigurerede autorisationsserver?
- Er det udløbet?
- Er denne ressource server dens målgruppe?
- Har tokenet den nødvendige myndighed til at få adgang til den anmodede ressource?
For at visualisere, lad os se på et sekvensdiagram for autorisationskodeflowet og se alle aktørerne i aktion:
Som vi kan se i trin 8, når klientapplikationen kalder ressourceserverens API for at få adgang til en beskyttet ressource, går den først til autorisationsserveren for at validere det token, der er indeholdt i anmodningens Tilladelse: Bærer header og svarer derefter på klienten.
Trin 9 er det, vi fokuserer på i denne vejledning.
Okay, lad os nu hoppe ind i kodedelen. Vi opretter en autorisationsserver ved hjælp af Keycloak, en ressource-server, der validerer JWT-tokens, en anden ressource-server, der validerer uigennemsigtige tokens, og et par JUnit-tests for at simulere klientapps og bekræfte svar.
3. Autorisationsserver
Først opretter vi en autorisationsserver eller den ting, der udsteder tokens.
Til det bruger vi Keycloak indlejret i en Spring Boot-applikation. Keycloak er en open source-identitets- og adgangsstyringsløsning. Da vi fokuserer på ressource-serveren i denne vejledning, dykker vi ikke dybere ned i den.
Vores integrerede Keycloak Server har to klienter defineret - fooClient og barClient - svarende til vores to ressource server applikationer.
4. Ressource-server - Brug af JWT'er
Vores ressource server vil have fire hovedkomponenter:
- Model - den ressource, der skal beskyttes
- API - en REST-controller til at eksponere ressourcen
- Sikkerhedskonfiguration - en klasse til at definere adgangskontrol for den beskyttede ressource, som API'en udsætter
- ansøgning.yml - en konfigurationsfil til at erklære egenskaber, herunder oplysninger om autorisationsserveren
Lad os se dem en efter en for vores ressource server, der håndterer JWT-tokens, efter at have kigget på afhængighederne.
4.1. Maven afhængigheder
Hovedsageligt har vi brug for spring-boot-starter-oauth2-resource-server, Spring Boot's starter til ressource server support. Denne starter inkluderer Spring Security som standard, så vi behøver ikke tilføje det eksplicit:
org.springframework.boot spring-boot-starter-web 2.2.6.RELEASE org.springframework.boot spring-boot-starter-oauth2-resource-server 2.2.6.RELEASE org.apache.commons commons-lang3 3.9
Bortset fra det har vi også tilføjet websupport.
Til vores demonstrationsformål genererer vi tilfældigt ressourcer i stedet for at hente dem fra en database med noget hjælp fra Apache's commons-lang3 bibliotek.
4.2. Model
Hvis vi holder det enkelt, bruger vi det Foo, en POJO, som vores beskyttede ressource:
offentlig klasse Foo {privat lang id; privat strengnavn; // konstruktør, getters og setters}
4.3. API
Her er vores hvile controller, der skal laves Foo tilgængelig til manipulation:
@RestController @RequestMapping (værdi = "/ foos") offentlig klasse FooController {@GetMapping (værdi = "/ {id}") offentlig Foo findOne (@PathVariable Lang id) {returner ny Foo (Long.parseLong (randomNumeric (2)) ), tilfældig Alfabetisk (4)); } @GetMapping offentlig liste findAll () {Liste fooList = ny ArrayList (); fooList.add (ny Foo (Long.parseLong (randomNumeric (2)), randomAlphabetic (4))); fooList.add (ny Foo (Long.parseLong (randomNumeric (2)), randomAlphabetic (4))); fooList.add (ny Foo (Long.parseLong (randomNumeric (2)), randomAlphabetic (4))); returner fooList; } @ResponseStatus (HttpStatus.CREATED) @PostMapping public void create (@RequestBody Foo newFoo) {logger.info ("Foo created"); }}
Som det er tydeligt, har vi bestemmelsen om at GET alle Foos, FÅ en Foo efter id og POST a Foo.
4.4. Sikkerhedskonfiguration
I denne konfigurationsklasse definerer vi adgangsniveauer for vores ressource:
@Configuration offentlig klasse JWTSecurityConfig udvider WebSecurityConfigurerAdapter {@Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http .authorizeRequests (authz -> authz .antMatchers (HttpMethod.GET, "/foos/**").hasAhor)" .antMatchers (HttpMethod.POST, "/foos").hasAuthority("SCOPE_write") .anyRequest (). godkendt ()) .oauth2ResourceServer (oauth2 -> oauth2.jwt ()); }}
Enhver med adgangstoken, der har Læs rækkevidde kan få Foos. For at POSTE et nyt Foo, deres token skal have en skrive rækkevidde.
Derudover vi tilføjede et opkald til jwt () bruger oauth2ResourceServer () DSL for at angive den type tokens, der understøttes af vores server her.
4.5. ansøgning.yml
I applikationsegenskaberne ud over det sædvanlige portnummer og kontekststi, vi er nødt til at definere stien til vores autorisationsservers udsteder-URI, så ressource-serveren kan finde udbyderkonfigurationen:
server: port: 8081 servlet: kontekststi: / ressource-server-jwt fjeder: sikkerhed: oauth2: ressource server: jwt: udsteder-uri: // localhost: 8083 / auth / realms / baeldung
Ressource-serveren bruger disse oplysninger til at validere JWT-tokens, der kommer ind fra klientapplikationen, som i trin 9 i vores sekvensdiagram.
For at denne validering fungerer ved hjælp af udsteder-uri ejendom, skal autorisationsserveren være oppe og køre. Ellers ville ressource-serveren ikke starte.
Hvis vi har brug for at starte det uafhængigt, så kan vi levere jwk-set-uri ejendom i stedet for at pege på autorisationsservers slutpunkt, der udsætter offentlige nøgler:
jwk-set-uri: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / certs
Og det er alt, hvad vi har brug for for at få vores server til at validere JWT-tokens.
4.6. Testning
Til test opretter vi en JUnit. For at udføre denne test har vi brug for autorisationsserveren samt ressource-serveren i gang.
Lad os kontrollere, at vi kan få Foos fra ressource-server-jwt med en Læs scoped token i vores test:
@Test offentlig ugyldighed givenUserWithReadScope_whenGetFooResource_thenSuccess () {String accessToken = obtainAccessToken ("læs"); Svarrespons = RestAssured.given () .header (HttpHeaders.AUTHORIZATION, "Bearer" + accessToken) .get ("// localhost: 8081 / resource-server-jwt / foos"); assertThat (respons.as (List.class)). hasSizeGreaterThan (0); }
I ovenstående kode får vi på linie # 3 et adgangstoken med Læs omfang fra autorisationsserveren, der dækker trin fra 1 til 7 i vores sekvensdiagram.
Trin 8 udføres af Stol trygt på'S få() opkald. Trin 9 udføres af ressource-serveren med de konfigurationer, vi så, og er gennemsigtig for os som brugere.
5. Ressource-server - Brug af uigennemsigtige tokens
Lad os derefter se de samme komponenter til vores ressource server, der håndterer uigennemsigtige tokens.
5.1. Maven afhængigheder
For at understøtte uigennemsigtige tokens skal vi desuden bruge oauth2-oidc-sdk afhængighed:
com.nimbusds oauth2-oidc-sdk 8.19 runtime
5.2. Model og controller
Til denne tilføjer vi en Bar ressource:
offentlig klassebar {privat lang id; privat strengnavn; // konstruktør, getters og setters}
Vi har også en BarController med slutpunkter svarende til vores FooController før at skille ud Bars.
5.3. ansøgning.yml
I ansøgning.yml her skal vi tilføje en introspektion-uri svarende til vores autorisationsservers introspektionsendepunkt. Som nævnt før er dette, hvordan et uigennemsigtigt token bliver valideret: Holde adgangsniveauer svarende til Foo til Bar ressource også denne konfigurationsklasse ringer også til opaqueToken () bruger oauth2ResourceServer () DSL for at angive brugen af den uigennemsigtige token-type: Her specificerer vi også de klientoplysninger, der svarer til autorisationsserverens klient, vi bruger. Vi definerede disse tidligere i vores ansøgning.yml. Vi opretter en JUnit til vores uigennemsigtige tokenbaserede ressource server, svarende til hvordan vi gjorde det til JWT-en. Lad os i dette tilfælde kontrollere, om en skrive scoped adgangstoken kan POST a Bar til ressource-server-uigennemsigtig: Hvis vi får status CREATED tilbage, betyder det, at ressource-serveren med succes validerede det uigennemsigtige token og oprettede Bar for os. I denne vejledning så vi, hvordan vi konfigurerer en Spring Security-baseret ressource-serverapplikation til validering af JWT såvel som uigennemsigtige tokens. Som vi så, med minimal opsætning gjorde Spring det muligt problemfrit at validere tokens med en udsteder og sende ressourcer til den anmodende part - i vores tilfælde en JUnit-test. Som altid er kildekoden tilgængelig på GitHub.server: port: 8082 servlet: kontekststi: / ressource-server-uigennemsigtig fjeder: sikkerhed: oauth2: ressourcer: uigennemsigtig: introspektion-uri: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / token / introspect introspection-client-id: barClient introspection-client-secret: barClientSecret
5.4. Sikkerhedskonfiguration
@Configuration offentlig klasse OpaqueSecurityConfig udvider WebSecurityConfigurerAdapter {@Value ("$ {spring.security.oauth2.resourceserver.opaque.introspection-uri}") String introspectionUri; @Value ("$ {spring.security.oauth2.resourceserver.opaque.introspection-client-id}") String clientId; @Value ("$ {spring.security.oauth2.resourceserver.opaque.introspection-client-secret}") String clientSecret; @ Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http .authorizeRequests (authz -> authz .antMatchers (HttpMethod.GET, "/bars/**").hasAuthority("SCOPE_read") .antMatchers (HttpMethod.POST, " / barer").hasAuthority("SCOPE_write ") .anyRequest (). godkendt ()) .oauth2ResourceServer (oauth2 -> oauth2 .opaqueToken (token -> token.introspectionUri (denne.introspectionUri) .introspectionClientCredentials clientSecret))); }}
5.5. Testning
@Test offentlig ugyldighed givenUserWithWriteScope_whenPostNewBarResource_thenCreated () {String accessToken = obtainAccessToken ("læs skriv"); Bar newBar = new Bar (Long.parseLong (randomNumeric (2)), randomAlphabetic (4)); Svarrespons = RestAssured.given () .contentType (ContentType.JSON). Header (HttpHeaders.AUTHORIZATION, "Bearer" + accessToken) .body (newBar) .log () .all () .post ("// localhost: 8082 / ressource-server-uigennemsigtig / søjler "); assertThat (respons.getStatusCode ()). er EqualTo (HttpStatus.CREATED.value ()); }
6. Konklusion