使用Spring Boot和MongoDB构建一个React式应用程序

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

如果您要处理大量流数据,React式应用程序可以使您更好地扩展。 它们是非阻塞的,并且往往效率更高,因为它们在等待发生事情时不会占用处理能力。

React系统包含异步I / O。 异步I / O背后的概念很简单:通过回收本来会在等待I / O活动时处于空闲状态的资源来缓解资源利用效率低下的问题。 异步I / O颠倒了I / O处理的常规设计:向客户端通知新数据,而不是要求它们;而是向客户端通知新数据。 这使客户可以在等待时做其他事情。

如果要构建React式应用程序,则需要它一直到整个数据库都是React式的。 将阻塞的JDBC驱动程序与Spring WebFlux一起使用,您会对其性能感到失望。 使用React性NoSQL数据库(例如Cassandra,MongoDB,Couchbase和Redis),其性能将给您留下深刻的印象。

在本教程中,您将学习如何使用Spring Boot,Spring WebFlux和Spring Data创建与NoSQL数据库后端(在本例中为MongoDB)通信的React式Web服务。

我只是对你说了几句。 让我们越过它们。

如果您已经了解NoSQL和Reactive编程,并且只想看一些代码,请随时跳过前两部分,并从“构建Spring Boot资源服务器”开始。

注意:本系列的第一部分演示了如何将Spring Boot和Spring Data与关系数据库PostgreSQL一起使用。 您可以在此处查看该帖子。

什么是NoSQL?为什么使用MongoDB?

NoSQL是任何非关系数据库的术语。 在关系数据库(例如SQL,MySQL等)中,数据存储在具有强类型和表列之间定义明确的关系的表中。 关系数据库的紧密,定义明确的结构既是优点也是缺点。 这是一个权衡。 NoSQL数据库使该模型爆炸式增长,并提供了其他模型,这些模型可提供更大的灵活性并易于扩展。

扩展的微服务/集群模型为关系数据库带来了很多问题。 它们只是不能在多台计算机上运行并保持同步。 开发NoSQL数据库部分是为了解决此问题。 通常,它们是在考虑群集和水平缩放的情况下构建的。 为了以另一种方式展示这种方式(传统上是使用SQL数据库),如果需要更多功能,则必须调整数据库所运行的服务器的大小。 它几乎是单片的,而且即使当今有所有现代虚拟服务器的时髦功能,也很难动态地做到这一点。 在Internet规模上,更好的模型是拥有一个灵活的数据库集群,它们可以在数据库之间自动同步,并允许您根据需求增加实例(并在需求减少时减少实例)。 这意味着增加功率并不需要更昂贵的机器。 您可以根据需要简单地添加更多,相对便宜的计算机。

NoSQL数据库的另一个潜在好处是它们的灵活性。 像MongoDB这样的基于文档的NoSQL数据库可以在文档中存储任意数据。 可以将字段动态添加到存储的文档中,而无需增加表迁移的开销。 当然,这并不能解决版本控制的问题,仍然取决于应用程序来处理不断变化的数据结构(并不总是琐碎的),但是至少您并没有与数据库打架。

综上所述,请记住,SQL /关系数据库不会走到任何地方。 它们经过验证,快速且超级可靠。 在某些情况下,它们更便宜,更容易。 例如,对于简单的网站或博客,MySQL很难被击败。 但是即使在企业环境中,有时您也想要关系数据库强制执行的结构。 如果您有一个相当静态的数据模型并且不需要扩展到Internet规模,则SQL可能是最佳选择。 这些类型的设计考虑因素值得考虑,因为它们是新的且浮华的,因此在您深入数据库选择之前。

我在本教程中使用的是MongoDB,因为一开始它很容易。 如果您使用Spring Data MongoDB,它甚至更容易!

积极React!

React性是另一种行话。 感觉就像人们喜欢在聚会和会议上随意表达的那种字眼,只是含糊其词。 像“存在的”或“ ennui”。 让我们定义一下。

如果您看一下Spring WebFlux文档,他们会很好地概述什么是React性

术语“React性”是指围绕响应变化而构建的编程模型-网络组件响应I / O事件,UI控制器响应鼠标事件等。 从这个意义上说,非阻塞是React性的,因为随着操作完成或数据可用,我们现在处于响应通知的模式,而不是被阻塞。

因此,React式意味着:非阻塞,异步且以流处理为中心。

