Implementering av OAuth 2.0 autorisasjonsrammen ved bruk av Jakarta EE

1. Oversikt

I denne opplæringen skal vi gi en implementering av OAuth 2.0 Authorization Framework ved hjelp av Jakarta EE og MicroProfile. Viktigst, vi skal implementere samspillet mellom OAuth 2.0-rollene gjennom tilskuddstypen Autorisasjonskode. Motivasjonen bak denne skrivingen er å gi støtte til prosjekter som er implementert ved hjelp av Jakarta EE, da dette ennå ikke gir støtte for OAuth.

For den viktigste rollen, autorisasjonsserveren, vi skal implementere autorisasjonsendepunktet, tokenendepunktet og i tillegg JWK nøkkelendepunktet, som er nyttig for ressursserveren for å hente den offentlige nøkkelen.

Ettersom vi ønsker at implementeringen skal være enkel og enkel for et raskt oppsett, skal vi bruke en forhåndsregistrert butikk med klienter og brukere, og åpenbart en JWT-butikk for tilgangstokener.

Før du hopper rett inn i emnet, er det viktig å merke seg at eksemplet i denne opplæringen er for pedagogiske formål. For produksjonssystemer anbefales det sterkt å bruke en moden, velprøvd løsning som Keycloak.

2. OAuth 2.0 Oversikt

I denne delen skal vi gi en kort oversikt over OAuth 2.0-rollene og bevillingsflyten for autorisasjonskoden.

2.1. Roller

OAuth 2.0-rammeverket innebærer samarbeid mellom de fire følgende rollene:

  • Ressursinnehaver: Vanligvis er dette sluttbrukeren - det er enheten som har noen ressurser som er verdt å beskytte
  • Ressursserver: En tjeneste som beskytter ressurseierens data, som vanligvis publiseres gjennom et REST API
  • Klient: Et program som bruker ressurseierens data
  • Autorisasjonsserver: En applikasjon som gir tillatelse - eller autoritet - til klienter i form av utløpende tokens

2.2. Autorisasjonstilskuddstyper

EN tilskuddstype er hvordan en klient får tillatelse til å bruke ressurseierens data, til slutt i form av et tilgangstoken.

Naturligvis foretrekker forskjellige typer klienter forskjellige typer tilskudd:

  • Godkjennelseskoden: Foretrekkes oftestom det er en webapplikasjon, en innfødt applikasjon eller en enkeltsidesapplikasjon, selv om innfødte og ensidige apper krever ekstra beskyttelse kalt PKCE
  • Oppdater Token: Et spesielt fornyelsestilskudd, egnet for webapplikasjoner for å fornye sitt eksisterende token
  • Klientlegitimasjon: Foretrukket for tjeneste-til-tjeneste-kommunikasjon, si når ressurseieren ikke er sluttbruker
  • RessursinnehaverPassord: Foretrukket for første parts autentisering av innfødte applikasjoner, si når mobilappen trenger sin egen påloggingsside

I tillegg kan klienten bruke implisitt tilskuddstype. Imidlertid er det vanligvis sikrere å bruke autorisasjonskodetildelingen med PKCE.

2.3. Autorisasjonskode Grant Flow

Siden tillatelsesflyten for autorisasjonskoden er den vanligste, la oss også se på hvordan det fungerer, og det er faktisk det vi skal bygge i denne opplæringen.

En applikasjon - en klient - ber om tillatelse ved å omdirigere til autorisasjonsserverens /autorisere endepunkt. Til dette endepunktet gir applikasjonen en Ring tilbake endepunkt.

Autorisasjonsserveren vil vanligvis be sluttbrukeren - ressurseieren - om tillatelse. Hvis sluttbrukeren gir tillatelse, da autorisasjonsserveren viderekobler tilbake til tilbakeringing med en kode.

Søknaden mottar denne koden og deretter foretar et autentisert anrop til autorisasjonsserveren / token endepunkt. Med “autentisert” mener vi at applikasjonen beviser hvem det er som en del av denne samtalen. Hvis alt vises i rekkefølge, reagerer autorisasjonsserveren med tokenet.

