Brug af JWT med Spring Security OAuth

1. Oversigt

I denne vejledning diskuterer vi, hvordan vi får vores Spring Security OAuth2-implementering til at gøre brug af JSON Web Tokens.

Vi fortsætter også med at bygge oven på Spring REST API + OAuth2 + Angular-artiklen i denne OAuth-serie.

2. OAuth2 autorisationsserveren

Tidligere tilbød Spring Security OAuth-stakken muligheden for at oprette en autorisationsserver som en Spring Application. Vi måtte derefter konfigurere det til at bruge JwtTokenStore så vi kunne bruge JWT-tokens.

OAuth-stakken er imidlertid udfaset af Spring, og nu bruger vi Keycloak som vores autorisationsserver.

Så denne gang opretter vi vores autorisationsserver som en integreret Keycloak-server i en Spring Boot-app. Det udsteder JWT-tokens som standard, så der er ikke behov for nogen anden konfiguration i denne henseende.

3. Ressource-server

Lad os nu se på, hvordan vi konfigurerer vores ressource server til at bruge JWT.

Vi gør dette i en ansøgning.yml fil:

server: port: 8081 servlet: kontekststi: / resource-server fjeder: sikkerhed: oauth2: resourceserver: jwt: udsteder-uri: // localhost: 8083 / auth / realms / baeldung jwk-set-uri: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / certs

JWT'er inkluderer alle oplysninger inden for tokenet. Så ressourceserveren skal kontrollere tokens underskrift for at sikre, at dataene ikke er blevet ændret. Det jwk-set-uri ejendom indeholder den offentlige nøgle som serveren kan bruge til dette formål.

Det udsteder-uri egenskab peger på base Authorization Server URI, som også kan bruges til at verificere udstedelse krav, som en ekstra sikkerhedsforanstaltning.

Også, hvis jwk-set-uri egenskab er ikke indstillet, vil ressource serveren forsøge at bruge udsteder-ui for at bestemme placeringen af ​​denne nøgle fra slutdatoen for autorisationsserverens metadata.

Vigtigere er at tilføje udsteder-uri ejendom mandater det vi burde have autorisationsserveren kørende, før vi kan starte Resource Server-applikationen.

Lad os nu se, hvordan vi kan konfigurere JWT-understøttelse ved hjælp af Java-konfiguration:

@Configuration offentlig klasse SecurityConfig udvider WebSecurityConfigurerAdapter {@Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http.cors () .and () .authorizeRequests () .antMatchers (HttpMethod.GET, "/ user / info", "/ api) / foos / ** ") .hasAuthority (" SCOPE_read ") .antMatchers (HttpMethod.POST," / api / foos ") .hasAuthority (" SCOPE_write ") .anyRequest () .authenticated () .and () .oauth2ResourceServer ( ) .jwt (); }}

Her tilsidesætter vi standard Http-sikkerhedskonfiguration. Så vi er nødt til at specificere det eksplicit vi ønsker, at dette opfører sig som en ressource-server, og at vi bruger JWT-formaterede adgangstokens ved hjælp af metoderne oauth2ResourceServer () og jwt ()henholdsvis.

Ovenstående JWT-konfiguration er, hvad standard Spring Boot-instansen giver os. Dette kan også tilpasses, som vi snart vil se.

4. Brugerdefinerede krav i poletten

Lad os nu oprette nogle infrastrukturer for at kunne tilføje et par brugerdefinerede krav i Access Token returneret af autorisationsserveren. Standardkravene leveret af rammen er alt sammen godt og godt, men det meste af tiden har vi brug for nogle ekstra oplysninger i tokenet for at bruge på klientsiden.

Lad os tage et eksempel på et brugerdefineret krav, organisation, der indeholder navnet på en given brugers organisation.

4.1. Konfiguration af autorisationsserver