构建一个Spring Boot资源服务器

从GitHub存储库克隆启动项目,并检出start分支:

git clone -b start https://github.com/oktadeveloper/okta-spring-boot-mongo-webflux-example.git

入门项目是一个简单的Spring Boot入门项目,在build.gradle文件中已经具有必需的依赖build.gradle

让我们快速看一下相关性:

compile('org.springframework.boot:spring-boot-starter-webflux')  
compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')  
compileOnly('org.projectlombok:lombok')  
compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo')

第一个是针对Spring WebFlux(Spring MVC的响应版本)。 第二个引入了Spring需要的React性MongoDB依赖关系。 第三个是名为Lombok的项目,该项目使我们不必在Java代码中键入一堆构造函数,getter和setter(您可以在其网页上查看该项目)。 最后一个依赖项是嵌入式的内存MongoDB数据库。 这个数据库非常适合测试,像这样的简单教程,并且不持久。

可以使用简单的Gradle命令运行该应用程序:

./gradlew bootRun

当然,如果您此时运行该应用程序,则不会做太多事情。 Spring Boot将加载,但是还没有定义任何控制器,资源或存储库,因此没有太多事情发生。

为MongoDB定义模型类

为了清楚起见,本教程将与我之前提到的本系列第一部分并行。 您将要构建一个存储皮划艇类型的简单服务器。 我总是建议首先定义数据结构来开始任何项目。

com.okta.springbootmongo包中创建一个Kayak.java类文件:

package com.okta.springbootmongo;  

import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  
import org.springframework.data.mongodb.core.mapping.Document;  

@Document
@Data  
@AllArgsConstructor  
@NoArgsConstructor  
public class Kayak {  
    private String name;  
    private String owner;  
    private Number value;  
    private String makeModel;  
}

@Document注释与@Entity的NoSQL等效。 它告诉Spring Boot此类正在定义数据模型。 在NoSQL世界中,这意味着创建文档而不是表条目。 其他三个注释是Lombok帮助程序,它们自动生成getter,setter和构造函数。

皮划艇文档具有五个属性:名称,所有者,值和类型。 这些会自动映射到适用于MongoDB的BSON类型。 什么是BSON类型? 看一下有关该主题的MongoDB文档。 它们是用于将数据保留在MongoDB文档中的二进制序列化类型。 它们定义了可以存储在MongoDB数据库中的原始类型。

将ReactiveMongoRepository添加到您的Spring Boot应用程序

使用@Document批注定义Kayak类可以使Spring Boot知道数据的结构,但实际上并没有提供任何从数据库保存或加载数据的方法。 为此,您需要定义一个存储库。

这样做的代码非常简单。 在com.okta.springbootmongo包中创建一个KayakRepository.java类文件:

package com.okta.springbootmongo;  
  
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;  

public interface KayakRepository extends ReactiveMongoRepository<Kayak, Long> {  
}

实际上,这为您提供了从数据库创建,更新,读取和删除文档所需的所有基本方法。 要了解操作方法,请深入研究ReactiveMongoRepository类和其他各种超类,尤其是ReactiveCrudRepository 。 查看ReactiveCrudRepository 的文档,以查看实现的方法。

ReactiveCrudRepository实际上提供了一组基本而完整的CRUD方法。 ReactiveMongoRepository在此基础上构建,以提供一些特定于MongoDB的查询功能。

使用Spring WebFlux实现控制器

添加存储库后,您有足够的能力以编程方式操作数据。 但是,没有定义Web端点。 在前面的教程中,添加REST端点,这是需要的所有是对加@RepositoryRestResource注释到KayakRepository类。 这通过所有CRUD方法为我们自动生成了功能齐全的REST资源。 但是,此快捷方式不适用于Spring WebFlux。 必须明确定义任何公共Web终结点。

添加以下KayakController.java

package com.okta.springbootmongo;  
  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.*;  
import reactor.core.publisher.Flux;  

@Controller  
@RequestMapping(path = "/kayaks")  
public class KayakController {  
  
    private KayakRepository kayakRepository;  
    
    public KayakController(KayakRepository kayakRepository) {
        this.kayakRepository = kayakRepository;
    }
  
    @PostMapping()  
    public @ResponseBody  
    Mono<Kayak> addKayak(@RequestBody Kayak kayak) {  
        return kayakRepository.save(kayak);  
    }  
  
