Primjena OAuth 2.0 okvira za autorizaciju pomoću Jakarte EE

1. Pregled

U ovom uputstvu pružit ćemo implementaciju OAuth 2.0 Autorizacijskog okvira koristeći Jakarta EE i MicroProfile. Što je najvažnije, implementirat ćemo interakciju uloga OAuth 2.0 putem tipa odobrenja Autorizacijskog koda. Motivacija iza ovog pisanja je pružiti podršku projektima koji se provode pomoću Jakarta EE jer to još uvijek ne pruža podršku za OAuth.

Za najvažniju ulogu, Autorizacijski poslužitelj, implementirat ćemo autorizacijsku krajnju točku, krajnju točku tokena i dodatno, ključnu krajnju točku JWK, što je korisno za Resource Server za dohvaćanje javnog ključa.

Kako želimo da implementacija bude jednostavna i laka za brzo postavljanje, koristit ćemo unaprijed registriranu trgovinu klijenata i korisnika i očito JWT trgovinu za pristupne tokene.

Prije nego što prijeđete točno u temu, važno je napomenuti da je primjer u ovom vodiču u obrazovne svrhe. Za proizvodne sustave toplo se preporučuje korištenje zrelog, dobro testiranog rješenja kao što je Keycloak.

2. Pregled OAuth 2.0

U ovom ćemo odjeljku dati kratki pregled uloga OAuth 2.0 i tijeka odobrenja Autorizacijskog koda.

2.1. Uloge

OAuth 2.0 okvir podrazumijeva suradnju između četiri sljedeće uloge:

  • Vlasnik resursa: Obično je ovo krajnji korisnik - entitet ima neke resurse koje vrijedi zaštititi
  • Resursni poslužitelj: Usluga koja štiti podatke vlasnika resursa, obično ih objavljujući putem REST API-ja
  • Klijent: Aplikacija koja koristi podatke vlasnika resursa
  • Autorizacijski poslužitelj: Aplikacija koja klijentima daje odobrenje - ili ovlaštenje u obliku tokena koji ističu

2.2. Vrste davanja odobrenja

A vrsta odobrenja je način na koji klijent dobiva dopuštenje za upotrebu podataka vlasnika resursa, u konačnici u obliku pristupnog tokena.

Naravno, različite vrste klijenata preferiraju različite vrste potpora:

  • Kod odobrenja: Preferirano najčešćebilo da je web aplikacija, izvorna aplikacija ili aplikacija na jednoj stranici, iako nativne i jednostrane aplikacije zahtijevaju dodatnu zaštitu pod nazivom PKCE
  • Osvježi žeton: Posebna potpora za obnovu, prikladno za web aplikacije da obnove svoj postojeći žeton
  • Vjerodajnice klijenta: Preferira se za komunikacija usluga prema usluzi, recimo kad vlasnik resursa nije krajnji korisnik
  • Vlasnik resursaZaporka: Preferira se za osobna provjera autentičnosti nativnih aplikacija, recite kada mobilna aplikacija treba vlastitu stranicu za prijavu

Osim toga, klijent može koristiti implicitno vrsta odobrenja. Međutim, obično je sigurnije koristiti odobrenje autorizacijskog koda s PKCE.

2.3. Kod odobrenja Tijek dodjele

Budući da je tijek dodjele autorizacijskog koda najčešći, pogledajmo i kako to funkcionira, i to je zapravo ono što ćemo izgraditi u ovom vodiču.

Prijava - klijent - traži dopuštenje preusmjeravanjem na autorizacijski poslužitelj /ovlastiti krajnja točka. U tu krajnju točku aplikacija daje a uzvratiti poziv krajnja točka.

Autorizacijski poslužitelj obično će zatražiti dopuštenje od krajnjeg korisnika - vlasnika resursa. Ako krajnji korisnik odobri, tada autorizacijski poslužitelj preusmjerava natrag na povratni poziv s kodirati.

