Overlad Java-godkendelse med JSON Web Tokens (JWT'er)

Gør dig klar til at opbygge eller kæmper med, sikker godkendelse i din Java-applikation? Usikker på fordelene ved at bruge tokens (og specifikt JSON web-tokens), eller hvordan de skal implementeres? Jeg er begejstret for at besvare disse spørgsmål og mere til dig i denne vejledning!

Inden vi dykker ned i JSON Web Tokens (JWT'er) og JJWT-biblioteket (oprettet af Stormpaths CTO, Les Hazlewood og vedligeholdt af et samfund af bidragydere), lad os dække nogle grundlæggende.

1. Authentication vs Token Authentication

Sættet af protokoller, som en applikation bruger til at bekræfte brugeridentitet, er godkendelse. Applikationer har traditionelt opretholdt identitet gennem sessionscookies. Dette paradigme er afhængig af server-side-lagring af session-id'er, som tvinger udviklere til at oprette sessionslagring, der enten er unik og serverspecifik, eller implementeres som et helt separat sessionslagerlag.

Token-godkendelse blev udviklet til at løse problemer server-ID-id'er ikke gjorde og kunne ikke. Ligesom traditionel godkendelse præsenterer brugerne verificerbare legitimationsoplysninger, men udstedes nu et sæt tokens i stedet for et sessions-id. De oprindelige legitimationsoplysninger kunne være standardbrugernavn / adgangskode-par, API-nøgler eller endda tokens fra en anden tjeneste. (Stormpaths API Key Authentication Feature er et eksempel på dette.)

1.1. Hvorfor poletter?

Meget enkelt kan brug af tokens i stedet for session-id'er sænke din serverbelastning, strømline tilladelsesstyring og give bedre værktøjer til understøttelse af en distribueret eller skybaseret infrastruktur. I tilfælde af JWT opnås dette primært gennem den statsløse karakter af disse typer tokens (mere om det nedenfor).

Tokens tilbyder en bred vifte af applikationer, herunder: CSRF-beskyttelsesplaner (Cross Site Request Forgery), OAuth 2.0-interaktioner, session-id'er og (i cookies) som godkendelsesrepræsentationer. I de fleste tilfælde angiver standarder ikke et bestemt format for tokens. Her er et eksempel på et typisk Spring Security CSRF-token i en HTML-form:

Hvis du prøver at sende denne formular uden det rigtige CSRF-token, får du et fejlsvar, og det er nytten af ​​tokens. Ovenstående eksempel er et "dumt" token. Dette betyder, at der ikke er nogen iboende betydning, der skal hentes fra selve symbolet. Det er også her, at JWT'er gør en stor forskel.

2. Hvad er der i en JWT?

JWT'er (udtalt "jots") er URL-sikre, kodede, kryptografisk signerede (undertiden krypterede) strenge, der kan bruges som tokens i en række applikationer. Her er et eksempel på, at en JWT bruges som et CSRF-token:

I dette tilfælde kan du se, at token er meget længere end i vores tidligere eksempel. Ligesom vi så før, hvis formularen sendes uden tokenet, får du et fejlsvar.

Så hvorfor JWT?

Ovenstående token er kryptografisk signeret og kan derfor verificeres, hvilket giver bevis for, at det ikke er blevet manipuleret med. JWT'er er også kodet med en række yderligere oplysninger.

Lad os se på anatomien i en JWT for bedre at forstå, hvordan vi presser al denne godhed ud af den. Du har måske bemærket, at der er tre forskellige sektioner adskilt af perioder (.):

HeadereyJhbGciOiJIUzI1NiJ9
NyttelasteyJqdGkiOiJlNjc4ZjIzMzQ3ZTM0MTBkYjdlNjg3Njc4MjNiMmQ3MCIsImlhdC

I6MTQ2NjYzMzMxNywibmJmIjoxNDY2NjMzMzE3LCJleHAiOjE0NjY2MzY5MTd9

Underskriftrgx_o8VQGuDa2AqCHSgVOD5G68Ld_YYM7N7THmvLIKc

Hver sektion er base64 URL-kodet. Dette sikrer, at det kan bruges sikkert i en URL (mere om dette senere). Lad os se nærmere på hvert afsnit individuelt.

2.1. Overskriften

Hvis du base64 for at afkode overskriften, får du følgende JSON-streng:

{"alg": "HS256"}

Dette viser, at JWT blev underskrevet med HMAC ved hjælp af SHA-256.

2.2. Nyttelasten

Hvis du afkoder nyttelasten, får du følgende JSON-streng (formateret for klarhedens skyld):

{"jti": "e678f23347e3410db7e68767823b2d70", "iat": 1466633317, "nbf": 1466633317, "exp": 1466636917}

Inden for nyttelasten er der, som du kan se, et antal nøgler med værdier. Disse nøgler kaldes "krav", og JWT-specifikationen har syv af disse specificeret som "registrerede" krav. De er:

udstedelseUdsteder
underEmne
audPublikum
ekspUdløb
nbfIkke før
iatUdstedt kl
jtiJWT-ID

Når du bygger en JWT, kan du fremsætte eventuelle brugerdefinerede krav, du ønsker. Ovenstående liste repræsenterer simpelthen de krav, der er reserveret både i den anvendte nøgle og den forventede type. Vores CSRF har et JWT-id, en "Udstedt på" -tid, en "Ikke før" -tid og en udløbstid. Udløbstiden er nøjagtigt et minut efter den udstedte tid.

2.3. Underskriften

Endelig oprettes signaturafsnittet ved at tage overskriften og nyttelasten sammen (med. Imellem) og føre den gennem den specificerede algoritme (HMAC ved hjælp af SHA-256, i dette tilfælde) sammen med en kendt hemmelighed. Bemærk, at hemmeligheden er altid et byte-array og skal have en længde, der giver mening for den anvendte algoritme. Nedenfor bruger jeg en tilfældig base64-kodet streng (for læsbarhed), der er konverteret til et byte-array.

Det ser sådan ud i pseudokode:

computeHMACSHA256 (header + "." + nyttelast, base64DecodeToByteArray ("4pE8z3PBoHjnV1AhvGk + e8h2p + ShZpOnpr8cwHmMh1w ="))

Så længe du kender hemmeligheden, kan du generere signaturen selv og sammenligne dit resultat med signaturafsnittet i JWT for at kontrollere, at den ikke er blevet manipuleret med. Teknisk kaldes en JWT, der er blevet kryptografisk underskrevet, en JWS. JWT'er kan også krypteres og kaldes derefter en JWE. (I praksis bruges udtrykket JWT til at beskrive JWE'er og JWS'er.)

Dette bringer os tilbage til fordelene ved at bruge en JWT som vores CSRF-token. Vi kan verificere signaturen, og vi kan bruge oplysningerne kodet i JWT til at bekræfte dens gyldighed. Så ikke kun skal strengrepræsentationen af ​​JWT matche det, der er gemt på serversiden, vi kan sikre, at den ikke udløber blot ved at inspicere eksp påstand. Dette sparer serveren fra at opretholde yderligere tilstand.

Vi har dækket meget jord her. Lad os dykke ned i en kode!

3. Opsæt JJWT-vejledningen

JJWT (//github.com/jwtk/jjwt) er et Java-bibliotek, der leverer end-til-end JSON Web Token oprettelse og verifikation. Evigt fri og open source (Apache License, version 2.0), den blev designet med en bygningsfokuseret grænseflade, der skjuler det meste af dens kompleksitet.

De primære operationer ved brug af JJWT involverer bygning og parsing af JWT'er. Vi vil se på disse operationer næste, så komme ind på nogle udvidede funktioner i JJWT, og endelig vil vi se JWT'er i aktion som CSRF-tokens i en Spring Security, Spring Boot-applikation.

Koden, der er demonstreret i de følgende afsnit, kan findes her. Bemærk: Projektet bruger Spring Boot fra starten, da det er let at interagere med det API, det udsætter.

For at opbygge projektet skal du udføre følgende:

git klon //github.com/eugenp/tutorials.git cd tutorials / jjwt mvn ren installation

En af de store ting ved Spring Boot er, hvor let det er at affyre en applikation. For at køre JJWT Fun-applikationen skal du blot gøre følgende:

java -jar mål / *. jar 

Der er ti slutpunkter eksponeret i dette eksempelapplikation (jeg bruger httpie til at interagere med applikationen. Det kan findes her.)

http localhost: 8080
Tilgængelige kommandoer (forudsætter httpie - //github.com/jkbrzt/httpie): http // localhost: 8080 / Denne brugsmeddelelse http // localhost: 8080 / static-builder bygger JWT fra hardkodede krav http POST // localhost: 8080 / dynamisk-builder-generelle krav-1 = værdi-1 ... [claim-n = værdi-n] build JWT fra videregivet i krav (ved hjælp af generelle krav kort) http POST // localhost: 8080 / dynamisk-builder-specifik krav -1 = værdi-1 ... [claim-n = value-n] build JWT from passed in claims (ved hjælp af specifikke kravmetoder) http POST // localhost: 8080 / dynamic-builder-compress claim-1 = value-1 ... [claim-n = value-n] build DEFLATE komprimeret JWT fra videregivet i krav http // localhost: 8080 / parser? jwt = Parse bestået i JWT http // localhost: 8080 / parser-enforce? jwt = Parse bestået i JWT håndhæver det 'iss' registrerede krav og det 'hasMotorcycle' tilpassede krav http // localhost: 8080 / get-secrets Vis de signaturnøgler, der aktuelt er i brug. http // localhost: 8080 / refresh-secrets Generer nye signaturnøgler og vis dem. http POST // localhost: 8080 / sæt-hemmeligheder HS256 = base64-kodet-værdi HS384 = base64-kodet-værdi HS512 = base64-kodet-værdi Eksplicit indstillede hemmeligheder til brug i applikationen.

I de følgende afsnit undersøger vi hvert af disse slutpunkter og JJWT-koden indeholdt i håndtererne.

4. Opbygning af JWT'er med JJWT

På grund af JJWTs flydende grænseflade er oprettelsen af ​​JWT dybest set en tretrins proces:

  1. Definitionen af ​​tokens interne krav, som udsteder, emne, udløb og ID.
  2. Den kryptografiske underskrift af JWT (gør det til en JWS).
  3. Komprimering af JWT til en URL-sikker streng i henhold til JWT Compact Serialization regler.

Den endelige JWT vil være en tredelt base64-kodet streng, signeret med den specificerede signaturalgoritme og ved hjælp af den medfølgende nøgle. Efter dette punkt er tokenet klar til at blive delt med en anden part.

Her er et eksempel på JJWT i aktion:

String jws = Jwts.builder () .setIssuer ("Stormpath") .setSubject ("msilverman") .claim ("name", "Micah Silverman") .claim ("scope", "admins") // Fre 24. juni 2016 15:33:42 GMT-0400 (EDT) .setIssuedAt (Date.from (Instant.ofEpochSecond (1466796822L)) // Sat Jun 24 2116 15:33:42 GMT-0400 (EDT) .setExpiration (Date.from (Instant.ofEpochSecond (4622470422L))) .signWith (SignatureAlgorithm.HS256, TextCodec.BASE64.decode ("Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E =")) .compact.

Dette ligner meget koden i StaticJWTController.fixedBuilder metode til kodeprojektet.

På dette tidspunkt er det værd at tale om et par antimønstre relateret til JWT'er og signering. Hvis du nogensinde har set JWT-eksempler før, har du sandsynligvis stødt på et af disse underskrivende anti-mønster-scenarier:

  1. .signWith (SignatureAlgorithm.HS256, "hemmelig" .getBytes ("UTF-8"))
  2. .signWith (SignatureAlgorithm.HS256, "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E =". getBytes ("UTF-8"))
  3. .signWith (SignatureAlgorithm.HS512, TextCodec.BASE64.decode ("Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E ="))

Enhver af de HS typesignaturalgoritmer tager et byte-array. Det er praktisk for mennesker at læse for at tage en streng og konvertere den til et byte-array.

Anti-mønster 1 ovenfor viser dette. Dette er problematisk, fordi hemmeligheden svækkes ved at være så kort, og den er ikke et byte-array i sin oprindelige form. Så for at holde det læsbart kan vi base64 indkode byte-arrayet.

Imidlertid tager anti-mønster 2 ovenfor den base64-kodede streng og konverterer den direkte til et byte-array. Hvad der skal gøres, er at afkode base64-strengen tilbage i det originale byte-array.

Nummer 3 ovenfor viser dette. Så hvorfor er denne også et antimønster? Det er en subtil grund i dette tilfælde. Bemærk, at signaturalgoritmen er HS512. Byte-arrayet er ikke den maksimale længde HS512 kan understøtte, hvilket gør det til en svagere hemmelighed end hvad der er muligt for denne algoritme.

Eksempelkoden inkluderer en klasse kaldet Efterretningstjenesten der sikrer, at hemmeligheder af den rette styrke bruges til den givne algoritme. Ved applikationens starttidspunkt oprettes et nyt sæt hemmeligheder for hver af HS-algoritmerne. Der er slutpunkter for at opdatere hemmelighederne såvel som for eksplicit at indstille hemmelighederne.

Hvis du har projektet kørt som beskrevet ovenfor, skal du udføre følgende, så JWT-eksemplerne nedenfor matcher svarene fra dit projekt.

http POST localhost: 8080 / sæt-secrets \ HS256 = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E =" \ HS384 = "VW96zL + tYlrJLNCQ0j6QPTp + d1q75n / Wa8LVvpWyG8pPZOP6AA5X7XOIlI90sDwx" \ HS512 = "cd + Pr1js + w2qfT2BoCD + tPcYp9LbjpmhSMEJqUob1mcxZ7 + Wmik4AYdjX + DlDjmE4yporzQ9tm7v3z / j + QbdYg =="

Nu kan du ramme / statisk-builder slutpunkt:

http // localhost: 8080 / static-builder

Dette producerer en JWT, der ser sådan ud:

eyJhbGciOiJIUzI1NiJ9. eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwibmFtZSI6Ik1pY2FoIFNpbHZlcm1hbiIsInNjb3BlIjoiYWRtaW5zIiwiaWF kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ

Hit nu:

http //localhost:8080/parser?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwibmFtZSI6Ik1pY2FoIFNpbHZlcm1hbiIsInNjb3BlIjoiYWRtaW5zIiwiaWF0IjoxNDY2Nzk2ODIyLCJleHAiOjQ2MjI0NzA0MjJ9.kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ

Svaret har alle de krav, som vi inkluderede, da vi oprettede JWT.

HTTP / 1.1 200 OK Indholdstype: applikation / json; charset = UTF-8 ... {"jws": {"body": {"exp": 4622470422, "iat": 1466796822, "iss": "Stormpath "," name ":" Micah Silverman "," scope ":" admins "," sub ":" msilverman "}," header ": {" alg ":" HS256 "}," signatur ":" kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ "}," status ":" SUCCESS "}

Dette er parsingsoperationen, som vi kommer ind på i næste afsnit.

Lad os nu ramme et slutpunkt, der tager krav som parametre og bygger en brugerdefineret JWT til os.

http -v POST localhost: 8080 / dynamic-builder-general iss = Stormpath sub = msilverman hasMotorcycle: = true

Bemærk: Der er en subtil forskel mellem harMotorcykel krav og de andre krav. httpie antager, at JSON-parametre er strenge som standard. For at indsende rå JSON ved hjælp af httpie bruger du := form snarere end =. Uden det ville det underkaste sig “HasMotorcycle”: “true”, hvilket ikke er det, vi ønsker.

Her er output:

POST / dynamisk-builder-generel HTTP / 1.1 Accepter: application / json ... {"hasMotorcycle": true, "iss": "Stormpath", "sub": "msilverman"} HTTP / 1.1 200 OK Indholdstype: ansøgning / jSON; charset = UTF-8 ... { "JWT": "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwiaGFzTW90b3JjeWNsZSI6dHJ1ZX0.OnyDs-zoL3-rw1GaSl_KzZzHK9GoiNocu-YwZ_nQNZU", "status": "succes"} 

Lad os se på koden, der bakker dette slutpunkt:

@RequestMapping (værdi = "/ dynamisk-builder-generel", metode = POST) offentlig JwtResponse dynamicBuilderGeneric (@RequestBody Map krav) kaster UupportedEncodingException {String jws = Jwts.builder (). SetClaims (claims) .signWith (SignatureAlgor, secretService.getHS256SecretBytes ()) .compact (); returner nye JwtResponse (jws); }

Linje 2 sikrer, at den indgående JSON automatisk konverteres til et Java-kort, hvilket er super praktisk for JJWT, da metoden på linje 5 simpelthen tager kortet og indstiller alle krav på én gang.

Så kort som denne kode er, har vi brug for noget mere specifikt for at sikre, at de krav, der er godkendt, er gyldige. Bruger .setClaims (Kortkrav) metoden er praktisk, når du allerede ved, at påstandene på kortet er gyldige. Det er her Java-typesikkerheden kommer ind i JJWT-biblioteket.

For hvert af de registrerede krav defineret i JWT-specifikationen er der en tilsvarende Java-metode i JJWT, der tager den spec-korrekte type.

Lad os ramme et andet slutpunkt i vores eksempel og se hvad der sker:

http -v POST localhost: 8080 / dynamic-builder-specific iss = Stormpath sub: = 5 hasMotorcycle: = true

Bemærk, at vi har bestået et helt tal, 5, for "sub" -kravet. Her er output:

POST / dynamisk-builder-specifik HTTP / 1.1 Accepter: applikation / json ... {"hasMotorcycle": true, "iss": "Stormpath", "sub": 5} HTTP / 1.1 400 Forbindelse med dårlig anmodning: tæt indhold- Type: application / json; charset = UTF-8 ... {"exceptionType": "java.lang.ClassCastException", "message": "java.lang.Integer kan ikke kastes til java.lang.String", "status ":" FEJL "}

Nu får vi et fejlsvar, fordi koden håndhæver typen af ​​de registrerede krav. I dette tilfælde, under skal være en streng. Her er koden, der bakker dette slutpunkt:

@RequestMapping (værdi = "/ dynamisk-builder-specifik", metode = POST) offentlig JwtResponse dynamicBuilderSpecific (@RequestBody Map-krav) kaster UupportedEncodingException {JwtBuilder builder = Jwts.builder (); claimss.forEach ((key, value) -> {switch (key) {case "iss": builder.setIssuer ((String) value); break; case "sub": builder.setSubject ((String) value); break ; sag "aud": builder.setAudience ((String) værdi); pause; sag "exp": builder.setExpiration (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ())))); break ; sag "nbf": builder.setNotBefore (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ()))); break; case "iat": builder.setIssuedAt (Date.from (Instant.ofEpochSecond) (Long.parseLong (value.toString ()))); break; case "jti": builder.setId ((String) værdi); break; standard: builder.claim (nøgle, værdi);}}); builder.signWith (SignatureAlgorithm.HS256, secretService.getHS256SecretBytes ()); returner nye JwtResponse (builder.compact ()); }

