微服务openid等_使用OpenID Connect在Quarkus中保护微服务

微服务openid等

This is the fourth part in a series on building a microservice from the ground up with Quarkus, Kotlin and Debezium. The service is for sending SMS messages.

Ť他是建设从地面微服务了Quarkus,Kotlin和Debezium一系列的第四部分。 该服务用于发送SMS消息。

In Part One we built the basic framework and added persistence. In the second part we used CDC to generate events from the persisted SMS Messages. We built a message handler to process the messages that were sent to the Kafka topic. In part three we added a couple of SMS providers and a router that sent the messages to the third party provider based on a simple random algorithm. The tests used wiremock to stub out the responses from the providers.

第一部分中,我们构建了基本框架并添加了持久性。 在第二部分中,我们使用CDC从持久的SMS消息中生成事件。 我们构建了一个消息处理程序来处理发送给Kafka主题的消息。 在第三部分中,我们添加了一些SMS提供商和一个路由器,该路由器基于简单的随机算法将消息发送给第三方提供商。 该测试使用Wiremock筛选出提供商的响应。

The current state of the service leaves the endpoints wide open to any caller. We need to add security and only allow authorized users or clients to access the service. It is increasingly common to delegate authorization to a third party such as Okta, Google, Auth0 or use an off-the-shelf solution such as Keycloak or Gluu. We will use OpenID Connect to get an access token and ensure that only tokens minted from a provider that we whitelist are accepted. For testing we will use Keycloak and for running the real service we will use Okta.

服务的当前状态使端点对任何调用者都敞开。 我们需要增加安全性,仅允许授权用户或客户端访问该服务。 将授权委派给第三方(例如Okta,Google,Auth0)或使用现成的解决方案(例如Keycloak或Gluu)越来越普遍。 我们将使用OpenID Connect获取访问令牌,并确保仅接受从我们列入白名单的提供商中铸造的令牌。 为了进行测试,我们将使用Keycloak,为了运行真实的服务,我们将使用Okta。

OpenID Connect(OIDC) (OpenID Connect (OIDC))

OpenID is an identity layer built on top of OAuth 2.0. It allows users to authenticate with a provider and obtain an access token in the form of a JWT. The protocol allows clients to obtain information about a user through an ID Token and/or a userinfo endpoint. JWTs contain standard claims that are embedded in the access token such as sub and iss that identify who issued the token and who the bearer of the token is.

O penID是建立在OAuth 2.0之上的身份层。 它允许用户向提供者进行身份验证,并以JWT的形式获取访问令牌。 该协议允许客户端通过ID令牌和/或userinfo端点获取有关用户的信息。 JWT包含嵌入在访问令牌中的标准声明 ,例如subiss ,用于标识谁发行了令牌以及令牌的持有者。

Compliant OIDC providers must supply a configuration discovery endpoint that allows applications to discover all the necessary endpoints and public key location information.

兼容的OIDC提供程序必须提供一个配置发现端点,该端点允许应用程序发现所有必需的端点和公共密钥位置信息。

Here is a snippet showing some of the properties that can be expected in the response:

这是一个片段,显示了响应中可以预期的一些属性:

"issuer": "https://server.example.com",
"authorization_endpoint":
"https://server.example.com/connect/authorize",
"token_endpoint":
"https://server.example.com/connect/token",
"token_endpoint_auth_methods_supported":
["client_secret_basic", "private_key_jwt"],
"token_endpoint_auth_signing_alg_values_supported":
["RS256", "ES256"],
"userinfo_endpoint":
"https://server.example.com/connect/userinfo",
"check_session_iframe":
"https://server.example.com/connect/check_session",
"end_session_endpoint":
"https://server.example.com/connect/end_session",
"jwks_uri":
"https://server.example.com/jwks.json",
"registration_endpoint":
"https://server.example.com/connect/register"

签署和验证JWT (Signing and Verifying JWTs)

The OIDC provider is responsible for signing the JWT using an asymmetric signing algorithm. The private key is held by the provider and the public key is made available to clients so they can verify the signature of the JWT.

吨他OIDC提供商负责使用非对称签名算法签署JWT。 私有密钥由提供者持有,公共密钥可供客户端使用,以便他们可以验证JWT的签名。

The header part of a JWT contains the information needed to find the public key to verify it. i.e.