Aplikacija prima ovaj kôd, a zatim upućuje ovjereni poziv poslužitelju za autorizaciju /znak krajnja točka. Pod "autentificirano" podrazumijevamo da aplikacija dokazuje tko je ona u sklopu ovog poziva. Ako se sve pojavi u redu, autorizacijski poslužitelj odgovara tokenom.

S tokenom u ruci, aplikacija upućuje svoj zahtjev API-ju - poslužitelj resursa - i taj će API provjeriti token. Može zatražiti od autorizacijskog poslužitelja da provjeri token koristeći svoj / introspekt krajnja točka. Ili, ako je token samostalni, poslužitelj resursa može optimizirati za lokalno provjeravanje potpisa tokena, kao što je slučaj s JWT-om.

2.4. Što podržava Jakarta EE?

Još ne puno. U ovom uputstvu većinu ćemo stvari graditi od temelja.

3. OAuth 2.0 autorizacijski poslužitelj

U ovoj provedbi usredotočit ćemo se na najčešće korištena vrsta bespovratnih sredstava: Autorizacijski kod.

3.1. Registracija klijenta i korisnika

Poslužitelj za autorizaciju trebao bi, naravno, znati o klijentima i korisnicima prije nego što može autorizirati njihove zahtjeve. Uobičajeno je da autorizacijski poslužitelj ima korisničko sučelje za to.

No radi jednostavnosti koristit ćemo unaprijed konfigurirani klijent:

INSERT INTO klijenti (client_id, client_secret, redirect_uri, scope, odobreni_grant_tipovi) VRIJEDNOSTI ('webappclient', 'webappclientsecret', '// localhost: 9180 / callback', 'resource.read resource.write', 'auth_code refresh_token');
@Entity @Table (name = "client") javna klasa Client {@Id @Column (name = "client_id") private String clientId; @Column (name = "client_secret") privatni niz clientSecret; @Column (name = "redirect_uri") privatni niz redirectUri; @Column (name = "scope") opseg privatnog niza; // ...}

I unaprijed konfigurirani korisnik:

INSERT INTO korisnika (user_id, lozinka, uloge, opsezi) VRIJEDNOSTI ('appuser', 'appusersecret', 'USER', 'resource.read resource.write');
@Entity @Table (name = "users") javna klasa Korisnik implementira Principal {@Id @Column (name = "user_id") private String userId; @Column (name = "password") privatna lozinka niza; @Column (name = "role") uloge privatnog niza; @Column (name = "scopes") private String opsezi; // ...}

Imajte na umu da smo radi ovog vodiča koristili lozinke u običnom tekstu, ali u proizvodnom okruženju treba ih raspršiti.

Za ostatak ovog vodiča pokazat ćemo kako appuser - vlasnik resursa - može odobriti pristup webappclient - prijava - primjenom Kodeksa o autorizaciji.

3.2. Krajnja točka autorizacije

Glavna uloga krajnje točke autorizacije je prvo provjerite autentičnost korisnika i zatražite dopuštenja - ili opsega - koje aplikacija želi.

Prema uputama OAuth2 specifikacija, ova bi krajnja točka trebala podržavati HTTP GET metodu, iako može podržavati i HTTP POST metodu. U ovoj ćemo implementaciji podržavati samo HTTP GET metodu.

Prvi, krajnja točka autorizacije zahtijeva provjeru autentičnosti korisnika. Specifikacija ovdje ne zahtijeva određeni način, stoga upotrijebimo autentifikaciju obrasca iz Jakarta EE 8 Security API:

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

Korisnik će biti preusmjeren na /login.jsp za provjeru autentičnosti i tada će biti dostupan kao CallerPrincipal kroz SecurityContext API:

Ravnatelj glavnice = securityContext.getCallerPrincipal ();

Ovo možemo sastaviti pomoću JAX-RS:

@FormAuthenticationMechanismDefinition (loginToContinue = @LoginToContinue (loginPage = "/login.jsp", errorPage = "/login.jsp")) @Path ("autorizirajte") javna klasa AuthorizationEndpoint {// ... @GET @Produces (MediaType. TEXT_HTML) javni odgovor doGet (@Context HttpServletRequest zahtjev, @Context HttpServletResponse odgovor, @Context UriInfo uriInfo) baca ServletException, IOException {MultivaluedMap params = uriInfo.getQueryPara; Ravnatelj glavnice = securityContext.getCallerPrincipal (); // ...}}

U ovom trenutku, autorizacijska krajnja točka može započeti obradu zahtjeva koji mora sadržavati vrsta_odgovora i client_id parametri i - po želji, ali preporučljivo - redirect_uri, opseg, i država parametri.

The client_id bi trebao biti valjani klijent, u našem slučaju iz klijentima tablica baze podataka.

The preusmjeriti_uri, ako je navedeno, također bi trebao odgovarati onome što nalazimo u klijentima tablica baze podataka.

I, jer radimo Autorizacijski kod, vrsta_odgovora je kodirati.

Budući da je autorizacija postupak u više koraka, možemo privremeno pohraniti ove vrijednosti u sesiju:

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

A zatim se pripremite da pitate korisnika koja dopuštenja aplikacija može koristiti, preusmjeravajući na tu stranicu:

Niz dozvoljeniScopes = checkUserScopes (user.getScopes (), requiredScope); request.setAttribute ("opsezi", allowedScopes); request.getRequestDispatcher ("/ autorizirati.jsp"). proslijediti (zahtjev, odgovor);

3.3. Odobrenje opsega korisnika

U ovom trenutku preglednik korisniku daje autorizacijsko korisničko sučelje i korisnik vrši odabir. Zatim, preglednik predaje izbor korisnika u HTTP POST:

@POST @Consumes (MediaType.APPLICATION_FORM_URLENCODED) @Produces (MediaType.TEXT_HTML) javni odgovor doPost (@Context HttpServletRequest request, @Context HttpServletResponse response, MultivaluedMap params) metaputa Exception {Multivalued. "ORIGINAL_PARAMS"); // ... Niz odobrenjaStatus = params.getFirst ("status_ odobrenja"); // DA ILI NE // ... ako DA Lista odobrenScopes = params.get ("opseg"); // ...}

Zatim generiramo privremeni kod koji se odnosi na user_id, client_id, iredirect_uri, sve će to aplikacija upotrijebiti kasnije kad pogodi krajnju točku tokena.

Pa kreirajmo Autorizacijski kod JPA entitet s automatski generiranim ID-om:

@Entity @Table (name) javni razred AuthorizationCode {@Id @GeneratedValue (strategy = GenerationType.AUTO) @Column (name = "code") privatni kôd niza; // ...}

A zatim ga popunite:

AuthorizationCode authCode = novi AuthorizationCode (); authCode.setClientId (clientId); authCode.setUserId (userId); odobrenjeCode.setApprovedScopes (String.join ("", odobreniScopes)); authCode.setExpirationDate (LocalDateTime.now (). plusMinutes (2)); authCode.setRedirectUri (redirectUri);

Kada spremimo grah, atribut koda se automatski popunjava i tako ga možemo dobiti i poslati natrag klijentu:

appDataRepository.save (authCode); Šifra niza = authCode.getCode ();

Imajte na umu da naš autorizacijski kod istječe za dvije minute - s ovim isticanjem trebali bismo biti što konzervativniji. Može biti kratko jer će ga klijent odmah zamijeniti za pristupni token.

Zatim preusmjeravamo natrag na aplikaciju redirect_uri, dajući mu kod kao i bilo koji država parametar koji je aplikacija navela u svom /ovlastiti zahtjev:

StringBuilder sb = novi StringBuilder (redirectUri); // ... sb.append ("? code ="). append (code); Stanje niza = params.getFirst ("stanje"); if (stanje! = null) {sb.append ("& state ="). append (stanje); } URI lokacija = UriBuilder.fromUri (sb.toString ()). Build (); povratak Response.seeOther (lokacija) .build ();

Ponovno to primijetite preusmjeritiUri je li sve što postoji u klijentima tablica, a ne preusmjeriti_uri parametar zahtjeva.

Dakle, naš sljedeći korak je da klijent primi ovaj kôd i zamijeni ga za pristupni token pomoću krajnje točke tokena.

3.4. Krajnja točka žetona

Za razliku od krajnje točke autorizacije, krajnje točke tokena ne treba preglednik za komunikaciju s klijentom, i mi ćemo ga stoga implementirati kao krajnju točku JAX-RS:

@Path ("token") javna klasa TokenEndpoint {List supportedGrantTypes = Collections.singletonList ("auth_code"); @Inject private AppDataRepository appDataRepository; @Inject Instance pooblastiloGrantTypeHandlers; @POST @Produces (MediaType.APPLICATION_JSON) @Consumes (MediaType.APPLICATION_FORM_URLENCODED) javni token odgovora (Parametri višeznačne karte, @HeaderParam (HttpHeaders.AUTHORIZATION) String authHeader}) baca JOSEEEx} {JOSEEEx}}

Krajnja točka tokena zahtijeva POST, kao i kodiranje parametara pomoću application / x-www-form-urlencoded vrsta medija.

Kao što smo razgovarali, podržavat ćemo samo autorizacijski kod vrsta odobrenja:

Popis supportedGrantTypes = Collections.singletonList ("autorizacijski kod");

Dakle, primljeno grant_type kao potreban parametar treba podržati:

String grantType = params.getFirst ("grant_type"); Objects.requireNonNull (grantType, "potrebni su parametri grant_type"); if (! supportedGrantTypes.contens (grantType)) {JsonObject error = Json.createObjectBuilder () .add ("error", "unsupported_grant_type") .add ("error_description", "type grant mora biti jedan od:" + supportedGrantTypes). izgraditi(); vratiti Response.status (Response.Status.BAD_REQUEST) .entity (error) .build (); }

Dalje, provjeravamo autentičnost klijenta putem HTTP Basic autentifikacije. Odnosno, provjeravamo ako je primljeno client_id i client_secret, kroz Ovlaštenje Zaglavlje, odgovara registriranom klijentu:

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

Konačno, mi prenosimo proizvodnju TokenResponse odgovarajućem obrađivaču vrste odobrenja:

javno sučelje AuthorizationGrantTypeHandler {TokenResponse createAccessToken (String clientId, MultivaluedMap params) baca iznimku; }

Kako nas više zanima vrsta odobrenja autorizacijskog koda, osigurali smo odgovarajuću implementaciju kao CDI grah i ukrasili ga s Imenovan napomena:

@Named ("autorizacijski kod")

Tijekom izvođenja, a prema primljenom grant_type vrijednost, odgovarajuća implementacija aktivira se kroz mehanizam CDI Instance:

String grantType = params.getFirst ("grant_type"); // ... AuthorizationGrantTypeHandler autorizacijaGrantTypeHandler = autorizacijaGrantTypeHandlers.select (NamedLiteral.of (grantType)). Get ();

Sada je vrijeme za proizvodnju /znakOdgovor.

3.5. RSA Privatni i javni ključevi

Prije generiranja tokena potreban nam je RSA privatni ključ za potpisivanje tokena.

U tu svrhu koristit ćemo OpenSSL:

# PRIVATNI KLJUČ openssl genpkey -algoritam RSA -out private-key.pem -pkeyopt rsa_keygen_bits: 2048

The private-key.pem pruža se poslužitelju putem MicroProfile Config potpisivanjeKljuča svojstvo pomoću datoteke META-INF / microprofile-config.properties:

signkey = / META-INF / private-key.pem

Poslužitelj može čitati svojstvo koristeći ubrizgano Config objekt:

String signaturekey = config.getValue ("signaturekey", String.class);

Slično tome, možemo generirati odgovarajući javni ključ:

# JAVNI KLJUČ openssl rsa -pubout -u private-key.pem -out public-key.pem

I upotrijebite MicroProfile Config potvrdaKljuč da ga pročitate:

verifykey = / META-INF / public-key.pem

Poslužitelj bi ga trebao učiniti dostupnim za resursni poslužitelj za svrha provjere. Ovo je gotovo kroz krajnju točku JWK.

Nimbus JOSE + JWT je knjižnica koja ovdje može biti od velike pomoći. Prvo dodajmo nimbus-jose-jwt ovisnost:

 com.nimbusds nimbus-jose-jwt 7.7 

A sada možemo iskoristiti Nimbusovu podršku za JWK kako bismo pojednostavili našu krajnju točku:

@Path ("jwk") @ApplicationScoped javna klasa JWKEndpoint {@GET javni odgovor getKey (@QueryParam ("format") Format niza) baca iznimku {// ... String verifykey = config.getValue ("verifykey", String. razred); Niz pemEncodedRSAPublicKey = PEMKeyUtils.readKeyAsString (verifikacijski ključ); if (format == null || format.equals ("jwk")) {JWK jwk = JWK.parseFromPEMEncodedObjects (pemEncodedRSAPublicKey); vratiti Response.ok (jwk.toJSONString ()). type (MediaType.APPLICATION_JSON) .build (); } else if (format.equals ("pem")) {return Response.ok (pemEncodedRSAPublicKey) .build (); } // ...}}

Koristili smo format parametar za prebacivanje između formata PEM i JWK. MicroProfile JWT koji ćemo koristiti za implementaciju poslužitelja resursa podržava oba ova formata.

3.6. Krajnji odgovor žetona

Sada je vrijeme za dato AuthorizationGrantTypeHandler za stvaranje odgovora na token. U ovoj implementaciji podržavat ćemo samo strukturirane JWT tokene.

Za izradu tokena u ovom formatu ponovno ćemo upotrijebiti knjižnicu Nimbus JOSE + JWT, ali postoje i brojne druge JWT knjižnice.

Dakle, da bismo stvorili potpisani JWT, prvo moramo konstruirati JWT zaglavlje:

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

Zatim gradimo korisni teret što je a Postavi standardiziranih i prilagođenih zahtjeva:

Trenutno odmah = Instant.now (); Dugo ističeInMin = 30L; Date in30Min = Date.from (now.plus (expiresInMin, ChronoUnit.MINUTES)); JWTClaimsSet jwtClaims = novi JWTClaimsSet.Builder () .issuer ("// localhost: 9080") .subject (authCode.getUserId ()) .claim ("upn", authCode.getUserId ()) .audience ("// localhost: 9280 ") .claim (" doseg ", authCode.getApprovedScopes ()) .claim (" groups ", Arrays.asList (authCode.getApprovedScopes (). Split (" ")) .expirationTime (in30Min) .notBeforeTime (Date. od (sada)) .issueTime (Datum.od (sada)) .jwtID (UUID.randomUUID (). toString ()) .build (); SignedJWT signedJWT = novi SignedJWT (jwsHeader, jwtClaims);

Pored standardnih zahtjeva za JWT, dodali smo još dva zahtjeva - upn i skupine - kako ih treba MicroProfile JWT. The upn bit će preslikana na Jakarta EE Security CallerPrincipal i skupine bit će mapiran u Jakarta EE Uloge.

Sad kad imamo zaglavlje i korisni teret, moramo pristupni token potpisati RSA privatnim ključem. Odgovarajući javni ključ RSA bit će izložen kroz krajnju točku JWK ili dostupan na drugi način, tako da ga poslužitelj resursa može koristiti za provjeru pristupnog tokena.

Kako smo osigurali privatni ključ kao PEM format, trebali bismo ga preuzeti i transformirati u RSAPrivateKey:

SignedJWT signedJWT = novi SignedJWT (jwsHeader, jwtClaims); // ... String signaturekey = config.getValue ("signaturekey", String.class); Niz pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString (potpisni ključ); RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects (pemEncodedRSAPrivateKey);

Sljedeći, potpisujemo i serializiramo JWT:

potpisaoJWT.sign (novi RSASSASigner (rsaKey.toRSAPrivateKey ())); Niz accessToken = signedJWT.serialize ();

I konačno konstruiramo token odgovor:

vratiti Json.createObjectBuilder () .add ("vrsta_tokena", "Donositelj") .add ("access_token", accessToken) .add ("expires_in", expiresInMin * 60) .add ("doseg", auth. .izgraditi();

koji je, zahvaljujući JSON-P, serializiran u JSON format i poslan klijentu:

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

4. OAuth 2.0 klijent

U ovom ćemo dijelu biti izgradnja internetskog OAuth 2.0 klijenta koristeći Servlet, MicroProfile Config i JAX RS Client API-je.

Točnije, implementirat ćemo dva glavna servleta: jedan za traženje krajnje točke autorizacije poslužitelja za autorizaciju i dobivanje koda pomoću vrste dodjele autorizacijskog koda, a drugi servlet za upotrebu primljenog koda i traženje pristupnog tokena od krajnje točke tokena autorizacijskog servera .

Uz to, implementirat ćemo još dva servleta: jedan za dobivanje novog pristupnog tokena pomoću vrste odobrenja tokena za osvježavanje i drugi za pristup API-ima poslužitelja resursa.

4.1. Pojedinosti o klijentu OAuth 2.0

Kako je klijent već registriran na autorizacijskom poslužitelju, prvo moramo navesti podatke o registraciji klijenta:

  • client_id: Klijentski identifikator i obično ga izdaje autorizacijski poslužitelj tijekom postupka registracije.
  • client_secret: Klijentska tajna.
  • redirect_uri: Mjesto na kojem se može dobiti autorizacijski kod.
  • opseg: Klijent je zatražio dozvole.

Uz to, klijent bi trebao znati autorizaciju poslužitelja za autorizaciju i krajnje točke tokena:

  • auth_uri: Lokacija krajnje točke autorizacije poslužitelja za autorizaciju koju možemo koristiti za dobivanje koda.
  • token_uri: Lokacija krajnje točke tokena poslužitelja za autorizaciju koju možemo koristiti za dobivanje tokena.

Sve ove informacije pružaju se putem datoteke MicroProfile Config, META-INF / microprofile-config.properties:

# Registracija klijenta 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. Zahtjev za autorizacijskim kodom

Tok dobivanja autorizacijskog koda započinje s klijentom preusmjeravanjem preglednika na krajnju točku autorizacije poslužitelja za autorizaciju.

To se obično događa kada korisnik pokušava pristupiti API-ju zaštićenog resursa bez autorizacije ili izričitim pozivanjem klijenta /ovlastiti staza:

@WebServlet (urlPatterns = "/ odobriti") javna klasa AuthorizationCodeServlet proširuje HttpServlet {@Inject private Config config; @Override protected void doGet (zahtjev HttpServletRequest, odgovor HttpServletResponse) baca ServletException, IOException {// ...}}

U doGet () metodu započinjemo generiranjem i spremanjem vrijednosti stanja sigurnosti:

Stanje niza = UUID.randomUUID (). ToString (); request.getSession (). setAttribute ("CLIENT_LOCAL_STATE", stanje);

Zatim dohvaćamo podatke o konfiguraciji klijenta:

String authUri = config.getValue ("provider.authorizationUri", String.class); Niz clientId = config.getValue ("client.clientId", String.class); Niz redirectUri = config.getValue ("client.redirectUri", String.class); Niz opsega = config.getValue ("client.scope", String.class);

Zatim ćemo dodati ove dijelove podataka kao parametre upita krajnjoj točki autorizacije poslužitelja za autorizaciju:

Niz odobrenjaLocation = authUri + "? Response_type = code" + "& client_id =" + clientId + "& redirect_uri =" + redirectUri + "& scope =" + scope + "& state =" + state;

I na kraju, preusmjerit ćemo preglednik na ovaj URL:

response.sendRedirect (authLocation);

Nakon obrade zahtjeva, krajnja točka autorizacije poslužitelja za autorizaciju generirat će i dodati kôd, uz primljeni parametar stanja, i na preusmjeriti_uri i preusmjerit će natrag preglednik // localhost: 9081 / povratni poziv? code = A123 & state = Y.

4.3. Pristup zahtjevu tokena

Klijentski servlet povratnog poziva, /uzvratiti poziv, započinje provjerom valjanosti primljenog država:

Niz localState = (niz) request.getSession (). GetAttribute ("CLIENT_LOCAL_STATE"); if (! localState.equals (request.getParameter ("state"))) {request.setAttribute ("error", "Atribut state se ne podudara!"); otprema ("/", zahtjev, odgovor); povratak; }

Sljedeći, upotrijebit ćemo kôd koji smo prethodno dobili da bismo zatražili token za pristup kroz krajnju točku tokena poslužitelja za autorizaciju:

String kod = request.getParameter ("kod"); Klijent klijent = ClientBuilder.newClient (); WebTarget target = client.target (config.getValue ("provider.tokenUri", String.class)); Obrazac obrasca = novi obrazac (); form.param ("vrsta_dodatka", "autorizacijski kod"); form.param ("kod", kod); 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_URLENCSELA;

Kao što vidimo, za ovaj poziv ne postoji interakcija preglednika, a zahtjev se izrađuje izravno koristeći API klijenta JAX-RS kao HTTP POST.

Kako završna točka tokena zahtijeva provjeru autentičnosti klijenta, uključili smo vjerodajnice klijenta client_id i client_secret u Ovlaštenje Zaglavlje.

Klijent može koristiti ovaj pristupni token za pozivanje API-ja poslužitelja resursa što je predmet sljedećeg pododjeljka.

4.4. Zaštićeni pristup resursima

U ovom trenutku imamo valjani pristupni token i možemo nazvati poslužitelj resursa /čitati i /pisati Apis.

Napraviti to, moramo pružiti Ovlaštenje Zaglavlje. Korištenjem JAX-RS klijentskog API-ja to se jednostavno čini putem Zaglavlje Invocation.Builder () metoda:

resourceWebTarget = webTarget.path ("resurs / čitanje"); Invocation.Builder invocationBuilder = resourceWebTarget.request (); response = invocationBuilder .header ("autorizacija", tokenResponse.getString ("access_token")) .get (String.class);

5. OAuth 2.0 Resursni poslužitelj

U ovom ćemo odjeljku graditi sigurnu web aplikaciju koja se temelji na JAX-RS, MicroProfile JWT i MicroProfile Config. MicroProfile JWT brine se za provjeru valjanosti primljenog JWT i mapiranje JWT opsega u uloge Jakarte EE.

5.1. Ovisnosti Mavena

Pored ovisnosti o Java EE Web API-u, trebaju nam i MicroProfile Config i MicroProfile JWT API-ji:

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

5.2. JWT autentifikacijski mehanizam

MicroProfile JWT pruža implementaciju mehanizma provjere autentičnosti Bearer Token. Ovo se brine za obradu JWT-a prisutnog u Ovlaštenje zaglavlje, stavlja na raspolaganje Jakarta EE Security Principal kao JsonWebToken koja drži zahtjeve za JWT i preslikava dosege u uloge Jakarte EE. Za više informacija pogledajte Jakarta EE Security API.

Da biste omogućili JWT mehanizam za provjeru autentičnosti na poslužitelju, moramo dodaj LoginConfig bilješka u aplikaciji JAX-RS:

@ApplicationPath ("/ api") @DeclareRoles ({"resource.read", "resource.write"}) @LoginConfig (authMethod = "MP-JWT") javna klasa OAuth2ResourceServerApplication proširuje aplikaciju {}

Dodatno, MicroProfile JWT treba RSA javni ključ da bi potvrdio JWT potpis. To možemo pružiti introspekcijom ili, radi jednostavnosti, ručnim kopiranjem ključa s autorizacijskog poslužitelja. U oba slučaja moramo navesti mjesto javnog ključa:

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

Konačno, MicroProfile JWT mora provjeriti br zahtjev za dolazni JWT, koji bi trebao biti prisutan i odgovarati vrijednosti svojstva MicroProfile Config:

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

To je obično mjesto poslužitelja za autorizaciju.

5.3. Osigurane krajnje točke

U demonstracijske ćemo svrhe dodati API resursa s dvije krajnje točke. Jedan je a čitati krajnju točku kojoj mogu pristupiti korisnici koji imaju resurs.čitati opseg i drugo pisati krajnja točka za korisnike s resurs.pisati opseg.

Ograničenje opsega vrši se putem @RolesAllowed napomena:

@Path ("/ resource") @RequestScoped javna klasa ProtectedResource {@Inject private JsonWebToken principal; @GET @RolesAllowed ("resource.read") @Path ("/ read") javni niz read () {return "Zaštićeni resurs pristupljeno:" + principal.getName (); } @POST @RolesAllowed ("resource.write") @Path ("/ write") javni String write () {return "Zaštićeni resurs kojem pristupa:" + principal.getName (); }}

6. Pokretanje svih poslužitelja

Da bismo pokrenuli jedan poslužitelj, samo moramo pozvati naredbu Maven u odgovarajućem direktoriju:

sloboda mvn paketa: run-server

Autorizacijski poslužitelj, klijent i poslužitelj resursa izvodit će se i biti dostupni na sljedećim mjestima:

# Autorizacijski poslužitelj // localhost: 9080 / # Klijent // localhost: 9180 / # Poslužitelj resursa // localhost: 9280 / 

Dakle, možemo pristupiti klijentovoj početnoj stranici, a zatim kliknuti na „Get Access Token“ za pokretanje tijeka autorizacije. Nakon primanja pristupnog tokena možemo pristupiti poslužitelju resursa čitati i pisati Apis.

Ovisno o dodijeljenim opsezima, poslužitelj resursa odgovorit će ili uspješnom porukom ili ćemo dobiti HTTP 403 zabranjeni status.

7. Zaključak

U ovom smo članku pružili implementaciju OAuth 2.0 Autorizacijskog poslužitelja koji se može koristiti s bilo kojim kompatibilnim OAuth 2.0 klijentskim i resursnim poslužiteljem.

Da bismo objasnili cjelokupni okvir, osigurali smo i implementaciju za klijenta i poslužitelj resursa. Da bismo implementirali sve ove komponente, koristili smo se upotrebom API-ja Jakarta EE 8, posebno CDI, Servlet, JAX RS, Jakarta EE Security. Uz to, koristili smo pseudo-Jakarta EE API-je MicroProfile: MicroProfile Config i MicroProfile JWT.

Potpuni izvorni kod za primjere dostupan je na GitHubu. Imajte na umu da kôd uključuje primjer autorizacijskog koda i vrste odobrenja tokena za osvježavanje.

Napokon, važno je biti svjestan obrazovne prirode ovog članka i da se navedeni primjeri ne bi trebali koristiti u proizvodnim sustavima.