OAuth 2.0-bronserver met Spring Security 5
1. Overzicht
In deze tutorial leren we hoe u een OAuth 2.0-bronserver instelt met Spring Security 5.
We zullen dit doen met zowel JWT als ondoorzichtige tokens, de twee soorten drager-tokens die worden ondersteund door Spring Security.
Voordat we verder gaan met de implementatie en codevoorbeelden, zullen we wat achtergrondinformatie vastleggen.
2. Een beetje achtergrond
2.1. Wat zijn JWT's en ondoorzichtige tokens?
JWT of JSON Web Token is een manier om gevoelige informatie veilig over te dragen in het algemeen aanvaarde JSON-formaat. De opgenomen informatie kan over de gebruiker gaan, of over het token zelf, zoals de vervaldatum en uitgever.
Aan de andere kant is een ondoorzichtig token, zoals de naam suggereert, ondoorzichtig in termen van de informatie die het bevat. Het token is slechts een identificatie die verwijst naar de informatie die is opgeslagen op de autorisatieserver - het wordt gevalideerd via introspectie aan het einde van de server.
2.2. Wat is een bronserver?
In de context van OAuth 2.0, een resource server is een applicatie die bronnen beschermt via OAuth-tokens. Deze tokens worden uitgegeven door een autorisatieserver, meestal aan een clienttoepassing. De taak van de bronserver is om het token te valideren voordat een bron aan de client wordt aangeboden.
De geldigheid van een token wordt bepaald door verschillende dingen:
- Is dit token afkomstig van de geconfigureerde autorisatieserver?
- Is het niet verlopen?
- Is deze bronserver het beoogde publiek?
- Heeft het token de vereiste autoriteit om toegang te krijgen tot de gevraagde bron?
Laten we, om dit te visualiseren, een sequentiediagram bekijken voor de autorisatiecodestroom en alle actoren in actie zien:
Zoals we kunnen zien in stap 8, gaat de clienttoepassing die de API van de bronserver aanroept om toegang te krijgen tot een beschermde bron, eerst naar de autorisatieserver om het token in het verzoek te valideren. Autorisatie: drager header, en reageert vervolgens op de client.
Stap 9 is waar we ons in deze tutorial op concentreren.
Oké, laten we nu in het codegedeelte springen. We zullen een autorisatieserver opzetten met behulp van Keycloak, een bronserver die JWT-tokens valideert, een andere bronserver die ondoorzichtige tokens valideert, en een paar JUnit-tests om client-apps te simuleren en reacties te verifiëren.
3. Autorisatieserver
Eerst gaan we een autorisatieserver opzetten, of het ding dat tokens uitgeeft.
Daarvoor gebruiken we Keycloak ingebed in een Spring Boot-applicatie. Keycloak is een open-source oplossing voor identiteits- en toegangsbeheer. Omdat we ons in deze tutorial concentreren op de bronserver, zullen we er niet dieper op ingaan.
Onze embedded Keycloak Server heeft twee gedefinieerde clients - fooClient en barClient - overeenkomend met onze twee resource servertoepassingen.
4. Bronserver - JWT's gebruiken
Onze bronserver heeft vier hoofdcomponenten:
- Model - de bron die moet worden beschermd
- API - een REST-controller om de bron vrij te geven
- Beveiligingsconfiguratie - een klasse om toegangscontrole te definiëren voor de beschermde bron die de API blootlegt
- application.yml - een configuratiebestand om eigenschappen te declareren, inclusief informatie over de autorisatieserver
Laten we ze een voor een bekijken voor onze bronserver die JWT-tokens verwerkt, nadat we een kijkje hebben genomen naar de afhankelijkheden.
4.1. Afhankelijkheden van Maven
We hebben voornamelijk de spring-boot-starter-oauth2-resource-server, Spring Boot's starter voor resource server-ondersteuning. Deze starter bevat standaard Spring Security, dus we hoeven het niet expliciet toe te voegen:
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
Afgezien daarvan hebben we ook webondersteuning toegevoegd.
Voor onze demonstratiedoeleinden zullen we bronnen willekeurig genereren in plaats van ze uit een database te halen, met wat hulp van Apache commons-lang3 bibliotheek.
4.2. Model
Om het simpel te houden, gebruiken we Foo, een POJO, als onze beschermde bron:
openbare klasse Foo {lange privé-id; private String naam; // constructeur, getters en setters}
4.3. API
Hier is onze restcontroller, om te maken Foo beschikbaar voor manipulatie:
@RestController @RequestMapping (value = "/ foos") openbare klasse FooController {@GetMapping (value = "/ {id}") openbare Foo findOne (@PathVariable Lange id) {return nieuwe Foo (Long.parseLong (randomNumeric (2) ), willekeurig Alfabetisch (4)); } @GetMapping openbare lijst findAll () {Lijst fooList = nieuwe ArrayList (); fooList.add (nieuwe Foo (Long.parseLong (randomNumeric (2)), randomAlphabetic (4))); fooList.add (nieuwe Foo (Long.parseLong (randomNumeric (2)), randomAlphabetic (4))); fooList.add (nieuwe Foo (Long.parseLong (randomNumeric (2)), randomAlphabetic (4))); terugkeer fooList; } @ResponseStatus (HttpStatus.CREATED) @PostMapping public void create (@RequestBody Foo newFoo) {logger.info ("Foo gemaakt"); }}
Zoals duidelijk is, hebben we de mogelijkheid om alles te KRIJGEN Foos, KRIJG een Foo door id, en POST een Foo.
4.4. Beveiligingsconfiguratie
In deze configuratieklasse definiëren we toegangsniveaus voor onze bron:
@Configuration public class JWTSecurityConfig breidt WebSecurityConfigurerAdapter uit {@Override protected void configure (HttpSecurity http) genereert uitzondering {http .authorizeRequests (authz -> authz .antMatchers (HttpMethod.GET, "/foos/**").has .antMatchers (HttpMethod.POST, "/foos").hasAuthority("SCOPE_write") .anyRequest (). geauthenticeerd ()) .oauth2ResourceServer (oauth2 -> oauth2.jwt ()); }}
Iedereen met een toegangstoken met de extensie lezen scope kan krijgen Foos. Om een nieuw Foo, moet hun token een schrijven reikwijdte.
Bovendien, we hebben een oproep toegevoegd aan jwt () de ... gebruiken oauth2ResourceServer () DSL om hier het type tokens aan te geven dat door onze server wordt ondersteund.
4.5. application.yml
In de applicatie-eigenschappen, naast het gebruikelijke poortnummer en contextpad, we moeten het pad naar de uitgever-URI van onze autorisatieserver definiëren, zodat de bronserver zijn providerconfiguratie kan ontdekken:
server: poort: 8081 servlet: contextpad: / resource-server-jwt spring: security: oauth2: resourceserver: jwt: issuer-uri: // localhost: 8083 / auth / realms / baeldung
De bronserver gebruikt deze informatie om de JWT-tokens te valideren die binnenkomen vanuit de clienttoepassing, zoals beschreven in stap 9 van ons sequentiediagram.
Om deze validatie te laten werken met behulp van de uitgever-uri eigenschap, moet de autorisatieserver actief zijn. Anders zou de bronserver niet starten.
Als we het zelfstandig moeten starten, kunnen we het jwk-set-uri eigenschap om te verwijzen naar het eindpunt van de autorisatieserver dat openbare sleutels vrijgeeft:
jwk-set-uri: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / certs
En dat is alles wat we nodig hebben om onze server JWT-tokens te laten valideren.
4.6. Testen
Om te testen, zullen we een JUnit opzetten. Om deze test uit te voeren, hebben we zowel de autorisatieserver als de bronserver nodig.
Laten we verifiëren dat we kunnen krijgen Foos van resource-server-jwt met een lezen scoped token in onze test:
@Test openbare ongeldige gegevenUserWithReadScope_whenGetFooResource_thenSuccess () {String accessToken = getAccessToken ("lezen"); Antwoordantwoord = RestAssured.given () .header (HttpHeaders.AUTHORIZATION, "Bearer" + accessToken) .get ("// localhost: 8081 / resource-server-jwt / foos"); assertThat (response.as (List.class)). hasSizeGreaterThan (0); }
In de bovenstaande code krijgen we op regel # 3 een toegangstoken met lezen bereik van de autorisatieserver, die de stappen 1 tot en met 7 van ons sequentiediagram omvat.
Stap 8 wordt uitgevoerd door Wees gerustgesteld‘S krijgen() bellen. Stap 9 wordt uitgevoerd door de bronserver met de configuraties die we hebben gezien en is transparant voor ons als gebruikers.
5. Bronserver - Ondoorzichtige tokens gebruiken
Laten we vervolgens dezelfde componenten bekijken voor onze bronserver die ondoorzichtige tokens verwerkt.
5.1. Afhankelijkheden van Maven
Om ondoorzichtige tokens te ondersteunen, hebben we bovendien de oauth2-oidc-sdk afhankelijkheid:
com.nimbusds oauth2-oidc-sdk 8.19 runtime
5.2. Model en controller
Voor deze zullen we een Bar bron:
openbare klasse Bar {privé lange id; private String naam; // constructeur, getters en setters}
We hebben ook een BarController met eindpunten vergelijkbaar met onze FooController ervoor, om uit te delen Bars.
5.3. application.yml
In de application.yml hier moeten we een introspectie-uri overeenkomend met het introspectie-eindpunt van onze autorisatieserver. Zoals eerder vermeld, is dit hoe een ondoorzichtig token wordt gevalideerd: Toegangsniveaus vergelijkbaar houden met die van Foo voor de Bar bron ook, deze configuratieklasse doet ook een oproep naar opaqueToken () de ... gebruiken oauth2ResourceServer () DSL om het gebruik van het ondoorzichtige token-type aan te geven: Hier specificeren we ook de clientreferenties die overeenkomen met de client van de autorisatieserver die we zullen gebruiken. We hebben deze eerder in ons gedefinieerd application.yml. We zullen een JUnit opzetten voor onze ondoorzichtige token-gebaseerde bronserver, vergelijkbaar met hoe we het deden voor de JWT-server. Laten we in dit geval eens kijken of a schrijven scoped access token kan een Bar naar resource-server-ondoorzichtig: Als we de status CREATED terugkrijgen, betekent dit dat de bronserver met succes het ondoorzichtige token heeft gevalideerd en de Bar voor ons. In deze zelfstudie hebben we gezien hoe u een op Spring Security gebaseerde resource servertoepassing configureert voor het valideren van zowel JWT als ondoorzichtige tokens. Zoals we zagen, Met minimale setup maakte Spring het mogelijk om de tokens naadloos te valideren bij een uitgever en middelen naar de vragende partij sturen - in ons geval een JUnit-test. Zoals altijd is de broncode beschikbaar op GitHub.server: poort: 8082 servlet: contextpad: / resource-server-ondoorzichtig spring: security: oauth2: resourceserver: opaque: introspection-uri: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / token / introspect introspection-client-id: barClient introspection-client-secret: barClientSecret
5.4. Beveiligingsconfiguratie
@Configuration openbare klasse OpaqueSecurityConfig breidt WebSecurityConfigurerAdapter {@Value ("$ {spring.security.oauth2.resourceserver.opaque.introspection-uri}") String introspectionUri uit; @Value ("$ {spring.security.oauth2.resourceserver.opaque.introspection-client-id}") String clientId; @Value ("$ {spring.security.oauth2.resourceserver.opaque.introspection-client-secret}") String clientSecret; @Override protected void configure (HttpSecurity http) genereert uitzondering {http .authorizeRequests (authz -> authz .antMatchers (HttpMethod.GET, "/bars/**").hasAuthority("SCOPE_read") .antMatchers (HttpMethod.POST, " /bars").hasAuthority("SCOPE_write ") .anyRequest (). geverifieerd ()) .oauth2ResourceServer (oauth2 -> oauth2 .opaqueToken (token -> token.introspectionUri (this.introspectionUri) .introspectionClient. clientSecret))); }}
5.5. Testen
@Test openbare leegte gegevenUserWithWriteScope_whenPostNewBarResource_thenCreated () {String accessToken = getAccessToken ("lezen schrijven"); Bar newBar = nieuwe Bar (Long.parseLong (randomNumeric (2)), randomAlphabetic (4)); Antwoordantwoord = RestAssured.given () .contentType (ContentType.JSON) .header (HttpHeaders.AUTHORIZATION, "Bearer" + accessToken) .body (newBar) .log () .all () .post ("// localhost: 8082 / resource-server-opaque / bars "); assertThat (response.getStatusCode ()). isEqualTo (HttpStatus.CREATED.value ()); }
6. Conclusie