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

news2024/12/24 8:31:25

在本部分中,我们将把 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)和移动应用程序的推荐流程。该流程将涉及用户,因此我们的登录页面将发挥作用。

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

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

相关文章

Android CarService源码分析

文章目录 一、CarService的基本架构1.1、Android Automative整体框架1.2、Framework CarService1.3、目录结构1.3.1、CarService1.3.2、Car APP 二、CarService的启动流程2.1、系统启动后在SystemServer进程中启动CarServiceHelperService2.2、CarService启动 三、CarService源…

浅聊雷池社区版(WAF)的tengine

雷池社区版是一个开源的免费Web应用防火墙&#xff08;WAF&#xff09;&#xff0c;专为保护Web应用免受各种网络攻击而设计。基于强大的Tengine&#xff0c;雷池社区版提供了一系列先进的安全功能&#xff0c;适用于中小企业和个人用户。 Tengine的故事始于2011年&#xff0c;…

Android-三方框架的源码

ARouter Arouter的整体思路是moduelA通过中间人ARouter把路由信息的存到仓库WareHouse&#xff1b;moduleB发起路由时&#xff0c;再通过中间人ARouter从仓库WareHouse取出路由信息&#xff0c;这要就实现了没有依赖的两者之间的跳转与通信。其中涉及Activity的跳转、服务prov…

微信原生小程序上传与识别以及监听多个checkbox事件打开pdf

1.点击上传并识别 组件样式<van-field border"{{ false }}" placeholder"请输入银行卡卡号" model:value"{{bankNo}}" label"卡号"><van-icon bindtap"handleChooseImg" slot"right-icon" name"sca…

网工内推 | 运维工程师,最高10K*15薪,思科认证优先

01 乐歌股份 招聘岗位&#xff1a;服务器运维工程师 职责描述&#xff1a; 1、负责公司云上云下所有服务器的日常运维工作&#xff0c;包括应用部署、巡检、备份、日志、监控&#xff0c;故障处理&#xff0c;性能优化等&#xff0c;保障公司相关系统稳定运行。 2、为开发、测…

【linux】粘滞位.yum

粘滞位 1.为什么我们普通用户可以删掉别人的文件&#xff08;包括root&#xff09;?合理吗&#xff1f; 2.删除一个文件和目标文件有关系吗&#xff1f; 没关系&#xff0c;和所处的目录有关系。 1.我们先以root身份创建一个目录&#xff0c;接着在这个目录下创建一个文件 2…

LLM之幻觉(二):大语言模型LLM幻觉缓减技术综述

LLM幻觉缓减技术分为两大主流&#xff0c;梯度方法和非梯度方法。梯度方法是指对基本LLM进行微调&#xff1b;而非梯度方法主要是在推理时使用Prompt工程技术。LLM幻觉缓减技术&#xff0c;如下图所示&#xff1a; LLM幻觉缓减技术值得注意的是&#xff1a; 检索增强生成&…

【开发篇】五、文章内容审核接口的内存问题优化

文章目录 1、初始实现思路&#xff1a;Async注解新开一个线程去审核2、改进思路一&#xff1a;加LinkedBlockingQueue阻塞队列3、改进思路二&#xff1a;RabbitMQ4、总结 背景&#xff1a;文章微服务中有一个文章审核接口&#xff0c;接口内又调用阿里云的内容安全接口进行文字…

【运维】WSL1如何升级到WSL2

升级WSL1到WSL2&#xff1a;简便快捷版 在这篇博客中&#xff0c;我们将研究如何通过一种更简便的方式&#xff0c;将WSL1迅速升级到WSL2&#xff0c;避免官方文档的繁冗步骤。如果你觉得官方方法太过冗长&#xff0c;那么这里提供的步骤可能更适合你。 官网的办法是&#xf…

Cloudflare cdn 基本使用

个人版免费试用&#xff0c;一个邮箱账号只能缓存一个网站cdn。 地址&#xff1a;cloudflare.com 创建站点 在网站创建站点&#xff0c;填上你的域名 点击进入网站 缓存全局配置 可清除缓存&#xff0c;设置浏览器缓存时间 我设置了always online,防止服务器经常不稳定 缓…

Git学习笔记(第1章):Git概述

