Itt jársz most: Kezdőlap > Alkalmazásfejlesztés > OAuth 2 authorizáció - A kliensek konfigurálása

Szűrő megjelenítése

OAuth 2 authorizáció - A kliensek konfigurálása

Először is tisztázzuk, mi is lenne a cél. Miután már rendelkezünk egy OAuth authorization server hozzáféréssel, ahhoz hogy azt használatba vehessük, a klienseket (minden olyan alkalmazást, amit OAuth authorizációval akarunk védeni), regisztrálnunk kell a serveren. Ez a folyamat teljes egészében attól függ, hogy milyen OAuth authorization servert használunk, bár a lényeg nagyjából ugyanaz lesz mindenhol. A kliens az alábbiak egyike lesz:

  • Authorization Code Grant Flow alapú UI (frontend) alkalmazás: a regisztrált alkalmazás elindíthat egy Auth Code authorizációs folyamatot, tehát a már korábban említett két-lépcsős authorizálást.
  • Client Credentials Grant Flow alapú service alkalmazás: az authorizációs folyamatban nem vesz részt egy valós személy, effektíve két service alkalmazás közötti "privát" csatornát definiálunk.
  • Resource Server alkalmazás: egy olyan alkalmazás, amihez hozzáférhetünk egy OAuth authorization server által kiállított tokennel és az valamilyen adatokat tud számunkra biztosítani. Gyakorlatilag az előző két típus túloldalán, a fogadó oldalon álló alkalmazásról beszélünk.

A regisztrációs folyamat lehet manuális, tehát egy adminisztrátor felhasználó kézzel rögzíti a kliens alkalmazás adatait és típusát, illetve sok esetben a CI/CD pipeline-ok integrált lépéseként történik meg, egy konfigurációs file tartalma alapján. Mivel a korábbi cikkeimben a saját authorizációs serveremet mutattam be, azon keresztül fogom most részletezni a lépéseket, de például egy Auth0-val is hasonló paramétereket kell majd megadnunk. A folyamat eredménye egy client ID és secret pár lesz, melyet a kliens alkalmazás használhat önmaga azonosítására. Resource Server regisztráció esetén valószínűleg meg kell adnunk egy audience értéket is, mely ugyan nem kötelező, de az OAuth specifikáció mindenképp javasolja használatát. Ezzel ugyanis a kiállított access tokenre tehetünk egy megszorítást, melyet így már csak olyan alkalmazások fogadhatnak el, amik önmagukat az adott audience-ként azonosítják. Tehát ha a token az XYZ Resource Server részére van kiállítva, ugyanazt a tokent ABC Resource Server nem fogadja el. Az audience érték egyébként a specifikáció szerint egy URL, ami nem kell valós domainre mutasson (tehát pl. a https://my.awesome.service.localhost egy a specifikáció által érvényesnek tekintett audience), a gyakorlatban azonban bármilyen érték megengedett.

Teljesen authorization server provider függő, de akár megadhatunk további megszorításokat is, például megadhatjuk, melyik kliens milyen scope-okat kérhet egy másik alkalmazás elérésére. Tehát például meghatározhatjuk, hogy "A" kliens "B" Resource Servert írási és olvasási kérésekkel is hívhatja, "C" kliens ugyanazt a "B" Resource Servert azonban csak olvasási kérésekkel. Így a jogosultságok nagyon szépen kezelhetővé válnak és biztosak lehetünk abban, hogy adott kliens biztosan csak a számára engedélyezett műveleteket hajtja végre a túlsó félen. Ennyit az elméletről, nézzük a gyakorlatot.

A Resource Server beállítása

Kezdjük a "nehezebb" részével, mivel a Resource Serveren több lépést is meg kell tennünk, hogy megfelelő módon működjön az integráció az Authorization Serverrel:

  • Adjuk hozzá a projekthez a szükséges függéseket.
  • Be kell állítani, mely endpoint milyen scope-ot vár: ez egy viszonylag hosszadalmas folyamat lehet, ha sok endpointtal rendelkezik az alkalmazás, legalábbis a megfelelő jogosultságok megállapítása tekintetében.
  • Engedélyezni kell az alkalmazásban a Resource Serverként való működést: ez effektíve az access token kérésenkénti feldolgozását illetve az OAuth Authorization Serverrel való verifikálását takarja.

Spring esetén az alábbi függésekre lesz szükségünk:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<!-- az alábbi csak akkor kell, ha JWT tokeneket használunk -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>

A Spring (pontosabban a Spring Boot) egészen rugalmasan áll ehhez a témához, igazából kétféle módon is beállíthatjuk a scope-okat az endpointokra. Az egyik opció, mellyel egyúttal a Resource Server módot is engedélyezzük, egy SecurityFilterChain bean létrehozása:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        return http
                .authorizeRequests()
                    // ez az endpoint public lesz
                    .regexMatchers(HttpMethod.GET, "/translations\\?packs=.+$")
                        .permitAll()
                    // erre az endpointra a read:translations scope-ot várjuk ...
                    .mvcMatchers(HttpMethod.GET, "/translations/**")
                        .hasAuthority("SCOPE_read:translations")
                     // ... erre pedig a write:translations scope-ot
                    .mvcMatchers("/translations/**")
                        .hasAuthority("SCOPE_write:translations")
                    .and()

                // végül bekapcsoljuk a Resource Server módot (vagy ::opaqueToken ha nem JWT access tokent használunk)
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                
                // ... további beállítások

                .build();
    }
}

