JWT gebruiken met Spring Security OAuth

1. Overzicht

In deze zelfstudie bespreken we hoe u onze Spring Security OAuth2-implementatie kunt krijgen om gebruik te maken van JSON Web Tokens.

We blijven ook bouwen aan het Spring REST API + OAuth2 + Angular-artikel in deze OAuth-serie.

2. De OAuth2-autorisatieserver

Eerder bood de Spring Security OAuth-stack de mogelijkheid om een ​​Authorization Server in te richten als Spring Application. We moesten het vervolgens configureren om te gebruiken JwtTokenStore zodat we JWT-tokens konden gebruiken.

De OAuth-stack is echter verouderd door Spring en nu gebruiken we Keycloak als onze autorisatieserver.

Dus deze keer zullen we onze Authorization Server instellen als een embedded Keycloak-server in een Spring Boot-app. Het geeft standaard JWT-tokens uit, dus er is in dit opzicht geen andere configuratie nodig.

3. Bronserver

Laten we nu eens kijken hoe we onze Resource Server kunnen configureren om JWT te gebruiken.

We doen dit in een application.yml het dossier:

server: poort: 8081 servlet: contextpad: / resource-server spring: security: oauth2: resourceserver: jwt: issuer-uri: // localhost: 8083 / auth / realms / baeldung jwk-set-uri: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / certs

JWT's bevatten alle informatie binnen het token. De bronserver moet dus de handtekening van het token verifiëren om er zeker van te zijn dat de gegevens niet zijn gewijzigd. De jwk-set-uri eigendom bevat de openbare sleutel die de server voor dit doel kan gebruiken.

De uitgever-uri eigenschap verwijst naar de basis-autorisatieserver-URI, die ook kan worden gebruikt om het iss claim, als een extra veiligheidsmaatregel.

Ook als het jwk-set-uri eigenschap niet is ingesteld, zal de Resource Server proberen de uitgever-ui om de locatie van deze sleutel te bepalen, vanaf het metadata-eindpunt van de Authorization Server.

Belangrijk is dat het toevoegen van de uitgever-uri eigendom verplicht dat we zouden de Authorization Server moeten hebben draaien voordat we de Resource Server-applicatie kunnen starten.

Laten we nu eens kijken hoe we JWT-ondersteuning kunnen configureren met behulp van Java-configuratie:

@Configuration public class SecurityConfig breidt WebSecurityConfigurerAdapter uit {@Override protected void configure (HttpSecurity http) genereert uitzondering {http.cors () .and () .authorizeRequests () .antMatchers (HttpMethod.GET, "/ user / info", "/ api / foos / ** ") .hasAuthority (" SCOPE_read ") .antMatchers (HttpMethod.POST," / api / foos ") .hasAuthority (" SCOPE_write ") .anyRequest () .authenticated () .en () .oauth2ResourceServer ( ) .jwt (); }}

Hier overschrijven we de standaard Http-beveiligingsconfiguratie. We moeten dat dus expliciet specificeren we willen dat dit zich gedraagt ​​als een Resource Server en dat we JWT-geformatteerde Access Tokens gebruiken met behulp van de methoden oauth2ResourceServer () en jwt ()respectievelijk.

De bovenstaande JWT-configuratie is wat de standaard Spring Boot-instantie ons biedt. Dit kan ook worden aangepast, zoals we binnenkort zullen zien.

4. Aangepaste claims in het token

Laten we nu een infrastructuur opzetten om er een paar te kunnen toevoegen aangepaste claims in het toegangstoken dat wordt geretourneerd door de autorisatieserver. De standaardclaims die door het raamwerk worden geboden, zijn allemaal goed en wel, maar meestal hebben we wat extra informatie in het token nodig om te gebruiken aan de kant van de klant.

Laten we een voorbeeld nemen van een aangepaste claim, organisatie, dat de naam van de organisatie van een bepaalde gebruiker bevat.

4.1. Autorisatieserverconfiguratie