    @GetMapping()  
    public @ResponseBody  
    Flux<Kayak> getAllKayaks() {  
        return kayakRepository.findAll();  
    }
}

该控制器添加了两个端点:

  • POST /kayaks增加了新的皮划艇
  • GET /kayaks列出所有皮划艇

您还将注意到,该类使用Spring依赖项注入将KayakRepository实例自动装配到控制器中,并且您将看到如何使用存储库持久化Kayak域类。

此类看起来非常像关系的,阻止的版本。 许多幕后工作使这种情况成为现实。 不用担心,这是100%React性的非阻塞代码。

测试您的Spring Boot服务器

至此,您已经可以完全运行皮划艇REST资源服务器。 在测试之前,请将以下方法添加到MainApplication类。 当应用程序加载时,这只是将一些测试数据注入数据库。

@Bean  
ApplicationRunner init(KayakRepository repository) {  
  
  Object[][] data = {  
      {"sea", "Andrew", 300.12, "NDK"},  
      {"creek", "Andrew", 100.75, "Piranha"},  
      {"loaner", "Andrew", 75, "Necky"}  
  };  
  
  return args -> {  
      repository  
          .deleteAll()  
          .thenMany(  
              Flux  
                  .just(data)  
                  .map(array -> {  
                      return new Kayak((String) array[0], (String) array[1], (Number) array[2], (String) array[3]);  
                  })  
                  .flatMap(repository::save)  
          )  
          .thenMany(repository.findAll())  
          .subscribe(kayak -> System.out.println("saving " + kayak.toString()));
  };  
}

HTTPie是一个很棒的命令行实用程序,可以轻松地对资源服务器运行请求。 如果未安装HTTPie,请使用brew install httpie进行brew install httpie 。 或前往他们的网站并实现它。 或者只是跟随。

确保您的Spring Boot应用正在运行。 如果不是,请使用./gradlew bootRun启动它。

针对您的资源服务器运行GET请求: http :8080/kayaks ,这是http GET http://localhost:8080/kayaks简写。

您将获得:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
transfer-encoding: chunked
[
    {
        "makeModel": "NDK",
        "name": "sea",
        "owner": "Andrew",
        "value": 300.12
    },
    {
        "makeModel": "Piranha",
        "name": "creek",
        "owner": "Andrew",
        "value": 100.75
    },
    {
        "makeModel": "Necky",
        "name": "loaner",
        "owner": "Andrew",
        "value": 75
    }
]

现在尝试将新的皮划艇过帐到服务器。

http POST :8080/kayaks name="sea2" owner="Andrew" value="500" makeModel="P&H"

您应该看到:

HTTP/1.1 200 OK
Content-Length: 62
Content-Type: application/json;charset=UTF-8
{
    "makeModel": "P&H",
    "name": "sea2",
    "owner": "Andrew",
    "value": 500
}

如果您重复GET请求http :8080/kayaks ,则会在列表中看到新的皮划艇!

设置安全认证

现在,您需要集成Okta for OAuth 2.0并将基于令牌的身份验证添加到资源服务器。 本部分与本教程第1部分中的部分完全相同,因此,如果您已完成此操作,则只需要您的Client ID,就可以跳到下一部分。

如果尚未注册,请访问developer.okta.com并注册一个免费帐户。 拥有帐户后,通过单击“应用程序”顶部菜单项,然后单击“添加应用程序”按钮,打开开发人员仪表板并创建OpenID Connect(OIDC)应用程序

React式应用

选择单页应用程序

React式应用

默认应用程序设置很好,除了您需要添加登录重定向URIhttps://oidcdebugger.com/debug : https://oidcdebugger.com/debug 。 您将在稍后使用它来检索测试令牌。

注意:如果要实现像Angular或React这样的前端,则可能需要根据所使用的平台来更新默认的登录重定向URI。 由于本教程仅创建没有前端的资源服务器,因此暂时不重要。 我们所有的资源服务器将要做的就是使用授权服务器验证JSON Web令牌,而无需重定向。

另外,请注意您的客户ID ,稍后您将需要它。

React式应用

配置您的Spring Boot服务器以进行令牌认证

现在,您需要更新一些项目文件以为OAuth 2.0配置Spring Boot。

将以下依赖项添加到build.gradle文件中:

dependencies {
  ...
    compile('com.okta.spring:okta-spring-boot-starter:1.1.0')
  ...
}

