使用Spring Boot和React进行Bootiful开发

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。

在过去的几年中,React受到了很多积极的报导,使其成为Java开发人员的吸引人的前端选择! 一旦了解了它的工作原理,它就会变得很有意义,并且可以很有趣地进行开发。 不仅如此,而且速度也很快! 如果您一直在关注我,或者已经阅读了此博客,那么您可能还记得我的《 使用Spring Boot和Angular进行Bootiful开发》教程。 今天,我将向您展示如何构建相同的应用程序,除了这次使用React。 在深入探讨之前,让我们先讨论一下React有什么用处,以及我为什么选择在本文中探索它。

首先,React不是一个成熟的Web框架。 它更像是用于开发UI的工具包,如la GWT。 如果您想发出HTTP请求以从服务器获取数据,React将不提供任何实用程序。 但是,它确实有一个庞大的生态系统,提供许多库和组件。 我所说的巨大意味着什么? 这么说:根据npmjs.com , Angular有17,938个软件包 。 反应几乎三倍多在42428!

Angular是我的好朋友,已经有很长时间了。 我并没有放弃我的老朋友采用React。 我只是结交新朋友。 拥有很多具有不同背景和不同见解的朋友,对于人类的观点来说是件好事!

这篇文章展示了如何将UI和API构建为单独的应用程序。 您将学习如何使用Spring MVC创建REST端点,如何配置Spring Boot以允许CORS,以及如何创建一个React应用来显示其数据。 该应用程序将显示API中的啤酒列表,然后从GIPHY提取与啤酒名称匹配的GIF。 我还将向您展示如何集成Okta及其OpenID Connect(OIDC)支持以锁定API并向UI添加身份验证。

让我们开始吧!

使用Spring Boot构建API

注意:以下有关构建Spring Boot API的说明与使用Spring Boot和Angular进行Bootiful开发中的说明相同。 为了方便起见,我在下面将它们复制了下来。

要开始使用Spring Boot,请导航到start.spring.io 。 在“搜索依赖项”字段中,选择以下内容:

  • H2 :内存数据库
  • JPA :Java的标准ORM
  • 其余存储库 :允许您将JPA存储库公开为REST端点
  • Web :具有Jackson(用于JSON),Hibernate Validator和嵌入式Tomcat的Spring MVC

如果你喜欢命令行更好,你可以使用下面的命令来下载一个demo.zip与文件HTTPie 。

http https://start.spring.io/starter.zip \
dependencies==h2,data-jpa,data-rest,web -d

创建一个名为spring-boot-react-example目录,其中包含server目录。 将demo.zip的内容demo.zipserver目录中。

在您喜欢的IDE中打开“服务器”项目,然后运行DemoApplication或使用./mvnw spring-boot:run从命令行启动它。

在其中创建com.example.demo.beer程序包和Beer.java文件。 此类将是保存您的数据的实体。

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 + '\'' +'}';}
}

添加一个利用Spring Data在此实体上执行CRUD的BeerRepository类。

package com.example.demo.beer;import org.springframework.data.jpa.repository.JpaRepository;interface BeerRepository extends JpaRepository<Beer, Long> {
}

添加使用此存储库的BeerCommandLineRunner并创建一组默认数据。

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);}
}

重建您的项目,您应该会在终端上看到印刷的啤酒清单。

a添加@RepositoryRestResource注释BeerRepository揭露其所有CRUD操作的REST端点。

import org.springframework.data.rest.core.annotation.RepositoryRestResource;@RepositoryRestResource
interface BeerRepository extends JpaRepository<Beer, Long> {
}

添加一个BeerController类来创建一个端点,该端点过滤出的啤酒数量少于大啤酒。

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");}
}

重新构建您的应用程序并导航到http://localhost:8080/good-beers 。 您应该在浏览器中看到优质啤酒的列表。

使用HTTPie时,您也应该在终端窗口中看到相同的结果。