Hiervoor moeten we een aantal configuraties toevoegen aan ons realm-definitiebestand, baeldung-realm.json:

  • Voeg een kenmerk toe organisatie aan onze gebruiker [e-mail beveiligd]:
    "attributes": {"organisatie": "baeldung"},
  • Voeg een ... toe protocolMapper gebeld organisatie naar de jwtClient configuratie:
    "protocolMappers": [{"id": "06e5fc8f-3553-4c75-aef4-5a4d7bb6c0d1", "name": "organisatie", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribuut -mapper "," consentRequired ": false," config ": {" userinfo.token.claim ":" true "," user.attribute ":" organisatie "," id.token.claim ":" true "," access.token.claim ":" true "," claim.name ":" organisation "," jsonType.label ":" String "}}],

Voor een zelfstandige Keycloak-configuratie kan dit ook worden gedaan via de Admin-console.

Bovendien is het belangrijk om dat te onthouden de bovenstaande JSON-configuratie is specifiek voor Keycloak en kan verschillen voor andere OAuth-servers.

Nu deze nieuwe configuratie actief is, krijgen we een extra attribuut organisatie = baeldung, in de token-payload voor [e-mail beveiligd]:

{jti: "989ce5b7-50b9-4cc6-bc71-8f04a639461e" exp: 1585242462 nbf: 0 iat: 1585242162 iss: "// localhost: 8083 / auth / realms / baeldung" sub: "a5461470-33eb-4b2d-824d4-b0f "typ:" Bearer "azp:" jwtClient "auth_time: 1585242162 session_state:" 384ca5cc-8342-429a-879c-c15329820006 "acr:" 1 "scope:" profile write read "organisation:" baeldung "preferred_username:" [email protected ] "}

4.2. Gebruik het toegangstoken in de Angular-client

Vervolgens willen we gebruik maken van de token-informatie in onze Angular Client-applicatie. Daarvoor gebruiken we de angular2-jwt-bibliotheek.

We maken gebruik van de organisatie claim in onze AppService, en voeg een functie toe getOrganization:

getOrganization () {var token = Cookie.get ("access_token"); var payload = this.jwtHelper.decodeToken (token); this.organization = payload.organization; deze.organisatie teruggeven; }

Deze functie maakt gebruik van JwtHelperService van de angular2-jwt bibliotheek om het toegangstoken te decoderen en onze aangepaste claim te krijgen. Nu hoeven we het alleen nog maar in ons AppComponent:

@Component ({selector: 'app-root', sjabloon: `Spring Security Oauth - Autorisatiecode 

{{organisatie}}

`}) exportklasse AppComponent implementeert OnInit {openbare organisatie =" "; constructor (privéservice: AppService) {} ngOnInit () {this.organization = this.service.getOrganization (); }}

5. Toegang tot extra claims op de bronserver

Maar hoe kunnen we toegang krijgen tot die informatie aan de kant van de Resource Server?

5.1. Toegang tot authenticatieserverclaims

Dat is heel simpel: we moeten het gewoon doen haal het uit het org.springframework.security.oauth2.jwt.Jwt‘S AuthenticationPrincipal, zoals we zouden doen voor elk ander kenmerk in UserInfoController:

@GetMapping ("/ user / info") openbare kaart getUserInfo (@AuthenticationPrincipal Jwt principal) {Map map = new Hashtable (); map.put ("user_name", principal.getClaimAsString ("preferred_username")); map.put ("organisatie", principal.getClaimAsString ("organisatie")); return Collections.unmodifiableMap (kaart); } 

5.2. Configuratie om claims toe te voegen / verwijderen / hernoemen

Nu, wat als we meer claims aan de Resource Server-kant willen toevoegen? Of enkele verwijderen of hernoemen?

Laten we zeggen dat we het organisatie claim die binnenkomt van de authenticatieserver om de waarde in hoofdletters te krijgen. Bovendien, als de claim niet aanwezig is op een gebruiker, moeten we de waarde ervan instellen als onbekend.

Om dit te bereiken, zullen we eerst moeten voeg een klasse toe die de Converter interface en gebruik MappedJwtClaimSetConverter om claims om te zetten:

public class OrganizationSubClaimAdapter implementeert Converter {privé finale MappedJwtClaimSetConverter gedelegeerde = MappedJwtClaimSetConverter.withDefaults (Collections.emptyMap ()); openbare kaartconversie (kaartclaims) {kaart geconverteerdClaims = this.delegate.convert (claims); Tekenreeks organisatie = omgezetClaims.get ("organisatie")! = Null? (String) geconverteerdeClaims.get ("organisatie"): "onbekend"; geconverteerdeClaims.put ("organisatie", organisation.toUpperCase ()); return geconverteerdeClaims; }}

Ten tweede, in onze SecurityConfig klasse, we moeten voeg de onze toe JwtDecoder voorbeeld om die van Spring Boot te vervangen en stel onze OrganizationSubClaimAdapter als zijn claimconvertor:

@Bean openbare JwtDecoder customDecoder (OAuth2ResourceServerProperties-eigenschappen) {NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri (properties.getJwt (). GetJwkSetUri ()). Build (); jwtDecoder.setClaimSetConverter (nieuwe OrganizationSubClaimAdapter ()); terug jwtDecoder; } 

Nu wanneer we onze /gebruikers informatie API voor de gebruiker [e-mail beveiligd], we krijgen de organisatie net zo ONBEKEND.

Merk op dat het overschrijven van de default JwtDecoder bean geconfigureerd door Spring Boot moet zorgvuldig worden gedaan om ervoor te zorgen dat alle benodigde configuratie nog steeds is inbegrepen.

6. Sleutels laden vanuit een Java Keystore

In onze vorige configuratie hebben we de standaard openbare sleutel van de autorisatieserver gebruikt om de integriteit van ons token te verifiëren.

We kunnen ook een sleutelpaar en certificaat gebruiken dat is opgeslagen in een Java Keystore-bestand om het ondertekeningsproces uit te voeren.

6.1. Genereer JKS Java KeyStore-bestand

Laten we eerst de sleutels genereren - en meer specifiek een .jks bestand - met behulp van het opdrachtregelprogramma belangrijk hulpmiddel:

keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass

De opdracht genereert een bestand met de naam mytest.jks die onze sleutels bevat - de openbare en privésleutels.

Zorg er ook voor sleutelpas en storepass zijn hetzelfde.

6.2. Exporteer openbare sleutel

Vervolgens moeten we onze openbare sleutel exporteren vanuit gegenereerde JKS, we kunnen de volgende opdracht gebruiken om dit te doen:

keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey

Een voorbeeldantwoord ziet er als volgt uit:

----- BEGIN publieke sleutel ----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp OsMquZMyOyteO2RsVeMLF / hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2 / 5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo / KHk4nz + Fa6P3L8 + L90E / 3qwf6j3 DKWnAgJFRY8AbSYXt1d5ELiIG1 / gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR xxCLXT + tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43 + a9Qn5icSRnDfTAesQ3Cr lAWJKl2kcWU1HwJqw + dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK eQIDAQAB ----- END publieke sleutel --- - ----- BEGIN CERTIFICATE ----- MIIDCzCCAfOgAwIBAgIEGtZIUzANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJ1 czELMAkGA1UECBMCY2ExCzAJBgNVBAcTAmxhMQ0wCwYDVQQDEwR0ZXN0MB4XDTE2 MDMxNTA4MTAzMFoXDTE2MDYxMzA4MTAzMFowNjELMAkGA1UEBhMCdXMxCzAJBgNV BAgTAmNhMQswCQYDVQQHEwJsYTENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAICCtlreMdhLQ5eNQu736TrDKrmTMjsrXjtkbFXj Cxf4VyHmL4nCq9EkM1ZKHRxAQjIhl0A8 + aa4o06t0Rz8tv + ViQQmKu8h4Ey77KTM urIr1zezXWBOyOaV6Pyh5OJ8 / hWuj9y / Pi / dBP96sH + o9wylpwICRUWPAG0mF7dX eRC4iBtf4BKswtH2ZjYYX6wbccFl65aVA09Cn739EFZj0ccQi10 / rRHtbHlhhKnj i y + b10S6ps2XAXtUWfZEEJuN / mvUJ + YnEkZw30wHrENwq5QFiSpdpHFlNR8CasPn WUUmdV + JBFzTMsz3TwWxplOjB3YacsCO0imU 5l + + + AQ51CnkCAwEAAaMhMB8wHQYD VR0OBBYEFOGefUBGquEX9Ujak34PyRskHk WMA0GCSqGSIb3DQEBCwUAA4IBAQB3 1eLfNeq45yO1cXNl0C1IQLknP2WXg89AHEbKkUOA1ZKTOizNYJIHW5MYJU / zScu0 yBobhTDe5hDTsATMa9sN5CPOaLJwzpWV / ZC6WyhAWTfljzZC6d2rL3QYrSIRxmsp / J1Vq9WkesQdShnEGy7GgRgJn4A8CKecHSzqyzXulQ7Zah6GoEUD VJB + + BheP4aN hiYY1OuXD HsdKeQqS + + + 7eM5U7WW6dz2Q8mtFJ5qAxjY75T0pPrHwZMlJUhUZ Q2V FfweJEaoNB9w9McPe1cAiE + oeejZ0jq0el3 / dJsx3rlVqZN + lMhRJJeVHFyeb3XF lLFCUGhA7hxn2xf3x1JW ----- END CERTIFICAAT-----

6.3. Maven-configuratie

Vervolgens willen we niet dat het JKS-bestand wordt opgepikt door het maven-filterproces - dus zullen we ervoor zorgen dat het wordt uitgesloten in de pom.xml:

   src / main / resources true * .jks 

Als we Spring Boot gebruiken, moeten we ervoor zorgen dat ons JKS-bestand wordt toegevoegd aan het klassepad van de toepassing via de Spring Boot Maven-plug-in - addResources:

   org.springframework.boot spring-boot-maven-plugin waar 

6.4. Autorisatieserver

Nu gaan we Keycloak configureren om onze Keypair te gebruiken mytest.jks, door het toe te voegen aan het JSON-bestand van de realmdefinitie KeyProvider sectie als volgt:

{"id": "59412b8d-aad8-4ab8-84ec-e546900fc124", "name": "java-keystore", "providerId": "java-keystore", "subComponents": {}, "config": {" keystorePassword ": [" mypass "]," keyAlias ​​": [" mytest "]," keyPassword ": [" mypass "]," active ": [" true "]," keystore ": [" src / main / resources /mytest.jks "]," prioriteit ": [" 101 "]," enabled ": [" true "]," algoritme ": [" RS256 "]}},

Hier hebben we de prioriteit naar 101, groter dan enig ander Keypair voor onze Autorisatieserver, en ingesteld actief naar waar. Dit wordt gedaan om ervoor te zorgen dat onze Resource Server dit specifieke Keypair uit het jwk-set-uri eigenschap die we eerder hebben gespecificeerd.

Nogmaals, deze configuratie is specifiek voor Keycloak en kan verschillen voor andere OAuth Server-implementaties.

7. Conclusie

In dit korte artikel hebben we ons gericht op het opzetten van ons Spring Security OAuth2-project om JSON Web Tokens te gebruiken.

De volledige implementatie van deze tutorial is te vinden op GitHub.