Ligesom før accepterer metoden en Kort af krav som parameter. Denne gang kalder vi dog den specifikke metode for hvert af de registrerede krav, som håndhæver typen.

En forbedring af dette er at gøre fejlmeddelelsen mere specifik. Lige nu ved vi kun, at et af vores påstande ikke er den rigtige type. Vi ved ikke, hvilket krav der var fejlagtigt, eller hvad det skulle være. Her er en metode, der giver os en mere specifik fejlmeddelelse. Det beskæftiger sig også med en fejl i den aktuelle kode.

privat ugyldighed sikreType (String registreretKrav, Objektværdi, Klasse forventetType) {boolsk isCorrectType = forventetType.isInstans (værdi) || expectType == Long.class && value instanceof Integer; if (! isCorrectType) {String msg = "Forventet type:" + forventet Type.getCanonicalName () + "til registreret krav: '" + registreretKrav + "', men fik værdi:" + værdi + "af typen:" + værdi. getClass (). getCanonicalName (); smid ny JwtException (msg); }}

Linje 3 kontrollerer, at den overførte værdi er af den forventede type. Hvis ikke, a JwtException kastes med den specifikke fejl. Lad os se på dette i aktion ved at foretage det samme opkald, som vi gjorde tidligere:

http -v POST localhost: 8080 / dynamic-builder-specific iss = Stormpath sub: = 5 hasMotorcycle: = true
POST / dynamisk-builder-specifik HTTP / 1.1 Accepter: applikation / json ...Brugeragent: HTTPie / 0.9.3 {"hasMotorcycle": true, "iss": "Stormpath", "sub": 5} HTTP / 1.1 400 Forespørgsel om dårlig forbindelse: luk Content-Type: application / json; charset = UTF -8 ... {"exceptionType": "io.jsonwebtoken.JwtException", "message": "Forventet type: java.lang.Streng for registreret krav: 'sub', men fik værdi: 5 af typen: java.lang .Heltal "," status ":" FEJL "}

