通过OpenIddict设计一个授权服务器03-客户凭证流程

在本部分中,我们将把 OpenIddict 添加到项目中,并实施第一个授权流程:客户端凭证流。

添加 OpenIddict 软件包

首先,我们需要安装 OpenIddict NuGet 软件包

dotnet add package OpenIddict
dotnet add package OpenIddict.AspNetCore
dotnet add package OpenIddict.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.InMemory

在这里插入图片描述
除了主库,我们还安装了 OpenIddict.AspNetCore 软件包,该软件包可将 OpenIddict 集成到 ASPNET Core 主机中。
OpenIddict.EntityFrameworkCore 包支持 Entity Framework Core。现在我们将使用内存实现,为此我们使用了 Microsoft.EntityFrameworkCore.InMemory. 包。

设置 OpenIddict

我们将首先介绍启动和运行 OpenIddict 所需的最低条件。必须启用至少一个 OAuth 2.0/OpenID Connect 流程。我们选择启用客户端凭证流,它适用于机器到机器应用程序。在本系列的下一部分,我们将使用 PKCE 实现授权代码流,这是单页应用程序 (SPA) 和本地/移动应用程序的推荐流程。
开始对 Startup.cs 进行以下更改:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
       .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
       {
           options.LoginPath = "/account/login";
       });
builder.Services.AddDbContext<DbContext>(options =>
{
    // 使用内存存储
    options.UseInMemoryDatabase(nameof(DbContext));

    // 注册OpenIddict所需的实体集。
    options.UseOpenIddict();
});
builder.Services.AddOpenIddict()
        // 注册 OpenIddict 核心组件
        .AddCore(options =>
        {
            // 配置 OpenIddict 以使用 EF Core 存储器/模型
            options.UseEntityFrameworkCore()
                .UseDbContext<DbContext>();
        })
        // 注册 OpenIddict 服务器组件
        .AddServer(options =>
        {
            options
                .AllowClientCredentialsFlow();

            options
                .SetTokenEndpointUris("/connect/token");

            //令牌的加密和签名
            options
                .AddEphemeralEncryptionKey()
                .AddEphemeralSigningKey();

            //注册范围(权限)
            options.RegisterScopes("api");

            //注册 ASP.NET Core 主机并配置 ASP.NET Core 特定选项 
            options
                .UseAspNetCore()
                .EnableTokenEndpointPassthrough();
        });

var app = builder.Build();

app.UseStaticFiles();

app.UseRouting();
app.UseAuthentication();
app.MapDefaultControllerRoute();
app.Run();

首先,在 ConfigureServices 方法中注册 DbContext。OpenIddict 原生支持 Entity Framework Core、Entity Framework 6 和 MongoDB,你也可以提供自己的存储。
在本例中,我们将使用 Entity Framework Core,并使用内存数据库。options.UseOpenIdDict 调用会注册 OpenIddict 所需的实体集。

接下来是注册 OpenIddict 本身。AddOpenIddict() 调用会注册 OpenIddict 服务,并返回一个 OpenIddictBuilder 类,我们可以用它来配置 OpenIddict。

首先注册的是核心组件。OpenIddict 被指示使用 Entity Framework Core,并使用前面提到的 DbContext。
接下来,注册服务器组件并启用客户端凭证流。为使该流程正常运行,我们需要注册一个令牌端点。我们需要自己实现这个端点。我们稍后再做这项工作。

要使 OpenIddict 能够加密和签名令牌,我们需要注册两个密钥,一个用于加密,一个用于签名。在本例中,我们将使用短暂密钥。短暂密钥会在应用程序关闭时自动丢弃,因此使用这些密钥签名或加密的有效负载会自动失效。这种方法只能在开发过程中使用。在生产过程中,建议使用 X.509 证书。

RegisterScopes 定义了支持哪些作用域(权限)。在本例中,我们只有一个名为 api 的作用域,但授权服务器可以支持多个作用域
UseAspNetCore() 调用用于将 AspNetCore 设置为 OpenIddict 的主机。我们还调用了 EnableTokenEndpointPassthrough,否则会阻止对未来令牌端点的请求。
要检查 OpenIddict 是否配置正确,我们可以启动应用程序并导航到:https://localhost:5001/.well-known/openid-configuration,得到的回应应该是这样的:

{
  "issuer": "https://localhost:5001/",
  "token_endpoint": "https://localhost:5001/connect/token",
  "jwks_uri": "https://localhost:5001/.well-known/jwks",
  "grant_types_supported": [
    "client_credentials"
  ],
  "scopes_supported": [
    "openid",
    "api"
  ],
  "claims_supported": [
    "aud",
    "exp",
    "iat",
    "iss",
    "sub"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "subject_types_supported": [
    "public"
  ],
  "token_endpoint_auth_methods_supported": [
    "client_secret_post",
    "private_key_jwt",
    "client_secret_basic"
  ],
  "claims_parameter_supported": false,
  "request_parameter_supported": false,
  "request_uri_parameter_supported": false,
  "authorization_response_iss_parameter_supported": true
}

在这里插入图片描述
在本指南中,我们将使用 Postman 测试授权服务器,但也可以使用其他工具。
下面是一个使用 Postman 的授权请求示例。授权类型是客户凭据流。我们指定了访问令牌 url、客户端 ID 和秘密,以验证客户端身份。我们还请求访问 api 范围。
直接请求https://localhost:5001/connect/token
在这里插入图片描述

如果我们请求令牌,操作将失败:client_id 无效。这是有道理的,因为我们还没有在授权服务器上注册任何客户端。
发送post请求
在这里插入图片描述
用代码实现

using Flurl.Http;

namespace AuthClient
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var dic = new Dictionary<string, string>();
            dic.Add("grant_type", "client_credentials");
            dic.Add("client_id","postman");
            dic.Add("client_secret", "postman-secret");
            dic.Add("scope", "api");
            string url = "https://localhost:5001/connect/token";
            var response = url.PostUrlEncodedAsync(dic).Result;
            Console.WriteLine(response.ResponseMessage.Content.ReadAsStringAsync().Result);
            Console.WriteLine("完成");
        }
    }
}

我们可以通过将客户端添加到数据库来创建客户端。为此,我们创建了一个名为 TestData 的类。测试数据实现了 IHostedService 接口,这使我们能够在应用程序启动时在 Startup.cs 中执行生成测试数据的操作。

using Microsoft.EntityFrameworkCore;
using OpenIddict.Abstractions;

namespace AuthorizationServer
{
    public class TestData : IHostedService
    {
        private readonly IServiceProvider _serviceProvider;

        public TestData(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            using var scope = _serviceProvider.CreateScope();

            var context = scope.ServiceProvider.GetRequiredService<DbContext>();
            await context.Database.EnsureCreatedAsync(cancellationToken);

            var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();

            if (await manager.FindByClientIdAsync("postman", cancellationToken) is null)
            {
                await manager.CreateAsync(new OpenIddictApplicationDescriptor
                {
                    ClientId = "postman",
                    ClientSecret = "postman-secret",
                    DisplayName = "Postman",
                    Permissions =
                    {
                        OpenIddictConstants.Permissions.Endpoints.Token,
                        OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,
                        OpenIddictConstants.Permissions.Prefixes.Scope + "api"
                    }
                }, cancellationToken);
            }
        }

        public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    }
}

客户端在测试数据中注册。客户端 ID 和秘密用于客户端与授权服务器之间的身份验证。权限决定了客户端的选项。
在这种情况下,我们允许客户端使用客户端凭据流,访问令牌端点,并允许客户端请求 api 范围。
在 Startup.cs 中注册测试数据服务,以便在应用程序启动时执行:

builder.Services.AddHostedService<TestData>();

在这里插入图片描述
如果我们再次尝试用 Postman 获取访问令牌,请求仍然会失败。这是因为我们还没有创建令牌端点。我们现在就创建。
在这里插入图片描述

创建一个名为 AuthorizationController 的新控制器,我们将在此托管端点:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using System.Security.Claims;

namespace AuthorizationServer.Controllers
{
    
