使用Spring Boot和GraphQL构建安全的API

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

GraphQL是Facebook在2012年开发的一种数据查询语言,用于解决REST API和传统数据库模型的缺点。 通常,当程序员编写REST API数据查询时,他们默认只在需要一部分数据时才检索整个数据结构。 例如,如果您要查找博客文章中的评论数,开发人员通常可能会检索整个文章和所有关联的字段以及所有评论及其所有关联字段, 只是为了计算结果中的评论数数组。

这是非常低效的。 但是,现代计算机速度很快。 只要您有成百上千的用户,这些天甚至是共享服务器都非常快。 但是,当您达到Facebook规模,在互联网上接触到相当多的人时,这种情况就会崩溃。 这种低效率变得不可行或昂贵。

当然,可以编写更好的SQL查询(或NoSQL查询)并编写特定的API前端调用来计算效率更高的博客文章数量-但是您必须为每个特定的数据查询执行此操作案件。

还存在需要更多数据的问题。 如果您想要一组用户博客文章及其相关评论,并且还想要另一组不相关的数据怎么办? 通常,您分别对REST API进行两次调用。 实际上,在检索所有必需的数据时,REST API交互可能会在特定的API交互过程中导致数十个调用。 同样,在现代宽带上并且用户相对较少,这是有效的。 随着成千上万甚至数十亿的用户,它崩溃了。 毫秒效率至关重要。 减少网络呼叫的数量很重要。

必须有一种更好的方法,一种更通用的方法,以允许前端仅请求其所需的数据,并通过组合对数据的请求来减少网络交互的次数。

这就是Facebook开发GraphQL的原因。 它提供了一个框架来描述您的数据模型,并允许数据的使用者确切地要求他们想要什么并检索可预测的结果。

什么时候使用GraphQL?

GraphQL具有强大的功能和灵活性,并且在Internet规模上使用时,可以显着提高传统REST API的性能。 那你为什么使用它呢? 好吧,以我的经验,在较小的项目中,我觉得我经常只是将很多REST逻辑转移到GraphQL模式定义中,在那里验证和授权代码最终嵌套在一个相当丑陋的巨大模式中。 我还觉得我在重复数据类型定义:一次是在GraphQL中,另一次是在ORM中(这可能不是所有实现的问题)。

GraphQL还带来了一系列独特的安全问题–它没有错。 它与使用REST API或SQL查询将有所不同,因此您需要花一些时间来研究安全性。 我会考虑在应用程序Swift扩展或数据集可能随时间显着发展的情况下使用它。 如果我在一个较小的项目中只有一组定义良好的数据,那么我可能只会坚持使用REST API。

如何使用GraphQL?

从开发人员的角度来看,有两个主要组件:

  • 类型说明
  • 查询语言

类型描述最终看起来很像ORM(如果您熟悉的话)。 您定义类型和这些类型上的字段。 稍后,您还将定义函数以从数据库中检索该信息。

例如,让我们看一个非常简单的博客文章定义。

type Post {  
    id: ID!  
    text: String!  
}

很基本。 post具有idtext字段。 让我们在Post类型定义中添加一些注释。

type Post {  
    id: ID!  
    text: String!  
    comments: [Comment!]!  
}  
  
type Comment {  
    id: ID!  
    text: String!  
}

现在,Post类型包含一个Comments类型的数组。 我们如何将其转换为可用的GraphQL模式定义?

我们需要做两件事:

  1. 定义“查询”类型-所有GraphQL查询进入我们数据的入口点
  2. 为每种类型的每个字段编写方法,这些方法从数据类型返回所请求的字段

定义GraphQL模式查询

在简单的示例应用程序,我们正在建立,我们希望能够查询的帖子,所以让我们来添加一个查询类型与post Post类型的字段。 我们的GraphQL模式现在看起来像这样:

type Query {  
    post(id: ID!): Post  
}

type Post {  
    id: ID!  
    text: String!  
    comments: [Comment!]!  
}  
  
type Comment {  
    id: ID!  
    text: String!  
}

重申一下,Post和Comment类型直接描述了我们的数据结构,而Query类型则是GraphQL-ism,这是一种特殊类型,用于定义模式的只读入口点。 还有一种Mutation类型,可在架构中提供可变的访问点。