Nu har vi en meget specifik fejlmeddelelse, der fortæller os, at under påstanden er den ved en fejltagelse.

Lad os cirkel tilbage til den fejl i vores kode. Problemet har intet at gøre med JJWT-biblioteket. Spørgsmålet er, at JSON til Java Object mapper indbygget i Spring Boot er for smart til vores eget bedste.

Hvis der er en metode, der accepterer et Java-objekt, konverterer JSON-kortlæggeren automatisk et bestået antal, der er mindre end eller lig med 2.147.483.647 til en Java Heltal. På samme måde konverterer den automatisk et bestået antal, der er større end 2.147.483.647 til en Java Lang. Til iat, nbfog eksp krav fra en JWT, vi ønsker, at vores sikreType-test skal bestå, om det kortlagte objekt er et heltal eller et langt. Derfor har vi den ekstra klausul til at bestemme, om den overførte værdi er den rigtige type:

 boolsk isCorrectType = forventet Type.isInstans (værdi) || expectType == Long.class && value instanceof Integer;

Hvis vi forventer en lang, men værdien er en forekomst af heltal, siger vi stadig, at det er den rigtige type. Med en forståelse af hvad der sker med denne validering, kan vi nu integrere det i vores dynamicBuilderSpecific metode:

@RequestMapping (værdi = "/ dynamisk-builder-specifik", metode = POST) offentlig JwtResponse dynamicBuilderSpecific (@RequestBody Map-krav) kaster UupportedEncodingException {JwtBuilder builder = Jwts.builder (); claims.forEach ((nøgle, værdi) -> {switch (nøgle) {sag "iss": sikre Type (nøgle, værdi, String.class); builder.setIssuer ((String) værdi); bryde; sags "sub": sureType (nøgle, værdi, String.class); builder.setSubject ((String) værdi); break; case "aud": sikreType (key, værdi, String.class); builder.setAudience ((String) værdi); break ; sag "exp": sikre Type (nøgle, værdi, Long.class); builder.setExpiration (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ())))); break; case "nbf": sureType (nøgle, værdi, Long.class); builder.setNotBefore (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ()))); break; case "iat": sikre Type (nøgle, værdi, Long.class); builder.setIssuedAt (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ()))); break; case "jti": ensureType (key, value, String.class); builder .setId ((String) -værdi); pause; standard: builder.claim (nøgle, værdi);}}); builder.signWith (SignatureAlgorithm.HS256, secretService.getHS256SecretBytes ()); returner nye JwtResponse (builder.compact ()); }