    public class AuthorizationController : Controller
    {
        [HttpPost("~/connect/token")]
        public IActionResult Exchange()
        {
            var request = HttpContext.GetOpenIddictServerRequest() ??
                          throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");

            ClaimsPrincipal claimsPrincipal;

            if (request.IsClientCredentialsGrantType())
            {
                // 注意:OpenIddict 会自动验证客户端凭证:
                // 如果 client_id 或 client_secret 无效,则不会调用此操作。
                var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

                // Subject (sub)是必填字段,我们在此使用客户 ID 作为主题标识符。
                identity.AddClaim(OpenIddictConstants.Claims.Subject, request.ClientId ?? throw new InvalidOperationException());

                // 添加一些要求,别忘了添加目的地,否则它不会被添加到访问令牌中。
                identity.AddClaim("some-claim", "some-value", OpenIddictConstants.Destinations.AccessToken);
                //上面这句话不起作用,下面的可以
                identity.AddClaim(new Claim("some-claim2", "some-value2").SetDestinations(OpenIddictConstants.Destinations.AccessToken));
                claimsPrincipal = new ClaimsPrincipal(identity);

                claimsPrincipal.SetScopes(request.GetScopes());
            }
            else
            {
                throw new InvalidOperationException("The specified grant type is not supported.");
            }

            // 返回 SignInResult 结果时,OpenIddict 将向用户发放相应的访问/身份令牌。
            return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }
    }
}

其中一个操作是 “Exchange”。所有流程(不仅是客户证书流程)都使用该操作来获取访问令牌。

在客户凭据流中,令牌是根据客户凭据签发的。而在授权码流中,使用的是同一个端点,但随后会用授权码来交换令牌。我们将在第四部分看到这一点。

目前,我们需要重点关注客户端凭证流程。当请求进入 Exchange 操作时,客户端凭证(ClientId 和 ClientSecret)已经通过 OpenIddict 验证。因此,我们不需要对请求进行验证,只需创建一个声称委托人并使用该委托人登录即可。

声明主体与账户控制器中使用的声明不同,后者基于 Cookie 身份验证处理程序,仅在授权服务器本身的上下文中使用,以确定用户是否已通过身份验证。

我们必须创建基于 OpenIddictServerAspNetCoreDefaults.AuthenticationScheme 的请求声明。这样,当我们在该方法末尾调用 SignIn 时,OpenIddict 中间件就会处理登录并返回一个访问令牌作为对客户端的响应。

只有当我们指定目的地时,才会在访问令牌中加入权利要求声明中定义的权利要求。示例中的 "some-value "请求将被添加到访问令牌中。
主题(Subject)要求是必填项,您无需指定目的地,因为它将包含在访问令牌中。

我们还通过调用 claimsPrincipal.SetScopes(request.GetScopes()); 授权所有请求的作用域。OpenIddict 已经检查了所请求的作用域是否被允许(一般情况下和针对当前客户端)。我们之所以要在此处手动添加作用域,是因为我们可以根据需要过滤此处授予的作用域。

一枚令牌定乾坤

让我们再次尝试用 Postman 获取访问令牌,这次应该能成功。
在这里插入图片描述
从 OpenIddict v3 开始,访问令牌默认采用 Jason Web Token(JWT)格式。这使我们能够使用 jwt.io 检查令牌(感谢 Auth0 提供的服务!)。
一个问题是,令牌不仅要签名,还要加密。OpenIddict 默认会对访问令牌进行加密。我们可以在 Startup.cs 中配置 OpenIddict 时禁用这种加密。
在这里插入图片描述
现在,当我们重启授权服务器并请求一个新的令牌时。将令牌粘贴到 jwt.io 并查看令牌内容:
在这里插入图片描述

可以看到,客户 ID postman 被设置为Subject(sub)。此外,访问令牌中还添加了 some-claim claim。

接下来干什么