要注意的另一件事是字段可以有参数。 如果查看Query类型上的post字段,您会注意到它具有ID类型的参数。 可以在查询中指定此参数,并将该参数传递给调用该函数以检索数据。

顺便说一句,感叹号仅表示该类型不可为空。

GraphQL中的类型

GraphQL基本上是关于在类型上定义字段并查询这些字段。 它是强类型的,并且具有5种内置标量类型:

  • Int:32位整数
  • 浮点数:带符号的双精度浮点值
  • 字符串:UTF-8字符序列
  • 布尔值:对或错
  • ID:唯一标识符,序列化为字符串

还可以定义自定义标量类型,例如Date类型,并且必须提供用于序列化,反序列化和验证的方法。

也可以指定枚举和列表/数组。 我们在上面的Post类型中创建了Comments列表。

GraphQL官方文档提供了有关类型和架构的更多信息。

在Java中使用GraphQL

在本教程中,我们将使用一个名为graphql-tools的项目将GraphQL与Spring Boot集成在一起。 根据项目github页面自述文件, graphql-tools “允许您使用GraphQL模式语言来构建您的graphql-java模式,并允许您BYOO(带上自己的对象)来填充实现。”

如果您拥有或想要拥有定义数据模型的普通旧Java对象(PO​​JO),则此方法非常有用。 我做的。

我们的POJO会是什么样?

class Post {  
  
    protected int id;  
    protected String text;  
  
    Post(int id) {  
        this.id = id;  
        this.text = "";  
        this.comments = new ArrayList();  
    }  
  
    Post(int id, String text, ArrayList comments) {  
        this.id = id;  
        this.text = text;  
        this.comments = comments;  
    }  
  
    protected ArrayList comments;  
  
}

class Comment {  
  
    private int id;  
    private String text;  
  
    Comment(int id, String text) {  
        this.id = id;  
        this.text = text;  
    }  
      
}

这些类将定义我们的数据模型的基本类型,并将与GraphQL模式中的类型相对应。 它们将在单独的Java类文件中定义。 (明白我对重复数据定义的意思吗?)

我们还需要定义几个“解析器”。 graphql-tools使用解析器来解析非标量类型。 GraphQL架构中包含非标量类型的每种类型都需要具有关联的解析器。 因此,我们的Query类型和Post类型需要解析器,而我们的评论类型则不需要解析器。

这是后解析器的外观:

class PostResolver implements GraphQLResolver {  
  
    public List getComments(Post post) {  
        return post.comments;  
    }  
}

这是查询解析器的框架:

class Query implements GraphQLQueryResolver {  
 
    Post getPost(int id) {
	    // Do something to retrieve the post
    }  
}

下载Spring Boot示例应用程序

现在,我们进入了令人兴奋的部分! 构建一个使用GraphQL的Spring Boot应用程序,并使用Okta保护该应用程序(我们将继续讲到这,Okta使超级简单)。

现在是继续下载示例应用程序的好时机。 它基于graphql-java github页面上这个很棒的项目中的example-graphql-tools项目。

git clone https://github.com/oktadeveloper/okta-springboot-graphql-example.git

