OAuth2.0 动态注册客户端

什么是 OAuth 2.0 客户端自动注册?

OAuth 2.0 客户端注册通常是在授权服务器的管理界面或通过静态配置文件手动完成的。客户端自动注册是指应用在启动或运行过程中通过代码与 OAuth 2.0 授权服务器交互,自动注册并获取 client_idclient_secret 等必要的认证信息。这个过程不仅简化了客户端的管理,还允许系统在不同上下文中动态创建和配置客户端。

客户端自动注册的典型场景

客户端自动注册适用于以下场景:

  1. 多租户应用:每个租户都有独立的客户端配置,系统需要为每个租户动态创建 OAuth 客户端。
  2. 微服务架构:微服务可能在部署过程中需要注册自己的客户端,以便与授权服务器通信。
  3. 动态环境:系统需要在运行时根据需求注册和配置不同的客户端,而不是提前硬编码配置。

动态客户端注册的流程

OAuth 2.0 动态客户端注册流程使得客户端能够通过API动态注册自身,而无需手动操作。这一过程涉及多个步骤,确保客户端能够与授权服务器有效通信并获得必要的凭证信息。以下是动态客户端注册的详细流程说明:

1. 客户端发送注册请求

客户端想要动态注册时,会向授权服务器发送一个HTTP POST请求。该请求包含关于客户端的基本信息,通常为JSON格式。请求的内容根据具体应用的需求可能有所不同,但主要包括以下信息:

  • client_name:客户端的名称,通常是易于识别的应用名。
  • redirect_uris:回调URL列表,授权服务器在授权码授权流程中会使用该URL。
  • grant_types:客户端支持的授权类型,例如authorization_codeclient_credentials等。
  • response_types:客户端期望的响应类型,如code(授权码)。
  • scope:客户端请求的权限范围。
  • token_endpoint_auth_method:客户端将如何在令牌端点进行身份验证,常见的方式有client_secret_basicclient_secret_post等。
  • token_endpoint_auth_signing_alg:客户端在令牌端点进行身份验证时使用的签名算法。
  • jwks_uri:公钥信息URL,当客户端身份验证方式为private_key_jwt,客户端需要提供公钥信息URL。
示例请求:
{"client_name": "SampleApp","redirect_uris": ["https://client.example.com/callback"],"grant_types": ["authorization_code"],"response_types": ["code"],"scope": "openid profile email","token_endpoint_auth_method": "client_secret_basic","token_endpoint_auth_signing_alg": "RS256"
}

2. 授权服务器验证请求

授权服务器在接收到客户端的注册请求后,会对请求内容进行验证:

  • 校验字段格式:检查提交的数据是否符合预期的格式和标准。比如,回调URL是否是有效的URL格式,授权类型是否在支持的范围内。
  • 策略验证:授权服务器可以根据其内部策略决定是否允许注册特定类型的客户端。例如,某些服务器可能只允许预先批准的客户端进行注册。

3. 授权服务器生成客户端凭证

如果注册请求通过验证,授权服务器会为客户端生成必要的凭证信息,包括:

  • client_id:唯一标识客户端的ID,通常是由授权服务器随机生成的字符串。
  • client_secret:客户端密钥,在需要认证的授权类型中使用(例如客户端凭证授权类型)。同样是随机生成并安全存储。
  • 其他属性如client_id_issued_at(客户端ID生成时间)、client_secret_expires_at(客户端密钥的过期时间,如果有)等。

生成这些信息后,授权服务器会将客户端的注册信息存储在其内部数据库中。

4. 授权服务器返回注册响应

在生成客户端凭证后,授权服务器会将客户端信息作为HTTP响应返回给客户端,通常也是以JSON格式提供。返回的响应将包含以下信息:

  • client_id:客户端的唯一ID。
  • client_secret:客户端密钥(如果存在)。
  • client_secret_expires_at:客户端密钥的过期时间(如果适用)。
  • client_id_issued_at:客户端ID的生成时间。
  • 其他可选的注册信息(如Logo URI、政策URL等)。
