Implementácia autorizačného rámca OAuth 2.0 pomocou Jakarta EE

1. Prehľad

V tomto výučbe poskytneme implementáciu pre autorizačný rámec OAuth 2.0 pomocou Jakarta EE a MicroProfile. Najdôležitejšie je, že implementujeme interakciu rolí OAuth 2.0 prostredníctvom typu grantu Autorizačný kód. Motiváciou tohto písania je poskytnúť podporu projektom, ktoré sa implementujú pomocou Jakarta EE, pretože to ešte neposkytuje podporu pre OAuth.

Pre najdôležitejšiu rolu je autorizačný server, ideme implementovať koncový bod autorizácie, koncový bod tokenu a navyše kľúčový koncový bod JWK, čo je užitočné pre Resource Server na získanie verejného kľúča.

Pretože chceme, aby implementácia bola jednoduchá a ľahká pre rýchle nastavenie, použijeme vopred zaregistrovaný ukladací priestor klientov a používateľov a samozrejme úložisko JWT pre prístupové tokeny.

Pred priamym prechodom do témy je dôležité poznamenať, že príklad v tomto návode slúži na vzdelávacie účely. Pre produkčné systémy sa dôrazne odporúča používať vyspelé a osvedčené riešenie, napríklad Keycloak.

2. Prehľad protokolu OAuth 2.0

V tejto časti poskytneme stručný prehľad rolí OAuth 2.0 a toku grantov autorizačného kódu.

2.1. Úlohy

Rámec OAuth 2.0 implikuje spoluprácu medzi štyrmi nasledujúcimi rolami:

  • Vlastník zdrojov: Spravidla ide o koncového používateľa - je to subjekt, ktorý má nejaké zdroje, ktoré stojí za to chrániť
  • Zdrojový server: Služba, ktorá chráni údaje vlastníka prostriedku, zvyčajne ich zverejňuje prostredníctvom rozhrania REST API
  • Zákazník: Aplikácia, ktorá používa údaje vlastníka prostriedku
  • Autorizačný server: Aplikácia, ktorá udeľuje povolenie - alebo oprávnenie - klientom vo forme tokenov, ktorých platnosť končí

2.2. Druhy grantových grantov

A typ grantu je to, ako klient získa povolenie na použitie údajov vlastníka prostriedkov, nakoniec vo forme prístupového tokenu.

Rôzne typy klientov samozrejme uprednostňujú rôzne typy grantov:

  • autorizačný kód: Najčastejšie uprednostňovanéči to je webová aplikácia, natívna aplikácia alebo jednostránková aplikácia, aj keď natívne a jednostránkové aplikácie vyžadujú dodatočnú ochranu nazývanú PKCE
  • Obnoviť token: Špeciálny grant na obnovu, vhodné pre webové aplikácie obnoviť svoj existujúci token
  • Poverenie klienta: Preferované pre komunikácia medzi službami, napríklad keď vlastník zdroja nie je koncovým používateľom
  • Vlastník zdrojovHeslo: Preferované pre autentifikácia natívnych aplikácií prvou stranou, keď mobilná aplikácia potrebuje vlastnú prihlasovaciu stránku

Okrem toho môže klient použiť implicitné typ grantu. Spravidla je však bezpečnejšie použiť udelenie autorizačného kódu s programom PKCE.

2.3. Tok udelenia autorizačného kódu

Pretože tok grantov autorizačného kódu je najbežnejší, poďme tiež skontrolovať, ako to funguje, a to je vlastne to, čo v tomto návode postavíme.

Aplikácia - klient - požaduje povolenie presmerovaním na server autorizačného servera /povoliť koncový bod. V tomto koncovom bode poskytuje aplikácia a zavolaj späť koncový bod.

Autorizačný server zvyčajne požiada koncového používateľa - vlastníka zdroja - o povolenie. Ak koncový používateľ udelí povolenie, potom autorizačný server presmeruje späť na spätné volanie s kód.