Med symbolet i hånden, applikasjonen gjør sin forespørsel til API - ressursserveren - og det API-et vil bekrefte tokenet. Det kan be autorisasjonsserveren om å bekrefte tokenet ved hjelp av det / introspekt endepunkt. Eller hvis tokenet er selvstendig, kan ressursserveren optimaliseres med lokal verifisering av tokens signatur, slik tilfellet er med JWT.

2.4. Hva støtter Jakarta EE?

Ikke mye ennå. I denne opplæringen bygger vi det meste fra grunnen av.

3. Autoriseringsserver for OAuth 2.0

I denne implementeringen vil vi fokusere på den mest brukte tilskuddstypen: Godkjennelseskoden.

3.1. Registrering av klient og bruker

En autorisasjonsserver vil selvfølgelig trenge å vite om klientene og brukerne før den kan godkjenne deres forespørsler. Og det er vanlig at en autorisasjonsserver har et brukergrensesnitt for dette.

For enkelhets skyld bruker vi en forhåndskonfigurert klient:

INSERT INTO clients (client_id, client_secret, redirect_uri, scope, authorized_grant_types) VALUES ('webappclient', 'webappclientsecret', '// localhost: 9180 / callback', 'resource.read resource.write', 'autorisasjon_kode refresh_token');
@Entity @Table (name = "clients") public class Client {@Id @Column (name = "client_id") private String clientId; @Column (name = "client_secret") private String clientSecret; @Column (name = "redirect_uri") private String redirectUri; @Column (name = "scope") privat strengomfang; // ...}

Og en forhåndskonfigurert bruker:

INSERT INTO users (user_id, password ,roller, scopes) VALUES ('appuser', 'appusersecret', 'USER', 'resource.read resource.write');
@Entity @Table (name = "brukere") offentlig klasse Bruker implementerer rektor {@Id @Column (name = "user_id") private streng brukerId; @Column (name = "password") privat strengpassord; @Column (name = "roller ") private Stringroller; @Column (name = "scopes") private strengomfang; // ...}

Merk at av hensyn til denne opplæringen har vi brukt passord i ren tekst, men i et produksjonsmiljø, bør de hashes.

For resten av denne opplæringen viser vi hvordan appuser - ressurseieren - kan gi tilgang til webappclient - søknaden - ved å implementere autorisasjonskoden.

3.2. Autorisasjonsendepunkt

Hovedrollen til autorisasjonsendepunktet er å først autentisere brukeren og be om tillatelsene - eller omfang - som applikasjonen ønsker.

Som instruert av OAuth2-spesifikasjonene, bør dette endepunktet støtte HTTP GET-metoden, selv om det også kan støtte HTTP POST-metoden. I denne implementeringen støtter vi bare HTTP GET-metoden.

Først, autorisasjonsendepunktet krever at brukeren blir godkjent. Spesifikasjonen krever ikke en bestemt måte her, så la oss bruke skjemaautentisering fra Jakarta EE 8 Security API:

@FormAuthenticationMechanismDefinition (loginToContinue = @LoginToContinue (loginPage = "/login.jsp", errorPage = "/login.jsp"))

Brukeren blir omdirigert til /login.jsp for autentisering og vil da være tilgjengelig som en CallerPrincipal gjennom SecurityContext API:

Rektor rektor = securityContext.getCallerPrincipal ();

Vi kan sette disse sammen ved hjelp av JAX-RS:

@FormAuthenticationMechanismDefinition (loginToContinue = @LoginToContinue (loginPage = "/login.jsp", errorPage = "/login.jsp")) @Path ("authorize") public class AuthorizationEndpoint {// ... @GET @Produces (MediaType. TEXT_HTML) offentlig respons doGet (@Context HttpServletRequest-forespørsel, @Context HttpServletResponse-svar, @Context UriInfo uriInfo) kaster ServletException, IOException {MultivaluedMap params = uriInfo.getQueryParameters (); Rektor rektor = securityContext.getCallerPrincipal (); // ...}}

På dette tidspunktet kan autorisasjonssluttpunktet begynne å behandle søknaden om søknaden, som må inneholde respons_type og klient-ID parametere og - valgfritt, men anbefalt - redirect_uri, omfang, og stat parametere.

De klient-ID skal være en gyldig klient, i vårt tilfelle fra klienter databasetabell.

De redirect_uri, hvis spesifisert, skal også matche det vi finner i klienter databasetabell.

Og fordi vi gjør autorisasjonskode, respons_type er kode.

Siden autorisasjon er en trinnvis prosess, kan vi midlertidig lagre disse verdiene i økten:

request.getSession (). setAttribute ("ORIGINAL_PARAMS", params);

Og så forbered deg på å spørre brukeren hvilke tillatelser applikasjonen kan bruke, og omdirigere til den siden:

Streng tillattScopes = checkUserScopes (user.getScopes (), wantedScope); request.setAttribute ("scopes", allowedScopes); request.getRequestDispatcher ("/ authorize.jsp"). videresend (forespørsel, svar);

3.3. Godkjenning av brukeromfang

På dette punktet gir nettleseren et autorisasjonsgrensesnitt for brukeren, og brukeren tar et valg. Deretter, nettleseren sender brukerens valg i en HTTP-POST:

@POST @Consumes (MediaType.APPLICATION_FORM_URLENCODED) @Produces (MediaType.TEXT_HTML) public Response doPost (@Context HttpServletRequest request, @Context HttpServletResponse response, MultivaluedMap params) throws Exception {MultivaluedMap original (MultivaluedMap original). "ORIGINAL_PARAMS"); // ... StrenggodkjenningStatus = params.getFirst ("godkjenningsstatus"); // JA ELLER NEI // ... hvis JA-listen er godkjentSkoper = params.get ("omfang"); // ...}

Deretter genererer vi en midlertidig kode som refererer til user_id, client_id, ogredirect_uri, som alle applikasjonen vil bruke senere når den treffer token-endepunktet.

Så la oss lage en Godkjennelseskoden JPA-enhet med en automatisk generert ID:

@Entity @Table (navn) offentlig klasse AuthorizationCode {@Id @GeneratedValue (strategi = GenerationType.AUTO) @Column (name = "code") privat strengkode; // ...}

Og fyll den deretter:

AuthorizationCode authorCode = ny AuthorizationCode (); authorisationCode.setClientId (clientId); authorisationCode.setUserId (userId); authorisationCode.setApprovedScopes (String.join ("", autoriserte Scope)); authorisationCode.setExpirationDate (LocalDateTime.now (). plusMinutes (2)); authorisationCode.setRedirectUri (redirectUri);

Når vi lagrer bønnen, fylles kodeattributtet automatisk, slik at vi kan få det og sende det tilbake til klienten:

appDataRepository.save (autorisasjonskode); Strengkode = autorisasjonskode.getkode ();

Noter det autorisasjonskoden vår utløper om to minutter - vi bør være så konservative som vi kan med denne utløpet. Det kan være kort siden klienten skal bytte den med en gang for et tilgangstoken.

Vi omdirigerer tilbake til applikasjonens redirect_uri, gir den koden så vel som noen stat parameteren som applikasjonen spesifiserte i /autorisere be om:

StringBuilder sb = ny StringBuilder (redirectUri); // ... sb.append ("? code ="). append (code); Strengtilstand = params.getFirst ("tilstand"); if (state! = null) {sb.append ("& state ="). append (state); } URI-plassering = UriBuilder.fromUri (sb.toString ()). Build (); return Response.seeOther (location) .build ();

Merk igjen at omdirigereUri er det som eksisterer i klienter bord, ikke redirect_uri forespørselparameter.

Så vårt neste trinn er at klienten mottar denne koden og bytter den mot et tilgangstoken ved hjelp av token-endepunktet.

3.4. Token Endpoint

I motsetning til autorisasjonsendepunktet, token-endepunktet trenger ikke en nettleser for å kommunisere med klienten, og vi vil derfor implementere det som et JAX-RS-sluttpunkt:

@Path ("token") offentlig klasse TokenEndpoint {List supportedGrantTypes = Collections.singletonList ("autorisasjonskode"); @Inject private AppDataRepository appDataRepository; @Inject Instance autorisasjonGrantTypeHandlers; @POST @Produces (MediaType.APPLICATION_JSON) @Consumes (MediaType.APPLICATION_FORM_URLENCODED) public Response token (MultivaluedMap params, @HeaderParam (HttpHeaders.AUTHORIZATION) String authHeader) kaster JOSEException // ...

Token-endepunktet krever en POST, samt koding av parametrene ved hjelp av application / x-www-form-urlencoded Media type.

Som vi diskuterte, vil vi bare støtte Godkjennelseskoden tilskuddstype:

List supportedGrantTypes = Collections.singletonList ("autorisasjonskode");

Så, mottatt tilskuddstype som en nødvendig parameter skal støttes:

Streng grantType = params.getFirst ("grant_type"); Objects.requireNonNull (grantType, "grant_type params is required"); if (! supportedGrantTypes.contains (grantType)) {JsonObject error = Json.createObjectBuilder () .add ("error", "unsupported_grant_type") .add ("error_description", "grant type should be one of:" + supportGrantTypes). bygge(); returner Response.status (Response.Status.BAD_REQUEST) .entity (feil) .build (); }

Deretter sjekker vi klientautentisering gjennom HTTP Basic autentisering. Det er, vi sjekker hvis mottatt klient-ID og klienthemmelighet, gjennom Autorisasjon Overskrift, samsvarer med en registrert klient:

Streng [] clientCredentials = ekstrakt (authHeader); String clientId = clientCredentials [0]; String clientSecret = clientCredentials [1]; Klientklient = appDataRepository.getClient (clientId); if (client == null || clientSecret == null ||! clientSecret.equals (client.getClientSecret ())) {JsonObject error = Json.createObjectBuilder () .add ("error", "invalid_client") .build () ; returner Response.status (Response.Status.UNAUTHORIZED) .entity (error) .build (); }

Til slutt delegerer vi produksjonen av TokenResponse til en tilsvarende tilskuddstypebehandler:

offentlig grensesnitt AuthorizationGrantTypeHandler {TokenResponse createAccessToken (String clientId, MultivaluedMap params) kaster Unntak; }

Siden vi er mer interessert i godkjenningskodetypen, har vi gitt en tilstrekkelig implementering som en CDI-bønne og dekorert den med Navngitt kommentar:

@Named ("autorisasjonskode")

Ved kjøretid, og ifølge mottatt tilskuddstype verdi aktiveres den tilsvarende implementeringen gjennom CDI Instance-mekanismen:

Streng grantType = params.getFirst ("grant_type"); // ... AuthorizationGrantTypeHandler autorisasjonGrantTypeHandler = autorisasjonGrantTypeHandlers.select (NamedLiteral.of (grantType)). Get ();

Det er nå på tide å produsere / token‘S svar.

3.5. RSA Private og offentlige nøkler

Før vi genererer tokenet, trenger vi en RSA privat nøkkel for å signere tokens.

For dette formålet bruker vi OpenSSL:

# PRIVATE KEY openssl genpkey -algorithm RSA -out private-key.pem -pkeyopt rsa_keygen_bits: 2048

De private-key.pem blir levert til serveren gjennom MicroProfile Config signeringstast eiendom ved hjelp av filen META-INF / microprofile-config.properties:

signeringsnøkkel = / META-INF / private-key.pem

Serveren kan lese eiendommen ved hjelp av den injiserte Konfig gjenstand:

String signaturnøkkel = config.getValue ("signeringsnøkkel", String.class);

På samme måte kan vi generere den tilsvarende offentlige nøkkelen:

# OFFENTLIG NØKKEL openssl rsa -pubout -in private-key.pem -out public-key.pem

Og bruk MicroProfile Config verificationKey å lese den:

verifiseringsnøkkel = / META-INF / public-key.pem

Serveren skal gjøre den tilgjengelig for ressursserveren for formålet med verifisering. Dette har blitt gjort gjennom et JWK-sluttpunkt.

Nimbus JOSE + JWT er et bibliotek som kan være til stor hjelp her. La oss først legge til nimbus-jose-jwt avhengighet:

 com.nimbusds nimbus-jose-jwt 7.7 

Og nå kan vi utnytte Nimbus JWK-støtte for å forenkle endepunktet vårt:

@Path ("jwk") @ApplicationScoped public class JWKEndpoint {@GET public Response getKey (@QueryParam ("format") Strengformat) kaster unntak {// ... String verificationkey = config.getValue ("verificationkey", String. klasse); Streng pemEncodedRSAPublicKey = PEMKeyUtils.readKeyAsString (verifiseringstast); if (format == null || format.equals ("jwk")) {JWK jwk = JWK.parseFromPEMEncodedObjects (pemEncodedRSAPublicKey); returner Response.ok (jwk.toJSONString ()). type (MediaType.APPLICATION_JSON) .build (); } annet hvis (format.equals ("pem")) {return Response.ok (pemEncodedRSAPublicKey) .build (); } // ...}}

Vi har brukt formatet parameter for å veksle mellom PEM- og JWK-formatene. MicroProfile JWT som vi vil bruke til å implementere ressursserveren støtter begge disse formatene.

3.6. Token Endpoint Response

Det er nå tid for en gitt AuthorizationGrantTypeHandler for å lage token-responsen. I denne implementeringen støtter vi bare de strukturerte JWT-tokens.

For å lage et token i dette formatet, bruker vi igjen Nimbus JOSE + JWT-biblioteket, men det er også mange andre JWT-biblioteker.

Så, for å lage en signert JWT, vi må først konstruere JWT-overskriften:

JWSHeader jwsHeader = ny JWSHeader.Builder (JWSAlgorithm.RS256) .type (JOSEObjectType.JWT) .build ();

Deretter bygger vi nyttelasten hvilken er en Sett av standardiserte og tilpassede krav:

Øyeblikkelig nå = Øyeblikkelig. Nå (); Lang utløperInMin = 30L; Date in30Min = Date.from (now.plus (expiresInMin, ChronoUnit.MINUTES)); JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder () .issuer ("// localhost: 9080") .subject (authorisationCode.getUserId ()) .claim ("upn", authorisationCode.getUserId ()). Audience ("// localhost: 9280 ") .claim (" scope ", authorisationCode.getApprovedScopes ()) .claim (" groups ", Arrays.asList (authorisationCode.getApprovedScopes (). Split (" "))) .expirationTime (in30Min) .notBeforeTime (Date. fra (nå)) .issueTime (Date.from (now)) .jwtID (UUID.randomUUID (). toString ()) .build (); SignertJWT signertJWT = ny SignertJWT (jwsHeader, jwtClaims);

I tillegg til standard JWT-krav, har vi lagt til to krav til - upn og grupper - etter behov av MicroProfile JWT. De upn vil bli kartlagt til Jakarta EE Security CallerPrincipal og grupper blir kartlagt til Jakarta EE Roller.

Nå som vi har toppteksten og nyttelasten, vi må signere tilgangstokenet med en RSA privat nøkkel. Den tilsvarende RSA offentlige nøkkelen vil bli eksponert gjennom JWK-endepunktet eller bli gjort tilgjengelig på andre måter, slik at ressursserveren kan bruke den til å verifisere tilgangstokenet.

Siden vi har gitt den private nøkkelen som et PEM-format, bør vi hente den og forvandle den til en RSAPrivateKey:

SignertJWT signertJWT = ny SignertJWT (jwsHeader, jwtClaims); // ... String signaturnøkkel = config.getValue ("signaturnøkkel", String.class); Streng pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString (signeringsnøkkel); RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects (pemEncodedRSAPrivateKey);

Neste, vi signerer og serierer JWT:

signertJWT.sign (ny RSASSASigner (rsaKey.toRSAPrivateKey ())); Streng accessToken = signertJWT.serialize ();

Og endelig vi konstruerer et tokensvar:

returner Json.createObjectBuilder () .add ("token_type", "Bearer") .add ("access_token", accessToken) .add ("expires_in", expiresInMin * 60) .add ("scope", authorisationCode.getApprovedScopes ()) .bygge();

som, takket være JSON-P, serielliseres til JSON-format og sendes til klienten:

{"access_token": "acb6803a48114d9fb4761e403c17f812", "token_type": "Bearer", "expires_in": 1800, "scope": "resource.read resource.write"}

4. OAuth 2.0-klient

I denne delen vil vi være det bygge en nettbasert OAuth 2.0-klient ved hjelp av Servlet, MicroProfile Config og JAX RS Client APIer.

Mer presist implementerer vi to hovedservlets: en for å be om autorisasjonsserverens autorisasjonsendepunkt og få en kode ved hjelp av godkjenningskode-tildelingstypen, og en annen servlet for å bruke den mottatte koden og be om et tilgangstoken fra autorisasjonsserverens token-endepunkt .

I tillegg implementerer vi to servlets til: En for å få et nytt tilgangstoken ved hjelp av tilskuddstypen for oppdateringstoken, og et annet for å få tilgang til ressursserverens APIer.

4.1. OAuth 2.0 klientdetaljer

Siden klienten allerede er registrert på autorisasjonsserveren, må vi først oppgi klientregistreringsinformasjonen:

  • klient-ID: Client Identifier og den blir vanligvis utstedt av autorisasjonsserveren under registreringsprosessen.
  • klienthemmelighet: Klienthemmelighet.
  • redirect_uri: Sted hvor du skal motta autorisasjonskoden.
  • omfang: Kunden ba om tillatelser.

I tillegg bør klienten kjenne autorisasjonsserverens autorisasjon og token-endepunkter:

  • autorisasjon_uri: Plassering av godkjenningsserverautorisasjonsendepunktet som vi kan bruke for å få en kode.
  • token_uri: Plassering av autorisasjonsserverens token endepunkt som vi kan bruke for å få et token.

All denne informasjonen blir gitt gjennom MicroProfile Config-filen, META-INF / microprofile-config.properties:

# Client registration client.clientId = webappclient client.clientSecret = webappclientsecret client.redirectUri = // localhost: 9180 / callback client.scope = resource.read resource.write # Provider provider.authorizationUri = // 127.0.0.1:9080/authorize provider .tokenUri = // 127.0.0.1:9080/token

4.2. Autorisasjonskode forespørsel

Strømmen med å få en autorisasjonskode starter med klienten ved å omdirigere nettleseren til autorisasjonsserverens autorisasjonsendepunkt.

Vanligvis skjer dette når brukeren prøver å få tilgang til et beskyttet ressurs-API uten autorisasjon, eller ved eksplisitt ved å påkalle klienten /autorisere sti:

@WebServlet (urlPatterns = "/ authorize") offentlig klasse AuthorizationCodeServlet utvider HttpServlet {@Inject private Config config; @Override beskyttet ugyldig doGet (HttpServletRequest forespørsel, HttpServletResponse svar) kaster ServletException, IOException {// ...}}

I doGet () metode, starter vi med å generere og lagre en sikkerhetsstatusverdi:

Strengtilstand = UUID.randomUUID (). ToString (); request.getSession (). setAttribute ("CLIENT_LOCAL_STATE", tilstand);

Deretter henter vi informasjon om klientkonfigurasjonen:

Streng autorisasjonUri = config.getValue ("provider.authorizationUri", String.class); String clientId = config.getValue ("client.clientId", String.class); String redirectUri = config.getValue ("client.redirectUri", String.class); String scope = config.getValue ("client.scope", String.class);

Vi legger deretter til disse informasjonene som spørringsparametere til autorisasjonsserverens autorisasjonsendepunkt:

Streng autorisasjonLocation = autorisasjonUri + "? Response_type = kode" + "& client_id =" + clientId + "& redirect_uri =" + redirectUri + "& scope =" + scope + "& state =" + state;

Og til slutt omdirigerer vi nettleseren til denne URL:

respons.sendRedirect (authorisationLocation);

Etter å ha behandlet forespørselen, autorisasjonsserverens autorisasjonsendepunkt genererer og legger til en kode, i tillegg til mottatt tilstandsparameter, til redirect_uri og vil omdirigere nettleseren // localhost: 9081 / tilbakeringing? code = A123 & state = Y.

4.3. Forespørsel om tilgangstoken

Servlet for tilbakeringing fra klienten, /Ring tilbake, begynner med å validere det mottatte stat:

String localState = (String) request.getSession (). GetAttribute ("CLIENT_LOCAL_STATE"); if (! localState.equals (request.getParameter ("state"))) {request.setAttribute ("error", "The state attribute matches not!"); forsendelse ("/", forespørsel, svar); komme tilbake; }

Neste, vi bruker koden vi tidligere mottok for å be om et tilgangstoken gjennom autorisasjonsserverens token-endepunkt:

Strengkode = request.getParameter ("kode"); Klientklient = ClientBuilder.newClient (); WebTarget target = client.target (config.getValue ("provider.tokenUri", String.class)); Form form = ny Form (); form.param ("grant_type", "autorisasjonskode"); form.param ("kode", kode); form.param ("redirect_uri", config.getValue ("client.redirectUri", String.class)); TokenResponse tokenResponse = target.request (MediaType.APPLICATION_JSON_TYPE). Header (HttpHeaders.AUTHORIZATION, getAuthorizationHeaderValue ()) .post (Entity.entity (form, MediaType.APPLICATION_FORM_URLENCODED_TYesponse)

Som vi kan se, er det ingen nettleserinteraksjon for denne samtalen, og forespørselen blir gjort direkte ved hjelp av JAX-RS-klient-API som en HTTP POST.

Ettersom token-endepunktet krever klientautentisering, har vi tatt med klientlegitimasjonen klient-ID og klienthemmelighet i Autorisasjon Overskrift.

Klienten kan bruke dette tilgangstokenet til å påkalle ressursserver-API-ene som er gjenstand for neste underavsnitt.

4.4. Beskyttet ressurstilgang

På dette tidspunktet har vi et gyldig tilgangstoken, og vi kan ringe ressursserverens /lese og /skrive APIer.

Å gjøre det, vi må gi Autorisasjon Overskrift. Ved hjelp av JAX-RS Client API gjøres dette ganske enkelt gjennom Invocation.Builder header () metode:

resourceWebTarget = webTarget.path ("ressurs / les"); Invocation.Builder invocationBuilder = resourceWebTarget.request (); respons = invocationBuilder .header ("autorisasjon", tokenResponse.getString ("access_token")) .get (String.class);

5. OAuth 2.0 ressursserver

I denne delen bygger vi en sikret webapplikasjon basert på JAX-RS, MicroProfile JWT og MicroProfile Config. MicroProfile JWT tar seg av validering av mottatt JWT og kartlegging av JWT-omfang til Jakarta EE-roller.

5.1. Maven avhengigheter

I tillegg til avhengigheten av Java EE Web API, trenger vi også MicroProfile Config og MicroProfile JWT API:

 javax javaee-web-api 8.0 gitt org.eclipse.microprofile.config microprofile-config-api 1.3 org.eclipse.microprofile.jwt microprofile-jwt-auth-api 1.1 

5.2. JWT-autentiseringsmekanisme

MicroProfile JWT gir en implementering av Bearer Token Authentication-mekanismen. Dette tar seg av behandlingen av JWT som er tilstede i Autorisasjon header, gjør tilgjengelig en Jakarta EE Security Principal som en JsonWebToken som har JWT-påstandene, og kartlegger omfanget til Jakarta EE-roller. Ta en titt på Jakarta EE Security API for mer bakgrunn.

For å aktivere JWT-autentiseringsmekanisme i serveren, vi må Legg til LoginConfig kommentar i JAX-RS-applikasjonen:

@ApplicationPath ("/ api") @DeclareRoles ({"resource.read", "resource.write"}) @LoginConfig (authMethod = "MP-JWT") offentlig klasse OAuth2ResourceServerApplication utvider applikasjonen {}

I tillegg MicroProfile JWT trenger den offentlige RSA-nøkkelen for å bekrefte JWT-signaturen. Vi kan tilby dette enten ved introspeksjon eller for enkelhets skyld ved å kopiere nøkkelen manuelt fra autorisasjonsserveren. I begge tilfeller må vi oppgi plasseringen til den offentlige nøkkelen:

mp.jwt.verify.publickey.location = / META-INF / public-key.pem

Til slutt må MicroProfile JWT verifisere iss krav fra den innkommende JWT, som skal være til stede og matche verdien til MicroProfile Config-egenskapen:

mp.jwt.verify.issuer = // 127.0.0.1:9080

Vanligvis er dette stedet for autorisasjonsserveren.

5.3. De sikrede endepunktene

For demonstrasjonsformål legger vi til et ressurs-API med to sluttpunkter. Den ene er en lese endepunkt som er tilgjengelig for brukere som har resource.read omfang og en annen skrive sluttpunkt for brukere med resource.write omfang.

Begrensningen på omfanget gjøres gjennom @RolesAllowed kommentar:

@Path ("/ resource") @RequestScoped offentlig klasse ProtectedResource {@Inject private JsonWebToken rektor; @GET @RolesAllowed ("resource.read") @Path ("/ read") offentlig Strenglesning () {return "Beskyttet ressurs tilgang til:" + principal.getName (); } @ POST @ RollesAllowed ("resource.write") @ Path ("/ write") offentlig String skriv () {return "Beskyttet ressurs tilgjengelig av:" + principal.getName (); }}

6. Kjører alle servere

For å kjøre en server, trenger vi bare å påkalle Maven-kommandoen i den tilsvarende katalogen:

mvn pakkefrihet: run-server

Autorisasjonsserveren, klienten og ressursserveren vil kjøre og være tilgjengelig på følgende steder:

# Autorisasjonsserver // localhost: 9080 / # Client // localhost: 9180 / # Ressursserver // localhost: 9280 / 

Så vi kan få tilgang til klientens startside, og deretter klikker vi på "Get Access Token" for å starte autorisasjonsflyten. Etter å ha mottatt tilgangstokenet, kan vi få tilgang til ressursserverens lese og skrive APIer.

Avhengig av de tildelte omfangene, vil ressursserveren svare enten ved en vellykket melding, ellers får vi en HTTP 403-forbudt status.

7. Konklusjon

I denne artikkelen har vi gitt en implementering av en OAuth 2.0 autorisasjonsserver som kan brukes med hvilken som helst kompatibel OAuth 2.0-klient og ressursserver.

For å forklare det overordnede rammeverket har vi også gitt en implementering for klienten og ressursserveren. For å implementere alle disse komponentene har vi brukt Jakarta EE 8 APIer, spesielt CDI, Servlet, JAX RS, Jakarta EE Security. I tillegg har vi brukt pseudo-Jakarta EE API-ene til MicroProfile: MicroProfile Config og MicroProfile JWT.

Den fullstendige kildekoden for eksemplene er tilgjengelig på GitHub. Merk at koden inneholder et eksempel på både godkjenningskoden og tildelingstypene for oppdateringstoken.

Til slutt er det viktig å være klar over den pedagogiske naturen til denne artikkelen, og at eksemplet som er gitt ikke skal brukes i produksjonssystemer.


$config[zx-auto] not found$config[zx-overlay] not found