示例响应:
{"settings.client.require-authorization-consent": false,"grant_types": ["authorization_code"],"registration_client_uri": "http://127.0.0.1:8080/connect/register?client_id=ECnm9EdfP44eGV2KmF2J_C5ERw_5rY4_LR-vSKZDJuM","redirect_uris": ["http://120.0.0.1:8070/callback"],"client_id": "ECnm9EdfP44eGV2KmF2J_C5ERw_5rY4_LR-vSKZDJuM","token_endpoint_auth_method": "client_secret_basic","scope": "LOGIN","client_id_issued_at": 1729435078,"client_secret": "sc6FzJ-azjcZ_W6cKMS_jb1tAEiZpVD__y9tQhgj6Zq8wySXM6GWX7QG-H8w0YZX","client_name": "client_1","settings.client.require-proof-key": false,"response_types": ["code"],"id_token_signed_response_alg": "RS256","registration_access_token": "eyJraWQiOiI2ODBmNTVlOC1lMmZhLTQ0Y2UtYTZlYi1mMGNmNGZjYmNiOTAiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJFQ25tOUVkZlA0NGVHVjJLbUYySl9DNUVSd181clk0X0xSLXZTS1pESnVNIiwiYXVkIjoiRUNubTlFZGZQNDRlR1YyS21GMkpfQzVFUndfNXJZNF9MUi12U0taREp1TSIsIm5iZiI6MTcyOTQzNTA3OCwic2NvcGUiOlsiY2xpZW50LnJlYWQiXSwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwIiwiZXhwIjoxNzI5NDM1Mzc4LCJpYXQiOjE3Mjk0MzUwNzgsImp0aSI6ImYxNGE4NTY2LTgxM2UtNGFiZS04Njc3LTc0ZDE2ZjBjMmI1YSJ9.R92KEFzL9is7-wuBTXygT-7l6DEmtv6VArKv1jpmpLQwU4nULB878FqecMti_dEeUVTQ5GXvtBey49Fcld8vaqAjLPTkXp7M7J0UQ6auWSrjoDXlfyHVf5KODExiKmbcxqnrLCphCw2TBok848gcbpJIhTRJknsc6SqU7rbzp68_WY2y3L7PujPjq8B9kD2L9rvlHFw3qCi1Pd2eQ7GlL3e_dCDD7CEBXKSeMhOfv_BGrGTG_Iikd_8vDty6nGxGCyTP1e0JRdzwdIj8JmvK1HgIV0w4zq6bU3ipSmM3UJk2qUqcfH_z9KZ5yj9kIFpKjZNMgT_MxllKzlvJ-vnxtw","client_secret_expires_at": 0
}

5. 客户端使用注册信息

客户端在获得client_idclient_secret后,可以使用这些信息向授权服务器发起OAuth 2.0相关请求(例如获取访问令牌)。每个后续请求都会携带这些凭证信息,授权服务器会根据客户端的身份验证方式验证请求。

  • 在授权码流程中,客户端会使用client_idclient_secret来换取访问令牌。
  • 在客户端凭证流程中,客户端直接用这些信息与授权服务器交换访问令牌。

使用 Spring Authorization Server 实现动态客户端注册

为了在项目中实现动态客户端注册功能,可以使用Spring Authorization Server实现授权服务来支持OAuth 2.0的动态客户端注册。

项目依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>3.1.5</version>
</dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-authorization-server</artifactId><version>1.2.1</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId><version>3.1.5</version>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.1.5</version>
</dependency>

配置授权服务器

配置Spring Authorization Server 授权服务器,开启客户端动态注册。

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);// 开启客户端注册端点http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(oidc -> oidc.clientRegistrationEndpoint(clientRegistrationEndpoint -> {clientRegistrationEndpoint.authenticationProviders(configureCustomClientMetadataConverters());}));// 资源服务配置http.oauth2ResourceServer(oauth2ResourceServer ->oauth2ResourceServer.jwt(Customizer.withDefaults()));// 认证失败跳转到登录页面return http.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))).build();
}

配置注册客户端

注册客户端用于向授权服务器注册新客户端。客户端必须配置范围client.create,并可选地client.read分别用于注册客户端和检索客户端。注册客户端用于在动态注册过程中获取初始访问令牌

@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {RegisteredClient registrarClient = RegisteredClient.withId("1").clientId("registrar-client").clientSecret("{noop}relive27-client").clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).scope("client.create").scope("client.read").build();JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);jdbcRegisteredClientRepository.save(registrarClient);return jdbcRegisteredClientRepository;
}

自定义客户端元数据

