Tavaszi REST API + OAuth2 + szögletes

1. Áttekintés

Ebben az oktatóanyagban egy REST API-t biztosítunk az OAuth2-vel, és egy egyszerű Angular kliensből használjuk fel.

A kiépítendő alkalmazás három különálló modulból áll:

  • Authorization Server
  • Erőforrás-kiszolgáló
  • Felhasználói felület engedélyezési kódja: az engedélyezési kódfolyamatot használó kezelőfelület

Az OAuth-csomagot a Spring Security 5-ben fogjuk használni. Ha használni szeretné a Spring Security OAuth örökölt veremét, olvassa el ezt a korábbi cikket: Spring REST API + OAuth2 + Angular (a Spring Security OAuth örökség verem használata).

Ugorjunk be rögtön.

2. Az OAuth2 hitelesítési kiszolgáló (AS)

Egyszerűen fogalmazva, a Authorization Server olyan alkalmazás, amely jogkivonatokat ad ki az engedélyezéshez.

Korábban a Spring Security OAuth verem lehetőséget kínált egy Authorization Server telepítésére tavaszi alkalmazásként. De a projekt elavult, főleg azért, mert az OAuth egy nyílt szabvány, sok olyan jól bevált szolgáltatóval, mint például az Okta, a Keycloak és a ForgeRock, hogy csak néhányat említsünk.

Ezek közül a Keycloak-ot fogjuk használni. Ez egy nyílt forráskódú Identity and Access Management szerver, amelyet a Red Hat felügyel, Java-ban fejlesztett a JBoss. Nem csak az OAuth2, hanem más szabványos protokollokat is támogat, mint például az OpenID Connect és a SAML.

Ehhez az oktatóanyaghoz egy beágyazott Keycloak szervert hozunk létre egy Spring Boot alkalmazásban.

3. Az erőforrás-kiszolgáló (RS)

Most beszéljük meg az Erőforrás-kiszolgálót; ez lényegében a REST API, amelyet végül el akarunk használni.

3.1. Maven konfiguráció

Az erőforrás-kiszolgálónk nagyjából megegyezik az előző Authorization Server-kiszolgálóval, a Keycloak részt és a másikat használja egy további spring-boot-starter-oauth2-erőforrás-kiszolgáló függőség:

 org.springframework.boot spring-boot-starter-oauth2-resource-server 

3.2. Biztonsági konfiguráció

Mivel a Spring Boot-ot használjuk, megadhatjuk a minimálisan szükséges konfigurációt a Boot tulajdonságokkal.

Megcsináljuk ezt egy alkalmazás.yml fájl:

szerver: port: 8081 servlet: context-path: / erőforrás-kiszolgáló rugó: security: oauth2: resourceserver: jwt: kibocsátó-uri: // localhost: 8083 / auth / realms / baeldung jwk-set-uri: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / certs

Itt megadtuk, hogy JWT tokent fogunk használni az engedélyezéshez.

A jwk-set-uri tulajdonság a nyilvános kulcsot tartalmazó URI-ra mutat, hogy az erőforrás-kiszolgálónk ellenőrizhesse a tokenek integritását.

A kibocsátó-uri tulajdonság egy további biztonsági intézkedést jelent a tokenek kibocsátójának hitelesítésére (amely az Authorization Server). Ennek a tulajdonságnak a hozzáadása azonban azt is előírja, hogy az Authorization Server-nek futtatnia kell, mielőtt elindíthatnánk az Resource Server alkalmazást.

Ezután állítsuk be a az API biztonsági konfigurációja a végpontok biztonságához:

A @Configuration public class SecurityConfig kiterjeszti a WebSecurityConfigurerAdapter {@Orride védett érvénytelen konfigurációt (HttpSecurity http) a {http.cors () .and () .authorizeRequests () .antMatchers (HttpMethod.GET, "/ user / info", "/ api / foos / ** ") .hasAuthority (" SCOPE_read ") .antMatchers (HttpMethod.POST," / api / foos ") .hasAuthority (" SCOPE_write ") .anyRequest () .authenticated () .and () .oauth2ResourceServer ). jwt (); }}

Mint láthatjuk, a GET módszereinknél csak olyan kérelmeket engedélyezünk, amelyeknek van olvas hatálya. A POST módszerhez a kérelmezőnek rendelkeznie kell a ír hatóság mellett olvas. Bármely más végpont esetében a kérést csak bármely felhasználóval hitelesíteni kell.

Is, a oauth2ResourceServer () metódus megadja, hogy ez egy erőforrás-kiszolgáló, a jwt () -formázott tokenek.

Egy másik szempont, amelyet itt meg kell jegyezni, a módszer használata cors () hogy engedélyezzék az Access-Control fejléceket a kéréseken. Ez különösen fontos, mivel Angular ügyféllel van dolgunk, és kéréseink egy másik eredet URL-ből származnak.

3.4. A modell és a tár

Ezután definiáljuk a javax.persistence.Entity modellünkhöz, Foo:

@Entity public class Foo {@Id @GeneratedValue (strategy = GenerationType.IDENTITY) private Long id; privát karakterlánc neve; // kivitelező, mérőeszközök és beállítók}

Akkor szükségünk van egy Foos. A Spring-et fogjuk használni PagingAndSortingRepository:

nyilvános felület Az IFooRepository kiterjeszti a PagingAndSortingRepository {} 

3.4. A szolgáltatás és a megvalósítás

Ezt követően meghatározunk és megvalósítunk egy egyszerű szolgáltatást az API-nkhoz:

nyilvános felület IFooService {Opcionális findById (hosszú id); Foo mentés (Foo foo); Iterálható findAll (); } @Service public class A FooServiceImpl megvalósítja az IFooService {private IFooRepository fooRepository; public FooServiceImpl (IFooRepository fooRepository) {this.fooRepository = fooRepository; } @Override public Opcionális findById (hosszú id) {return fooRepository.findById (id); } @Orride public Foo save (Foo foo) {return fooRepository.save (foo); } @Orride public Iterable findAll () {return fooRepository.findAll (); }} 

3.5. Egy minta vezérlő

Most valósítsunk meg egy egyszerű vezérlőt, amely leleplezi a sajátunkat Foo erőforrás DTO-n keresztül:

@RestController @RequestMapping (value = "/ api / foos") nyilvános osztály FooController {private IFooService fooService; public FooController (IFooService fooService) {this.fooService = fooService; } @CrossOrigin (origins = "// localhost: 8089") @GetMapping (value = "/ {id}") public FooDto findOne (@PathVariable Long id) {Foo entitás = fooService.findById (id) .orElseThrow (() -> új ResponseStatusException (HttpStatus.NOT_FOUND)); return convertToDto (entitás); } @GetMapping nyilvános gyűjtemény findAll () {Iterable foos = this.fooService.findAll (); FooDtos = new ArrayList () lista; foos.forEach (p -> fooDtos.add (convertToDto (p))); visszatér fooDtos; } védett FooDto convertToDto (Foo entitás) {FooDto dto = új FooDto (entitás.getId (), entitás.getName ()); return dto; }}

Figyelje meg a @CrossOrigin felett; ez a vezérlőszintű konfiguráció, amelyet engedélyeznünk kell a CORS-nak az Angular App-ból a megadott URL-en.

Itt van a miénk FooDto:

nyilvános osztály FooDto {private long id; privát karakterlánc neve; }

4. Kezelőfelület - Beállítás

Most egy egyszerű front-end Angular megvalósítást fogunk megvizsgálni az ügyfél számára, amely hozzáférni fog a REST API-hoz.

Először az Angular CLI-t használjuk az előtér-modulok előállításához és kezeléséhez.

Először telepítjük a csomópontot és az npm-et, mivel az Angular CLI egy npm eszköz.

Akkor használnunk kell a frontend-maven-plugin hogy megépítsük Angular projektünket Maven segítségével:

   com.github.eirslett frontend-maven-plugin 1.3 v6.10.2 3.10.10 src / main / resources install node és npm install-node-and-npm npm install npm npm run build npm run build 

És végül, hozzon létre egy új modult az Angular CLI használatával:

új oauthApp

A következő részben az Angular alkalmazás logikáját tárgyaljuk.

5. Engedélyezési kód folyamata szöggel

Itt fogjuk használni az OAuth2 engedélyezési kód folyamatát.

Felhasználási esetünk: Az ügyfélalkalmazás kódot kér az Authorization Server-től, és megjelenik egy bejelentkezési oldal. Miután a felhasználó megadta érvényes hitelesítő adatait és elküldte, az Authorization Server megadja nekünk a kódot. Ezután a kezelőfelület egy hozzáférési token megszerzésére használja.

5.1. Otthoni alkatrész

Kezdjük a fő összetevőnkkel, a HomeComponent, ahol az összes művelet megkezdődik:

@Component ({selector: 'home-header', szolgáltatók: [AppService], sablon: `Bejelentkezés Üdvözöljük !! Kijelentkezés

`}) export osztály HomeComponent {public isLoggedIn = false; konstruktor (privát _szolgáltatás: AppService) {} ngOnInit () {this.isLoggedIn = this._service.checkCredentials (); legyen i = window.location.href.indexOf ('kód'); if (! this.isLoggedIn && i! = -1) {this._service.retrieveToken (window.location.href.substring (i + 5)); }} login () {window.location.href = '// localhost: 8083 / auth / realms / baeldung / protokoll / openid-connect / auth? response_type = kód & hatókör = openid% 20write% 20read & client_id = '+ this._service.clientId +' & redirect_uri = '+ this._service.redirectUri; } kijelentkezés () {this._service.logout (); }}

Kezdetben, ha a felhasználó nincs bejelentkezve, csak a bejelentkezési gomb jelenik meg. Erre a gombra kattintva a felhasználó eljut a hozzáférési rendszer engedélyezési URL-jéhez, ahol beírja a felhasználónevet és a jelszót. Sikeres bejelentkezés után a felhasználót visszautaljuk a hitelesítési kóddal, majd a kód segítségével lekérjük a hozzáférési tokent.

5.2. App Service

Most nézzük meg AppService - található app.service.ts - amely tartalmazza a szerver interakciók logikáját:

  • retrieveToken (): hozzáférési jogkivonat beszerzése engedélyezési kód használatával
  • saveToken (): hozzáférési tokenünk mentése egy cookie-ba az ng2-cookies könyvtár segítségével
  • getResource (): egy Foo objektum megszerzése a szerverről az azonosítója használatával
  • checkCredentials (): annak ellenőrzése, hogy a felhasználó be van-e jelentkezve, vagy sem
  • Kijelentkezés(): a hozzáférési token cookie törléséhez és a felhasználó kijelentkezéséhez
export osztály Foo {konstruktor (nyilvános azonosító: szám, nyilvános név: karakterlánc) {}} @Injectable () export osztály AppService {public clientId = 'newClient'; public redirectUri = '// localhost: 8089 /'; konstruktor (private _http: HttpClient) {} retrieveToken (code) {let params = new URLSearchParams (); params.append ('grant_type', 'authorization_code'); params.append ('client_id', this.clientId); params.append ('client_secret', 'newClientSecret'); params.append ('redirect_uri', this.redirectUri); params.append ('kód', kód); let fejlécek = new HttpHeaders ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8'}); this._http.post ('// localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / token', params.toString (), {headers: header}) .subscribe (data => this.saveToken ( adatok), err => alert ('Érvénytelen hitelesítő adatok')); } saveToken (token) {var expireDate = new Date (). getTime () + (1000 * token.expires_in); Cookie.set ("access_token", token.access_token, expireDate); console.log ('Hozzáférési token'); window.location.href = '// localhost: 8089'; } getResource (resourceUrl): Megfigyelhető {var headers = new HttpHeaders ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8', 'Authorization': 'Bearer' + Cookie.get ('access_token')}); adja vissza ezt :_http.get (resourceUrl, {fejlécek: fejlécek}) .catch ((hiba: bármelyik) => Megfigyelhető.throw (hiba.json (). hiba || 'Szerverhiba')); } checkCredentials () {return Cookie.check ('access_token'); } kijelentkezés () {Cookie.delete ('access_token'); window.location.reload (); }}

Ban,-ben retrieveToken módszerrel az ügyfél hitelesítő adatainkat és az Alap hitelesítést használjuk a POST hoz / openid-connect / token végpont a hozzáférési jogkivonat megszerzéséhez. A paramétereket URL-kódolású formátumban küldjük. Miután megszereztük a hozzáférési tokent, sütiben tároljuk.

A sütitárolás itt különösen fontos, mert a sütit csak tárolási célokra használjuk, és nem közvetlenül a hitelesítési folyamatot hajtjuk végre. Ez segít megvédeni a webhelyek közötti kérelmek hamisítását (CSRF) támadásokat és sebezhetőségeket.

5.3. Foo komponens

Végül a mi FooComponent Foo adataink megjelenítéséhez:

@Component ({selector: 'foo-details', szolgáltatók: [AppService], sablon: `ID {{foo.id}} Név {{foo.name}} New Foo`}) exportálási osztály FooComponent {public foo = new Foo (1, 'minta foo'); private foosUrl = '// localhost: 8081 / erőforrás-szerver / api / foos /'; konstruktor (privát _szolgáltatás: AppService) {} getFoo () {this._service.getResource (this.foosUrl + this.foo.id) .subscribe (data => this.foo = data, error => this.foo.name = 'Hiba'); }}

5.5. App Component

Egyszerű AppComponent hogy gyökérkomponensként működjön:

@Component ({selector: 'app-root', template: `Spring Security Oauth - Authorization Code`}) exportálási osztály AppComponent {} 

És a AppModule ahol az összes alkatrészünket, szolgáltatásunkat és útvonalunkat becsomagoljuk:

@NgModule ({deklarációk: [AppComponent, HomeComponent, FooComponent], import: [BrowserModule, HttpClientModule, RouterModule.forRoot ([{path: '', component: HomeComponent, pathMatch: 'full'}], {onSameUroadNavigation })], szolgáltatók: [], bootstrap: [AppComponent]}) exportálási osztály AppModule {} 

7. Futtassa a kezelőfelületet

1. A front-end modulok futtatásához először fel kell építenünk az alkalmazást:

mvn tiszta telepítés

2. Ezután el kell navigálnunk az Angular alkalmazás könyvtárunkba:

cd src / main / resources

3. Végül elindítjuk az alkalmazást:

npm kezdés

A szerver alapértelmezés szerint a 4200-as porton indul; bármely modul portjának módosításához módosítsa:

"start": "ng tálalás"

ban ben csomag.json; Például a 8089-es porton való futtatásához adja hozzá:

"start": "ng serve - port 8089"

8. Következtetés

Ebben a cikkben megtudtuk, hogyan engedélyezhetjük alkalmazásunkat az OAuth2 használatával.

Az oktatóanyag teljes megvalósítása megtalálható a GitHub projektben.