Spring REST API + OAuth2 + Angular (s využitím staršieho zásobníka Spring Security OAuth)

1. Prehľad

V tomto tutoriáli zabezpečíme REST API s OAuth a spotrebujeme ho od jednoduchého uhlového klienta.

Aplikácia, ktorú vytvoríme, bude pozostávať zo štyroch samostatných modulov:

  • Autorizačný server
  • Zdrojový server
  • Implicitné používateľské rozhranie - front-end aplikácia využívajúca implicitný tok
  • Heslo používateľského rozhrania - klientska aplikácia využívajúca tok hesiel

Poznámka: tento článok využíva starší projekt Spring OAuth. Verziu tohto článku, ktorá používa nový zásobník Spring Security 5, nájdete v našom článku Spring REST API + OAuth2 + Angular.

Dobre, poďme priamo dovnútra.

2. Autorizačný server

Najskôr začnime nastavovať autorizačný server ako jednoduchú aplikáciu Spring Boot.

2.1. Konfigurácia Maven

Nastavíme nasledujúcu sadu závislostí:

 org.springframework.boot spring-boot-starter-web org.springframework spring-jdbc mysql mysql-connector-java runtime org.springframework.security.oauth spring-security-oauth2 

Všimnite si, že používame jar-jdbc a MySQL, pretože použijeme implementáciu úložiska tokenov podporovanú JDBC.

2.2. @EnableAuthorizationServer

Teraz začnime konfigurovať autorizačný server zodpovedný za správu prístupových tokenov:

@Configuration @EnableAuthorizationServer verejná trieda AuthServerOAuth2Config rozširuje AuthorizationServerConfigurerAdapter {@Autowired @Qualifier ("authenticationManagerBean") súkromný AuthenticationManager authenticationManager; @Override public void configure (AuthorizationServerSecurityConfigurer oauthServer) hodí výnimku {oauthServer .tokenKeyAccess ("permitAll ()") .checkTokenAccess ("isAuthenticated ()"); } @Override public void configure (klienti ClientDetailsServiceConfigurer) vyvolá výnimku {clients.jdbc (dataSource ()) .withClient ("sampleClientId") .authorizedGrantTypes ("implicitný") .scopes ("čítanie") .autoApprove (true). A ( ) .withClient ("clientIdPassword") .secret ("tajomstvo") .authorizedGrantTypes ("heslo", "autorizačný_kód", "obnovovací_token") .skopy ("čítať"); } @Override public void configure (AuthorizationServerEndpointsConfigurer endpoints) vyvolá výnimku {endpoints .tokenStore (tokenStore ()) .authenticationManager (authenticationManager); } @Bean public TokenStore tokenStore () {vrátiť nový JdbcTokenStore (dataSource ()); }}

Poznač si to:

  • Na udržanie tokenov sme použili a JdbcTokenStore
  • Zaregistrovali sme klienta pre „implicitné”Typ grantu
  • Zaregistrovali sme iného klienta a autorizovali sme „heslo“, “autorizačný kód“A„refresh_token”Typy grantov
  • Aby bolo možné použiťheslo”Typ grantu, ktorý musíme zapojiť a používať AuthenticationManager fazuľa

2.3. Konfigurácia zdroja údajov

Ďalej nakonfigurujme náš zdroj údajov tak, aby ho mohol používať server JdbcTokenStore:

@Value ("classpath: schema.sql") súkromný zdrojový schemaScript; @Bean public DataSourceInitializer dataSourceInitializer (DataSource dataSource) {DataSourceInitializer initializer = nový DataSourceInitializer (); initializer.setDataSource (dataSource); initializer.setDatabasePopulator (databasePopulator ()); návratový inicializátor; } private DatabasePopulator databasePopulator () {ResourceDatabasePopulator populator = nový ResourceDatabasePopulator (); populator.addScript (schemaScript); spätný populátor; } @Bean public DataSource dataSource () {DriverManagerDataSource dataSource = nový 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")); vrátiť dátový zdroj; }

Všimnite si, že tak, ako to používame JdbcTokenStore musíme inicializovať databázovú schému, takže sme použili DataSourceInitializer - a nasledujúca schéma SQL:

rozbaľovacia tabuľka, ak existuje oauth_client_details; vytvoriť tabuľku oauth_client_details (client_id VARCHAR (255) PRIMARY KEY, resource_ids VARCHAR (255), client_secret VARCHAR (255), scope VARCHAR (255), authorized_grant_types VARCHAR (255), web_server_redirect_uri VARCHAR (255), oprávnenia VARCHAR (255), oprávnenia VARCHAR (255), oprávnenia VARCHAR (255), oprávnenia VARCHAR (255), oprávnenia VARCHAR (255), autority VARCHAR (255) , refresh_token_validity INTEGER, additional_information VARCHAR (4096), autoapprove VARCHAR (255)); rozbaľovacia tabuľka, ak existuje oauth_client_token; vytvorte tabulku oauth_client_token (token_id VARCHAR (255), token LONG VARBINARY, authentication_id VARCHAR (255) PRIMARY KEY, meno_uzivatela VARCHAR (255), client_id VARCHAR (255)); rozbaľovacia tabuľka, ak existuje oauth_access_token; vytvor tabulku oauth_access_token (token_id VARCHAR (255), token LONG VARBINARY, authentication_id VARCHAR (255) PRIMARY KEY, user_name VARCHAR (255), client_id VARCHAR (255), autentifikácia LONG VARBINARY, refresh_token VARCHAR (255)); rozbaľovacia tabuľka, ak existuje oauth_refresh_token; vytvorte tabulku oauth_refresh_token (token_id VARCHAR (255), token LONG VARBINARY, autentifikácia LONG VARBINARY); rozbaľovacia tabuľka, ak existuje oauth_code; vytvoriť tabuľku oauth_code (kód VARCHAR (255), autentifikácia LONG VARBINARY); rozbaľovacia tabuľka, ak existuje oauth_approvals; vytvoriť tabuľku oauth_approvals (userId VARCHAR (255), clientId VARCHAR (255), rozsah VARCHAR (255), stav VARCHAR (10), expiresAt TIMESTAMP, lastModifiedAt TIMESTAMP); rozbaľovacia tabuľka, ak existuje ClientDetails; vytvoriť tabuľku ClientDetails (appId VARCHAR (255) PRIMARY KEY, resourceIds VARCHAR (255), appSecret VARCHAR (255), scope VARCHAR (255), grantTypes VARCHAR (255), redirectUrl VARCHAR (255), autority VARCHAR (255), access_token_validity , refresh_token_validity INTEGER, additionalInformation VARCHAR (4096), autoApproveScopes VARCHAR (255));

Upozorňujeme, že explicitne nevyhnutne nepotrebujeme DatabasePopulator fazuľa - mohli by sme jednoducho použiť a schema.sql - ktoré Spring Boot štandardne využíva.

2.4. Konfigurácia zabezpečenia

Na záver zabezpečme autorizačný server.

Keď klientská aplikácia potrebuje získať prístupový token, urobí to po jednoduchom autorizačnom procese na základe prihlásenia pomocou formulára:

@Configuration verejná trieda ServerSecurityConfig rozširuje WebSecurityConfigurerAdapter {@Override protected void configure (AuthenticationManagerBuilder auth) vyvolá výnimku {auth.inMemoryAuthentication () .withUser ("john"). Heslo ("123"). Role ("USER"); } @Override @Bean public AuthenticationManager authenticationManagerBean () vyvolá výnimku {return super.authenticationManagerBean (); } @Override protected void configure (HttpSecurity http) vyvolá výnimku {http.authorizeRequests () .antMatchers ("/ login"). PermitAll () .anyRequest (). Authenticated () .and () .formLogin (). PermitAll () ; }}

Tu je krátka poznámka konfigurácia prihlásenia do formulára nie je pre tok hesiel potrebná - iba pre implicitný tok - takže ho budete môcť preskočiť v závislosti od toho, aký tok OAuth2 používate.

3. Zdrojový server

Teraz poďme diskutovať o zdrojovom serveri; toto je v podstate REST API, ktoré nakoniec chceme vedieť konzumovať.

3.1. Konfigurácia Maven

Naša konfigurácia servera zdrojov je rovnaká ako predchádzajúca konfigurácia aplikácie autorizačného servera.

3.2. Konfigurácia obchodu s tokenmi

Ďalej nakonfigurujeme naše TokenStore na prístup k rovnakej databáze, ktorú autorizačný server používa na ukladanie prístupových tokenov:

@Autowired private Environment env; @Bean public DataSource dataSource () {DriverManagerDataSource dataSource = nový 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")); vrátiť dátový zdroj; } @Bean public TokenStore tokenStore () {vrátiť nový JdbcTokenStore (dataSource ()); }

Upozorňujeme, že pre túto jednoduchú implementáciu zdieľame úložisko tokenov podporovaných SQL aj keď autorizačné a zdrojové servery sú samostatné aplikácie.

Dôvod samozrejme je, že Resource Server musí byť schopný skontrolujte platnosť prístupových tokenov vydané autorizačným serverom.

3.3. Služba vzdialeného tokenu

Namiesto použitia a TokenStore na našom serveri zdrojov môžeme použiť RemoteTokeServices:

@Primary @Bean public RemoteTokenServices tokenService () {RemoteTokenServices tokenService = nový RemoteTokenServices (); tokenService.setCheckTokenEndpointUrl ("// localhost: 8080 / spring-security-oauth-server / oauth / check_token"); tokenService.setClientId ("fooClientIdPassword"); tokenService.setClientSecret ("tajomstvo"); návratová služba tokenService; }

Poznač si to:

  • Toto RemoteTokenService bude používať CheckTokenEndPoint na autorizačnom serveri na overenie a získanie AccessToken Overenie objekt z toho.
  • Nájdete na adrese AuthorizationServerBaseURL + ”/ oauth / check_token
  • Autorizačný server môže používať akýkoľvek typ TokenStore [JdbcTokenStore, JwtTokenStore, ...] - toto neovplyvní RemoteTokenService alebo zdrojový server.

3.4. Kontrolór vzorky

Ďalej implementujme jednoduchý radič vystavujúci a Foo zdroj:

@Controller verejná trieda FooController {@PreAuthorize ("# oauth2.hasScope ('read')") @RequestMapping (method = RequestMethod.GET, value = "/ foos / {id}") @ResponseBody public Foo findById (@PathVariable long id) {return new Foo (Long.parseLong (randomNumeric (2)), randomAlphabetic (4)); }}

Všimnite si, ako klient potrebuje "čítať" rozsah prístupu k tomuto zdroju.

Musíme tiež povoliť globálne zabezpečenie metód a konfiguráciu MethodSecurityExpressionHandler:

@Configuration @EnableResourceServer @EnableGlobalMethodSecurity (prePostEnabled = true) verejná trieda OAuth2ResourceServerConfig rozširuje GlobalMethodSecurityConfiguration {@Override chránený MethodSecurityExpressionHandler createExpressionHandler () {návrat nový OAuth2) }}

A tu je naše základné Foo Zdroj:

public class Foo {private long id; súkromné ​​meno reťazca; }

3.5. Konfigurácia webu

Nakoniec nastavíme veľmi základnú webovú konfiguráciu rozhrania API:

@Configuration @EnableWebMvc @ComponentScan ({"org.baeldung.web.controller"}) verejná trieda ResourceWebConfig implementuje WebMvcConfigurer {}

4. Klientske rozhranie - nastavenie

Teraz sa pozrieme na jednoduchú frontálnu implementáciu Angular pre klienta.

Najskôr použijeme Angular CLI na generovanie a správu našich front-end modulov.

Najskôr nainštalujeme uzol a npm - keďže Angular CLI je nástroj NPM.

Potom musíme použiť frontend-maven-plugin postaviť náš projekt Angular pomocou maven:

   com.github.eirslett frontend-maven-plugin 1.3 v6.10.2 3.10.10 src / main / resources nainštalovať uzol a npm install-node-and-npm npm nainštalovať npm npm run build npm run build 

A nakoniec, vygenerujte nový modul pomocou Angular CLI:

ng nová oauthApp

Upozorňujeme, že budeme mať dva klientske moduly - jeden pre tok hesiel a druhý pre implicitný tok.

V nasledujúcich častiach si rozoberieme logiku uhlovej aplikácie pre každý modul.

5. Tok hesla pomocou Angular

Budeme tu používať tok hesiel OAuth2 - a preto toto je iba dôkaz koncepcie, nie aplikácia pripravená na výrobu. Všimnete si, že poverenia klienta sú vystavené klientskemu rozhraniu - čomu sa budeme venovať v budúcom článku.

Náš prípad použitia je jednoduchý: keď používateľ poskytne svoje poverenia, klient ich použije na získanie prístupového tokenu z autorizačného servera.

5.1. App Service

Začnime s naším AppService - umiestnený na app.service.ts - ktorý obsahuje logiku pre interakcie so serverom:

  • obtainAccessToken (): na získanie poverení používateľa s prístupovým tokenom
  • saveToken (): uložiť náš prístupový token do súboru cookie pomocou knižnice ng2-cookies
  • getResource (): získať objekt Foo zo servera pomocou jeho ID
  • checkCredentials (): skontrolovať, či je používateľ prihlásený alebo nie
  • odhlásiť sa(): odstránenie cookie prístupového tokenu a odhlásenie používateľa
exportná trieda Foo {konštruktor (verejné id: číslo, verejné meno: reťazec) {}} @Injectable () exportná trieda AppService {konštruktor (súkromný _router: smerovač, súkromný _http: Http) {} obtainAccessToken (loginData) {let params = nový URLSearchParams (); params.append ('username', loginData.username); params.append ('heslo', loginData.password); params.append ('grant_type', 'heslo'); params.append ('client_id', 'fooClientIdPassword'); let headers = new Headers ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8', 'Authorization': 'Basic' + btoa ("fooClientIdPassword: secret")}); let options = new RequestOptions ({headers: headers}); this._http.post ('// localhost: 8081 / spring-security-oauth-server / oauth / token', params.toString (), možnosti). mapa (res => res.json ()) .subscribe (údaje => this.saveToken (data), err => alert ('Invalid Credentials')); } saveToken (token) {var expireDate = new Date (). getTime () + (1000 * token.expires_in); Cookie.set ("access_token", token.access_token, expireDate); this._router.navigate (['/']); } getResource (resourceUrl): Pozorovateľné {var headers = new Headers ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8', 'Authorization': 'Bearer' + Cookie.get ('prístupový token')}); var options = new RequestOptions ({headers: headers}); vrátiť this._http.get (resourceUrl, options) .map ((res: Response) => res.json ()) .catch ((error: any) => Observable.throw (error.json (). error || 'Chyba servera')); } checkCredentials () {if (! Cookie.check ('access_token')) {this._router.navigate (['/ login']); }} odhlásiť () {Cookie.delete ('access_token'); this._router.navigate (['/ login']); }}

Poznač si to:

  • Za účelom získania prístupového tokenu pošleme a POST k „/ oauth / token”Koncový bod
  • Na dosiahnutie tohto koncového bodu používame poverenia klienta a základné overenie
  • Potom zašleme poverenia používateľa spolu s ID klienta a kódom URL typu parametra šifrovania
  • Po získaní prístupového tokenu - ukladáme to do cookie

Uloženie súborov cookie je tu obzvlášť dôležité, pretože súbory cookie používame iba na účely ukladania a nie na účely priameho overovania. To pomáha chrániť pred útokmi a slabými miestami typu CSRF.

5.2. Prihlasovacia súčasť

Ďalej sa pozrime na naše LoginComponent ktorý je zodpovedný za prihlasovací formulár:

@Component ({selektor: 'login-form', poskytovatelia: [AppService], šablóna: `Login`}) trieda exportu LoginComponent {public loginData = {username:" ", heslo:" "}; konštruktor (private _service: AppService) {} login () {this._service.obtainAccessToken (this.loginData); }

5.3. Domáce komponenty

Ďalej náš HomeComponent ktorá je zodpovedná za zobrazovanie a manipuláciu s našou domovskou stránkou:

@Component ({selector: 'home-header', providers: [AppService], template: `Welcome !! Logout`}) export class HomeComponent {constructor (private _service: AppService) {} ngOnInit () {this._service.checkCredentials (); } odhlásenie () {this._service.logout (); }}

5.4. Foo komponent

Nakoniec náš FooComponent pre zobrazenie našich Foo detailov:

@Component ({selector: 'foo-details', providers: [AppService], template: `ID {{foo.id}} Name {{foo.name}} New Foo`})) export class FooComponent {public foo = new Foo (1, „sample foo“); private foosUrl = '// localhost: 8082 / spring-security-oauth-resource / foos /'; konštruktor (private _service: AppService) {} getFoo () {this._service.getResource (this.foosUrl + this.foo.id) .subscribe (data => this.foo = data, error => this.foo.name = 'Chyba'); }}

5.5. Komponent aplikácie

Naše jednoduché AppComponent pôsobiť ako koreňová zložka:

@Component ({selector: 'app-root', template: "}) export class AppComponent {}

A AppModule kde zabalíme všetky naše súčasti, služby a trasy:

@NgModule ({deklarácie: [AppComponent, HomeComponent, LoginComponent, FooComponent], importy: [BrowserModule, FormsModule, HttpModule, RouterModule.forRoot ([{cesta: '', komponent: HomeComponent}, {cesta: 'prihlásenie', komponent: LoginComponent}])], poskytovatelia: [], bootstrap: [AppComponent]}) trieda exportu AppModule {}

6. Implicitný tok

Ďalej sa zameriame na modul Implicit Flow.

6.1. App Service

Podobne začneme s našou službou, ale tentokrát namiesto získania prístupového tokenu použijeme knižnicu angular-oauth2-oidc:

@Injectable () exportná trieda AppService {konštruktor (súkromný _router: smerovač, súkromný _http: Http, súkromný oauthService: OAuthService) {this.oauthService.loginUrl = '// localhost: 8081 / spring-security-oauth-server / oauth / autorizovať "; this.oauthService.redirectUri = '// localhost: 8086 /'; this.oauthService.clientId = "sampleClientId"; this.oauthService.scope = "read foo bar"; this.oauthService.setStorage (sessionStorage); this.oauthService.tryLogin ({}); } obtainAccessToken () {this.oauthService.initImplicitFlow (); } getResource (resourceUrl): Pozorovateľné {var headers = new Headers ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8', 'Authorization': 'Bearer' + this.oauthService .getAccessToken ()}); var options = new RequestOptions ({headers: headers}); vrátiť this._http.get (resourceUrl, options) .map ((res: Response) => res.json ()) .catch ((error: any) => Observable.throw (error.json (). error || 'Chyba servera')); } isLoggedIn () {if (this.oauthService.getAccessToken () === null) {return false; } návrat pravdivý; } odhlásenie () {this.oauthService.logOut (); location.reload (); }}

Všimnite si, ako ho po získaní prístupového tokenu používame prostredníctvom servera Povolenie záhlavie, kedykoľvek spotrebujeme chránené zdroje z prostriedkov servera.

6.2. Domáce komponenty

Náš HomeComponent zvládnuť našu jednoduchú domovskú stránku:

@Component ({selector: 'home-header', providers: [AppService], template: `Login Welcome !! Logout

`}) exportovať triedu HomeComponent {public isLoggedIn = false; konštruktor (private _service: AppService) {} ngOnInit () {this.isLoggedIn = this._service.isLoggedIn (); } login () {this._service.obtainAccessToken (); } odhlásenie () {this._service.logout (); }}

6.3. Foo komponent

Náš FooComponent je úplne rovnaké ako v module toku hesiel.

6.4. Modul aplikácie

Nakoniec náš AppModule:

@NgModule ({deklarácie: [AppComponent, HomeComponent, FooComponent], importy: [BrowserModule, FormsModule, HttpModule, OAuthModule.forRoot (), RouterModule.forRoot ([{cesta: '', komponent: HomeComponent}]))], poskytovatelia: [], bootstrap: [AppComponent]}) trieda exportu AppModule {}

7. Spustite klientske rozhranie

1. Ak chcete spustiť ktorýkoľvek z našich front-end modulov, musíme si najskôr vytvoriť aplikáciu:

mvn čistá inštalácia

2. Potom musíme prejsť do nášho adresára Angular app:

cd src / main / resources

3. Nakoniec spustíme našu aplikáciu:

npm štart

Server sa štandardne spustí na porte 4200, aby ste mohli zmeniť port ktoréhokoľvek modulu, zmeňte

"start": "ng serve"

v balíček.json aby to bežalo napríklad na porte 8086:

"start": "ng serve --port 8086"

8. Záver

V tomto článku sme sa dozvedeli, ako autorizovať našu aplikáciu pomocou protokolu OAuth2.

Celú implementáciu tohto tutoriálu nájdete v projekte GitHub.


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