Spring REST API + OAuth2 + Angular (a Spring Security OAuth örökölt verem használatával)
1. Áttekintés
Ebben az oktatóanyagban egy REST API-t fogunk biztosítani az OAuth segítségével, és egy egyszerű Angular kliensből használjuk fel.
A kiépítendő alkalmazás négy különálló modulból áll:
- Authorization Server
- Erőforrás-kiszolgáló
- UI implicit - kezelői alkalmazás az Implicit Flow használatával
- Felhasználói felület jelszava - a Jelszófolyamatot használó kezelőfelület
jegyzet: ez a cikk a Spring OAuth örökölt projektet használja. A cikknek az új Spring Security 5 verem használatával készült verzióját tekintse meg a Spring REST API + OAuth2 + Angular cikkünket.
Rendben, ugorjunk be.
2. A hitelesítési kiszolgáló
Először kezdjük el beállítani az Authorization Server-t egyszerű Spring Boot alkalmazásként.
2.1. Maven konfiguráció
A következő függőségeket állítjuk be:
org.springframework.boot spring-boot-starter-web org.springframework spring-jdbc mysql mysql-connector-java runtime org.springframework.security.oauth spring-security-oauth2
Vegye figyelembe, hogy a spring-jdbc és a MySQL-t használjuk, mert a token store JDBC által támogatott megvalósítását fogjuk használni.
2.2. @EnableAuthorizationServer
Most kezdjük el konfigurálni a hozzáférési tokenek kezeléséért felelős hitelesítési kiszolgálót:
@Configuration @EnableAuthorizationServer nyilvános osztály Az AuthServerOAuth2Config kiterjeszti az AuthorizationServerConfigurerAdapter {@Autowired @Qualifier ("authenticationManagerBean") privát AuthenticationManager authenticationManager; A @Orride public void configure (AuthorizationServerSecurityConfigurer oauthServer) dobja a {oauthServer .tokenKeyAccess ("engedélyAll ()") .checkTokenAccess ("isAuthenticated ()" kivételt; } A @Orride public void configure (ClientDetailsServiceConfigurer ügyfelek) a (z) {clients.jdbc (dataSource ()) .withClient ("sampleClientId") .authorizedGrantTypes ("implicit") .scopes ("read") .autoApprove (true). ) .withClient ("clientIdPassword") .secret ("titkos") .authorizedGrantTypes ("jelszó", "jogosultsági kód", "frissítő_beszélt") .scopes ("olvasás"); } @Orride public void configure (AuthorizationServerEndpointsConfigurer endpoints) dobja a Kivétel {végpontokat .tokenStore (tokenStore ()) .authenticationManager (authenticationManager); } @Bean public TokenStore tokenStore () {return new JdbcTokenStore (dataSource ()); }}
Vegye figyelembe, hogy:
- A tokenek fennmaradása érdekében a JdbcTokenStore
- Regisztráltunk egy klienst a “beleértett”Támogatás típusa
- Regisztráltunk egy másik ügyfelet, és engedélyeztük aJelszó“, “megerősítő kód”És„refresh_token”Támogatási típusok
- A „Jelszó”Támogatás típusát be kell vezetnünk és felhasználnunk kell a AuthenticationManager bab
2.3. Adatforrás beállítása
Ezután állítsuk be az adatforrásunkat a JdbcTokenStore:
@Value ("classpath: schema.sql") privát erőforrás schemaScript; @Bean public DataSourceInitializer dataSourceInitializer (DataSource dataSource) {DataSourceInitializer inicializáló = új DataSourceInitializer (); inicializáló.setDataSource (dataSource); Initializer.setDatabasePopulator (databasePopulator ()); visszatérő inicializáló; } privát DatabasePopulator databasePopulator () {ResourceDatabasePopulator populator = új ResourceDatabasePopulator (); populator.addScript (schemaScript); visszatérő populátor; } @Bean public DataSource dataSource () {DriverManagerDataSource dataSource = új DriverManagerDataSource (); dataSource.setDriverClassName (env.getProperty ("jdbc.driverClassName")); dataSource.setUrl (env.getProperty ("jdbc.url")); dataSource.setUsername (env.getProperty ("jdbc.user")); dataSource.setPassword (env.getProperty ("jdbc.pass")); return dataSource; }
Vegye figyelembe, hogy ahogy használjuk JdbcTokenStore inicializálnunk kell az adatbázis-sémát, ezért használtuk DataSourceInitializer - és a következő SQL séma:
drop table, ha létezik oauth_client_details; táblázat létrehozása oauth_client_details (ügyfél_azonosító VARCHAR (255) ELSŐ KULCS, erőforrás_idek VARCHAR (255), ügyfél_secret VARCHAR (255), hatókör VARCHAR (255), engedélyezett_grant_típusok VARCHAR (255), webszerver_redirect_uri VARCHAR_intervallum (255) , refresh_token_validity INTEGER, további_információk VARCHAR (4096), automatikus jóváhagyás VARCHAR (255)); drop table ha létezik oauth_client_token; tábla létrehozása oauth_client_token (token_id VARCHAR (255), token LONG VARBINARY, authentication_id VARCHAR (255) PRIMARY KEY, user_name VARCHAR (255), client_id VARCHAR (255)); dobótábla, ha létezik oauth_access_token; tábla létrehozása oauth_access_token (token_id VARCHAR (255), token LONG VARBINARY, authentication_id VARCHAR (255) PRIMARY KEY, user_name VARCHAR (255), client_id VARCHAR (255), authentication LONG VARBINARY, refresh_token VARCHAR (255)); drop table, ha létezik, oauth_refresh_token; tábla létrehozása oauth_refresh_token (token_id VARCHAR (255), token LONG VARBINARY, hitelesítés LONG VARBINARY); drop table, ha létezik oauth_code; Oauth_code tábla létrehozása (kód VARCHAR (255), hitelesítés LONG VARBINARY); drop table, ha létezik oauth_approvals; tábla létrehozása oauth_approvals (userId VARCHAR (255), clientId VARCHAR (255), hatókör VARCHAR (255), status VARCHAR (10), expiresAt TIMESTAMP, lastModifiedAt TIMESTAMP); dobótábla, ha létezik ClientDetails; tábla létrehozása ClientDetails (appId VARCHAR (255) ELSŐ KULCS, resourceIds VARCHAR (255), appSecret VARCHAR (255), hatókör VARCHAR (255), grantTypes VARCHAR (255), redirectUrl VARCHAR (255), hatóságok VARCHAR (255), access_token_validity INT , refresh_token_validity INTEGER, további információ VARCHAR (4096), autoApproveScopes VARCHAR (255));
Ne feledje, hogy nem feltétlenül van szükségünk kifejezettre DatabasePopulator bab - egyszerűen használhatnánk a schema.sql - amelyet a Spring Boot alapértelmezés szerint használ.
2.4. Biztonsági konfiguráció
Végül védjük meg az Authorization Server-t.
Amikor az ügyfélalkalmazásnak hozzáférési tokent kell beszereznie, akkor ezt egy egyszerű űrlap-bejelentkezés által vezérelt hitelesítési folyamat után teszi meg:
A @Configuration public class ServerSecurityConfig kiterjeszti a WebSecurityConfigurerAdapter {@Orride védett érvénytelen konfigurációt (AuthenticationManagerBuilder auth) a {Excellation_MemoryAuthentication () .withUser ("john"). Jelszót ("123"). Szerepeket ("USER"); } @Override @Bean public AuthenticationManager authenticationManagerBean () dobja a Kivételt {return super.authenticationManagerBean (); } A @Orride védett void konfiguráció (HttpSecurity http) dobja a {http.authorizeRequests () .antMatchers ("/ login") kivételt. AllowAll () .anyRequest (). Hitelesített () .és () .formLogin (). AllowAll () ; }}
Itt egy rövid megjegyzés az űrlap bejelentkezési konfigurációja nem szükséges a Jelszó folyamathoz - csak az Implicit folyamathoz - így kihagyhatja azt attól függően, hogy milyen OAuth2 folyamatot használ.
3. Az erőforrás-kiszolgáló
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ó
Erőforrás-kiszolgálónk konfigurációja megegyezik az előző Authorization Server alkalmazás-konfigurációval.
3.2. Token Store konfiguráció
Ezután konfiguráljuk a TokenStore hozzáférés ugyanahhoz az adatbázishoz, amelyet az engedélyezési kiszolgáló használ a hozzáférési tokenek tárolásához:
@Autowired privát környezet env; @Bean public DataSource dataSource () {DriverManagerDataSource dataSource = új DriverManagerDataSource (); dataSource.setDriverClassName (env.getProperty ("jdbc.driverClassName")); dataSource.setUrl (env.getProperty ("jdbc.url")); dataSource.setUsername (env.getProperty ("jdbc.user")); dataSource.setPassword (env.getProperty ("jdbc.pass")); return dataSource; } @Bean public TokenStore tokenStore () {return new JdbcTokenStore (dataSource ()); }
Vegye figyelembe, hogy ehhez az egyszerű megvalósításhoz megosztjuk az SQL által támogatott token-tárolót annak ellenére, hogy az Authorization és az Resource kiszolgálók különálló alkalmazások.
Ennek oka természetesen az, hogy az erőforrás-kiszolgálónak képesnek kell lennie erre ellenőrizze a hozzáférési tokenek érvényességét az Authorization Server adta ki.
3.3. Távoli token szolgáltatás
Ahelyett, hogy a TokenStore az erőforrás-kiszolgálónkban használhatjuk RemoteTokeServices:
@Primary @Bean public RemoteTokenServices tokenService () {RemoteTokenServices tokenService = new RemoteTokenServices (); tokenService.setCheckTokenEndpointUrl ("// localhost: 8080 / spring-security-oauth-server / oauth / check_token"); tokenService.setClientId ("fooClientIdPassword"); tokenService.setClientSecret ("titok"); return tokenService; }
Vegye figyelembe, hogy:
- Ez RemoteTokenService használni fogja CheckTokenEndPoint az Authorization Server-en az AccessToken érvényesítéséhez és megszerzéséhez Hitelesítés objektum tőle.
- Ez megtalálható az AuthorizationServerBaseURL + címen. ”/ oauth / check_token“
- A Authorization Server bármilyen TokenStore típust használhat [JdbcTokenStore, JwtTokenStore,…] - ez nem érinti a RemoteTokenService vagy erőforrás-kiszolgáló.
3.4. Egy minta vezérlő
Ezután valósítsunk meg egy egyszerű vezérlőt, amely kiteszi a Foo forrás:
@Controller public class FooController {@PreAuthorize ("# oauth2.hasScope ('read')") @RequestMapping (method = RequestMethod.GET, value = "/ foos / {id}") @ResponseBody public Foo findById (@PathVariable long id) {visszatérés új Foo (Long.parseLong (randomNumeric (2)), randomAfabetic (4)); }}
Vegye figyelembe, hogy az ügyfélnek mire van szüksége "olvas" az erőforrás elérésének területe.
Engedélyeznünk kell a globális módszerbiztonságot és a konfigurálást is MethodSecurityExpressionHandler:
@Configuration @EnableResourceServer @EnableGlobalMethodSecurity (prePostEnabled = true) nyilvános osztály OAuth2ResourceServerConfig kiterjeszti a GlobalMethodSecurityConfiguration {@Override védett MethodSecurityExpressionHandler createExpressionHandsler (Return) (return) }}
És itt van az alapvető Foo Forrás:
nyilvános osztály Foo {private long id; privát karakterlánc neve; }
3.5. Webkonfiguráció
Végül állítsunk be egy nagyon egyszerű webkonfigurációt az API számára:
@Configuration @EnableWebMvc @ComponentScan ({"org.baeldung.web.controller"}) public class ResourceWebConfig implementálja a WebMvcConfigurer {}
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.
Először az Angular CLI-t fogjuk használni az elülső 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.
Ezután használnunk kell a frontend-maven-plugin hogy szögletes projektünket maven segítségével építsük fel:
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
Ne feledje, hogy két front-end modulunk lesz - az egyik a jelszófolyamathoz, a másik pedig az implicit folyamathoz.
A következő szakaszokban az egyes modulok Angular alkalmazáslogikáját tárgyaljuk.
5. A jelszóáramlás szögletes használatával
Itt az OAuth2 jelszófolyamatot fogjuk használni - ezért ez csak a koncepció bizonyítéka, nem pedig a gyártásra kész alkalmazás. Észre fogja venni, hogy az ügyfél hitelesítő adatai a kezelőfelületnek vannak kitéve - erre egy későbbi cikkünkben kitérünk.
Felhasználási esetünk egyszerű: amint a felhasználó megadja a hitelesítő adatait, a kezelői ügyfél felhasználja őket az Access Token megszerzéséhez az Authorization Server-től.
5.1. App Service
Kezdjük a mi AppService - található app.service.ts - amely tartalmazza a szerver interakciók logikáját:
- getAccessToken (): Hozzáférési token adott felhasználói hitelesítő adatok beszerzése
- 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áló osztály Foo {konstruktor (nyilvános azonosító: szám, nyilvános név: karakterlánc) {}} @Injectable () export osztály AppService {konstruktor (privát _router: Router, privát _http: Http) {} getAccessToken (loginData) {let params = new URLSearchParams (); params.append ('felhasználónév', loginData.username); params.append ('jelszó', loginData.password); params.append ('grant_type', 'jelszó'); params.append ('client_id', 'fooClientIdPassword'); let fejlécek = új Fejlécek ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8', 'Engedélyezés:' Basic '+ btoa ("fooClientIdPassword: titkos")}); let options = new RequestOptions ({fejlécek: fejlécek}); this._http.post ('// localhost: 8081 / spring-security-oauth-server / oauth / token', params.toString (), opciók) .map (res => res.json ()) .subscribe (adatok => this.saveToken (adatok), err => figyelmeztetés ('Érvénytelen hitelesítő adatok')); } saveToken (token) {var expireDate = new Date (). getTime () + (1000 * token.expires_in); Cookie.set ("access_token", token.access_token, expireDate); this._router.navigate (['/']); } getResource (resourceUrl): Megfigyelhető {var headers = new Headers ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8', 'Authorization': 'Bearer' + Cookie.get ('access_token')}); var options = new RequestOptions ({fejlécek: fejlécek}); adja vissza ezt :_http.get (resourceUrl, opciók) .map ((res: Response) => res.json ()) .catch ((hiba: bármely) => Megfigyelhető.throw (error.json (). hiba || 'Szerver hiba')); } checkCredentials () {if (! Cookie.check ('access_token')) {this._router.navigate (['/ login']); }} kijelentkezés () {Cookie.delete ('access_token'); this._router.navigate (['/ login']); }}
Vegye figyelembe, hogy:
- Hozzáférési token megszerzéséhez a POST hoz "/ oauth / token”Végpont
- Az ügyfél hitelesítő adatait és az Alap hitelesítést használjuk ennek a végpontnak a eléréséhez
- Ezután elküldjük a felhasználói hitelesítő adatokat, a kódolt ügyfél-azonosítóval és a megadás típusának paramétereivel együtt
- Miután megszereztük az Access 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ípusú támadásokat és sebezhetőségeket.
5.2. Bejelentkezés komponens
Ezután vessünk egy pillantást a mi BejelentkezésKomponens amely felelős a bejelentkezési űrlapért:
@Komponens ({selector: 'login-form', szolgáltatók: [AppService], sablon: `Bejelentkezés`}) exportálási osztály LoginComponent {public loginData = {felhasználónév:" ", jelszó:" "}; konstruktor (private _service: AppService) {} login () {this._service.obtainAccessToken (this.loginData); }
5.3. Otthoni alkatrész
Ezután a mi HomeComponent amely felelős a kezdőlapunk megjelenítéséért és manipulálásáért:
@Component ({selector: 'home-header', szolgáltatók: [AppService], sablon: `Welcome !! Logout`}) exportálási osztály HomeComponent {constructor (private _service: AppService) {} ngOnInit () {this._service.checkCredentials (); } kijelentkezés () {this._service.logout (); }}
5.4. 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: 8082 / spring-security-oauth-resource / 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: ``}) 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, LoginComponent, FooComponent], import: [BrowserModule, FormsModule, HttpModule, RouterModule.forRoot ([{útvonal: '', komponens: HomeComponent}, {elérési út: 'login', komponens: LoginComponent}])], szolgáltatók: [], bootstrap: [AppComponent]}) exportálási osztály AppModule {}
6. Implicit Flow
Ezután az Implicit Flow modulra koncentrálunk.
6.1. App Service
Ehhez hasonlóan a szolgáltatásunkkal is kezdünk, de ezúttal a angular-oauth2-oidc könyvtárat fogjuk használni, ahelyett, hogy saját magunk szereznénk hozzáférési tokent:
@Injectable () export osztály AppService {konstruktor (private _router: Router, private _http: Http, private oauthService: OAuthService) {this.oauthService.loginUrl = '// localhost: 8081 / spring-security-oauth-server / oauth / authorize "; this.oauthService.redirectUri = '// localhost: 8086 /'; this.oauthService.clientId = "sampleClientId"; this.oauthService.scope = "read foo bar olvasása"; this.oauthService.setStorage (sessionStorage); this.oauthService.tryLogin ({}); } getAccessToken () {this.oauthService.initImplicitFlow (); } getResource (resourceUrl): Megfigyelhető {var header = new Headers ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8', 'Authorization': 'Bearer' + this.oauthService .getAccessToken ()}); var options = new RequestOptions ({fejlécek: fejlécek}); adja vissza ezt :_http.get (resourceUrl, opciók) .map ((res: Response) => res.json ()) .catch ((hiba: bármely) => Megfigyelhető.throw (error.json (). hiba || 'Szerver hiba')); } isLoggedIn () {if (this.oauthService.getAccessToken () === null) {return false; } return true; } kijelentkezés () {this.oauthService.logOut (); hely.reload (); }}
Vegye figyelembe, hogy az Access Token megszerzése után hogyan használjuk a Engedélyezés fejléc, amikor védett erőforrásokat fogyasztunk az erőforrás-kiszolgálón belül.
6.2. Otthoni alkatrész
A mi HomeComponent kezelni egyszerű kezdőlapunkat:
@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.isLoggedIn (); } login () {this._szolgáltatás.obtainAccessToken (); } kijelentkezés () {this._service.logout (); }}
6.3. Foo komponens
A mi FooComponent pontosan ugyanaz, mint a jelszófolyamat modulban.
6.4. App modul
Végül a mi AppModule:
@NgModule ({deklarációk: [AppComponent, HomeComponent, FooComponent], import: [BrowserModule, FormsModule, HttpModule, OAuthModule.forRoot (), RouterModule.forRoot ([{útvonal: '', komponens: HomeComponent}],] [], 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 elindul a 4200-as porton, hogy megváltoztassa bármelyik modul portját
"start": "ng tálalás"
ban ben csomag.json hogy például a 8086-os porton fusson:
"start": "ng serve - port 8086"
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.