恭喜您,您已经使用 OpenIddict 实现了客户端凭证流!
您可能已经注意到,客户端凭证流未使用登录页面。该流程会立即将客户端凭据交换为令牌,适用于机器对机器应用程序。
接下来,我们将使用 PKCE 实现授权代码流,这是单页应用程序(SPA)和移动应用程序的推荐流程。该流程将涉及用户,因此我们的登录页面将发挥作用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot mybatis-plus swing实现报警监听

通过声音控制报警器&#xff0c;实现声光报警&#xff0c;使用beautyeye_lnf.jar美化界面如下 EnableTransactionManagement(proxyTargetClass true) SpringBootApplication EnableScheduling public class AlarmWarnApplication {public static void main(String[] args) …

大数据毕业设计:基于python美食推荐系统+爬虫+Echarts可视化+协同过滤推荐算法+Django框架(源码)✅

毕业设计&#xff1a;2023-2024年计算机专业毕业设计选题汇总&#xff08;建议收藏&#xff09; 毕业设计&#xff1a;2023-2024年最新最全计算机专业毕设选题推荐汇总 &#x1f345;感兴趣的可以先收藏起来&#xff0c;点赞、关注不迷路&#xff0c;大家在毕设选题&#xff…

Net Core Ocelot+Consul实现网关、服务注册、服务发现

什么是Ocelot? Ocelot是一个开源的ASP.NET Core微服务网关&#xff0c;它提供了API网关所需的所有功能&#xff0c;如路由、认证、限流、监控等。 Ocelot是一个简单、灵活且功能强大的API网关&#xff0c;它可以与现有的服务集成&#xff0c;并帮助您保护、监控和扩展您的微…

【每周AI简讯】GPT-5将有指数级提升,GPT Store正式上线

AI7 - Chat中文版最强人工智能 OpenAI的CEO奥特曼表示GPT-5将有指数级提升 GPT奥特曼参加Y-Combinator W24启动会上表示&#xff0c;我们已经非常接近AGI。GPT-5将具有更好的推理能力、更高的准确性和视频支持。 GPT Store正式上线 OpenAI正式推出GPT store&#xff0c;目前…

Android车载系统Car模块架构链路分析

一、模块主要成员 CarServiceHelperService SystemServer 中专门为 AAOS 设立的系统服务&#xff0c;用来管理车机的核心服务 CarService。该系统服务的具体实现在 CarServiceHelperServiceUpdatableImpl CarService Car模块核心服务APP&#xff0c;Android 13版本开始分为…

Java-NIO 开篇(1)

NIO简介 高性能的Java通信&#xff0c;离不开Java NIO组件&#xff0c;现在主流的技术框架或中间件服务器&#xff0c;都使用了Java NIO组件&#xff0c;譬如Tomcat、 Jetty、 Netty、Redis、RabbitMQ等的网络通信模块。在1.4版本之前&#xff0c; Java IO类库是阻塞式IO&…

0间隔24h采集线报+源码的资源网

一款网站程序零间隔24h采集线报源码的资源网&#xff0c;更新下载类目的采集 及 导入&#xff0c;这款网站程序&#xff1a;jizhiCMS 高仿新版某刀资源网模板进行自动采集。 安装方法&#xff1a; 将根目录文件上传服务器 将根目录文件的sql.sql导入mysql数据库 环境需要支…

springmvc上传与下载

文件上传 结构图 导入依赖 <dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>org.springframework</groupId><artifactId…

Golang 搭建 WebSocket 应用(二) - 基本群聊 demo

上一篇文章中&#xff0c;我们已经了解了 gorilla/websocket 的一些基本概念和简单的用法。 接下来&#xff0c;我们通过一个再复杂一点的例子来了解它的实际用法。 功能 这个例子来自源码里面的 examples/chat&#xff0c;它包含了以下功能&#xff1a; 用户访问群聊页面的…

基于JavaSocket重写Dubbo网络传输层

前言 我们知道&#xff0c;位于 Serialize 层上面的是负责网络传输的 Transport 层&#xff0c;它负责调用编解码器 Codec2 把要传输的对象编码后传输、再对接收到的字节序列解码。 站在客户端的角度&#xff0c;一次 RPC 调用的流程大概是这样的&#xff1a; Invoker 发起 …