JWT的标头部分包含查找公钥进行验证所需的信息。 即

{
"kid": "T_B6HlPCcu8jZjbWVZDi8s2l6euVEFTewiZba_zSd4E",
"typ": "JWT",
"alg": "RS256"
}

Public Keys are made available by using JSON Web Key Set (JWKS) which is an IETF standard. Keys are generally cached on the client side to minimise latency and are rotated on a scheduled basis on the server side.

使用IETF 标准 JSON Web密钥集(JWKS)可以使用公共密钥。 密钥通常在客户端缓存,以最大程度地减少延迟,并在服务器端按计划对密钥进行轮换。

Here is a typical response from the jwks endpoint showing the algorithm used to sign the JWT as well as the modulus and exponent of the public key.

这是来自jwks端点的典型响应,显示了用于签名JWT的算法以及公共密钥的模数和指数。

{
"keys"[
{
"kty":"RSA",
"alg":"RS256",
"kid":"T_B6HlPCcu8jZjbWVZDi8s2l6euVEFTewiZba_zSd4E",
"use":"sig",
"e":"AQAB",
"n":"osU_UWAPB27w4vqfy7c_iRqB3JpFUMnK3w34fU0hoRVeWnMYzk-lwEbOPeghndyaEpKGJZZ3Md7qmQ7gFmKaAcNxtbtyC0Bq4sKkPJlLc-QbZ46AfVv36zzUC_dWVLTUXEVGF8fAISG660WKzQqLgx0UQHtnR3ht4F0rgXOYZ5n4K8dHZt3q7kR-2V_j0ornNjiID9F_GF1XmeMSES4uRHlTYtTTKeV69y8c7-F8SKz97pnJdeZDsYjDM1jsG-UEeVk54GSfOuVe2hMkluIWnpoIzlH8-AheqVf6GVdL6E-gyjfErqG2oedojo1zN_9RTgs6vaMf0Sut7j5UjqExxQ"
}
]
}

When our microservice receives a request containing a JWT first check the cached keys to find a match for the kid in the header. If there is no match then it will need to ask the JWKS endpoint for the latest key set. Only when it finds a key that matches the kid can it then attempt to verify the signature of the JWT. If a JWT arrives that we cannot verify then access will be denied.

当我们的微服务收到包含JWT的请求时,首先检查缓存的密钥以在标头中找到孩子的匹配项。 如果没有匹配项,则需要向JWKS端点询问最新的密钥集。 只有找到与孩子匹配的钥匙,它才能尝试验证JWT的签名。 如果JWT到达我们无法验证,则访问将被拒绝。

在Quarkus中增加安全性 (Adding security in Quarkus)

To follow along with the code you can pull the branch

要遵循代码,您可以拉分支

git clone git@github.com:iainporter/sms-service.git
git checkout part_four

First we need to add the OIDC extension to the POM.xml

首先,我们需要将OIDC扩展名添加到POM.xml中

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>

Now that the dependency is added we can add the security annotation to each of the endpoints we wish to protect. We should now expect any tests for those endpoints to fail with 401 Unauthorised.

现在添加了依赖关系,我们可以将安全注释添加到我们希望保护的每个端点。 现在,我们应该期望这些端点的任何测试都将失败,并显示“ 401未经授权”。

Note that for this implementation all we care about is that the invoker has a valid JWT. We are not checking for roles or permissions. That is a topic for a later article. However, it would be nice to capture who invoked the service for audit purposes so let’s inject the JWT (line 2) and extract the sub claim from the JWT and persist that with the message (line 7).

注意,对于此实现,我们只关心调用者具有有效的JWT。 我们不检查角色或权限。 这是以后文章的主题。 但是,很高兴捕获谁出于审计目的调用了该服务,因此让我们注入JWT(第2行)并从JWT中提取子声明,并将其与消息一起保留(第7行)。

单元测试JWT验证 (Unit testing JWT verification)

In order to get the tests to pass we will have to generate a JWT that the application can verify. We need to do the following:

为了使测试通过,我们将必须生成一个JWT,应用程序可以对其进行验证。 我们需要执行以下操作:

  • generate an RSA key pair

    生成RSA密钥对
  • generate a JWT using the private key of the key pair

    使用密钥对的私钥生成JWT
  • stub the OIDC well known endpoint to return a URI that points to the wiremock instance

    对OIDC众所周知的端点进行存根以返回指向Wiremock实例的URI
  • stub the JWKS endpoint in our wiremock instance to return the public key of the key pair

    在我们的Wiremock实例中对JWKS端点进行存根以返回密钥对的公钥
  • include the JWT as a bearer token in the header of the requests

    将JWT作为承载令牌包含在请求的标头中