Bemærk: I al eksempelkoden i dette afsnit underskrives JWT'er med HMAC ved hjælp af SHA-256-algoritmen. Dette er for at holde eksemplerne enkle. JJWT-biblioteket understøtter 12 forskellige signaturalgoritmer, som du kan drage fordel af i din egen kode.

5. Analyse af JWT'er med JJWT

Vi så tidligere, at vores kodeeksempel har et slutpunkt til parsing af en JWT. At ramme dette slutpunkt:

http //localhost:8080/parser?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwibmFtZSI6Ik1pY2FoIFNpbHZlcm1hbiIsInNjb3BlIjoiYWRtaW5zIiwiaWF0IjoxNDY2Nzk2ODIyLCJleHAiOjQ2MjI0NzA0MjJ9.kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ

producerer dette svar:

HTTP / 1.1 200 OK Indholdstype: application / json; charset = UTF-8 ... {"claims": {"body": {"exp": 4622470422, "iat": 1466796822, "iss": "Stormpath "," name ":" Micah Silverman "," scope ":" admins "," sub ":" msilverman "}," header ": {" alg ":" HS256 "}," signatur ":" kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ "}," status ":" SUCCESS "}

Det parser metode til StaticJWTController klasse ser sådan ud:

@RequestMapping (værdi = "/ parser", metode = GET) offentlig JwtResponse-parser (@RequestParam String jwt) kaster Uunderstøttet kodningException {Jws jws = Jwts.parser (). SetSigningKeyResolver (secretService.getSigningKeyRes). returner nye JwtResponse (jws); }