为了支持在注册客户端时可以增加一些参数,便于我们做一些额外的业务操作,可以自定义默认值AuthenticationProvider以支持自定义客户端元数据参数。以下示例显示了支持自定义客户端元数据参数require-authorization-consentrequire-proof-key的实现。

public static Consumer<List<AuthenticationProvider>> configureCustomClientMetadataConverters() {List<String> customClientMetadata = Arrays.asList("require-authorization-consent", "require-proof-key");return (authenticationProviders) -> {CustomRegisteredClientConverter registeredClientConverter =new CustomRegisteredClientConverter(customClientMetadata);CustomClientRegistrationConverter clientRegistrationConverter =new CustomClientRegistrationConverter(customClientMetadata);authenticationProviders.forEach((authenticationProvider) -> {if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider) {OidcClientRegistrationAuthenticationProvider provider = (OidcClientRegistrationAuthenticationProvider) authenticationProvider;provider.setRegisteredClientConverter(registeredClientConverter);provider.setClientRegistrationConverter(clientRegistrationConverter);}if (authenticationProvider instanceof OidcClientConfigurationAuthenticationProvider) {OidcClientConfigurationAuthenticationProvider provider = (OidcClientConfigurationAuthenticationProvider) authenticationProvider;provider.setClientRegistrationConverter(clientRegistrationConverter);}});};
}...

使用 Spring Security OAuth2 Client 创建客户端服务

本节主要用于Spring Security OAuth2 Client 让客户端通过注册端点向授权服务器注册自身,获取client_idclient_secret等凭证。

首先参考之前文章《Spring Security 持久化OAuth2客户端》创建一个OAuth2客户端服务,该文章主要介绍将客户端配置持久化到数据库。

其次我们创建一个Spring 启动事件,用于在服务启动成功后执行下列步骤:

  • 构建注册请求:客户端创建一个HTTP POST请求,包含注册所需的客户端信息,如redirect_urisgrant_typestoken_endpoint_auth_method等。

  • 发送注册请求:使用HTTP客户端库,将请求发送到授权服务器的注册端点。

  • 解析注册响应:授权服务器成功注册后会返回包含client_idclient_secret的JSON响应。客户端解析响应并将这些凭证信息存储到数据库中,以便后续使用。

客户端注册事件

自定义OAuth2ClientRegistrationEvent实现ApplicationListener<ApplicationReadyEvent>监听器。ApplicationReadyEvent事件表明服务已准备好。