To help with these tasks we can use the Nimbus JOSE library

为了帮助完成这些任务,我们可以使用Nimbus JOSE库

<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>8.20</version>
<scope>test</scope>
</dependency>

Taking advantage of the QuarkusTestResourceLifecycleManager class in Quarkus we can set up the wiremock service and add the requisite stubs before Quarkus completes startup.

利用Quarkus中的QuarkusTestResourceLifecycleManager类,我们可以在Quarkus完成启动之前设置Wiremock服务并添加必需的存根。

Test subclasses can use the private key (line 9)to generate a JWT using the method below

测试子类可以使用私钥(第9行)使用以下方法生成JWT

The OIDC configuration stub is complete but the only part our service needs for the tests is the jwks_uri to get the public keys

OIDC配置存根已经完成,但是我们服务测试所需的唯一部分是jwks_uri以获取公钥

The mocked jwks_uri needs to return the public key part of the key pair we generated above.

模拟的jwks_uri需要返回我们上面生成的密钥对的公共密钥部分。

The final piece we need is to generate a token using the private key from the key pair to use in the tests.

我们需要的最后一步是使用密钥对中的私钥生成令牌,以在测试中使用。

Now we can test that that passing a valid token results in a successful post, not passing a token should result in in a 401 and passing a token from an unknown provider should result in a 403.

现在,我们可以测试传递有效令牌导致发布成功,不传递令牌应导致401,而从未知提供者传递令牌应导致403。

使用Keycloak进行组件测试 (Component Testing with Keycloak)

In the previous articles we wrote component tests with the help of testcontainers to ensure all the parts (microservice, database, kafka, wiremock) worked together. We can create a container for Keycloak and seed the instance with a realm that is set up for the test.

在之前的文章中,我们在测试容器的帮助下编写了组件测试,以确保所有部分(微服务,数据库,kafka,wiremock)协同工作。 我们可以为Keycloak创建一个容器,并为该实例设置一个为测试设置的领域。

Typically an sms microservice would not be directly invoked by a user. It would be used by other microservices as part of a workflow that involved sending an SMS message. For the purposes of this microservice we can use the client credentials grant to obtain an access token. That means that we need to set up a realm in keycloak and add a client that can use the client credentials grant to login. Once that is done we can export the realm and reference it as part of the docker container setup to seed the instance for the tests.

通常,sms微服务不会被用户直接调用。 其他微服务将使用它作为涉及发送SMS消息的工作流的一部分。 就此微服务而言,我们可以使用客户端凭据授予来获取访问令牌。 这意味着我们需要在密钥斗篷中设置一个领域,并添加一个可以使用客户端凭据授予进行登录的客户端。 完成后,我们可以导出领域并将其作为docker容器设置的一部分进行引用,以为测试植入实例。

Before the tests are run we can use the configured client details from the realm to get an access token that we can use in the tests

在运行测试之前,我们可以使用领域中配置的客户端详细信息来获取可以在测试中使用的访问令牌

使用Okta作为OIDC提供程序来运行服务 (Running the Service with Okta as the OIDC provider)

You can use any OIDC provider but we will run through how to use Okta as the provider.

您可以使用任何OIDC提供程序,但我们将逐步介绍如何使用Okta作为提供程序。

If you have not already done so, you can sign up for a free developer account at https://developer.okta.com/signup/

如果您尚未这样做,可以在https://developer.okta.com/signup/上注册一个免费的开发人员帐户。

Once you have signed up add an Authorization server at

注册后,请在以下位置添加授权服务器

API -> Authorization Servers -> Add Authorization Server

API->授权服务器->添加授权服务器

with these properties

具有这些特性

Name: SMS Authorization ServerAudience: https://api.porterhead.com/sms

名称:SMS授权服务器受众: https ://api.porterhead.com/sms

Add Scope: sms (set as default)

添加范围:短信(默认设置)

Create an application and set up a client for logging in with client credentials

创建一个应用程序并设置一个客户端以使用客户端凭据登录

Add the server url and client-id properties to the config/application.properties file