JMeter请求参数Parameters,带中文或特殊字符(+/=)时,例如登录密码或者token等,需要勾选编码

以前的登录接口密码参数不包含特殊字符&#xff0c;为了安全&#xff0c;产品今天修改了需求&#xff0c;密码必须由数字&#xff0c;字母和特殊字符构成&#xff0c;之前利用JMeter接口编写的脚本报错了&#xff0c;调整了一下&#xff0c;里面踩了一点坑&#xff0c;记录下来…

AM5-DB低压备自投装置在河北冠益荣信科技公司洞庭变电站工程中的应用

摘 要&#xff1a;随着电力需求的不断增加&#xff0c;电力系统供电可靠性要求越来越高&#xff0c;许多供电系统已具备两回或多回供电线路。备用电源自动投入装置可以有效提高供电的可靠性&#xff0c;该类装置能够在工作电源因故障断开后&#xff0c;自动且迅速地将备用电源投…

SpringMVC JSON数据处理见解6

6.JSON数据处理 6.1.添加json依赖 springmvc 默认使用jackson作为json类库,不需要修改applicationContext-servlet.xml任何配置&#xff0c;只需引入以下类库springmvc就可以处理json数据&#xff1a; <!--spring-json依赖--> <dependency><groupId>com.f…

react umi/max 封装页签组件

1. models/tabs // 全局共享数据示例 import { useState } from react;const useUser () > {const [items, setItems] useState<any[]>([]); // 页签的全局Item数据const [key, setKey] useState<string>(/home); // 页签的高亮Keyreturn {items,setItems…

Alinx ZYNQ 7020 LED调试--in RAM

设置拨码开关为JTAG方式 烧写LED bit stream a. 点击“Program device”烧录程序到FPGA中&#xff08;重新上电程序就丢失了&#xff09; b. /01_led/led.runs/impl_1/led.bit 程序烧录到Flash中 ZYNQ与以往的直接烧录Flash不同&#xff0c;首先必须PS&#xff0c;然后烧…

C语言总结十二:文件操作详细总结

在操作系统中&#xff0c;为了统一对各种硬件的操作&#xff0c;简化接口&#xff0c;不同的硬件设备也都被看成一个文件。对这些文件的操作&#xff0c;等同于对磁盘上普通文件的操作。我们不去探讨硬件设备是如何被映射成文件的&#xff0c;把任意 I/O 设备&#xff0c;转换成…

边缘计算AI智能分析网关V4客流统计算法的概述

客流量统计AI算法是一种基于人工智能技术的数据分析方法&#xff0c;通过机器学习、深度学习等算法&#xff0c;实现对客流量的实时监测和统计。该算法主要基于机器学习和计算机视觉技术&#xff0c;其基本流程包括图像采集、图像预处理、目标检测、目标跟踪和客流量统计等步骤…

HTML快速上手

前腰&#xff1a;本文只是概括重要的 html 标签&#xff0c;这些标签的使用频率较高&#xff0c;更多标签相关的资源您可以跳转 Mmdn 进行深入的学习。 1.HTML 基础 就其核心而言&#xff0c;HTML 是一种相当简单的、由不同 元素 组成的标记语言&#xff0c;它可以被应用于文本…

一款基于Frida的Android- SO动态库逆向命令行工具

前言 YJ是一款基于Frida框架的款Native层逆向分析的交互式工具&#xff0c;就像在GUN-LINUX上使用GDB工具一样&#xff0c;设计YJ的灵感来自GNU-GDB调试工具&#xff0c;它通过交互命令模式轻松地向展示你想要窥探的内存数据 Frida是一个底层hook工具及框架。提供了hook工具的…

防火墙如何处理nat(私网用户访问Internet场景)

目录 私网用户访问Internet场景源NAT的两种转换方式NAT No-PAT NAPT配置思路规划 NAPT配置命令配置接口IP地址并将接口加入相应安全区域配置安全策略配置NAT地址池配置源NAT策略配置缺省路由配置黑洞路由 私网用户访问Internet场景 多个用户共享少量公网地址访问Internet的时候…