创建一个名为src/main/resources/application.yml的新配置文件

okta:
  oauth2:
    issuer: https://{yourOktaDomain}/oauth2/default
    groupsClaim: groups
    clientId: {yourClientId}

com.okta.springbootmongo包中创建一个SecurityConfiguration.java类:

package com.okta.springbootmongo;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@EnableWebFluxSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange()
            .anyExchange().authenticated()
            .and()
            .oauth2ResourceServer()
            .jwt();
        return http.build();
    }
}

测试您的受保护服务器

停止您的Spring Boot服务器并使用以下./gradlew bootRun重新启动它: ./gradlew bootRun

在命令行中,运行一个简单的GET请求。

http :8080/kayaks

您会得到未经授权的401 /。

HTTP/1.1 401 Unauthorized
Cache-Control: no-store
Content-Type: application/json;charset=UTF-8

生成访问令牌

要立即访问服务器,您需要一个有效的访问令牌。 您可以使用OpenID Connect调试器来帮助您完成此任务。 在另一个窗口中,打开oidcdebugger.com

  • 授权URIhttps://{yourOktaDomain} /oauth2/default/v1/authorize
  • 重定向URI :不变。 这是您在上面的OIDC应用程序中添加的值。
  • 客户ID :来自您刚创建的OIDC应用程序。
  • 范围openid profile email
  • 状态:您要通过OAuth重定向过程传递的任何值。 我将其设置为{}
  • Nonce :可以一个人呆着。 Nonce表示“编号已使用一次”,是一种简单的安全措施,用于防止同一请求被多次使用。
  • 响应类型token
  • 响应方式form_post
React式应用

点击发送请求。 如果您尚未登录developer.okta.com,则需要登录。如果(可能的话)已经登录,则将为您的登录身份生成令牌。

React式应用

使用您的访问令牌

您可以通过在Bearer类型的Authorization请求标头中包含令牌来使用令牌。

将令牌存储在shell变量中:

TOKEN=eyJraWQiOiJldjFpay1DS3UzYjJXS3QzSVl1MlJZc3VJSzBBYUl3NkU4SDJfNVJr...

然后使用HTTPie发出GET请求:

http :8080/kayaks "Authorization: Bearer $TOKEN"

请注意上面的双引号。 单引号不起作用,因为shell变量未扩展。

添加基于组的授权

现在,您将通过添加基于组成员身份来控制对特定控制器端点的访问的功能,来使授权方案更加完善。

要在Okta中使用基于组的授权,您需要在访问令牌中添加一个“组”声明。 创建一个Admin组(“用户” >“组” >“添加组” )并将您的用户添加到其中。 您可以使用注册时使用的帐户,也可以创建一个新用户(“用户” >“添加人” )。 导航到“ API” >“授权服务器” ,单击“授权服务器”选项卡,然后编辑默认选项卡。 点击索赔标签,然后添加索赔。 将其命名为“组”,并将其包含在访问令牌中。 将值类型设置为“ Groups”,并将过滤器设置为.*的正则表达式。

声明包含用户分配到的组。 您用来登录developer.okta.com网站的默认用户也将是“所有人”组和“管理员”组的成员。

还需要更新SecurityConfiguration类以使用基于组的授权。 更新Java文件以匹配以下内容:

package com.okta.springbootmongo;

import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration {

  @Bean
  public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
      http
          .authorizeExchange()
          .pathMatchers(HttpMethod.POST, "/kayaks/**").hasAuthority("Admin")
          .anyExchange().authenticated()
          .and()
          .oauth2ResourceServer()
          .jwt();
      return http.build();
  }
}

用简单的英语来说,这告诉Spring Boot要求对/kayak端点的任何POST都具有组成员身份Admin ,对于所有其他请求,仅需要有效的JWT。

您的基于组的授权策略由以下两行定义:

.pathMatchers(HttpMethod.POST, "/kayaks/**").hasAuthority("Admin")  
.anyExchange().authenticated()

有关更多信息,请参阅ServerHttpSecurity类的文档

您可能想知道为什么它说hasAuthority()而不是hasRole()hasGroup() 。 这是因为授权是Spring调用服务器发送的文本字符串以表示权限成员身份的方式,无论是角色还是组。 hasRole()假定角色采用特定格式:“ ROLE_ADMIN”。 可以重写此方法,但是hasAuthority()是直接使用授权字符串的简单方法。 没有hasGroup()方法,因为前两个示例(如果未明确说明)涵盖了该用例。