http localhost:8080/good-beers

使用Create React App创建一个项目

这些天来,创建API似乎很容易,这在很大程度上要归功于Spring Boot。 在本部分中,我希望向您展示使用React创建UI也非常容易。 如果您按照以下步骤操作,则将创建一个新的React应用,从API获取啤酒名称和图像,并创建用于显示数据的组件。

要创建一个React项目,请确保您已安装Node.js , Create React App和Yarn 。

npm install -g create-react-app@1.4.3

在终端窗口中,cd进入spring-boot-react-example目录的根目录并运行以下命令。 该命令将创建一个具有TypeScript支持的新React应用程序。

create-react-app client --scripts-version=react-scripts-ts

运行此过程之后,您将拥有一个新的client目录,其中安装了所有必需的依赖项。 为了验证一切正常,将cd进入client目录并运行yarn start 。 如果一切正常,您应该在浏览器中看到以下内容。

到目前为止,您已经创建了一个good-beers API和一个React应用程序,但是尚未创建UI来显示API中的啤酒列表。 为此,请打开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的组件生命周期将调用componentDidMount()方法。 上面的代码使用fetch ,这是XMLHttpRequest的现代替代。 根据caniuse.com,大多数浏览器均支持该功能 。

您会看到它使用响应数据设置了beers状态。 要初始化此组件的状态,您需要重写构造函数。

constructor(props: any) {super(props);this.state = {beers: [],isLoading: false};
}

为此,您需要将参数类型添加到类签名中。 下面的代码显示了此时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语法,可通过JavaScript呈现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 ,则会看到“正在加载...”消息。 如果您在浏览器的控制台中查看,可能会看到有关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进行跨域访问。

为Spring Boot配置CORS

在服务器项目中,打开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() {

进行了这些更改之后,重新启动服务器,刷新浏览器,您应该能够从Spring Boot API中看到啤酒列表。

创建一个BeerList组件

为了使此应用程序更易于维护,请将啤酒清单的获取和呈现从App.tsx到其自己的BeerList组件。 创建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 ,使其仅包含一个外壳和对<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;

创建一个GiphyImage组件

为了使其看起来更好一点,添加GIPHY组件以根据啤酒的名称获取图像。 创建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.tsxrender()方法以使用此组件。

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>);
}

结果应类似于以下带有图像的啤酒名称列表。

您刚刚创建了一个React应用,该应用使用跨域请求与Spring Boot API进行通讯。 恭喜你!

添加PWA支持

Create React App开箱即用地支持渐进式Web应用程序(PWA)。 要了解其集成方式,请打开client/README.md并搜索“制作渐进式Web应用程序”。

要查看其工作方式,请在client目录中运行yarn build 。 该命令完成后,您将看到类似以下的消息。

The build folder is ready to be deployed.
You may serve it with a static server:yarn global add serveserve -s build

运行建议的命令,您应该能够打开浏览器以查看http://localhost:5000 。 您的浏览器可能会在其控制台中显示CORS错误,因此BeerController.java再次打开BeerController.java并调整其允许的来源以允许端口5000。

@CrossOrigin(origins = {"http://localhost:3000", "http://localhost:5000"})

重新启动服务器,并且http://localhost:5000应该加载啤酒名称和图像。

我在Chrome中进行了Lighthouse审核,发现此应用目前仅获得73/100的评分。

您会在上面的屏幕截图中注意到“清单没有至少512px的图标”。 听起来很容易修复。 您可以从此页面下载512像素的免费啤酒图标。

注意:此图标由Freepik从www.flaticon.com制作 。 它由CC 3.0 BY许可。

将下载的beer.png复制到client/public 。 修改client/public/manifest.json以具有特定于此应用程序的名称,并添加512像素的图标。

{"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"
}