Til dette er vi nødt til at tilføje et par konfigurationer til vores realm definitionsfil, baeldung-realm.json:

  • Tilføj en attribut organisation til vores bruger [e-mail beskyttet]:
    "attributter": {"organisation": "baeldung"},
  • Tilføj en protocolMapper hedder organisation til jwtClient konfiguration:
    "protocolMappers": [{"id": "06e5fc8f-3553-4c75-aef4-5a4d7bb6c0d1", "name": "organisation", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute -mapper "," consentRequired ": false," config ": {" userinfo.token.claim ":" true "," user.attribute ":" organisation "," id.token.claim ":" true "," access.token.claim ":" true "," claim.name ":" organisation "," jsonType.label ":" String "}}],

For en enkeltstående Keycloak-opsætning kan dette også gøres ved hjælp af administrationskonsollen.

Derudover er det vigtigt at huske det JSON-konfigurationen ovenfor er specifik for Keycloak og kan variere for andre OAuth-servere.

Med denne nye konfiguration i gang får vi en ekstra attribut organisation = baeldung, i token-nyttelasten for [e-mail beskyttet]:

{jti: "989ce5b7-50b9-4cc6-bc71-8f04a639461e" exp: 1585242462 nbf: 0 iat: 1585242162 iss: "// localhost: 8083 / auth / realms / baeldung" sub: "a5461470-33eb-4b2d-82d4-bad "typ:" Bearer "azp:" jwtClient "auth_time: 1585242162 session_state:" 384ca5cc-8342-429a-879c-c15329820006 "acr:" 1 "scope:" profil skriv læs "organisation:" baeldung "foretrukket_brugernavn:" [e-mailbeskyttet ] "}

4.2. Brug adgangstokenet i den kantede klient

Dernæst vil vi bruge tokenoplysningerne i vores Angular Client-applikation. Vi bruger angular2-jwt-biblioteket til det.

Vi gør brug af organisation krav i vores AppService, og tilføj en funktion getOrganization:

getOrganization () {var token = Cookie.get ("access_token"); var nyttelast = this.jwtHelper.decodeToken (token); this.organization = nyttelast.organisering; returner denne. organisation; }

Denne funktion gør brug af JwtHelperService fra angular2-jwt bibliotek for at afkode Access Token og få vores brugerdefinerede krav. Nu er alt, hvad vi skal gøre, at vise det i vores AppComponent:

@Component ({selector: 'app-root', skabelon: 'Spring Security Oauth - Autorisationskode 

{{organisation}}

`}) eksportklasse AppComponent implementerer OnInit {offentlig organisation =" "; konstruktør (privat tjeneste: AppService) {} ngOnInit () {this.organization = this.service.getOrganization (); }}

5. Få adgang til ekstra krav i ressource-serveren

Men hvordan kan vi få adgang til disse oplysninger på Resource Server-siden?

5.1. Adgang til godkendelsesserverkrav

Det er virkelig simpelt: vi skal bare udtrække det fra org.springframework.security.oauth2.jwt.Jwt'S AuthenticationHoved, som vi ville gøre for enhver anden attribut i UserInfoController:

@GetMapping ("/ bruger / info") offentligt kort getUserInfo (@AuthenticationPrincipal Jwt-hovedstol) {Map map = new Hashtable (); map.put ("brugernavn", principal.getClaimAsString ("foretrukket_brugernavn")); map.put ("organisation", principal.getClaimAsString ("organisation")); returnere Collections.unmodifiableMap (kort); } 

5.2. Konfiguration til at tilføje / fjerne / omdøbe krav

Hvad nu, hvis vi vil tilføje flere krav på Resource Server-siden? Eller fjerne eller omdøbe nogle?

Lad os sige, at vi vil ændre organisation krav, der kommer ind fra godkendelsesserveren for at få værdien med store bogstaver. Derudover, hvis kravet ikke findes på en bruger, skal vi indstille dets værdi som ukendt.

For at opnå dette skal vi først tilføj en klasse, der implementerer Konverter interface og anvendelser MappedJwtClaimSetConverter at konvertere krav:

offentlig klasse OrganizationSubClaimAdapter implementerer Converter {private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults (Collections.emptyMap ()); offentlig kortkonvertering (kortkrav) {Map convertClaims = this.delegate.convert (krav); Strengorganisation = convertClaims.get ("organisation")! = Null? (String) convertClaims.get ("organisation"): "ukendt"; convertClaims.put ("organisation", organization.toUpperCase ()); returneret konverterede krav }}

For det andet i vores Sikkerhedskonfig klasse, skal vi tilføj vores egne JwtDecoder eksempel for at tilsidesætte den, der leveres af Spring Boot og sæt vores OrganizationSubClaimAdapter som dens konverteringskrav:

@Bean offentlig JwtDecoder customDecoder (OAuth2ResourceServerProperties egenskaber) {NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri (properties.getJwt (). GetJwkSetUri ()). Build (); jwtDecoder.setClaimSetConverter (ny OrganizationSubClaimAdapter ()); returnere jwtDecoder; } 

Nu når vi rammer vores / bruger / info API til brugeren [e-mail beskyttet], vi får den organisation som UKENDT.

Bemærk, at tilsidesættelse af standard JwtDecoder bønne konfigureret af Spring Boot skal udføres omhyggeligt for at sikre, at al den nødvendige konfiguration stadig er inkluderet.

6. Indlæsning af nøgler fra en Java Keystore

I vores tidligere konfiguration brugte vi autorisationsservers standard offentlige nøgle til at kontrollere vores tokens integritet.

Vi kan også bruge et tastatur og certifikat, der er gemt i en Java Keystore-fil til at udføre signaturprocessen.

6.1. Generer JKS Java KeyStore-fil

Lad os først generere nøglerne - og mere specifikt en .jks fil - ved hjælp af kommandolinjeværktøjet nøgleværktøj:

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

Kommandoen genererer en fil, der kaldes mytest.jks som indeholder vores nøgler - de offentlige og private nøgler.

Sørg også for tastatur og lagerplads er det samme.

6.2. Eksporter offentlig nøgle

Dernæst skal vi eksportere vores offentlige nøgle fra genererede JKS, vi kan bruge følgende kommando til at gøre det:

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

Et prøvesvar vil se sådan ud:

----- BEGIN PUBLIC KEY ----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp OsMquZMyOyteO2RsVeMLF / hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2 / 5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo / KHk4nz + Fa6P3L8 + L90E / 3qwf6j3 DKWnAgJFRY8AbSYXt1d5ELiIG1 / gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR xxCLXT + tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43 + a9Qn5icSRnDfTAesQ3Cr lAWJKl2kcWU1HwJqw + dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK eQIDAQAB ----- END PUBLIC KEY --- - ----- 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 CERTIFIKAT-----

6.3. Maven-konfiguration

Dernæst ønsker vi ikke, at JKS-filen afhentes ved maven-filtreringsprocessen - så vi sørger for at udelukke den i pom.xml:

   src / main / resources true * .jks 

Hvis vi bruger Spring Boot, er vi nødt til at sikre os, at vores JKS-fil føjes til applikationsklassestien via Spring Boot Maven Plugin - addResources:

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

6.4. Autorisationsserver

Nu konfigurerer vi Keycloak til at bruge vores Keypair fra mytest.jksved at tilføje det til JSON-filens realm definition KeyProvider afsnit som følger:

{"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 "]," prioritet ": [" 101 "]," aktiveret ": [" sand "]," algoritme ": [" RS256 "]},

Her har vi indstillet prioritet til 101, større end noget andet tastatur til vores autorisationsserver, og indstillet aktiv til rigtigt. Dette gøres for at sikre, at vores ressource server vælger netop dette tastatur fra jwk-set-uri ejendom, vi specificerede tidligere.

Igen er denne konfiguration specifik for Keycloak og kan variere for andre OAuth Server-implementeringer.

7. Konklusion

I denne hurtige artikel fokuserede vi på at oprette vores Spring Security OAuth2-projekt til at bruge JSON Web Tokens.

Den fulde implementering af denne vejledning kan findes i over på GitHub.