将服务器url和client-id属性添加到config / application.properties文件

quarkus.oidc.auth-server-url=<Your Okta server url>
quarkus.oidc.client-id=<your client-id>

Ensure the rest of the properties are set up for talking to SMS Providers. See the README.

确保设置了其余属性以与SMS提供商进行对话。 请参阅自述文件

Build the service from the root directory

从根目录构建服务

mvn install

Use docker-compose to start everything up

使用docker-compose启动一切

cd sms-service
docker-compose up -d

Install the sms-connector for kafa-connect

安装用于kafa-connect的sms-connector


curl 'localhost:8083/connectors/' -i -X POST \
-H "Accept:application/json" -H "Content-Type:application/json" \
-d '{"name": "sms-connector", "config": {"connector.class": "io.debezium.connector.postgresql.PostgresConnector", "database.hostname": "postgres-db", "database.port": "5432", "database.user": "postgres", "database.password": "postgres", "database.dbname" : "sms", "database.server.name": "smsdb1", "table.whitelist": "public.outboxevent", "transforms" : "outbox","transforms.outbox.type" : "io.debezium.transforms.outbox.EventRouter", "transforms.OutboxEventRouter.event.key": "aggregate_id", "transforms.outbox.table.fields.additional.placement": "type:header:eventType"}}'

Login in to your OIDC provider that you configured above and obtain an access token.

登录到您在上面配置的OIDC提供程序,并获取访问令牌。

curl -i --request POST \
--url <your okta server url> \
--header 'accept: application/json' \
--header 'authorization: Basic <your client-id:secret>' \
--header 'cache-control: no-cache' \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'grant_type=client_credentials&scope=sms'

Send a message

发送消息

curl 'http://localhost:8080/v1/sms' -i -X POST  \
-H 'Content-Type: application/json' \
-H 'authorization: Bearer <your access token>'
-d '{"text":"Foo Bar!", "fromNumber": "+1234567890", "toNumber": "+<your mobile number>"}'

And expect a response such as this

并期待这样的回应

HTTP/1.1 202 Accepted
Content-Length: 0
Location: http://localhost:8080/v1/sms/e307458a-a0a8-4f13-9635-f9b27b4da0e5

We can use the location header to get the message details

我们可以使用位置标题获取消息详细信息

curl 'http://localhost:8080/v1/sms/e307458a-a0a8-4f13-9635-f9b27b4da0e5' -i -X GET  \
-H 'Content-Type: application/json' \
-H 'authorization: Bearer <your access token>'

If the message was successfully delivered the result should look similar to this

如果邮件成功发送,结果应类似于以下内容

HTTP/1.1 200 OK
Content-Length: 194
Content-Type: application/json
{
"createdAt":"2020-07-16T16:43:59.43561Z",
"fromNumber":"+1234567890",
"id":"b3a20fac-2d00-49d2-b3ef-b3a3e5ac02ca",
"status":"DELIVERED",
"text":"Foo Bar!",
"provider": "Twilio",
"principal": "backend-service",
"toNumber":"+1234567891",
"updatedAt":"2020-07-16T16:44:00.432926Z"
}

The service is now complete. We have a published API using OpenAPI. Messages are persisted in a Postgres database. A Debezium connector is tailing the Postgres WAL log and sending the change events to Kafka. A consumer is listening for the events and routing them to an SMS provider to send the message to a user’s mobile device. The service is protected using OpenId Connect. We have unit tests and component tests in place to ensure everything connects up correctly and can respond appropriately to failure. The final article in this series will deploy the service as a native executable in GraalVM.

服务现已完成。 我们已经使用OpenAPI发布了API。 邮件将保留在Postgres数据库中。 Debezium连接器在Postgres WAL日志中尾随,并将更改事件发送到Kafka。 消费者正在监听事件,并将事件路由到SMS提供程序,以将消息发送到用户的移动设备。 该服务受OpenId Connect保护。 我们已经进行了单元测试和组件测试,以确保所有内容正确连接并可以对故障做出适当的响应。 本系列的最后一篇文章将服务作为GraalVM中的本机可执行文件进行部署。

The source code for this article can be found here

本文的源代码可以在这里找到

翻译自: https://levelup.gitconnected.com/securing-a-microservice-in-quarkus-with-openid-connect-505204d1c9a9

微服务openid等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值