该项目实际上是两个子项目:

  • OktaShowToken :用于从Okta OAuth OIDC应用程序检索工作授权令牌
  • OktaGraphQL :基于GraphQL的 Spring Boot资源服务器(我们将在最后添加Okta Auth

创建Okta OAuth应用程序并安装HTTPie

本教程假设您已经有一个免费的Okta Developer帐户(如果没有,为什么不直接转到developer.okta.com并创建一个)。

为什么选择Okta?

在Okta,我们的目标是使身份管理比您以往更加轻松,安全和可扩展。 Okta是一项云服务,允许开发人员创建,编辑和安全地存储用户帐户和用户帐户数据,并将它们与一个或多个应用程序连接。 我们的API使您能够:

在Okta中创建新的OIDC应用

要在Okta上创建新的OIDC应用,您可以从默认设置开始,然后:

  1. 登录到您的开发人员帐户,导航到“ 应用程序” ,然后单击“ 添加应用程序”
  2. 选择“ Web” ,然后单击“ 下一步”
  3. 为应用程序命名,添加http://localhost:8080/login作为登录重定向URI,然后单击完成

我们将使用HTTPie,一个出色的HTTP命令行客户端。 因此,如果尚未安装该产品,请签出该产品,然后按照httpie.org上的安装说明进行操作

查看OktaGraphQL示例应用程序

让我们暂时忽略OktaShowToken项目,看看OktaGraphQL,它最初配置为无需身份验证即可运行。

您会注意到,除了com.okta.springbootgraphql.resolvers包中的POJO类和解析器以及src/main/resources/graphql-tools.graphqls的GraphQL模式定义之外,它并没有太多src/main/resources/graphql-tools.graphqls

在这一点上,大多数应该是不言自明的。 我只是指出,我们的Query类(查询​​解析器)是一种快速而肮脏的技巧,以避免必须设置实际的数据库数据源。 我们只是根据ID编号即时生成PostComment 。 在实际的实现中,您将在此处添加一些其他业务层逻辑,例如授权,并在数据源中查找数据。

class Query implements GraphQLQueryResolver {  
  
    Post getPost(int id) {  
  
        if (id == 1) {  
            ArrayList comments = new ArrayList() {{  
                add(new Comment(1, "GraphQL is amazing!"));  
            }};  
            return new Post(id, "Okta + GraphQL is pretty sweet.", comments);  
        }  
        else if (id == 2) {  
            ArrayList comments = new ArrayList() {{  
                add(new Comment(1, "I can't believe how easy this is."));  
            }};  
            return new Post(id, "Is GraphQL better than a REST API?", comments);  
        }  
        else {  
            return null;  
        }  
    }
}

运行您的第一个GraphQL查询

打开一个终端,从OktaGraphQL项目目录,使用./gradlew bootRun命令启动Spring Boot应用程序。

开始可能需要几秒钟。 您应该看到一些结束像这样的输出:

Tomcat started on port(s): 9000 (http) with context path ''
Started GraphQLToolsSampleApplication in 19.245 seconds (JVM running for 19.664)

使该终端窗口保持打开状态,然后打开另一个终端窗口。 再次导航到项目根目录。 使用以下命令运行我们的第一个GraphQL查询:

http POST http://localhost:9000/graphql/ < json-requests/post1-all-data.json

在这里,我们以内容类型application/json发出POST请求(因为这是HTTPie的默认HTTPie ),并且我们将在json-requests/post1-all-data.json文件中找到的查询作为请求正文发送。

来自json-requests/post1-all-data.json的请求主体:

{  
  "query": "{ post(id: '1') { id, text, comments { id, text} } }"  
}

预期结果:

HTTP/1.1 200 
Content-Length: 122
Content-Type: application/json;charset=UTF-8
Date: Fri, 03 Aug 2018 21:50:25 GMT
{
    "data": {
        "post": {
            "comments": [
                {
                    "id": "1",
                    "text": "GraphQL is amazing!"
                }
            ],
            "id": "1",
            "text": "Okta + GraphQL is pretty sweet."
        }
    }
}

此时,您可以使用JSON请求文件,请求较少的数据或请求其他帖子。

我们将继续添加身份验证。

为Okat添加Okta

此时,您需要从Okta OAuth应用程序设置中获取一些信息:

  • 客户编号
  • 客户机密
  • 您的Okta基本网址(类似这样的内容: https://dev-123456.oktapreview.com : https://dev-123456.oktapreview.com

可以在应用程序的常规设置中找到客户端ID和客户端密钥(选择“ 应用程序”菜单,选择要使用的应用程序,最后选择“ 常规”选项卡)。

在OktaGraphQL项目中,创建gradle.properties文件并填写以下属性:

oktaClientId={yourClientId}
oktaBaseUrl=https://{yourOktaDomain}

src/main/resources/application.yml文件中,添加以下属性:

okta:  
   oauth2: 
      issuer: ${oktaBaseUrl}/oauth2/default  
      clientId: ${oktaClientId}  
      scopes: 'email profile openid'

将以下依赖项添加到OktaGraphQL项目中的build.gradle文件中。 这些是Spring Boot OAuth依赖项和Okta Spring Boot启动器

compile group: 'com.okta.spring', name: 'okta-spring-boot-starter', version: '0.6.0'  
compile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: '2.3.3.RELEASE'  
compile ('org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.0.1.RELEASE')

现在,我们向GraphQLToolsSampleApplication类添加两个注释( @EnableResourceServer@EnableOAuth2Sso )。 它看起来应该像这样:

package com.okta.springbootgraphql;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;  
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;  
  
@SpringBootApplication  
@EnableResourceServer  
@EnableOAuth2Sso  
public class GraphQLToolsSampleApplication {  
  
    public static void main(String[] args) {  
        SpringApplication.run(GraphQLToolsSampleApplication.class, args);  
    }  
}

停止您的Spring Boot应用程序(如果它仍在运行),然后使用./gradlew bootRun重新启动它。

对GraphQL服务器运行查询,您将看到401错误:

HTTP/1.1 401 
Cache-Control: no-store
Content-Type: application/json;charset=UTF-8
Date: Fri, 03 Aug 2018 22:10:50 GMT
Pragma: no-cache
Transfer-Encoding: chunked
WWW-Authenticate: Bearer realm="api://default", error="unauthorized", error_description="Full authentication is required to access this resource"
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

获取访问令牌

要立即访问受保护的GraphQL服务器,我们需要Okta访问令牌。 通常,这可以通过应用程序前端的上下文进行管理。 在本教程中,我们将使用OktaShowToken应用程序从Okta应用程序检索授权令牌。

在OktaShowToken项目中,创建gradle.properties文件并填写以下属性:

oktaClientId={yourClientId}
oktaClientSecret={yourClientSecret}
oktaBaseUrl=https://{yourOktaDomain}

打开一个终端,转到OktaShowToken项目根目录,然后运行./gradlew bootRun

应用程序启动完成后,导航至http://localhost:8080

您将完成Okta登录和身份验证过程。 您应该看到Okta登录屏幕。

登录后,您将看到一个包含访问令牌的文本页面。 让此页面保持打开状态和/或将此令牌复制到以后可以使用的位置。

使用访问令牌访问GraphQL端点

首先让我们将令牌值存储在一个临时的shell变量中:

TOKEN={accessTokenValue}

然后,让我们再次设置授权标头来运行请求。 您需要从OktaGraphQL目录运行以下命令。

http POST http://localhost:9000/graphql/ Authorization:"Bearer $TOKEN" < json-requests/post1-all-data.json

您应该看到以下熟悉的结果:

HTTP/1.1 200 
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 122
Content-Type: application/json;charset=UTF-8
Date: Fri, 03 Aug 2018 22:22:00 GMT
Expires: 0
Pragma: no-cache
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
{
    "data": {
        "post": {
            "comments": [
                {
                    "id": "1",
                    "text": "GraphQL is amazing!"
                }
            ],
            "id": "1",
            "text": "Okta + GraphQL is pretty sweet."
        }
    }
}

了解有关Spring Boot,GraphQL和安全API设计的更多信息

就是这样! GraphQL显然是一门很深的主题,如果您来自传统的REST API背景,则需要对其进行一些深入的研究,以转变范例并正确实现所有内容。

GraphQL人员在他们的文档中反复指出, GraphQL不会替代您的业务逻辑层 ,而是提供一种位于您的业务逻辑层和外界之间的数据访问查询语言和类型架构。 看看他们的授权文档,以了解一下。

但是正如您所看到的,Okta的Spring Boot Starter使保护GraphQL端点非常简单。 如果您有兴趣了解有关Spring Boot或GraphQL的更多信息,请查看以下相关文章:

如果您对此帖子有任何疑问,请在下面添加评论。 有关更多精彩内容, 在Twitter上关注@oktadev在Facebook上关注我们,或订阅我们的YouTube频道

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

``使用Spring Boot和GraphQL构建安全API''最初于2018年8月16日发布在Okta开发人员博客上。

翻译自: https://www.javacodegeeks.com/2018/08/build-a-secure-api-with-spring-boot-and-graphql.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值