进行此更改后,我的PWA得分达到82灯塔评分。 该报告最突出的抱怨是我没有使用HTTPS。 为了查看该应用使用HTTPS时的评分,我将其部署到Pivotal Cloud Foundry和Heroku 。 我很高兴发现它在两个平台上的得分都为💯。

要阅读我用于部署所有内容的脚本,请参阅本文附带的GitHub存储库中的cloudfoundry.shheroku.sh 。 我非常感谢@starbuxman和@codefinger在创建它们方面的帮助!

使用Okta添加身份验证

您可能会想,“这很酷,很容易看出人们为什么爱上React。” 试用过后,您可能会爱上另一个工具:使用Okta进行身份验证! 为什么选择Okta? 因为您可以免费获得7,000个每月活跃用户 ! 值得一试,特别是当您看到将auth添加到Spring Boot和使用Okta进行React很容易时。

Okta Spring启动启动器

要锁定后端,可以使用Okta的Spring Boot Starter 。 要集成此启动器,请将以下依赖项添加到server/pom.xml

<dependency><groupId>com.okta.spring</groupId><artifactId>okta-spring-boot-starter</artifactId><version>0.2.0</version>
</dependency>

您还需要添加<dependencyManagement>部分以升级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>

注意: 有一个问题与1563的Spring开机启动它不使用Spring Boot的DevTools工作。

现在,您需要配置服务器以使用Okta进行身份验证。 为此,您需要在Okta中创建OIDC应用。

在Okta中创建OIDC应用

登录到您的1563开发者帐户(或者注册 ,如果你没有一个帐户)并导航到应用程序 > 添加应用程序 。 单击“ 单页应用程序” ,再单击“ 下一步” ,然后为该应用程序命名。 将localhost:8080所有实例更改为localhost:3000 ,然后单击完成

将客户端ID复制到您的server/src/main/resources/application.properties文件中。 在其中时,添加与您的Okta域匹配的okta.oauth2.issuer属性。 例如:

okta.oauth2.issuer=https://{yourOktaDomain}.com/oauth2/default
okta.oauth2.clientId={clientId}

注意: { yourOktaDomain }的值应类似于dev-123456.oktapreview.com 。 确保在值中不包括-admin

更新server/src/main/java/com/okta/developer/demo/DemoApplication.java以将其启用为资源服务器。

import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;@EnableResourceServer
@SpringBootApplication

进行了这些更改之后,您应该能够重新启动服务器,并在尝试导航到http:// localhost:8080时看到访问被拒绝。

Okta的React支持

Okta的React SDK允许您将OIDC集成到React应用程序中。 您可以在npmjs.com上了解有关Okta的React SDK的更多信息。 要安装,请运行以下命令:

yarn add @okta/okta-react react-router-dom
yarn add -D @types/react-router-dom

Okta的React SDK依赖于react-router ,因此需要安装react-router-dom的原因。 在client/src/App.tsx配置路由是一种常见的做法,因此,用下面的TypeScript替换其代码,该TypeScript使用Okta设置身份验证。

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;

创建client/src/Home.tsx以包含App.tsx以前包含的应用程序外壳。 此类呈现应用程序外壳,登录/注销按钮以及<BeerList/>如果已通过身份验证)。

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应用,则可能会看到类似以下的错误:

./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';`

使用以下声明创建client/src/okta.d.ts来解决此问题。

declare module '@okta/okta-react';

重新启动客户端,您将看到在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 ,通过创建一个传递到类签名中的BeerListProps接口,将auth属性添加到道具中。

import { Auth } from './App';interface BeerListProps {auth: Auth;
}interface BeerListState {beers: Array<{}>;isLoading: boolean;
}class BeerList extends React.Component<BeerListProps, BeerListState> {...
}

将以下CSS规则添加到client/src/App.css以使“登录/注销”按钮更加可见。

.Buttons {margin-top: 10px;
}.Buttons button {font-size: 1em;
}

您的浏览器后台,请检查以下内容。