Aplikácia dostane tento kód a potom uskutoční autentizované volanie na autorizačný server / token koncový bod. Pod pojmom „autentifikovaný“ rozumieme, že aplikácia v rámci tohto hovoru dokazuje, o koho ide. Ak je všetko v poriadku, autorizačný server odpovie tokenom.

S tokenom v ruke aplikácia požiada API - zdrojový server - a toto API overí token. Môže požiadať autorizačný server o overenie tokenu pomocou jeho / introspekt koncový bod. Alebo ak je token samostatný, server zdrojov môže optimalizovať pomocou lokálne overenie podpisu tokenu, ako je to v prípade JWT.

2.4. Čo podporuje Jakarta EE?

Zatiaľ nie veľa. V tomto tutoriáli postavíme väčšinu vecí od základov.

3. Autorizačný server OAuth 2.0

V tejto implementácii sa zameriame na najčastejšie používaný typ grantu: Autorizačný kód.

3.1. Registrácia klienta a používateľa

Autorizačný server by samozrejme musel vedieť o klientoch a používateľoch skôr, ako bude môcť autorizovať ich požiadavky. A je bežné, že na to má autorizačný server používateľské rozhranie.

Pre jednoduchosť však použijeme predkonfigurovaného klienta:

INSERT INTO clients (client_id, client_secret, redirect_uri, scope, authorized_grant_types) VALUES ('webappclient', 'webappclientsecret', '// localhost: 9180 / callback', 'resource.read resource.write', 'authorized_code refresh_token');
@Entity @Table (name = "clients") verejná trieda 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") private String scope; // ...}

A predkonfigurovaný užívateľ:

INSERT INTO users (user_id, password, roles, scopes) VALUES ('appuser', 'appusersecret', 'USER', 'resource.read resource.write');
@Entity @Table (name = "users") verejná trieda Používateľ implementuje Principal {@Id @Column (name = "user_id") private String userId; @Column (name = "heslo") súkromné ​​reťazcové heslo; @Column (name = "role") súkromné ​​reťazcové role; @Column (name = "scopes") súkromné ​​rozsahy reťazcov; // ...}

Upozorňujeme, že v rámci tohto tutoriálu sme heslá používali ako obyčajný text, ale v produkčnom prostredí by mali byť hašované.

Vo zvyšku tohto tutoriálu si ukážeme ako uchádzač - vlastník zdroja - môže poskytnúť prístup k webappclient - žiadosť - implementáciou autorizačného kódu.

3.2. Koncový bod autorizácie

Hlavná úloha koncového bodu autorizácie je na prvom mieste autentifikujte používateľa a potom požiadajte o povolenia - alebo rozsahy - ktoré aplikácia požaduje.

Podľa pokynov v špecifikáciách OAuth2 by tento koncový bod mal podporovať metódu HTTP GET, aj keď môže podporovať aj metódu HTTP POST. V tejto implementácii budeme podporovať iba metódu HTTP GET.

Najprv, koncový bod autorizácie vyžaduje, aby bol užívateľ autentifikovaný. Špecifikácia tu nevyžaduje určitý spôsob, preto použijeme overenie formulára z bezpečnostného rozhrania API Jakarta EE 8:

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

Používateľ bude presmerovaný na /login.jsp na autentifikáciu a potom bude k dispozícii ako CallerPrincipal cez SecurityContext API:

Principal principal = securityContext.getCallerPrincipal ();

Môžeme ich spojiť pomocou JAX-RS:

@FormAuthenticationMechanismDefinition (loginToContinue = @LoginToContinue (loginPage = "/login.jsp", errorPage = "/login.jsp")) @Path ("autorizovať") verejná trieda AuthorizationEndpoint {// ... @GET @Produces (MediaType. TEXT_HTML) public Response doGet (@Context HttpServletRequest request, @Context HttpServletResponse response, @Context UriInfo uriInfo) hodí ServletException, IOException {MultivaluedMap params = uriInfo.getQueryParameters (); Principal principal = securityContext.getCallerPrincipal (); // ...}}

V tomto okamihu môže koncový bod autorizácie začať spracovávať žiadosť aplikácie, ktorá musí obsahovať typ_odpovede a client_id parametre a - voliteľne, ale odporúčajú sa - redirect_uri, rozsah, a štát parametre.

The client_id by mal byť platným klientom, v našom prípade z klientov databázová tabuľka.

The presmerovanie_uri, ak je uvedené, by sa malo zhodovať aj s tým, čo nájdeme v klientov databázová tabuľka.

A pretože robíme Autorizačný kód, typ_odpovede je kód.

Pretože autorizácia je proces pozostávajúci z niekoľkých krokov, môžeme v relácii dočasne uložiť tieto hodnoty:

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

Potom sa pripravte na to, aby ste sa používateľa spýtali, aké povolenia môže aplikácia použiť, a presmeruje ho na túto stránku:

Reťazec allowScopes = checkUserScopes (user.getScopes (), requiredScope); request.setAttribute ("scopes", allowScopes); request.getRequestDispatcher ("/ authorize.jsp"). forward (požiadavka, odpoveď);

3.3. Schválenie rozsahu používateľa

V tomto okamihu prehľadávač vytvorí používateľské používateľské rozhranie autorizácie a používateľ vykoná výber. Potom prehľadávač zadá výber používateľa v HTTP POST:

@POST @Consumes (MediaType.APPLICATION_FORM_URLENCODED) @Produces (MediaType.TEXT_HTML) verejná odpoveď doPost (@Context HttpServletRequest požiadavka, @Context HttpServletResponse odpoveď, MultivaluedMap parametre) (výnimka) (Originál) „ORIGINAL_PARAMS“); // ... Reťazec schválenieStatus = params.getFirst ("schválenie_status"); // ÁNO ALEBO NIE // ... ak ÁNO Zoznam schválenýScopes = params.get ("rozsah"); // ...}

Ďalej vygenerujeme dočasný kód, ktorý odkazuje na user_id, client_id, aredirect_uri, všetko z toho aplikácia použije neskôr, keď zasiahne koncový bod tokenu.

Vytvorme teda Autorizačný kód Entita JPA s automaticky generovaným ID:

@Entity @Table (name) verejná trieda AuthorizationCode {@Id @GeneratedValue (strategy = GenerationType.AUTO) @Column (name = "code") private String code; // ...}

A potom ho vyplňte:

AuthorizationCode authorizationCode = nový AuthorizationCode (); authorisationCode.setClientId (clientId); authorizedCode.setUserId (userId); authorisationCode.setApprovedScopes (String.join ("", authorizedScopes)); authorisationCode.setExpirationDate (LocalDateTime.now (). plusMinutes (2)); authorizationCode.setRedirectUri (redirectUri);

Keď fazuľu uložíme, atribút code sa vyplní automaticky, takže ho môžeme získať a odoslať späť klientovi:

appDataRepository.save (autorizačný kód); Kód reťazca = authorizationCode.getCode ();

Poznač si to platnosť nášho autorizačného kódu vyprší o dve minúty - s týmto vypršaním by sme mali byť maximálne konzervatívni. Môže to byť krátke, pretože klient to okamžite vymení za prístupový token.

Potom presmerujeme späť na príslušné aplikácie redirect_uri, čo mu dá kód rovnako ako akýkoľvek iný štát parameter, ktorý aplikácia uviedla vo svojom /povoliť požiadavka:

StringBuilder sb = nový StringBuilder (redirectUri); // ... sb.append ("? code ="). append (code); Stav reťazca = params.getFirst ("štát"); if (state! = null) {sb.append ("& state ="). append (state); } URI umiestnenie = UriBuilder.fromUri (sb.toString ()). Build (); vrátiť Response.seeOther (location) .build ();

Znova si všimnite, že redirectUri je všetko, čo existuje v klientov stôl, nie presmerovanie_uri parameter požiadavky.

Našim ďalším krokom je teda, aby klient dostal tento kód a vymenil ho za prístupový token pomocou koncového bodu tokenu.

3.4. Koncový bod tokenu

Na rozdiel od koncového bodu autorizácie koncový bod tokenu na komunikáciu s klientom nepotrebuje prehliadač, a preto ho implementujeme ako koncový bod JAX-RS:

@Path ("token") verejná trieda TokenEndpoint {List supportedGrantTypes = Collections.singletonList ("authorized_code"); @Inject private AppDataRepository appDataRepository; @Inject Instance authorizedGrantTypeHandlers; @POST @Produces (MediaType.APPLICATION_JSON) @Consumes (MediaType.APPLICATION_FORM_URLENCODED) token verejnej odpovede (parametre MultivaluedMap, @HeaderParam (HttpHeaders.AUTHORIZATION) reťazec authHeader) {J}}} výnimka

Koncový bod tokenu vyžaduje POST, ako aj kódovanie parametrov pomocou application / x-www-form-urlencoded Typ média.

Ako sme diskutovali, budeme podporovať iba autorizačný kód typ grantu:

Zoznam podporovanéGrantTypes = Collections.singletonList ("autorizačný_kód");

Takže, prijaté grant_type ako povinný parameter by mal byť podporovaný:

Reťazec grantType = params.getFirst ("grant_type"); Objects.requireNonNull (grantType, "sú požadované parametre grant_type"); if (! supportedGrantTypes.contains (grantType)) {JsonObject error = Json.createObjectBuilder () .add ("error", "unsupported_grant_type") .add ("error_description", "typ grantu by mal byť jeden z týchto typov:" + supportedGrantTypes). build (); vrátiť Response.status (Response.Status.BAD_REQUEST) .entity (error) .build (); }

Ďalej skontrolujeme autentifikáciu klienta prostredníctvom základného overenia HTTP. To znamená, že skontrolujeme ak prijal client_id a client_secret, cez Povolenie hlavička, zodpovedá registrovanému klientovi:

Reťazec [] clientCredentials = výpis (authHeader); Reťazec clientId = clientCredentials [0]; Reťazec clientSecret = clientCredentials [1]; Klient klient = appDataRepository.getClient (clientId); if (client == null || clientSecret == null ||! clientSecret.equals (client.getClientSecret ())) {chyba JsonObject = Json.createObjectBuilder () .add ("chyba", "invalid_client") .build () ; vrátiť Response.status (Response.Status.UNAUTHORIZED) .entity (error) .build (); }

Na záver delegujeme výrobu TokenResponse zodpovedajúcemu spracovateľovi typu grantu:

verejné rozhranie AuthorizationGrantTypeHandler {TokenResponse createAccessToken (reťazec clientId, parametre MultivaluedMap) vyvolá výnimku; }

Pretože nás viac zaujíma typ udelenia autorizačného kódu, poskytli sme adekvátnu implementáciu ako fazuľa CDI a ozdobili sme ju Menovaný anotácia:

@Named („autorizačný_kód“)

Za behu a podľa prijatých grant_type hodnota, príslušná implementácia sa aktivuje prostredníctvom mechanizmu inštancie CDI:

Reťazec grantType = params.getFirst ("grant_type"); // ... AuthorizationGrantTypeHandler authorizedGrantTypeHandler = authorizedGrantTypeHandlers.select (NamedLiteral.of (grantType)). Get ();

Teraz je čas na výrobu / tokenOdpoveď.

3.5. RSA Súkromný a verejný kľúč

Pred vygenerovaním tokenu potrebujeme na podpisovanie tokenov súkromný kľúč RSA.

Z tohto dôvodu budeme používať OpenSSL:

# PRIVÁTNY KLÍČ openssl genpkey -algoritmus RSA -out private-key.pem -pkeyopt rsa_keygen_bits: 2048

The private-key.pem sa poskytuje serveru prostredníctvom konfigurácie MicroProfile podpisovanieKey pomocou súboru META-INF / microprofile-config.properties:

podpisový kľúč = / META-INF / private-key.pem

Server môže načítať vlastnosť pomocou vloženého Konfig objekt:

Reťazec podpisový kľúč = config.getValue ("podpisový kľúč", String.class);

Podobne môžeme vygenerovať zodpovedajúci verejný kľúč:

# VEREJNÝ KĽÚČ openssl rsa -pubout -in private-key.pem -out public-key.pem

A použite konfiguráciu MicroProfile overovací kľúč prečítať:

Verifikačný kľúč = / META-INF / public-key.pem

Server by ho mal sprístupniť pre zdrojový server pre doménu účel overenia. Toto je hotové prostredníctvom koncového bodu JWK.

Nimbus JOSE + JWT je knižnica, ktorá tu môže byť veľkou pomocou. Najprv pridajme nimbus-jose-jwt závislosť:

 com.nimbusds nimbus-jose-jwt 7.7 

A teraz môžeme na zjednodušenie nášho koncového bodu využiť podporu Jimbingu Nimbus:

@Path ("jwk") @ApplicationScoped verejná trieda JWKEndpoint {@GET public Response getKey (@QueryParam ("format") formát reťazca) vyvolá výnimku {// ... String verificationkey = config.getValue ("verificationkey", String. trieda); Reťazec pemEncodedRSAPublicKey = PEMKeyUtils.readKeyAsString (overovací kľúč); if (format == null || format.equals ("jwk")) {JWK jwk = JWK.parseFromPEMEncodedObjects (pemEncodedRSAPublicKey); návrat Response.ok (jwk.toJSONString ()). type (MediaType.APPLICATION_JSON) .build (); } else if (format.equals ("pem")) {return Response.ok (pemEncodedRSAPublicKey) .build (); } // ...}}

Použili sme formát parameter prepínať medzi formátmi PEM a JWK. MicroProfile JWT, ktorý použijeme na implementáciu servera zdrojov, podporuje oba tieto formáty.

3.6. Odpoveď koncového bodu tokenu

Teraz je čas na dané AuthorizationGrantTypeHandler vytvoriť odpoveď na token. V tejto implementácii podporíme iba štruktúrované tokeny JWT.

Na vytvorenie tokenu v tomto formáte opäť použijeme knižnicu Nimbus JOSE + JWT, ale existuje aj množstvo ďalších knižníc JWT.

Takže, aby ste vytvorili podpísaný JWT, najskôr musíme skonštruovať hlavičku JWT:

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

Potom vytvoríme užitočné zaťaženie čo je a Nastaviť štandardizovaných a vlastných pohľadávok:

Okamžité hneď = Instant.now (); Long expiresInMin = 30L; Dátum in30Min = Date.from (now.plus (expiresInMin, ChronoUnit.MINUTES)); JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder () .issuer ("// localhost: 9080") .subject (authorizationCode.getUserId ()) .claim ("upn", authorizationCode.getUserId ()) .audience ("// localhost: 9280 ") .claim (" rozsah ", autorizáciaCode.getApprovedScopes ()) .claim (" skupiny ", Arrays.asList (autorizáciaCode.getApprovedScopes (). Split (" "))) .expirationTime (in30Min) .notBeforeTime (dátum. from (now)) .issueTime (Date.from (now)) .jwtID (UUID.randomUUID (). toString ()) .build (); SignedJWT signedJWT = nový SignedJWT (jwsHeader, jwtClaims);

Okrem štandardných nárokov JWT sme pridali ďalšie dva nároky - hore a skupiny - tak, ako to vyžaduje MicroProfile JWT. The hore bude namapovaná na Jakarta EE Security CallerPrincipal a skupiny bude mapovaná do Jakarty EE Úlohy.

Teraz, keď máme hlavičku a užitočné zaťaženie, musíme podpísať prístupový token pomocou súkromného kľúča RSA. Zodpovedajúci verejný kľúč RSA bude sprístupnený prostredníctvom koncového bodu JWK alebo sprístupnený inými prostriedkami, aby ho server prostriedkov mohol použiť na overenie prístupového tokenu.

Pretože sme poskytli súkromný kľúč ako formát PEM, mali by sme ho získať a transformovať na RSAPrivateKey:

SignedJWT signedJWT = nový SignedJWT (jwsHeader, jwtClaims); // ... String signaturekey = config.getValue ("podpisový kľúč", String.class); Reťazec pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString (podpisový kľúč); RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects (pemEncodedRSAPrivateKey);

Ďalšie, podpisujeme a serializujeme JWT:

signedJWT.sign (nový RSASSASigner (rsaKey.toRSAPrivateKey ())); Reťazec accessToken = signedJWT.serialize ();

A nakoniec zostrojíme odpoveď na token:

návrat Json.createObjectBuilder () .add ("token_type", "nosič") .add ("access_token", accessToken) .add ("expires_in", expiresInMin * 60) .add ("scope", authorizedCode.getApprovedScopes ()) .build ();

ktorý je vďaka JSON-P serializovaný do formátu JSON a odoslaný klientovi:

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

4. Klient OAuth 2.0

V tejto časti budeme budovanie webového klienta OAuth 2.0 pomocou rozhraní Servlet, MicroProfile Config a JAX RS Client API.

Presnejšie, implementujeme dva hlavné servlety: jeden na vyžiadanie autorizačného koncového bodu autorizačného servera a získanie kódu pomocou typu udelenia autorizačného kódu a druhý servlet na použitie prijatého kódu a vyžiadanie prístupového tokenu z koncového bodu tokenu autorizačného servera .

Ďalej implementujeme ďalšie dva servlety: Jeden na získanie nového prístupového tokenu pomocou typu grantu obnovovacieho tokenu a druhý na prístup k API servera prostriedkov.

4.1. Detaily klienta OAuth 2.0

Pretože klient je už zaregistrovaný v autorizačnom serveri, najskôr musíme poskytnúť informácie o registrácii klienta:

  • client_id: Identifikátor klienta a zvyčajne ho vydáva autorizačný server počas procesu registrácie.
  • client_secret: Tajomstvo klienta.
  • redirect_uri: Miesto, kde dostanete autorizačný kód.
  • rozsah: Klient požadoval povolenia.

Ďalej by mal klient poznať autorizačný server a koncové body tokenu:

  • autorizácia_uri: Umiestnenie koncového bodu autorizačného servera, ktorý môžeme použiť na získanie kódu.
  • token_uri: Umiestnenie koncového bodu tokenu autorizačného servera, ktorý môžeme použiť na získanie tokenu.

Všetky tieto informácie sú poskytované prostredníctvom súboru MicroProfile Config, META-INF / microprofile-config.properties:

# Registrácia klienta 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. Žiadosť o autorizačný kód

Postup získavania autorizačného kódu začína u klienta presmerovaním prehliadača do autorizačného koncového bodu autorizačného servera.

Spravidla sa to stane, keď sa používateľ pokúsi získať prístup k chránenému zdrojovému rozhraniu API bez autorizácie, alebo výslovne vyvolaním klienta /povoliť cesta:

@WebServlet (urlPatterns = "/ autorizovať") verejná trieda AuthorizationCodeServlet rozširuje HttpServlet {@Inject private Config config; @Override protected void doGet (požiadavka HttpServletRequest, odpoveď HttpServletResponse) vyvolá výnimku ServletException, IOException {// ...}}

V doGet () metóda začneme generovaním a uložením hodnoty stavu bezpečnosti:

Stav reťazca = UUID.randomUUID (). ToString (); request.getSession (). setAttribute ("CLIENT_LOCAL_STATE", štát);

Potom načítame informácie o konfigurácii klienta:

String authorizationUri = config.getValue ("provider.authorizationUri", String.class); Reťazec clientId = config.getValue ("client.clientId", String.class); String redirectUri = config.getValue ("client.redirectUri", String.class); Rozsah reťazca = config.getValue ("client.scope", String.class);

Potom tieto informácie doplníme ako parametre dotazu do koncového bodu autorizácie autorizačného servera:

Reťazec authorizedLocation = autorizáciaUri + "? Response_type = kód" + "& client_id =" + clientId + "& redirect_uri =" + redirectUri + "& scope =" + rozsah + "& state =" + stav;

Nakoniec prehliadač presmerujeme na túto adresu URL:

response.sendRedirect (authorizedLocation);

Po spracovaní žiadosti koncový bod autorizačného servera vygeneruje a pripojí kód, okrem prijatého parametra stavu, do presmerovanie_uri a presmeruje späť prehliadač // localhost: 9081 / callback? code = A123 & state = Y.

4.3. Žiadosť o prístupový token

Servlet spätného volania klienta, /zavolaj späť, začína validáciou prijatých správ štát:

String localState = (Reťazec) request.getSession (). GetAttribute ("CLIENT_LOCAL_STATE"); if (! localState.equals (request.getParameter ("state"))) {request.setAttribute ("error", "Atribút state sa nezhoduje!"); odoslanie („/“, požiadavka, odpoveď); návrat; }

Ďalšie, použijeme kód, ktorý sme predtým dostali, na vyžiadanie prístupového tokenu prostredníctvom koncového bodu tokenu autorizačného servera:

Kód reťazca = request.getParameter ("kód"); Klient klient = ClientBuilder.newClient (); Cieľ WebTarget = client.target (config.getValue ("provider.tokenUri", String.class)); Formulár formulár = nový Formulár (); form.param ("grant_typ", "autorizačný_kód"); form.param ("kód", kód); 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_URLENCODE),

Ako vidíme, pre toto volanie neexistuje žiadna interakcia s prehliadačom a požiadavka sa robí priamo pomocou klientskeho rozhrania API JAX-RS ako HTTP POST.

Pretože koncový bod tokenu vyžaduje autentifikáciu klienta, zahrnuli sme prihlasovacie údaje klienta client_id a client_secret v Povolenie hlavička.

Klient môže pomocou tohto prístupového tokenu vyvolať API servera zdrojov, ktoré sú predmetom nasledujúcej podsekcie.

4.4. Chránený prístup k zdrojom

V tomto okamihu máme platný prístupový token a môžeme zavolať /čítať a /napíš API.

Urobiť to, musíme poskytnúť Povolenie hlavička. Pomocou API klienta JAX-RS sa to jednoducho deje cez Hlavička Invocation.Builder () metóda:

resourceWebTarget = webTarget.path ("zdroj / čítanie"); Invocation.Builder invocationBuilder = resourceWebTarget.request (); response = invocationBuilder .header ("autorizácia", tokenResponse.getString ("access_token")) .get (String.class);

5. Server zdrojov OAuth 2.0

V tejto časti budeme budovať zabezpečenú webovú aplikáciu založenú na JAX-RS, MicroProfile JWT a MicroProfile Config. MicroProfile JWT sa stará o validáciu prijatých JWT a mapovanie rozsahov JWT na role Jakarta EE.

5.1. Maven závislosti

Okrem závislosti na webovom API Java EE potrebujeme aj rozhrania MicroProfile Config a MicroProfile JWT API:

 javax javaee-web-api 8.0 poskytnutý org.eclipse.microprofile.config microprofile-config-api 1.3 org.eclipse.microprofile.jwt mikroprofil-jwt-auth-api 1,1 

5.2. Mechanizmus autentifikácie JWT

MicroProfile JWT poskytuje implementáciu mechanizmu autentifikácie nosného tokenu. Toto sa stará o spracovanie JWT prítomného v Povolenie header, sprístupňuje Jakarta EE Security Principal ako a JsonWebToken ktorý je držiteľom nárokov JWT a mapuje rozsahy rolí Jakarty EE. Ak sa chcete dozvedieť viac, pozrite si bezpečnostné rozhranie Jakarta EE Security API.

Ak chcete povoliť Mechanizmus autentifikácie JWT na serveri, musíme pridať LoginConfig anotácia v aplikácii JAX-RS:

@ApplicationPath ("/ api") @DeclareRoles ({"resource.read", "resource.write"}) @LoginConfig (authMethod = "MP-JWT") verejná trieda OAuth2ResourceServerApplication rozširuje aplikáciu {}

Navyše, MicroProfile JWT potrebuje na overenie podpisu JWT verejný kľúč RSA. Môžeme to zabezpečiť buď introspekciou, alebo pre zjednodušenie manuálnym kopírovaním kľúča z autorizačného servera. V obidvoch prípadoch musíme uviesť umiestnenie verejného kľúča:

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

Nakoniec musí MicroProfile JWT overiť iss nárok prichádzajúceho JWT, ktorý by mal byť prítomný a zodpovedať hodnote vlastnosti MicroProfile Config:

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

Spravidla ide o umiestnenie autorizačného servera.

5.3. Zabezpečené koncové body

Na demonštračné účely pridáme zdrojové API s dvoma koncovými bodmi. Jeden je a čítať koncový bod, ktorý je prístupný používateľom majúcim resource.read rozsah a ďalší napíš koncový bod pre používateľov s zdroj.písať rozsah.

Obmedzenie rozsahu sa vykonáva prostredníctvom @RolesAllowed anotácia:

@Path ("/ resource") @RequestScoped verejná trieda ProtectedResource {@Inject súkromný principál JsonWebToken; @GET @RolesAllowed ("resource.read") @Path ("/ read") public String read () {return "Chránený zdroj, ku ktorému pristupuje:" + principal.getName (); } @POST @RolesAllowed ("resource.write") @Path ("/ write") public String write () {return "Chránený zdroj, ku ktorému pristupuje:" + principal.getName (); }}

6. Spustenie všetkých serverov

Na spustenie jedného servera stačí vyvolať príkaz Maven v zodpovedajúcom adresári:

sloboda balíka mvn: run-server

Autorizačný server, klient a server prostriedkov budú spustené a budú k dispozícii na nasledujúcich miestach:

# Autorizačný server // localhost: 9080 / # Klient // localhost: 9180 / # Server zdrojov // localhost: 9280 / 

Môžeme teda získať prístup na domovskú stránku klienta a potom kliknutím na „Získať prístupový token“ spustiť proces autorizácie. Po prijatí prístupového tokenu môžeme získať prístup k serveru zdrojov čítať a napíš API.

V závislosti od pridelených rozsahov server zdrojov odpovie buď úspešnou správou, alebo dostaneme stav HTTP 403 zakázaný.

7. Záver

V tomto článku sme poskytli implementáciu autorizačného servera OAuth 2.0, ktorý je možné použiť s akýmkoľvek kompatibilným klientom a serverom zdrojov OAuth 2.0.

Aby sme vysvetlili celkový rámec, poskytli sme tiež implementáciu pre klienta a server zdrojov. Na implementáciu všetkých týchto komponentov sme použili použitie Jakarta EE 8 API, najmä CDI, Servlet, JAX RS, Jakarta EE Security. Ďalej sme použili pseudo-Jakarta EE API z MicroProfile: MicroProfile Config a MicroProfile JWT.

Celý zdrojový kód príkladov je k dispozícii na GitHub. Upozorňujeme, že kód obsahuje príklad typov autorizačného kódu aj typu obnovovacieho tokenu.

Na záver je dôležité uvedomiť si vzdelávací charakter tohto článku a uvedený príklad by sa nemal používať vo výrobných systémoch.


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