Git是一个免费的、开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种项目。 Git易于学习&#xff0c;占地面积小&#xff0c;性能极快。它具有廉价的本地库&#xff0c;方便的暂存区域和多个工作流分支等特性。其性能优于Subversion、CVS、Perforce 和…

Docker本地私有仓库搭建配置指导

一、说明 因内网主机需要拉取镜像进行Docker应用&#xff0c;因此需要一台带外主机作为内网私有仓库来提供内外其他docker业务主机使用。参考架构如下&#xff1a; 相关资源&#xff1a;加密、Distribution registry、Create and Configure Docker Registry、Registry部署、D…

Ivanti Connect Secure 曝两大零日漏洞,已被大规模利用

威胁情报公司Volexity发现&#xff0c;影响 Ivanti 的 Connect Secure VPN 和 Policy Secure 网络访问控制 (NAC) 设备的两个零日漏洞正在被大规模利用。自1月11日开始&#xff0c;多个威胁组织在大范围攻击中利用CVE-2023-46805身份验证绕过和CVE-2024-21887命令注入漏洞。 V…

Joern环境的安装(Windows版)

Joern环境的安装(Windows版) 网上很少有关于Windows下安装Joern的教程&#xff0c;而我最初使用也是装在Ubuntu虚拟机中&#xff0c;这样使用很占内存&#xff0c;影响体验感。在Windows下使用源码安装Joern也是非常简单的过程&#xff1a; 提前需要的本地环境&#xff1a; …

YOLOv5全网独家首发:DCNv4更快收敛、更高速度、更高性能,效果秒杀DCNv3、DCNv2等 ,助力检测实现暴力涨点

💡💡💡本文独家改进:DCNv4更快收敛、更高速度、更高性能,完美和YOLOv5结合,助力涨点 DCNv4优势:(1) 去除空间聚合中的softmax归一化,以增强其动态性和表达能力;(2) 优化存储器访问以最小化冗余操作以加速。这些改进显著加快了收敛速度,并大幅提高了处理速度,DCN…

vue:处理base64格式文件pdf、图片预览

一、需求&#xff1a;后端返回是base64数据&#xff0c;需要前端处理来展示文件。 二、实现方法&#xff1a; 解释一下这段代码的功能&#xff1a; &#xff09;preview(item) 是一个函数&#xff0c;接受一个参数 item&#xff0c;其中包含了文件的相关信息。 &#xff09;首…

SpringBoot的自定义starter和SpringBoot Starter机制,以及综合案例和通用模块-短信发送,基于AOP技术实现日志切面

目录 1.SpringBoot Starter机制 1.1.什么是SpringBoot Starter 1.2.为什么要使用SpringBoot Starter 1.3.应用场景 1.4.自动加载核心注解说明 2.综合案例 2.1.命名规范 2.2.通用模块-短信发送 2.2.1.创建配置类Properties 2.2.2.编写短信业务功能 2.2.3.创建自动配置…

基于Python+django影片数据爬取与数据分析设计与实现

目录 一、 前言介绍&#xff1a; 二 、功能设计&#xff1a; 三、功能实现&#xff1a; 系统登录实现 管理员实现 用户模块实现 四、库表设计&#xff1a; 五、关键代码&#xff1a; 六、论文参考&#xff1a; 七、其他案例&#xff1a; 八、源码获取&#xff1a; 一…

各省快递量数据, shp+excel,2001-2021年,已实现数据可视化

基本信息. 数据名称: 各省快递量数据 数据格式: shpexcel 数据时间&#xff1a;2001-2021年 数据几何类型: 面 数据坐标系: WGS84 数据来源&#xff1a;网络公开数据 数据字段&#xff1a; 序号字段名称字段说明1a_2001快递量/万件_2001年2a_2002快递量/万件_2002年3…

FairyGUI Day 1 导入FairyGUI

FairyGUI Unity3d引擎版本&#xff1a;Uinty3d 20233.2.3f1 1、从资产商店中将FairyGUI购入我的资产中&#xff0c;目前是免费的。 2、从我的资产中将FairyGUI导入到当前项目中。 3、我遇到的问题&#xff0c;我的Assets下有两个文件夹分别是Resources和Scenes&#xff0c;导…