Nagyon fontos megjegyezni, hogy a Spring Security a scope-ok meghatározására a .hasAuthority matchert használja, ami mindenképp SCOPE_ prefixszel várja a scope neveket. Enélkül ROLE_ prefixet rendel hozzájuk automatikusan, ami OAuth környezetben nem jellemzően használt (és nem is fog ebben az esetben működni).

A másik lehetőség Spring Boot esetében a @PreAuthorize annotációk használata. Egy konfigurációs osztályon előbb el kell helyeznünk az @EnableGlobalMethodSecurity(prePostEnabled = true) annotációt, majd bármilyen Spring-managed bean osztály metódusain elhelyezhetünk egy-egy @PreAuthorize annotációt, például:

@PreAuthorize("isAuthenticated() && hasAuthority('SCOPE_write:entries')")

Ez gyakorlatilag kiváltja az endpoint szintű authorizálást, valamivel jobban szemcsézett authorizációs követelményeket támasztva, bár vitathatatlan hátránya, hogy a security konfiguráció így nem lesz központozott.

Az utolsó lépésként az application.yml-ben meg kell adnunk az alábbi pár sort és a service ezzel készen áll a Resource Serverként való működésre:

spring:
  security:
    oauth2:
      resourceserver:

        # JWT access tokenek esetén, opaque tokenekkel picit másabb paraméterekre lesz szükség
        jwt:

          # az Authorization Server címe, egyben pontosan ugyanez az érték kell szerepeljen a kiállított token issuer fieldjében
          issuer-uri: http://my-authorization-server.localhost

          # a Resource Server audience értéke, tehát ahogy a service önmagát azonosítja
          # ez egy opcionális érték, de ha a token tartalmaz audience értéket, kötelező megadni itt is
          audiences: https://my.awesome.service.localhost

Hasonló módon tudunk beállítani egy Node.js-ben írt alkalmazást mint Resource Servert. A példában egy Express alapú, TypeScriptben írt alkalmazás konfigurációját láthatjuk majd. Először is, szükségünk lesz az express-oauth2-jwt-bearer nevű függésre. Az authorizáció bekapcsolására egy middleware áll rendelkezésünkre, mely az issuert és az audiencet várja paraméterül:

auth({
    issuerBaseURL: "http://my-authorization-server.localhost",
    audience: "https://my.awesome.service.localhost"
})

Az elvárt scope-ot pedig a requiredScope függvény segítségével adhatjuk meg, ami szintén egy middlewaret ad, paramétere pedig "tisztán" a scope, például write:entries. Fontos megjegyezni, hogy az auth middlewaret globális middlewareként bekötve minden endpoint védetté válik, függetlenül attól, hogy használjuk-e a requiredScope-ot vagy sem, így ha vannak publikus endpointjaink is, az auth-ot kénytelenek leszünk endpointonként bekötni. Ez egy elég bosszantó hiányossága ennek a librarynek, amit egyébként maga az Auth0 publikált.