单击按钮登录后,输入用于创建Okta Developer帐户的电子邮件和密码。 当它将您重定向回您的应用程序时,您可能会在浏览器的控制台中看到“正在加载...”和CORS错误。

发生此错误是因为Spring的@CrossOrigin在Spring Security中不能很好地发挥作用。 要解决此问题,请在DemoApplication.java的主体中添加一个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;}
}

要使其在客户端上都能正常工作,请修改client/src/BeerList.tsxcomponentDidMount()方法以设置授权标头。

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;
}

更改构造函数,以便将error初始化为空字符串。

this.state = {beers: [],isLoading: false,error: ''
};

然后更改render()方法以在发生错误时显示错误。

render() {const {beers, isLoading, error} = this.state;if (isLoading) {return <p>Loading ...</p>;}if (error.length > 0) {return <p>Error: {error}</p>;}return (...)
}

现在您应该能够以经过身份验证的用户身份查看啤酒清单。

如果有效,那么恭喜!

清理那些TypeScript警告

您可能会注意到,浏览器的控制台报告了一些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.

要解决第一个问题,请更改client/src/BeerList.tsx ,使其构造函数如下:

constructor(props: BeerListProps) {...
}

对于第二个问题,在client/src/BeerList.tsx创建一个Beer接口。 将其放在顶部的其他接口旁边。

interface Beer {id: number;name: string;
}