创建一个非管理员用户

要测试基于组的授权方案,您需要一个不是管理员的用户。 转到developer.okta.com仪表板。

从顶部菜单中,选择“用户” >“人员” 。 单击添加人按钮。

给用户一个名字姓氏用户名(也将是主要电子邮件)。 值无关紧要,您将无需检查电子邮件。 您只需要知道电子邮件地址/用户名和密码,即可在一分钟内登录Okta。

密码:将下拉菜单更改为“由管理员设置”

为用户分配密码。

点击保存

您刚刚创建的用户不是Admin组的成员,而是默认组Everyone的成员。

基于测试组的授权

注销您的Okta开发人员仪表板。

返回OIDC调试器并生成一个新令牌。

这次,以新的非管理员用户身份登录。 系统会要求您选择一个安全问题,然后将您重定向到https://oidcdebugger.com/debug页面,您可以在其中复制令牌。

如果愿意,可以转到jsonwebtoken.io并解码新令牌。 在有效负载中,声明将显示用户的电子邮件/用户名,而声明将仅显示“所有人”组。

{
  "sub": "test@gmail.com",
  "groups": [
    "Everyone"
  ]
}

根据许可方案,该用户应该能够列出所有皮划艇,但不能添加新的皮划艇。

请记住,将令牌存储在shell脚本中,如下所示:

TOKEN=eyJraWQiOiI4UlE5REJGVUJOTnJER0VGaEExekd6bWJqREpSYTRTT1lhaGpsM3d4...

发出GET请求以列出所有皮划艇:

http :8080/kayaks "Authorization: Bearer $TOKEN"

HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
...

[
    {
        "makeModel": "NDK",
        "name": "sea",
        "owner": "Andrew",
        "value": 300.12
    },
    {
        "makeModel": "Necky",
        "name": "loaner",
        "owner": "Andrew",
        "value": 75
    },
    {
        "makeModel": "Piranha",
        "name": "creek",
        "owner": "Andrew",
        "value": 100.75
    }
]

尝试使用非管理员用户令牌添加新的皮划艇:

http POST :8080/kayaks "Authorization: Bearer $TOKEN" name="sea2" owner="Andrew" value="500" makeModel="P&H"

您将被拒绝!

HTTP/1.1 403 Forbidden
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
...

现在,注销developer.okta.com,并使用OIDC Debugger生成一个新令牌。 这次使用您原来的管理员帐户重新登录。

将新令牌存储在shell变量TOKEN

运行POST请求:

http POST :8080/kayaks "Authorization: Bearer $TOKEN" name="sea2" owner="Andrew" value="500" makeModel="P&H"

AM! 💥

HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
...
{
    "makeModel": "P&H",
    "name": "sea2",
    "owner": "Andrew",
    "value": 500
}

一切顺利,已通过身份验证

而已! 在本教程中,您使用Spring WebFlux创建了一个Spring Boot应用程序,使用嵌入式MongoDB数据库来持久化模型类,并向其中添加了资源服务器。 之后,我向您展示了如何使用Okta和OAuth 2.0添加JWT令牌身份验证。 最后,您了解了如何使用Okta和Spring Security将基于组的授权添加到控制器中的特定端点。

如果您想查看这个完整的项目,可以在oktadeveloper / okta-spring-boot-mongo-webflux-example上的GitHub上找到该仓库

如果还没有,请查看本系列的第1部分: 使用PostgreSQL使用Spring Boot和JPA构建基本应用程序。 它是同一个应用程序,但是使用了更传统的关系数据库和Spring MVC样式的阻止Web服务器。

了解有关Spring Boot,MongoDB和安全用户管理的更多信息

如果您想了解有关Spring Boot,Spring Security或Okta的更多信息,请查看以下任何出色的教程:

以下是一些来自Spring的优秀资源:

如果您喜欢这篇文章,您可能会喜欢我们发布的其他文章。 在Twitter上关注@oktadev ,并订阅我们的YouTube频道以获取更多有趣的教程。

“使用Spring Boot和MongoDB构建React性应用程序”最初于2019年2月21日发布在Okta开发人员博客上。

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

翻译自: https://www.javacodegeeks.com/2019/03/build-reactive-app-spring-boot-mongodb.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值