A Client Credentials kliensek beállítása

Most, hogy már van, ami fogadja az access tokent, ideje nekiállni beállítani a klienseket - kezdésképpen egy Client Credentials Grant Flow kommunikációt nézünk meg. Ha Springet használunk, viszonylag egyszerű a dolgunk. Mivel a kliens egy tokent fog majd generálni egy service elérésére, a kliensnek effektíve be kell jelentkeznie az Authorization Serveren. Ezért meg kell adnunk a client ID és secret párt, a scope-ot amire generálni szeretnénk a tokent, illetve opcionálisan az audience-t. A konfiguráció nagyjából így fog kinézni:

spring:
  security:
    oauth2:
      client:
        registration:
          my-awesome-service:
            authorization-grant-type: client_credentials
            client-id: my-client-id
            client-secret: my-client-secret
            provider: my-awesome-service-provider
            scope:
              - write:everything
              - read:everything
        provider:
          my-awesome-service-provider:
            token-uri: http://my-authorization-server.localhost/oauth/token?audience=https://my.awesome.service.localhost
            issuer-uri: http://my-authorization-server.localhost

Bontsuk szét a fenti konfigurációt. Először is regisztrálunk egy OAuth klienst, aminek a grant type-ja client_credentials. Megadjuk a kliens alkalmazás regisztrált azonosítóját és "jelszavát", illetve a scope-okat amire generáljuk a tokent. Providerre akkor lesz szükségünk, ha nem egy standard támogatott OAuth servicehez akarunk csatlakozni (pl. Facebook, GitHub, Google, stb.). A provider regisztrációban a token URI részeként megadjuk az audience-t, illetve beállítjuk az issuer címét, amit itt a Spring arra fog használni, hogy effektíve meg tudja hívni az Authorization Servert. Ezzel gyakorlatilag már készen áll a kliens alkalmazás a tokenek generáltatására és vele együtt az OAuth védett Resource Serverek meghívására. A hogyanra még visszatérünk.

Az Authorization Code kliensek beállítása

Nos ez viszonylag rövid magyarázat lesz: az authorization-grant-type értékét állítsuk authorization_code-ra és készen vagyunk, a többit a Spring intézi. Egy teljesen háttérben történő beszélgetés helyett azonban ilyenkor a Spring át fogja irányítani a felhasználót az Authorization Server "authorize" útvonalára, ahol a felhasználó engedélyezheti vagy elutasíthatja a hozzáférést. Nyilván ebből az okból fontos megjegyezni, hogy service-to-service kommunikációra nem használható az Authorization Code Grant Flow, továbbá JS front alkalmazás esetén az Authorization Code Grant Flow PKCE változatát kell használni, mivel a client secret ellenkező esetben megjelenne a publikusan elérhető forráskódban.

OAuth REST kliensek Spring Boot alatt

Az utolsó lépés, hogy ténylegesen elkezdjük hívni az OAuth védett Resource Servereket. A Spring már csak a WebClient-hez ad standard támogatást, ám ahhoz is némi manuális konfiguráció szükséges. Én egy másik utat választottam, azonban van egy kiváló Baeldung cikk a WebClient használatáról, a cikk végén mellékelem. Az általam választott megoldás egyébként a WebClient módszerén alapszik, csak bármilyen másfajta REST kliens implementációval működik. A lényege, hogy a Spring OAuth2AuthorizedClientManager osztálya segítségével tudunk kérni egy OAuth2AuthorizedClient objektumot, amiben lesz egy OAuth2AccessToken. Ez az objektum tartalmazza magát az access tokent, ráadásul amennyiben már az lejárat közelében van, a Spring kér egy újat, mielőtt visszaadná azt a hívó félnek. Így mindig rendelkezésünkre áll egy érvényes access token, amit a REST kliens el tud helyezni az Authorization headerben.

Az implementáció részletes leírása már némiképp túllépné a cikk eredeti célját, de alább mellékelek egy linket a forráskódra - illetve egy későbbi cikkben még lehet visszatérünk rá.

Spring WebClient integráció

Egyedi REST kliens integráció Spring OAuth támogatással

Kommentek

Komment írásához jelentkezz be
Bejelentkezés

Még senki nem szólt hozzá ehhez a bejegyzéshez.