@Component
@RequiredArgsConstructor
public class OAuth2ClientRegistrationEvent implements ApplicationListener<ApplicationReadyEvent> {private final JdbcClientRegistrationRepository registrationRepository;private final OAuth2ClientProperties properties;@SneakyThrows@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {URI clientsEndpoint = new URI("http://localhost:8080/connect/register");// We want to register a client for the code grantOIDCClientMetadata clientMetadata = new OIDCClientMetadata();clientMetadata.setGrantTypes(Collections.singleton(GrantType.AUTHORIZATION_CODE));clientMetadata.setRedirectionURI(URI.create("http://127.0.0.1:8070/oauth2/callback/messaging-client-authorization-code"));clientMetadata.setName("Test Client");clientMetadata.setScope(new Scope("message.read"));clientMetadata.setCustomField("require-authorization-consent", false);clientMetadata.setCustomField("require-proof-key", false);OIDCClientRegistrationRequest regRequest = new OIDCClientRegistrationRequest(clientsEndpoint,clientMetadata,this.getToken());HTTPResponse httpResponse = regRequest.toHTTPRequest().send();ClientRegistrationResponse regResponse = OIDCClientRegistrationResponseParser.parse(httpResponse);if (!regResponse.indicatesSuccess()) {ClientRegistrationErrorResponse errorResponse = (ClientRegistrationErrorResponse) regResponse;throw new IllegalStateException(errorResponse.getErrorObject().toString());}OIDCClientInformationResponse successResponse = (OIDCClientInformationResponse) regResponse;this.registrationRepository.save(new OAuth2ClientRegistrationMapper(successResponse).asClientRegistration());}private BearerAccessToken getToken() throws URISyntaxException, IOException, ParseException {AuthorizationGrant clientGrant = new ClientCredentialsGrant();OAuth2ClientProperties.Registration registration = properties.getRegistration().get("client-registration");ClientID clientID = new ClientID(registration.getClientId());Secret clientSecret = new Secret(registration.getClientSecret());ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret);Scope scope = new Scope(registration.getScope().toArray(new String[0]));OAuth2ClientProperties.Provider provider = properties.getProvider().get("client-registartion-provider");URI tokenEndpoint = new URI(provider.getTokenUri());TokenRequest request = new TokenRequest(tokenEndpoint, clientAuth, clientGrant, scope);TokenResponse response = TokenResponse.parse(request.toHTTPRequest().send());if (!response.indicatesSuccess()) {TokenErrorResponse errorResponse = response.toErrorResponse();throw new IllegalStateException(errorResponse.getErrorObject().toString());}AccessTokenResponse successResponse = response.toSuccessResponse();AccessToken accessToken = successResponse.getTokens().getAccessToken();return new BearerAccessToken(accessToken.getValue());}
}

最后让我们将授权服务和客户端服务都启动完成,浏览器访问http://127.0.0.1:8070/client/test展示效果。

结论

OAuth 2.0 动态客户端注册流程通过API简化了客户端的注册过程,极大地提升了自动化和可扩展性。动态注册特别适合在微服务环境中使用,允许服务在创建或启动时自动注册到授权服务器。

与往常一样,本文中使用的源代码可在 GitHub 上获得。

一起学习的小伙伴可以关注下我的公众号,我会经常更新技术实践类文章,所有文章都会有相应源码供大家学习交流!
在这里插入图片描述

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

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

相关文章

启纬科技发布6色无源电子纸手机壳InkaceE6

杭州启纬科技有限公司投稿:无源NFC技术的开创者和领导者,杭州启纬科技有限公司于北京时间2024年10月28号正式发布了面向iOS系统和安卓/鸿蒙系统的6色无源电子纸手机壳---InkaceE6系列产品及配套方案。 图1:手机壳高清图 图2:6色与3色、4色效果对比图,海边美女 启纬…

鸿蒙生态下开发挑战-鸿蒙低代码开发工具展望及优势

鸿蒙生态下开发挑战 在鸿蒙生态下开发时&#xff0c;开发者可能会遇到多方面的挑战&#xff0c;这些挑战主要涉及开发工具、技术难度、生态竞争以及市场定位等方面。以下是对这些挑战的详细分析&#xff1a; 一、开发工具不完善 尽管鸿蒙系统的开发工具DevEco Studio在逐步完…

CSS 超出一行省略号...,适用于纯数字、中英文

文本超出显示省略号... 代码&#xff1a; .ellipsis{ overflow: hidden; -webkit-line-clamp:1; text-overflow: ellipsis; display: -webkit-box; -webkit-box-orient: vertical; word-break: break-all; /** 纯数字、中英文都适用 */ }

vscode markdown-image 图片粘贴自动上传到本地目录设置

.vscode/settings.json文件内容 {"markdown-image.base.fileNameFormat": "${hash}-${YY}${MM}${DD}-${HH}${mm}${ss}","markdown-image.local.path": "./images","markdown-image.base.uploadMethod": "Local",…

java设计模式之结构型模式(7种)

结构型模式 描述如何将类或者对象按某种布局组成更大的结构。它分为结构型模式和对象结构型模式&#xff0c;前者采用继承机制来组织接口和类&#xff0c;后者通过组合或聚合来组合对象。 分为7种&#xff1a;代理模式、适配器模式、装饰者模式、桥接模式、外观模式、组合模式、…

Java序列化与反序列化

文章目录 一、Java序列化和反序列化1、序列化和反序列化的含义和用途序列化主要使用场景反序列化漏洞出现的原因 下一期 一、Java序列化和反序列化 1、序列化和反序列化的含义和用途 Java对象&#xff08;存在于内存&#xff09;———序列化——>>字符串/二进制流&…

Vue computed watch

computed watch watch current prev

基于Spring Boot+Vue的助农销售平台(协同过滤算法、限流算法、支付宝沙盒支付、实时聊天、图形化分析)

&#x1f388;系统亮点&#xff1a;协同过滤算法、节流算法、支付宝沙盒支付、图形化分析、实时聊天&#xff1b; 一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk1…

GetX的一些高级API

目录 前言 一、一些常用的API 二、局部状态组件 1.可选的全局设置和手动配置 2.局部状态组件 1.ValueBuilder 1.特点 2.基本用法 2.ObxValue 1.特点 2.基本用法 前言 这篇文章主要讲解GetX的一些高级API和一些有用的小组件。 一、一些常用的API GetX提供了一些高级…

第三届北京国际水利科技博览会将于25年3月在国家会议中心召开

由中国农业节水和农村供水技术协会、北京水利学会、振威国际会展集团等单位联合主办的第三届北京国际水利科技博览会暨供水技术与设备展&#xff08;北京水利展&#xff09;将于2025年3月31日至4月2日在北京•国家会议中心举办&#xff01; 博览会以“新制造、新服务、新业态”…

基于SpringBoot的学生读书笔记共享的设计与实现

一、项目背景 计算机的普及和互联网时代的到来使信息的发布和传播更加方便快捷。用户可以通过计算机上的浏览器访问多个应用系统&#xff0c;从中获取一些可以满足用户需求的管理系统。网站系统有时更像是一个大型“展示平台”&#xff0c;用户可以选择所需的信息进入系统查看…

org.springframework.boot:type=Admin,name=SpringApplication异常

org.springframework.boot:typeAdmin,nameSpringApplication异常 问题&#xff1a;更换最新版本idea之后&#xff0c;启动springboot项目报错 javax.management.InstanceNotFoundException: org.springframework.boot:typeAdmin,nameSpringApplication idea自动默认的启动设…

Netty核心源码与优化

1.Netty的优化 1.1 使用EventLoop的任务调度 直接使用 channel.writeAndFlush(data) 可能会导致线程切换&#xff0c;这是因为如果当前线程并不是该 Channel 所绑定的 EventLoop 线程&#xff0c;那么 writeAndFlush() 操作会将任务重新提交给关联的 EventLoop 线程执行&#…

CTF之web题集详情随手笔记

《Web安全》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484238&idx1&snca66551c31e37b8d726f151265fc9211&chksmc0e47a12f793f3049fefde6e9ebe9ec4e2c7626b8594511bd314783719c216bd9929962a71e6&scene21#wechat_redirect 1 WEB 1 靶场目…

使用Nginx作为反向代理和负载均衡器

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Nginx作为反向代理和负载均衡器 引言 Nginx 简介 安装 Nginx Ubuntu CentOS 配置 Nginx 作为反向代理 配置 Nginx 作为负载…

【PTA】图的邻接矩阵存储和遍历

图的邻接矩阵存储用一个一维数组存储各顶点数据元素&#xff0c;一个二维数组存储顶点之间的邻接关系。 如上面的无向加权图&#xff0c;顶点数据元素为“A-Z”之间的单个字符&#xff0c;为了使遍历输出结果唯一&#xff0c;要求顶点数据元素按由小到大(ASCII码)的顺序存储。…

数据结构之树

1.树的基本概念 1.树的定义 树是由n(n>0)个结点&#xff08;或元素&#xff09;组成的有限集合&#xff08;记为T&#xff09;。 如果n0,它是一棵空树&#xff0c;这是树的特例。 如果n>0&#xff0c;这个结点中有且仅有一个结点作为树的根结点&#xff0c;简称为根。…

国内PLC市场份额报告,西门子老大的地位从未动摇

【导读】国内PLC市场占有率&#xff0c;西门子依然是老大。 PLC市场集中度很高&#xff0c;从销售额来看&#xff0c;TOP3厂家占据一半以上的市场份额&#xff0c;以外资品牌为主&#xff0c;其中西门子排名第一&#xff0c;2022年市场份额约47.1%&#xff1b;三菱排名第二&…

计算机毕业设计 | 基于SpringBoot的健身房管理系统(附源码)

1&#xff0c;项目背景 随着人们生活水平的提高和健康意识的增强&#xff0c;健身行业逐渐兴起并迅速发展。而现代化的健身房管理系统已经成为健身房发展的必备工具之一。传统的健身房管理方式已经无法满足现代化健身房的需求&#xff0c;需要一种更加高效、智能、安全的管理系…

ADI常规SHARC音频处理器性能对比

1、 ADSP-2156x:是基于SHARC+ DSP架构的单核32位/40位/64位浮点处理器,不仅具有灵活的音频连接性和性能可扩展性,还提供多个引脚兼容版本(400MHz至1GHz)和多种片内存储器选项,数据手册链接:https://www.analog.com/media/en/technical-documentation/data-sheets/adsp-2…