然后将{ beers.map((beer: any) =>更改为{ beers.map((beer: Beer) =>

第三个问题可以通过在client/src/GiphyImage.tsx创建一个新的GiphyImageState接口来定义状态属性来解决。

interface GiphyImageState {giphyUrl: string;isLoading: boolean;
}class GiphyImage extends React.Component<GiphyImageProps, GiphyImageState> {...
}

进行了这些更改之后,您应该摆脱TypeScript警告。

了解有关Spring Boot和React的更多信息

要了解有关React,Spring Boot或Okta的更多信息,请查看以下资源:

  • Eric Vicenti的React工作坊简介 -强烈建议您学习React!
  • 我与Deepu K Sasidharan 在比利时Devoxx上进行的Angular vs React Smackdown演讲
  • Robin Wieruch 如何在React中获取数据
  • 15分钟内通过用户身份验证构建React应用程序
  • 使用身份验证构建预先应用
  • 使用Okta的React SDK创建自定义登录表单

您可以在GitHub上找到与本文相关的源代码。 主要示例(无身份验证)在master分支中,而Okta集成在okta分支中。 要签出本地计算机上的Okta分支,请运行以下命令。

git clone git@github.com:oktadeveloper/spring-boot-react-example.git
git checkout okta

如果您发现任何问题,请在下面添加评论,我们将尽力为您提供帮助。 如果您喜欢本教程,希望您在Twitter上关注我 。 要获得更多类似此类文章的通知,请关注@oktadev 。

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。

翻译自: https://www.javacodegeeks.com/2018/01/bootiful-development-spring-boot-react.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/348275.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机硬盘按不同接口,硬盘接口不同 速度差别竟然这么大

硬盘接口是硬盘与主机系统间的连接部件&#xff0c;作用是在硬盘缓存和主机内存之间传输数据。不同的硬盘接口决定着硬盘与计算机之间的连接速度&#xff0c;在整个系统中&#xff0c;硬盘接口的类型以及好坏都会直接影响程序运行快慢。说到硬盘接口我们常见的有SATA、PCI-E、M…

计算机组成原理setb,计算机组成原理与汇编语言4

第四章指令系统指令系统概述指令系统是指某一种计算机所有指令的集合。对计算机而言&#xff0c;这是一组二进制数的输入&#xff0c;实际上是一组电平的输入。这些输入能在一个指令周期内产生人们预先规定的动作。显然这不是一组随机的二进制数据输入。指令系统是联系硬件和软…

你活在一个计算机模拟中吗,一麻省理工教授认为,我们更有可能生活在计算机模拟宇宙中...

一位麻省理工学院的教授表示&#xff0c;我们“更有可能”生活在某种模拟宇宙中&#xff0c;因为我们自己离能够创造超现实的模拟也不远了。(参见在接受解释性网站Vox采访时&#xff0c;计算机科学家Rizwan Virk认为&#xff0c;如果我们生活在信息世界而不是物质世界中&#x…

服务器系统开机提示0xc000007b,0xc000007b蓝屏解决方法

0xc000007b是一种常见的蓝屏代码&#xff0c;导致出现此蓝屏代码的原因有很多&#xff0c;BIOS设置问题&#xff0c;驱动问题&#xff0c;硬件问题等等。硬件问题对于我们小白来说解决起来有点困难。我给大家带来了解决0xc000007b蓝屏软件问题的方法&#xff0c;赶紧来瞧瞧吧0x…

tms tck_记录合规性–关于TCK,规格和测试

tms tck使用软件规格非常困难。 不论在哪个地方提出&#xff1b; 您最终遇到了一个大问题&#xff1a;是否已实现所有指定的内容并对其进行了测试&#xff1f; 在瀑布驱动的方法学时代&#xff0c;这一直是一个问题&#xff0c;即使在撰写本文的今天&#xff0c;敏捷性和用户故…

3D环动画css3实现,CSS3 3D酷炫立方体变换动画的实现

我爱撸码&#xff0c;撸码使我感到快乐&#xff01;大家好&#xff0c;我是Counter&#xff0c;本章微博主要利用了CSS3的一些新特性&#xff0c;主要用到关键帧来使3D图形运动起来&#xff0c;涉及到了一些抽象的思想&#xff0c;立体的想象。先给大家看看完成的效果&#xff…

Java的新视差控件(JavaFX)

介绍 视差是一种视觉效果&#xff0c;您可以组合以不同速度移动的两个分层图像以获得深度感。 想想一下&#xff0c;当您在道路上行驶时&#xff0c;您会看到附近的树木在快速移动&#xff0c;而距离较远的树木将沿相同的方向移动&#xff0c;但速度较慢&#xff0c;结果是您感…

mockito 静态方法_Mockito –带有注释和静态方法的额外接口

mockito 静态方法在代码中&#xff0c;我最近遇到了一段非常糟糕的代码&#xff0c;该代码基于对对象执行某些操作的类转换。 当然&#xff0c;代码需要重构&#xff0c;但是如果您首先没有对该功能进行单元测试&#xff0c;则有时您可能无法做到/或者不想这样做&#xff08;这…

手机推送信息到本地服务器,服务器信息推送到手机

服务器信息推送到手机 内容精选换一换服务器的计费方式为“包年/包月”&#xff0c;如果在计费周期内不想再继续使用&#xff0c;请参考本节指导进行退订。执行退订操作前&#xff0c;请确保待退订的服务器数据已完成备份或者迁移&#xff0c;退订完成后资源将被完全删除&#…

Apache Camel 2.21发布–新增功能

我们刚刚发布了Apache Camel 2.21&#xff0c;我将在此博客中重点介绍值得注意的更改。 此版本不支持Spring Boot2。对Spring Boot 2的支持将在Camel 2.22中提供&#xff0c; 我们计划在2018年夏季之前发布。 1&#xff09;处理大型JMS消息 我们在JMS组件中添加了更好的支持&…

linux下网站服务器,Linux下使用Apache搭建Web网站服务器(示例代码)

[[email protected] ~]# yum install elinks –y主配置文件[[email protected] ~]# ls/etc/httpd/conf/httpd.conf/etc/httpd/conf/httpd.conf#我们又是怎么知道httpd的注配置文件是在你那里的呢&#xff1f;查看httpd注配置文件位置[[email protected] ~]# rpm -pql/mnt/Packa…

程序内存泄露监视_监视和检测Java应用程序中的内存泄漏

程序内存泄露监视因此&#xff0c;您的应用程序内存不足&#xff0c;您日夜不停地分析应用程序&#xff0c;以期捕获对象中的内存漏洞。 后续步骤将说明如何监视和检测您的内存泄漏&#xff0c;以确保您的应用程序处于安全状态。 1.怀疑内存泄漏 如果您怀疑有内存泄漏&#xf…

服务器可以装两个系统吗,云服务器可以装多个系统吗

云服务器可以装多个系统吗 内容精选换一换示例&#xff1a;购买并登录Windows弹性云服务器示例&#xff1a;购买并登录Linux弹性云服务器云平台提供了多种实例类型供您选择&#xff0c;不同类型的实例可以提供不同的计算能力和存储能力。同一实例类型下可以根据CPU和内存的配置…

基于FPGA,如何用Verilog HDL实现64位宽的扰码器?附上仿真结果。

文章目录前言一、扰码器1、什么是扰码器2、扰码的原理3、产生扰码的多项式二、Scrambler的Verilog实现1、scrambler.v2、scrambler_tb.v三、仿真结果四、总结前言 在数字信号处理系统中&#xff0c;因为发送端的数字信号序列可能会出现很长一段都是“0”&#xff0c;或很长一段…

基于FPGA,解扰码器Verilog的实现,以及扰码器与解扰码器的联合仿真。附上仿真结果。

文章目录前言一、扰码器二、解扰码器三、Descrambler的Verilog实现1、descrambler.v2、descrambler_tb.v四、扰码器与解扰码器的联合仿真1、scrambler_test.v2、scrambler_test_tb.v3、联合仿真结果五、总结前言 在数字信号处理系统中&#xff0c;因为发送端的数字信号序列可能…

光纤通信系统简介

文章目录前言一、直接检测光通信系统二、相干光通信系统三、直接检测与相干检测1、直接检测2、相干检测3、相干检测的优缺点&#xff08;1&#xff09;相干检测的缺点&#xff08;2&#xff09;相干检测的优点总结参考文献前言 光通信系统的基本组成结构如下图所示。光通信系统…

OFDM仿真程序,可直接运行,注释详细(没人注释比我还详细了)

OFDM仿真程序 clc clear allIFFT_bin_length128; %IFFT点数128个 carrier_count50; %子信道&#xff08;子载波&#xff09;数目 bits_per_symbol2; %4进制符号 symbols_per_carrier200;%每个子信道或者说子载波有200个符号 SNR0:1:40; for num1:41baseband_out_lengthcarrie…

Delta-Sigma调制(DSM)技术

前言 数字信号处理和通信系统的性能很大程度上受到了模拟信号到数字信号转换接口——ADC的精度和分辨率的限制。而传统的线性脉冲编码调制&#xff08;PCM&#xff09;ADC受到了制造工艺的限制&#xff0c;无法达到很高的分辨率。但基于Delta-Sigma调制技术的ADC可以在现有工艺…

无载波幅度和相位调制(CAP)与QAM调制的详细解析(可见光通信应用场景),以及CAP matlab程序下载链接

文章目录前言一、QAM调制&#xff1f;二、无载波幅度和相位调制&#xff08;CAP)三、CAP调制与QAM调制之间的联系&#xff08;异同点&#xff09;四、CAP调制相比于QAM调制的优缺点4.1、优点4.2、缺点五、无载波幅度和相位调制matlab程序五、Reference前言 目前的通信系统中&a…

Matlab中装载和存储实验数据的操作

一、装载实验数据 例如&#xff0c;以下程序&#xff0c;其中path是路径&#xff0c;strcat函数将后面的参数组合成一个字符串。load函数将由twoband_CAP4_400MBd_2000MSa_float_字符串与Tx.txt构成的: twoband_CAP4_400MBd_2000MSa_float_Tx.txt文件读取至matlab中。 numSam…