javascript
使用Spring Boot和React进行Bootiful开发
“我喜歡編寫身份驗(yàn)證和授權(quán)代碼。” ?從來沒有Java開發(fā)人員。 厭倦了一次又一次地建立相同的登錄屏幕? 嘗試使用Okta API進(jìn)行托管身份驗(yàn)證,授權(quán)和多因素身份驗(yàn)證。
在過去的幾年中,React受到了很多積極的報(bào)導(dǎo),使其成為Java開發(fā)人員的吸引人的前端選擇! 一旦了解了它的工作原理,它就會(huì)變得很有意義,并且可以很有趣地進(jìn)行開發(fā)。 不僅如此,而且速度也很快! 如果您一直在關(guān)注我,或者已經(jīng)閱讀了此博客,那么您可能還記得我的《 使用Spring Boot和Angular進(jìn)行Bootiful開發(fā)》教程。 今天,我將向您展示如何構(gòu)建相同的應(yīng)用程序,除了這次使用React。 在深入探討之前,讓我們先討論一下React有什么用處,以及我為什么選擇在本文中探索它。
首先,React不是一個(gè)成熟的Web框架。 它更像是用于開發(fā)UI的工具包,如la GWT。 如果您想發(fā)出HTTP請(qǐng)求以從服務(wù)器獲取數(shù)據(jù),React將不提供任何實(shí)用程序。 但是,它確實(shí)有一個(gè)龐大的生態(tài)系統(tǒng),提供許多庫(kù)和組件。 我所說的巨大意味著什么? 這么說:根據(jù)npmjs.com , Angular有17,938個(gè)軟件包 。 反應(yīng)幾乎三倍多在42428!
Angular是我的好朋友,已經(jīng)有很長(zhǎng)時(shí)間了。 我并沒有放棄我的老朋友采用React。 我只是結(jié)交新朋友。 擁有很多具有不同背景和不同見解的朋友,對(duì)于人類的觀點(diǎn)來說是件好事!
這篇文章展示了如何將UI和API構(gòu)建為單獨(dú)的應(yīng)用程序。 您將學(xué)習(xí)如何使用Spring MVC創(chuàng)建REST端點(diǎn),如何配置Spring Boot以允許CORS,以及如何創(chuàng)建一個(gè)React應(yīng)用來顯示其數(shù)據(jù)。 該應(yīng)用程序?qū)@示API中的啤酒列表,然后從GIPHY提取與啤酒名稱匹配的GIF。 我還將向您展示如何集成Okta及其OpenID Connect(OIDC)支持以鎖定API并向UI添加身份驗(yàn)證。
讓我們開始吧!
使用Spring Boot構(gòu)建API
注意:以下有關(guān)構(gòu)建Spring Boot API的說明與使用Spring Boot和Angular進(jìn)行Bootiful開發(fā)中的說明相同。 為了方便起見,我在下面將它們復(fù)制了下來。
要開始使用Spring Boot,請(qǐng)導(dǎo)航到start.spring.io 。 在“搜索依賴項(xiàng)”字段中,選擇以下內(nèi)容:
- H2 :內(nèi)存數(shù)據(jù)庫(kù)
- JPA :Java的標(biāo)準(zhǔn)ORM
- 其余存儲(chǔ)庫(kù) :允許您將JPA存儲(chǔ)庫(kù)公開為REST端點(diǎn)
- Web :具有Jackson(用于JSON),Hibernate Validator和嵌入式Tomcat的Spring MVC
如果你喜歡命令行更好,你可以使用下面的命令來下載一個(gè)demo.zip與文件HTTPie 。
http https://start.spring.io/starter.zip \ dependencies==h2,data-jpa,data-rest,web -d創(chuàng)建一個(gè)名為spring-boot-react-example目錄,其中包含server目錄。 將demo.zip的內(nèi)容demo.zip到server目錄中。
在您喜歡的IDE中打開“服務(wù)器”項(xiàng)目,然后運(yùn)行DemoApplication或使用./mvnw spring-boot:run從命令行啟動(dòng)它。
在其中創(chuàng)建com.example.demo.beer程序包和Beer.java文件。 此類將是保存您的數(shù)據(jù)的實(shí)體。
package com.example.demo.beer;import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id;@Entity public class Beer {@Id@GeneratedValueprivate Long id;private String name;public Beer() {}public Beer(String name) {this.name = name;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Beer{" +"id=" + id +", name='" + name + '\'' +'}';} }添加一個(gè)利用Spring Data在此實(shí)體上執(zhí)行CRUD的BeerRepository類。
package com.example.demo.beer;import org.springframework.data.jpa.repository.JpaRepository;interface BeerRepository extends JpaRepository<Beer, Long> { }添加使用此存儲(chǔ)庫(kù)的BeerCommandLineRunner并創(chuàng)建一組默認(rèn)數(shù)據(jù)。
package com.example.demo.beer;import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component;import java.util.stream.Stream;@Component public class BeerCommandLineRunner implements CommandLineRunner {private final BeerRepository repository;public BeerCommandLineRunner(BeerRepository repository) {this.repository = repository;}@Overridepublic void run(String... strings) throws Exception {// Top beers from https://www.beeradvocate.com/lists/top/Stream.of("Kentucky Brunch Brand Stout", "Good Morning", "Very Hazy", "King Julius","Budweiser", "Coors Light", "PBR").forEach(name ->repository.save(new Beer(name)));repository.findAll().forEach(System.out::println);} }重建您的項(xiàng)目,您應(yīng)該會(huì)在終端上看到印刷的啤酒清單。
a添加@RepositoryRestResource注釋BeerRepository揭露其所有CRUD操作的REST端點(diǎn)。
import org.springframework.data.rest.core.annotation.RepositoryRestResource;@RepositoryRestResource interface BeerRepository extends JpaRepository<Beer, Long> { }添加一個(gè)BeerController類來創(chuàng)建一個(gè)端點(diǎn),該端點(diǎn)過濾出的啤酒數(shù)量少于大啤酒。
package com.example.demo.beer;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors;@RestController public class BeerController {private BeerRepository repository;public BeerController(BeerRepository repository) {this.repository = repository;}@GetMapping("/good-beers")public Collection<Beer> goodBeers() {return repository.findAll().stream().filter(this::isGreat).collect(Collectors.toList());}private boolean isGreat(Beer beer) {return !beer.getName().equals("Budweiser") &&!beer.getName().equals("Coors Light") &&!beer.getName().equals("PBR");} }重新構(gòu)建您的應(yīng)用程序并導(dǎo)航到http://localhost:8080/good-beers 。 您應(yīng)該在瀏覽器中看到優(yōu)質(zhì)啤酒的列表。
使用HTTPie時(shí),您也應(yīng)該在終端窗口中看到相同的結(jié)果。
http localhost:8080/good-beers使用Create React App創(chuàng)建一個(gè)項(xiàng)目
這些天來,創(chuàng)建API似乎很容易,這在很大程度上要?dú)w功于Spring Boot。 在本部分中,我希望向您展示使用React創(chuàng)建UI也非常容易。 如果您按照以下步驟操作,則將創(chuàng)建一個(gè)新的React應(yīng)用,從API獲取啤酒名稱和圖像,并創(chuàng)建用于顯示數(shù)據(jù)的組件。
要?jiǎng)?chuàng)建一個(gè)React項(xiàng)目,請(qǐng)確保您已安裝Node.js , Create React App和Yarn 。
npm install -g create-react-app@1.4.3在終端窗口中,cd進(jìn)入spring-boot-react-example目錄的根目錄并運(yùn)行以下命令。 該命令將創(chuàng)建一個(gè)具有TypeScript支持的新React應(yīng)用程序。
create-react-app client --scripts-version=react-scripts-ts運(yùn)行此過程之后,您將擁有一個(gè)新的client目錄,其中安裝了所有必需的依賴項(xiàng)。 為了驗(yàn)證一切正常,將cd進(jìn)入client目錄并運(yùn)行yarn start 。 如果一切正常,您應(yīng)該在瀏覽器中看到以下內(nèi)容。
到目前為止,您已經(jīng)創(chuàng)建了一個(gè)good-beers API和一個(gè)React應(yīng)用程序,但是尚未創(chuàng)建UI來顯示API中的啤酒列表。 為此,請(qǐng)打開client/src/App.tsx并添加componentDidMount()方法。
componentDidMount() {this.setState({isLoading: true});fetch('http://localhost:8080/good-beers').then(response => response.json()).then(data => this.setState({beers: data, isLoading: false})); }React的組件生命周期將調(diào)用componentDidMount()方法。 上面的代碼使用fetch ,這是XMLHttpRequest的現(xiàn)代替代。 根據(jù)caniuse.com,大多數(shù)瀏覽器均支持該功能 。
您會(huì)看到它使用響應(yīng)數(shù)據(jù)設(shè)置了beers狀態(tài)。 要初始化此組件的狀態(tài),您需要重寫構(gòu)造函數(shù)。
constructor(props: any) {super(props);this.state = {beers: [],isLoading: false}; }為此,您需要將參數(shù)類型添加到類簽名中。 下面的代碼顯示了此時(shí)App類頂部的外觀。
class App extends React.Component<{}, any> {constructor(props: any) {super(props);this.state = {beers: [],isLoading: false};}// componentDidMount() and render() }更改render()方法以具有以下JSX。 JSX是Facebook的類XML語(yǔ)法,可通過JavaScript呈現(xiàn)HTML。
render() {const {beers, isLoading} = this.state;if (isLoading) {return <p>Loading...</p>;}return (<div className="App"><div className="App-header"><img src={logo} className="App-logo" alt="logo" /><h2>Welcome to React</h2></div><div><h2>Beer List</h2>{beers.map((beer: any) =><div key={beer.id}>{beer.name}</div>)}</div></div>); }如果在瀏覽器中查看http://localhost:3000 ,則會(huì)看到“正在加載...”消息。 如果您在瀏覽器的控制臺(tái)中查看,可能會(huì)看到有關(guān)CORS的問題。
Failed to load http://localhost:8080/good-beers: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.要解決此問題,您需要將Spring Boot配置為允許從http://localhost:3000進(jìn)行跨域訪問。
為Spring Boot配置CORS
在服務(wù)器項(xiàng)目中,打開server/src/main/java/com/example/demo/beer/BeerController.java并添加@CrossOrigin批注以啟用來自客戶端的跨域資源共享(CORS)( http://localhost:3000 )。
import org.springframework.web.bind.annotation.CrossOrigin; ...@GetMapping("/good-beers")@CrossOrigin(origins = "http://localhost:3000")public Collection goodBeers() {進(jìn)行了這些更改之后,重新啟動(dòng)服務(wù)器,刷新瀏覽器,您應(yīng)該能夠從Spring Boot API中看到啤酒列表。
創(chuàng)建一個(gè)BeerList組件
為了使此應(yīng)用程序更易于維護(hù),請(qǐng)將啤酒清單的獲取和呈現(xiàn)從App.tsx到其自己的BeerList組件。 創(chuàng)建src/BeerList.tsx并使用App.tsx的代碼填充它。
import * as React from 'react';class BeerList extends React.Component<{}, any> {constructor(props: any) {super(props);this.state = {beers: [],isLoading: false};}componentDidMount() {this.setState({isLoading: true});fetch('http://localhost:8080/good-beers').then(response => response.json()).then(data => this.setState({beers: data, isLoading: false}));}render() {const {beers, isLoading} = this.state;if (isLoading) {return <p>Loading...</p>;}return (<div><h2>Beer List</h2>{beers.map((beer: any) =><div key={beer.id}>{beer.name}</div>)}</div>);} }export default BeerList;然后更改client/src/App.tsx ,使其僅包含一個(gè)外殼和對(duì)<BeerList/>的引用。
import * as React from 'react'; import './App.css'; import BeerList from './BeerList';const logo = require('./logo.svg');class App extends React.Component<{}, any> {render() {return (<div className="App"><div className="App-header"><img src={logo} className="App-logo" alt="logo"/><h2>Welcome to React</h2></div><BeerList/></div>);} }export default App;創(chuàng)建一個(gè)GiphyImage組件
為了使其看起來更好一點(diǎn),添加GIPHY組件以根據(jù)啤酒的名稱獲取圖像。 創(chuàng)建client/src/GiphyImage.tsx并將以下代碼放入其中。
import * as React from 'react';interface GiphyImageProps {name: string; }class GiphyImage extends React.Component<GiphyImageProps, any> {constructor(props: GiphyImageProps) {super(props);this.state = {giphyUrl: '',isLoading: false};}componentDidMount() {const giphyApi = '//api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&limit=1&q=';fetch(giphyApi + this.props.name).then(response => response.json()).then(response => {if (response.data.length > 0) {this.setState({giphyUrl: response.data[0].images.original.url});} else {// dancing cat for no images foundthis.setState({giphyUrl: '//media.giphy.com/media/YaOxRsmrv9IeA/giphy.gif'});}this.setState({isLoading: false});});}render() {const {giphyUrl, isLoading} = this.state;if (isLoading) {return <p>Loading image...</p>;}return (<img src={giphyUrl} alt={this.props.name} width="200"/>);} }export default GiphyImage;更改BeerList.tsx的render()方法以使用此組件。
import GiphyImage from './GiphyImage'; ... render() {const {beers, isLoading} = this.state;if (isLoading) {return <p>Loading...</p>;}return (<div><h2>Beer List</h2>{beers.map((beer: any) =><div key={beer.id}>{beer.name}<br/><GiphyImage name={beer.name}/></div>)}</div>); }結(jié)果應(yīng)類似于以下帶有圖像的啤酒名稱列表。
您剛剛創(chuàng)建了一個(gè)React應(yīng)用,該應(yīng)用使用跨域請(qǐng)求與Spring Boot API進(jìn)行通訊。 恭喜你!
添加PWA支持
Create React App開箱即用地支持漸進(jìn)式Web應(yīng)用程序(PWA)。 要了解其集成方式,請(qǐng)打開client/README.md并搜索“制作漸進(jìn)式Web應(yīng)用程序”。
要查看其工作方式,請(qǐng)?jiān)赾lient目錄中運(yùn)行yarn build 。 該命令完成后,您將看到類似以下的消息。
The build folder is ready to be deployed. You may serve it with a static server:yarn global add serveserve -s build運(yùn)行建議的命令,您應(yīng)該能夠打開瀏覽器以查看http://localhost:5000 。 您的瀏覽器可能會(huì)在其控制臺(tái)中顯示CORS錯(cuò)誤,因此BeerController.java再次打開BeerController.java并調(diào)整其允許的來源以允許端口5000。
@CrossOrigin(origins = {"http://localhost:3000", "http://localhost:5000"})重新啟動(dòng)服務(wù)器,并且http://localhost:5000應(yīng)該加載啤酒名稱和圖像。
我在Chrome中進(jìn)行了Lighthouse審核,發(fā)現(xiàn)此應(yīng)用目前僅獲得73/100的評(píng)分。
您會(huì)在上面的屏幕截圖中注意到“清單沒有至少512px的圖標(biāo)”。 聽起來很容易修復(fù)。 您可以從此頁(yè)面下載512像素的免費(fèi)啤酒圖標(biāo)。
注意:此圖標(biāo)由Freepik從www.flaticon.com制作 。 它由CC 3.0 BY許可。
將下載的beer.png復(fù)制到client/public 。 修改client/public/manifest.json以具有特定于此應(yīng)用程序的名稱,并添加512像素的圖標(biāo)。
{"short_name": "Beer","name": "Good Beer","icons": [{"src": "favicon.ico","sizes": "192x192","type": "image/png"},{"src": "beer.png","sizes": "512x512","type": "image/png"}],"start_url": "./index.html","display": "standalone","theme_color": "#000000","background_color": "#ffffff" }進(jìn)行此更改后,我的PWA得分達(dá)到82燈塔評(píng)分。 該報(bào)告最突出的抱怨是我沒有使用HTTPS。 為了查看該應(yīng)用使用HTTPS時(shí)的評(píng)分,我將其部署到Pivotal Cloud Foundry和Heroku 。 我很高興發(fā)現(xiàn)它在兩個(gè)平臺(tái)上的得分都為💯。
要閱讀我用于部署所有內(nèi)容的腳本,請(qǐng)參閱本文附帶的GitHub存儲(chǔ)庫(kù)中的cloudfoundry.sh和heroku.sh 。 我非常感謝@starbuxman和@codefinger在創(chuàng)建它們方面的幫助!
使用Okta添加身份驗(yàn)證
您可能會(huì)想,“這很酷,很容易看出人們?yōu)槭裁磹凵蟁eact。” 試用過后,您可能會(huì)愛上另一個(gè)工具:使用Okta進(jìn)行身份驗(yàn)證! 為什么選擇Okta? 因?yàn)槟梢悦赓M(fèi)獲得7,000個(gè)每月活躍用戶 ! 值得一試,特別是當(dāng)您看到將auth添加到Spring Boot和使用Okta進(jìn)行React很容易時(shí)。
Okta Spring啟動(dòng)啟動(dòng)器
要鎖定后端,可以使用Okta的Spring Boot Starter 。 要集成此啟動(dòng)器,請(qǐng)將以下依賴項(xiàng)添加到server/pom.xml :
<dependency><groupId>com.okta.spring</groupId><artifactId>okta-spring-boot-starter</artifactId><version>0.2.0</version> </dependency>您還需要添加<dependencyManagement>部分以升級(jí)Spring Security的OAuth支持。
<dependencyManagement><dependencies><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>2.2.0.RELEASE</version></dependency></dependencies> </dependencyManagement>注意: 有一個(gè)問題與1563的Spring開機(jī)啟動(dòng)它不使用Spring Boot的DevTools工作。
現(xiàn)在,您需要配置服務(wù)器以使用Okta進(jìn)行身份驗(yàn)證。 為此,您需要在Okta中創(chuàng)建OIDC應(yīng)用。
在Okta中創(chuàng)建OIDC應(yīng)用
登錄到您的1563開發(fā)者帳戶(或者注冊(cè) ,如果你沒有一個(gè)帳戶)并導(dǎo)航到應(yīng)用程序 > 添加應(yīng)用程序 。 單擊“ 單頁(yè)應(yīng)用程序” ,再單擊“ 下一步” ,然后為該應(yīng)用程序命名。 將localhost:8080所有實(shí)例更改為localhost:3000 ,然后單擊完成 。
將客戶端ID復(fù)制到您的server/src/main/resources/application.properties文件中。 在其中時(shí),添加與您的Okta域匹配的okta.oauth2.issuer屬性。 例如:
okta.oauth2.issuer=https://{yourOktaDomain}.com/oauth2/default okta.oauth2.clientId={clientId}注意: { yourOktaDomain }的值應(yīng)類似于dev-123456.oktapreview.com 。 確保在值中不包括-admin !
更新server/src/main/java/com/okta/developer/demo/DemoApplication.java以將其啟用為資源服務(wù)器。
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;@EnableResourceServer @SpringBootApplication進(jìn)行了這些更改之后,您應(yīng)該能夠重新啟動(dòng)服務(wù)器,并在嘗試導(dǎo)航到http:// localhost:8080時(shí)看到訪問被拒絕。
Okta的React支持
Okta的React SDK允許您將OIDC集成到React應(yīng)用程序中。 您可以在npmjs.com上了解有關(guān)Okta的React SDK的更多信息。 要安裝,請(qǐng)運(yùn)行以下命令:
yarn add @okta/okta-react react-router-dom yarn add -D @types/react-router-domOkta的React SDK依賴于react-router ,因此需要安裝react-router-dom的原因。 在client/src/App.tsx配置路由是一種常見的做法,因此,用下面的TypeScript替換其代碼,該TypeScript使用Okta設(shè)置身份驗(yàn)證。
import * as React from 'react'; import './App.css'; import Home from './Home'; import { BrowserRouter as Router, Route } from 'react-router-dom'; import { Security, ImplicitCallback } from '@okta/okta-react';const config = {issuer: 'https://{yourOktaDomain}.com/oauth2/default',redirectUri: window.location.origin + '/implicit/callback',clientId: '{clientId}' };export interface Auth {login(): {};logout(): {};isAuthenticated(): boolean;getAccessToken(): string; }class App extends React.Component {render() {return (<Router><Securityissuer={config.issuer}client_id={config.clientId}redirect_uri={config.redirectUri}><Route path="/" exact={true} component={Home}/><Route path="/implicit/callback" component={ImplicitCallback}/></Security></Router>);} }export default App;創(chuàng)建client/src/Home.tsx以包含App.tsx以前包含的應(yīng)用程序外殼。 此類呈現(xiàn)應(yīng)用程序外殼,登錄/注銷按鈕以及<BeerList/>如果已通過身份驗(yàn)證)。
import * as React from 'react'; import './App.css'; import BeerList from './BeerList'; import { withAuth } from '@okta/okta-react'; import { Auth } from './App';const logo = require('./logo.svg');interface HomeProps {auth: Auth; }interface HomeState {authenticated: boolean; }export default withAuth(class Home extends React.Component<HomeProps, HomeState> {constructor(props: HomeProps) {super(props);this.state = {authenticated: false};this.checkAuthentication = this.checkAuthentication.bind(this);this.checkAuthentication();}async checkAuthentication() {const isAuthenticated = await this.props.auth.isAuthenticated();const {authenticated} = this.state;if (isAuthenticated !== authenticated) {this.setState({authenticated: isAuthenticated});}}componentDidUpdate() {this.checkAuthentication();}render() {const {authenticated} = this.state;let body = null;if (authenticated) {body = (<div className="Buttons"><button onClick={this.props.auth.logout}>Logout</button><BeerList auth={this.props.auth}/></div>);} else {body = (<div className="Buttons"><button onClick={this.props.auth.login}>Login</button></div>);}return (<div className="App"><div className="App-header"><img src={logo} className="App-logo" alt="logo"/><h2>Welcome to React</h2></div>{body}</div>);} });如果您在瀏覽器中查看React應(yīng)用,則可能會(huì)看到類似以下的錯(cuò)誤:
./src/Home.tsx (4,26): error TS7016: Could not find a declaration file for module '@okta/okta-react'. '/Users/mraible/spring-boot-react-example/client/node_modules/@okta/okta-react/dist/index.js' implicitly has an 'any' type.Try `npm install @types/@okta/okta-react` if it exists or add a new declaration (.d.ts) filecontaining `declare module '@okta/okta-react';`使用以下聲明創(chuàng)建client/src/okta.d.ts來解決此問題。
declare module '@okta/okta-react';重新啟動(dòng)客戶端,您將看到在BeerList組件上有一些工作要做。
./src/Home.tsx (44,21): error TS2339: Property 'auth' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<BeerList> & Readonly<{ children?: ReactNode; }> & ...'.在client/src/BeerList.tsx ,通過創(chuàng)建一個(gè)傳遞到類簽名中的BeerListProps接口,將auth屬性添加到道具中。
import { Auth } from './App';interface BeerListProps {auth: Auth; }interface BeerListState {beers: Array<{}>;isLoading: boolean; }class BeerList extends React.Component<BeerListProps, BeerListState> {... }將以下CSS規(guī)則添加到client/src/App.css以使“登錄/注銷”按鈕更加可見。
.Buttons {margin-top: 10px; }.Buttons button {font-size: 1em; }您的瀏覽器后臺(tái),請(qǐng)檢查以下內(nèi)容。
單擊按鈕登錄后,輸入用于創(chuàng)建Okta Developer帳戶的電子郵件和密碼。 當(dāng)它將您重定向回您的應(yīng)用程序時(shí),您可能會(huì)在瀏覽器的控制臺(tái)中看到“正在加載...”和CORS錯(cuò)誤。
發(fā)生此錯(cuò)誤是因?yàn)镾pring的@CrossOrigin在Spring Security中不能很好地發(fā)揮作用。 要解決此問題,請(qǐng)?jiān)贒emoApplication.java的主體中添加一個(gè)simpleCorsFilter bean。
package com.example.demo;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter;import java.util.Arrays; import java.util.Collections;@EnableResourceServer @SpringBootApplication public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}@Beanpublic FilterRegistrationBean simpleCorsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration config = new CorsConfiguration();config.setAllowCredentials(true);config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://localhost:5000"));config.setAllowedMethods(Collections.singletonList("*"));config.setAllowedHeaders(Collections.singletonList("*"));source.registerCorsConfiguration("/**", config);FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));bean.setOrder(Ordered.HIGHEST_PRECEDENCE);return bean;} }要使其在客戶端上都能正常工作,請(qǐng)修改client/src/BeerList.tsx的componentDidMount()方法以設(shè)置授權(quán)標(biāo)頭。
async componentDidMount() {this.setState({isLoading: true});try {const response = await fetch('http://localhost:8080/good-beers', {headers: {Authorization: 'Bearer ' + await this.props.auth.getAccessToken()}});const data = await response.json();this.setState({beers: data, isLoading: false});} catch (err) {this.setState({error: err});} }您還需要在BeerListState接口中添加error 。
interface BeerListState {beers: Array<{}>;isLoading: boolean;error: string; }更改構(gòu)造函數(shù),以便將error初始化為空字符串。
this.state = {beers: [],isLoading: false,error: '' };然后更改render()方法以在發(fā)生錯(cuò)誤時(shí)顯示錯(cuò)誤。
render() {const {beers, isLoading, error} = this.state;if (isLoading) {return <p>Loading ...</p>;}if (error.length > 0) {return <p>Error: {error}</p>;}return (...) }現(xiàn)在您應(yīng)該能夠以經(jīng)過身份驗(yàn)證的用戶身份查看啤酒清單。
如果有效,那么恭喜!
清理那些TypeScript警告
您可能會(huì)注意到,瀏覽器的控制臺(tái)報(bào)告了一些TypeScript警告。
./src/BeerList.tsx [16, 22]: Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type, the empty type ('{}'), or suppress this occurrence. [52, 27]: Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type, the empty type ('{}'), or suppress this occurrence. ./src/GiphyImage.tsx [7, 59]: Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type, the empty type ('{}'), or suppress this occurrence.要解決第一個(gè)問題,請(qǐng)更改client/src/BeerList.tsx ,使其構(gòu)造函數(shù)如下:
constructor(props: BeerListProps) {... }對(duì)于第二個(gè)問題,在client/src/BeerList.tsx創(chuàng)建一個(gè)Beer接口。 將其放在頂部的其他接口旁邊。
interface Beer {id: number;name: string; }然后將{ beers.map((beer: any) =>更改為{ beers.map((beer: Beer) => 。
第三個(gè)問題可以通過在client/src/GiphyImage.tsx創(chuàng)建一個(gè)新的GiphyImageState接口來定義狀態(tài)屬性來解決。
interface GiphyImageState {giphyUrl: string;isLoading: boolean; }class GiphyImage extends React.Component<GiphyImageProps, GiphyImageState> {... }進(jìn)行了這些更改之后,您應(yīng)該擺脫TypeScript警告。
了解有關(guān)Spring Boot和React的更多信息
要了解有關(guān)React,Spring Boot或Okta的更多信息,請(qǐng)查看以下資源:
- Eric Vicenti的React工作坊簡(jiǎn)介 -強(qiáng)烈建議您學(xué)習(xí)React!
- 我與Deepu K Sasidharan 在比利時(shí)Devoxx上進(jìn)行的Angular vs React Smackdown演講
- Robin Wieruch 如何在React中獲取數(shù)據(jù)
- 15分鐘內(nèi)通過用戶身份驗(yàn)證構(gòu)建React應(yīng)用程序
- 使用身份驗(yàn)證構(gòu)建預(yù)先應(yīng)用
- 使用Okta的React SDK創(chuàng)建自定義登錄表單
您可以在GitHub上找到與本文相關(guān)的源代碼。 主要示例(無身份驗(yàn)證)在master分支中,而Okta集成在okta分支中。 要簽出本地計(jì)算機(jī)上的Okta分支,請(qǐng)運(yùn)行以下命令。
git clone git@github.com:oktadeveloper/spring-boot-react-example.git git checkout okta如果您發(fā)現(xiàn)任何問題,請(qǐng)?jiān)谙旅嫣砑釉u(píng)論,我們將盡力為您提供幫助。 如果您喜歡本教程,希望您在Twitter上關(guān)注我 。 要獲得更多類似此類文章的通知,請(qǐng)關(guān)注@oktadev 。
“我喜歡編寫身份驗(yàn)證和授權(quán)代碼。” ?從來沒有Java開發(fā)人員。 厭倦了一次又一次地建立相同的登錄屏幕? 嘗試使用Okta API進(jìn)行托管身份驗(yàn)證,授權(quán)和多因素身份驗(yàn)證。
翻譯自: https://www.javacodegeeks.com/2018/01/bootiful-development-spring-boot-react.html
總結(jié)
以上是生活随笔為你收集整理的使用Spring Boot和React进行Bootiful开发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓双人联机游戏(安卓双人)
- 下一篇: Spring项目的按层打包已过时