Linje 4 indikerer, at vi forventer, at den indgående streng er en signeret JWT (en JWS). Og vi bruger den samme hemmelighed, der blev brugt til at underskrive JWT i at analysere den. Linje 5 analyserer påstandene fra JWT. Internt verificerer den signaturen, og den kaster en undtagelse, hvis signaturen er ugyldig.

Bemærk, at i dette tilfælde passerer vi i en SigningKeyResolver snarere end en nøgle i sig selv. Dette er et af de mest magtfulde aspekter af JJWT. Overskriften på JWT angiver algoritmen, der blev brugt til at underskrive den. Vi skal dog kontrollere JWT, før vi stoler på det. Det ser ud til at være en fangst 22. Lad os se på SecretService.getSigningKeyResolver metode:

privat SigningKeyResolver signaturKeyResolver = ny SigningKeyResolverAdapter () {@Override offentlig byte [] resolSigningKeyBytes (JwsHeader header, Claims claims) {return TextCodec.BASE64.decode (secrets.get (header.getAlgorithm ())); }};

Brug af adgangen til JwsHeader, Jeg kan inspicere algoritmen og returnere det rette byte-array til den hemmelighed, der blev brugt til at underskrive JWT. Nu vil JJWT kontrollere, at JWT ikke er blevet manipuleret med at bruge dette byte-array som nøglen.

Hvis jeg fjerner det sidste tegn i det passerede i JWT (som er en del af signaturen), er dette svaret:

HTTP / 1.1 400 Forbindelse med dårlig anmodning: luk Indholdstype: applikation / json; charset = UTF-8 Dato: Mandag, 27. juni 2016 13:19:08 GMT Server: Apache-Coyote / 1.1 Overførsel-kodning: klumpet {"exceptionType ":" io.jsonwebtoken.SignatureException "," message ":" JWT-signatur matcher ikke lokalt beregnet signatur. JWT-gyldighed kan ikke hævdes og bør ikke stole på. "," status ":" FEJL "}

6. JWT'er i praksis: Forårssikkerhed CSRF-tokens

Mens fokus for dette indlæg ikke er Spring Security, vil vi dykke ned i det lidt her for at fremvise en vis brug af JJWT-biblioteket i den virkelige verden.

Forfalskning på tværs af webstedsanmodninger er en sikkerhedssårbarhed, hvorved et ondsindet websted lokker dig til at indsende anmodninger til et websted, som du har etableret tillid til. En af de almindelige løsninger til dette er at implementere et synkroniseringstokenmønster. Denne tilgang indsætter et token i webformularen, og applikationsserveren kontrollerer det indgående token i dets lager for at bekræfte, at det er korrekt. Hvis tokenet mangler eller er ugyldigt, svarer serveren med en fejl.

Spring Security har indbygget synkroniseringstokenmønster. Endnu bedre, hvis du bruger Spring Boot- og Thymeleaf-skabeloner, indsættes synkroniseringstoken automatisk for dig.

Som standard er det token, som Spring Security bruger, et "dumt" token. Det er bare en række bogstaver og tal. Denne tilgang er bare fint, og den fungerer. I dette afsnit forbedrer vi den grundlæggende funktionalitet ved at bruge JWT'er som token. Ud over at kontrollere, at det indsendte token er det forventede, validerer vi JWT for yderligere at bevise, at token ikke er blevet manipuleret med og for at sikre, at det ikke udløber.

For at komme i gang skal vi konfigurere Spring Security ved hjælp af Java-konfiguration. Som standard kræver alle stier godkendelse, og alle POST-slutpunkter kræver CSRF-tokens. Vi vil slappe af lidt, så det, vi har bygget indtil videre, stadig fungerer.

@Configuration public class WebSecurityConfig udvider WebSecurityConfigurerAdapter {private String [] ignoreCsrfAntMatchers = {"/ dynamic-builder-compress", "/ dynamic-builder-general", "/ dynamic-builder-specific", "/ set-secrets"}; @ Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http .csrf () .ignoringAntMatchers (ignoreCsrfAntMatchers). Og (). AuthorizeRequests () .antMatchers ("/ **") .permitAll (); }}

Vi laver to ting her. For det første siger vi, at CSRF-tokens er det ikke krævet, når du sender til vores REST API-slutpunkter (linje 15). For det andet siger vi, at uautentificeret adgang skal tillades for alle stier (linje 17 - 18).

Lad os bekræfte, at Spring Security fungerer som vi forventer. Tænd for appen, og tryk på denne url i din browser:

// localhost: 8080 / jwt-csrf-form

Her er Thymeleaf-skabelonen til denne visning:

Dette er en meget grundlæggende form, der POST til det samme slutpunkt, når de sendes. Bemærk, at der ikke er nogen eksplicit henvisning til CSRF-tokens i formularen. Hvis du ser kilden, vil du se noget som:

Dette er al den bekræftelse, du har brug for at vide, at Spring Security fungerer, og at Thymeleaf-skabelonerne automatisk indsætter CSRF-tokenet.

For at gøre værdien til en JWT aktiverer vi en brugerdefineret CsrfTokenRepository. Sådan ændres vores Spring Security-konfiguration:

@Configuration public class WebSecurityConfig udvider WebSecurityConfigurerAdapter {@Autowired CsrfTokenRepository jwtCsrfTokenRepository; @ Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http .csrf () .csrfTokenRepository (jwtCsrfTokenRepository) .ignoringAntMatchers (ignoreCsrfAntMatchers). Og (). AuthorizeRequests () .antMatchers ("/ **). }}

For at forbinde dette har vi brug for en konfiguration, der udsætter en bønne, der returnerer det brugerdefinerede token-lager. Her er konfigurationen:

@Configuration public class CSRFConfig {@Autowired SecretService secretService; @Bean @ConditionalOnMissingBean offentlig CsrfTokenRepository jwtCsrfTokenRepository () {returner nye JWTCsrfTokenRepository (secretService.getHS256SecretBytes ()); }}

Og her er vores brugerdefinerede lager (de vigtige bits):

offentlig klasse JWTCsrfTokenRepository implementerer CsrfTokenRepository {privat statisk endelig Logger log = LoggerFactory.getLogger (JWTCsrfTokenRepository.class); privat byte [] hemmelighed; offentlig JWTCsrfTokenRepository (byte [] hemmelighed) {this.secret = hemmelighed; } @ Override offentlige CsrfToken generereToken (HttpServletRequest anmodning) {String id = UUID.randomUUID (). ToString (). Erstatte ("-", ""); Dato nu = ny dato (); Dato udløb = ny dato (System.currentTimeMillis () + (1000 * 30)); // 30 sekunder String token; prøv {token = Jwts.builder () .setId (id) .setIssuedAt (now) .setNotBefore (now) .setExpiration (exp) .signWith (SignatureAlgorithm.HS256, secret) .compact (); } catch (UnsupportedEncodingException e) {log.error ("Kan ikke oprette CSRf JWT: {}", e.getMessage (), e); token = id; } returner nyt DefaultCsrfToken ("X-CSRF-TOKEN", "_csrf", token); } @Override public void saveToken (CsrfToken token, HttpServletRequest request, HttpServletResponse response) {...} @Override public CsrfToken loadToken (HttpServletRequest request) {...}}

Det generereToken metoden opretter en JWT, der udløber 30 sekunder, efter den er oprettet. Med denne VVS på plads kan vi affyre applikationen igen og se på kilden til / jwt-csrf-form.

Nu ser det skjulte felt sådan ud:

Huzzah! Nu er vores CSRF-token en JWT. Det var ikke så svært.

Dette er dog kun halvdelen af ​​puslespillet. Som standard gemmer Spring Security simpelthen CSRF-tokenet og bekræfter, at det token, der sendes i en webformular, matcher det, der er gemt. Vi vil udvide funktionaliteten til at validere JWT og sikre, at den ikke er udløbet. For at gøre det tilføjer vi et filter. Her ser vores Spring Security-konfiguration ud nu:

@Configuration offentlig klasse WebSecurityConfig udvider WebSecurityConfigurerAdapter {... @ Override-beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http .addFilterAfter (ny JwtCsrfValidatorFilter (), CsrfFilter.class) .csrfs. .and (). authorizeRequests () .antMatchers ("/ **") .permitAll (); } ...}

På linje 9 har vi tilføjet et filter, og vi placerer det i filterkæden efter standard CsrfFilter. Så når vores filter er ramt, vil JWT-tokenet (som helhed) allerede være bekræftet til at være den korrekte værdi gemt af Spring Security.

Her er den JwtCsrfValidatorFilter (det er privat, da det er en indre klasse i vores Spring Security-konfiguration):

privat klasse JwtCsrfValidatorFilter udvider OncePerRequestFilter {@ Override beskyttet ugyldigt doFilterInternal (HttpServletRequest anmodning, HttpServletResponse svar, FilterChain filterChain) kaster ServletException, IOException {// BEMÆRK: En reel implementering skal have en ikke-cken request.getAttribute ("_ csrf"); hvis (// kun pleje, hvis det er en POST "POST" .equals (request.getMethod ()) && // ignorere, hvis anmodningsstien er i vores liste Arrays.binarySearch (ignoreCsrfAntMatchers, request.getServletPath ()) <0 && / / sørg for, at vi har et token-token! = null) {// CsrfFilter sørgede allerede for, at token matchede. // Her sørger vi for, at det ikke er udløbet, prøv {Jwts.parser () .setSigningKey (secret.getBytes ("UTF-8")) .parseClaimsJws (token.getToken ()); } fange (JwtException e) {// sandsynligvis en ExpiredJwtException, men dette vil håndtere enhver request.setAttribute ("undtagelse", e); respons.setStatus (HttpServletResponse.SC_BAD_REQUEST); RequestDispatcher dispatcher = request.getRequestDispatcher ("udløbet-jwt"); dispatcher.forward (anmodning, svar); }} filterChain.doFilter (anmodning, svar); }}

Se på linje 23 den. Vi analyserer JWT som før. I dette tilfælde, hvis en undtagelse smides, videresendes anmodningen til udløbet-jwt skabelon. Hvis JWT valideres, fortsætter behandlingen som normalt.

Dette lukker sløjfen ved at tilsidesætte standard Spring Security CSRF-tokenadfærd med et JWT-token-arkiv og validator.

Hvis du affyrer appen, skal du gå til / jwt-csrf-form, vent lidt mere end 30 sekunder og klik på knappen, du vil se noget som dette:

7. JJWT udvidede funktioner

Vi afslutter vores JJWT-rejse med et ord om nogle af de funktioner, der strækker sig ud over specifikationen.

7.1. Håndhæv krav

Som en del af parsingsprocessen giver JJWT dig mulighed for at specificere krævede krav og værdier, som disse krav skal have. Dette er meget praktisk, hvis der er visse oplysninger i dine JWT'er, der skal være til stede for at du kan betragte dem som gyldige. Det undgår en masse forgreningslogik for manuelt at validere krav. Her er metoden, der tjener / parser-håndhæve slutpunkt for vores prøveprojekt.

@RequestMapping (værdi = "/ parser-enforce", metode = GET) offentlig JwtResponse parserEnforce (@RequestParam String jwt) kaster UnsupportedEncodingException {Jws jws = Jwts.parser () .requireIssuer ("Stormpath"). true) .setSigningKeyResolver (secretService.getSigningKeyResolver ()) .parseClaimsJws (jwt); returner nye JwtResponse (jws); }

Linje 5 og 6 viser syntaksen for registrerede krav samt tilpassede krav. I dette eksempel betragtes JWT som ugyldig, hvis ISS-kravet ikke er til stede eller ikke har værdien: Stormpath. Det vil også være ugyldigt, hvis det brugerdefinerede hasMotorcycle-krav ikke er til stede eller ikke har værdien: true.

Lad os først oprette en JWT, der følger den lykkelige vej:

http -v POST localhost: 8080 / dynamisk-builder-specifik \ iss = Stormpath hasMotorcycle: = true sub = msilverman
POST / dynamisk-builder-specifik HTTP / 1.1 Accepter: application / json ... {"hasMotorcycle": true, "iss": "Stormpath", "sub": "msilverman"} HTTP / 1.1 200 OK Cache-Control: no-cache, no-butik, max-alder = 0, skal-revalidate Content-Type: application / jSON; charset = UTF-8 ... { "JWT": "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIn0.qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0", "-status ":" SUCCESS "}

Lad os nu validere den JWT:

http -v localhost: 8080 / parser-håndhæve JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIn0.qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0?
? GET / parser-håndhæve JWT = http -v localhost: 8080 / parser-håndhæve JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIn0.qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0 HTTP / 1.1 Accept:? * / * ... HTTP / 1.1 200 OK Cache-Control: no cache, no-store, max-age = 0, must-revalidate Content-Type: application / json; charset = UTF-8 ... {"jws": {"body": {"hasMotorcycle": true, "iss ":" Stormsti "," sub ":" msilverman "}," header ": {" alg ":" HS256 "}," signatur ":" qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0 "}," status ":" SUCCESS "}

Så langt så godt. Nu, denne gang, lad os have hasMotorcyklen ude:

http -v POST localhost: 8080 / dynamisk-builder-specifik iss = Stormpath sub = msilverman

Denne gang, hvis vi forsøger at validere JWT:

http -v localhost: 8080 / parser-enforce? jwt = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIn0.YMONlFM1cNaGg

vi får:

? GET / parser-håndhæve JWT = http -v localhost: 8080 / parser-håndhæve JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIn0.YMONlFM1tNgttUYukDRsi9gKIocxdGAOLaJBymaQAWc HTTP / 1.1 Accept:? * / * ... HTTP / 1.1 400 Bad Request Cache-Control: no-cache , no-store, max-age = 0, must-revalidate Connection: close Content-Type: application / json; charset = UTF-8 ... {"exceptionType": "io.jsonwebtoken.MissingClaimException", "message": "Forventet harMotorcykel hævder at være: sandt, men var ikke til stede i JWT-krav.", "Status": "FEJL"}

Dette indikerer, at vores krav til hasMotorcykel var forventet, men manglede.

Lad os gøre endnu et eksempel:

http -v POST localhost: 8080 / dynamic-builder-specific iss = Stormpath hasMotorcycle: = false sub = msilverman

Denne gang er det krævede krav til stede, men det har den forkerte værdi. Lad os se resultatet af:

http -v localhost: 8080 / parser-håndhæve JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjpmYWxzZSwic3ViIjoibXNpbHZlcm1hbiJ9.8LBq2f0eINB34AzhVEgsln_KDo-IyeM8kc-dTzSCr0c?
GET / parser-håndhæve JWT = http -v localhost:? 8080 / parser-håndhæve JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjpmYWxzZSwic3ViIjoibXNpbHZlcm1hbiJ9.8LBq2f0eINB34AzhVEgsln_KDo-IyeM8kc-dTzSCr0c HTTP / 1.1 Accept:? * / * ...HTTP / 1.1 400 Bad Request Cache-Control: no-cache, no-store, max-age = 0, must-revalidate Connection: close Content-Type: application / json; charset = UTF-8 ... {"exceptionType" : "io.jsonwebtoken.IncorrectClaimException", "message": "Forventet hasMotorcykel hævder at være: true, men var: false.", "status": "FEJL"}

Dette indikerer, at vores hasMotorcykel-krav var til stede, men havde en værdi, som ikke var forventet.

MissingClaimException og IncorrectClaimException er dine venner, når du håndhæver krav i dine JWT'er, og en funktion, som kun JJWT-biblioteket har.

7.2. JWT-kompression

Hvis du har mange krav på en JWT, kan den blive stor - så stor, at den muligvis ikke passer i en GET-url i nogle browsere.

Lad os lave en stor JWT:

http -v POST localhost: 8080 / dynamic-builder-specific \ iss = Stormpath hasMotorcycle: = true sub = msilverman the = quick brown = fox jumped = over doven = hund \ et eller andet sted = over rainbow = vej op = høj og = drømmene = du drømte = om

Her er JWT, der producerer:

eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIiwidGhlIjoicXVpY2siLCJicm93biI6ImZveCIsImp1bXBlZCI6Im92ZXIiLCJsYXp5IjoiZG9nIiwic29tZXdoZXJlIjoib3ZlciIsInJhaW5ib3ciOiJ3YXkiLCJ1cCI6ImhpZ2giLCJhbmQiOiJ0aGUiLCJkcmVhbXMiOiJ5b3UiLCJkcmVhbWVkIjoib2YifQ.AHNJxSTiDw_bWNXcuh-LtPLvSjJqwDvOOUcmkk7CyZA

Den sucker er stor! Lad os nu ramme et lidt andet slutpunkt med de samme krav:

http -v POST localhost: 8080 / dynamic-builder-compress \ iss = Stormpath hasMotorcycle: = true sub = msilverman the = quick brown = fox jumped = over doven = dog \ et eller andet sted = over rainbow = langt op = højt og = drømmene = du drømte = om

Denne gang får vi:

eyJhbGciOiJIUzI1NiIsImNhbGciOiJERUYifQ.eNpEzkESwjAIBdC7sO4JegdXnoC2tIk2oZLEGB3v7s84jjse_AFe5FOikc5ZLRycHQ3kOJ0Untu8C43ZigyUyoRYSH6_iwWOyGWHKd2Kn6_QZFojvOoDupRwyAIq4vDOzwYtugFJg1QnJv-5sY-TVjQqN7gcKJ3f-j8c-6J-baDFhEN_uGn58XtnpfcHAAD__w.3_wc-2skFBbInk0YAQ96yGWwr8r1xVdbHn-uGPTFuFE

62 tegn kortere! Her er koden til metoden, der bruges til at generere JWT:

@RequestMapping (værdi = "/ dynamisk-builder-komprimering", metode = POST) offentlig JwtResponse dynamicBuildercompress (@RequestBody Map-krav) kaster UupportedEncodingException {String jws = Jwts.builder (). SetClaims (claims) .compressWith (CompressionCodecs.DEFL .signWith (SignatureAlgorithm.HS256, secretService.getHS256SecretBytes ()) .compact (); returner nye JwtResponse (jws); }

Bemærk på linje 6, vi specificerer en kompressionsalgoritme, der skal bruges. Det er alt der er ved det.

Hvad med at analysere komprimerede JWT'er? JJWT-biblioteket registrerer automatisk komprimeringen og bruger den samme algoritme til at dekomprimere:

GET /parser?jwt=eyJhbGciOiJIUzI1NiIsImNhbGciOiJERUYifQ.eNpEzkESwjAIBdC7sO4JegdXnoC2tIk2oZLEGB3v7s84jjse_AFe5FOikc5ZLRycHQ3kOJ0Untu8C43ZigyUyoRYSH6_iwWOyGWHKd2Kn6_QZFojvOoDupRwyAIq4vDOzwYtugFJg1QnJv-5sY-TVjQqN7gcKJ3f-j8c-6J-baDFhEN_uGn58XtnpfcHAAD__w.3_wc-2skFBbInk0YAQ96yGWwr8r1xVdbHn-uGPTFuFE HTTP / 1.1 Accept: * / * ... HTTP / 1.1 200 OK Cache-Control: no-cache, nej -store, max-age = 0, skal revalidere Content-Type: application / json; charset = UTF-8 ... {"claims": {"body": {"and": "the", "brown" : "fox", "dreamed": "of", "dreams": "you", "hasMotorcycle": true, "iss": "Stormpath", "jumped": "over", "lazy": "dog" , "rainbow": "way", "et sted": "over", "sub": "msilverman", "the": "quick", "up": "high"}, "header": {"alg" : "HS256", "calg": "DEF"}, "signatur": "3_wc-2skFBbInk0YAQ96yGWwr8r1xVdbHn-uGPTFuFE"}, "status": "SUCCESS"}

Læg mærke til kalg påstand i overskriften. Dette blev automatisk kodet i JWT, og det giver tip til parseren om, hvilken algoritme der skal bruges til dekompression.

BEMÆRK: JWE-specifikationen understøtter komprimering. I en kommende udgivelse af JJWT-biblioteket vil vi støtte JWE og komprimerede JWE'er. Vi vil fortsat understøtte komprimering i andre typer JWT'er, selvom den ikke er specificeret.

8. Token-værktøjer til Java Devs

Mens kernefokus i denne artikel ikke var Spring Boot eller Spring Security, var det nemt at demonstrere alle de funktioner, der er diskuteret i denne artikel, ved hjælp af disse to teknologier. Du skal være i stand til at opbygge serveren og begynde at spille med de forskellige slutpunkter, vi har diskuteret. Bare hit:

http // localhost: 8080

Stormpath er også begejstret for at bringe et antal open source-udviklerværktøjer til Java-samfundet. Disse inkluderer:

8.1. JJWT (hvad vi har talt om)

JJWT er et brugervenligt værktøj til udviklere til at oprette og verificere JWT'er i Java. Som mange biblioteker, som Stormpath understøtter, er JJWT helt gratis og open source (Apache License, version 2.0), så alle kan se, hvad det gør, og hvordan det gør det. Tøv ikke med at rapportere problemer, foreslå forbedringer og endda indsende noget kode!

8.2. jsonwebtoken.io og java.jsonwebtoken.io

jsonwebtoken.io er et udviklerværktøj, vi oprettede for at gøre det let at afkode JWT'er. Indsæt blot en eksisterende JWT i det relevante felt for at afkode dens overskrift, nyttelast og signatur. jsonwebtoken.io er drevet af nJWT, det reneste gratis og open source (Apache License, version 2.0) JWT-bibliotek til Node.js-udviklere. Du kan også se kode genereret til forskellige sprog på dette websted. Selve hjemmesiden er open source og kan findes her.

java.jsonwebtoken.io er specifikt til JJWT-biblioteket. Du kan ændre overskrifterne og nyttelasten i det øverste højre felt, se JWT genereret af JJWT i øverste venstre felt og se en prøve af bygherren og parser Java-koden i de nederste felter. Selve hjemmesiden er open source og kan findes her.

8.3. JWT-inspektør

Det nye barn på blokken, JWT Inspector, er en Chrome-udvidelse med open source, der giver udviklere mulighed for at inspicere og debugge JWT'er direkte i browseren. JWT-inspektøren finder JWT'er på dit websted (i cookies, lokal / session-opbevaring og overskrifter) og gør dem let tilgængelige via din navigationslinje og DevTools-panelet.

9. JWT This Down!

JWT'er tilføjer noget intelligens til almindelige tokens. Evnen til at kryptografisk underskrive og verificere, indbygge udløbstider og kode andre oplysninger i JWT'er sætter scenen for virkelig statsløs sessionsstyring. Dette har stor indflydelse på evnen til at skalere applikationer.

På Stormpath bruger vi JWT'er til OAuth2-tokens, CSRF-tokens og påstande mellem mikrotjenester, blandt andre anvendelser.

Når du begynder at bruge JWT'er, går du måske aldrig tilbage til fortidens stumme tokens. Har du spørgsmål? Slå mig op på @afitnerd på Twitter.