spring-authorization-server 公共客户端方式获取授权码和Token的流程

news2025/1/18 14:01:53

spring-authorization-serve【版本1.2.1】官方文档中提及了关于RegisteredClient中所涉及的客户端身份验证方法,也就是RegisteredClient中提及的clientAuthenticationMethods属性对应的“none”值,目前clientAuthenticationMethods属性支持的值包含:client_secret_basic, client_secret_post, private_key_jwt, client_secret_jwt, and none (public clients)。
大家可参考官方文档来了解PKCE相关的标准规范定义。这里我推荐大家看一下附录B,如何通过code_verifier获取code_challenge。

PKCE Protocol Flow

通过code_verifier获取code_challenge

这里直接引用官方文档附录B示例如下:

客户端使用合适的随机数生成器创建出来32个八位字节序列。本例中表示值的八位字节(使用JSON数组表示法)为:

[116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121]

将此八位字节序列进行base64url编码作为code_verifier的值:dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

然后通过SHA256哈希函数对code_verifier进行哈希,生成:

[19, 211, 30, 150, 26, 26, 216, 236, 47, 22, 177, 12, 76, 152, 46, 8, 118, 168, 120, 173, 109, 241, 68, 86, 110, 225, 137, 74, 203, 112, 249, 195]

将该八位字节序列base64url编码作为code_challenge的值:E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM

最后在授权请求的url中追加如下参数code_challengecode_challenge_method

code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256

code_verifier和code_challenge的具体用法

下面在是PKCE官网提供的Abstract Protocol Flow如下:
在这里插入图片描述
A、客户端在请求授权服务的Authorization Endpoint (授权端点)获取Authorization Code(授权码)时,需要提交的参数需要包含“t(code_verifier)”,和“t_m”,“t(code_verifier)”也就是上面说的code_challenge,“t_m”就是code_challenge_method。这里有个操作是授权服务会把code_challenge和code_challenge_method存储起来。
B、授权服务返回Authorization Code(授权码)给客户端
C、客户端在请求授权服务的Token Endpoint获取token时需要提交参数code_verifier,授权服务会根据此次提交的code_verifier和A步存储的code_challenge_method生成code_challenge来验证是否和A步存储的code_challenge一致。
D、code_challenge一致则进行下面的流程直至成功返回Access Token。

在spring-authorization-server的运用

spring-authorization-server针对公共客户端也是依据PKCE的标准进行处理的。

创建应用程序

在获取授权码前首先需要创建一个SpringBoot应用程序可参考官网的Getting Started,然后稍作修改,如下:

application.yml

server:
  port: 6001

logging:
  level:
    org.springframework.security: trace

spring:
  security:
    oauth2:
      authorizationserver:
        client:
          public-client:
            registration:
              client-id: "my-app"
              client-authentication-methods:
                - "none"
              authorization-grant-types:
                - "authorization_code"
                - "refresh_token"
              redirect-uris:
                - "http://127.0.0.1:6001/oauth2/code"
              post-logout-redirect-uris:
                - "http://127.0.0.1:6001/"
              scopes:
                - "user_info"
                - "openid"
                - "profile"
                - "client.create"
            require-authorization-consent: true
            require-proof-key: true

SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
            throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0
        http
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                )
                // Accept access tokens for User Info and/or Client Registration
                .oauth2ResourceServer((resourceServer) -> resourceServer
                        .jwt(Customizer.withDefaults()));

        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
            throws Exception {
        http
                .authorizeHttpRequests((authorize) -> authorize
                        .anyRequest().authenticated()
                )
                // Form login handles the redirect to the login page from the
                // authorization server filter chain
                .formLogin(Customizer.withDefaults());

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER", "user_info", "openid", "profile", "client.create")
                .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        }
        catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }
}

maven pom.xml的dependencies:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        <version>3.2.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
        <version>3.2.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <version>3.2.2</version>
        <optional>true</optional>
    </dependency>
</dependencies>

一切配置完,然后启动程序。

授权码获取

访问授权码获取地址:

http://127.0.0.1:6001/oauth2/authorize?response_type=code&client_id=my-app&scope=user_info%20openid%20client.create&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&redirect_uri=http://127.0.0.1:600/oauth2/code

想使用公共客户端认证方式,code_challenge和code_challenge_method是必不可少的参数。

1、在浏览器上输入上面的地址,然后请求会自动跳转到登录页面,输入用户名(user)和密码(password)进行登录。
在这里插入图片描述
2、登录成功后,会自动跳转到授权同意页面,选中对应的权限进行提交。
在这里插入图片描述
3、提交成功后,跳转到对应redirect_uri的地址(对应第1步url中的redirect_uri参数),授权码对应链接中的code值
在这里插入图片描述

http://127.0.0.1:600/oauth2/code?code=UZtsq9o-_6jRdT1C8xrV62dkynoFQ1aBSZtWzhFkoRAzPD-SpQ8UDppbY3VquKUQjFd5niMqzROMnnZQ_IdBpyEGUJ6qkvVuMYJ81M3zqwJodx6f5OerkgcQw4989Vvq

Token的获取

postman请求,使用POST方式,访问获取token地址:http://127.0.0.1:6001/oauth2/token
在这里插入图片描述
上面的参数都是必传的,code的值是授权码。
返回token结果:
在这里插入图片描述
在这里插入图片描述
到这里公共客户端授权码和token的获取到此结束。

如何验证code_verifier的有效性

在请求获取token的时候CodeVerifierAuthenticator.java中的codeVerifierValid方法来验证提交的code_verifier的有效性,如下:

/**
 * codeVerifier 请求获取token接口提交的code_verifier参数
 * codeChallenge 授权服务存储的code_challenge
 * codeChallengeMethod 授权服务存储的code_challenge_method=S256
 **/
private boolean codeVerifierValid(String codeVerifier, String codeChallenge, String codeChallengeMethod) {
	if (!StringUtils.hasText(codeVerifier)) {
		return false;
	} else if ("S256".equals(codeChallengeMethod)) {
		try {
			MessageDigest md = MessageDigest.getInstance("SHA-256");
			byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
			String encodedVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
			return encodedVerifier.equals(codeChallenge);
		} catch (NoSuchAlgorithmException ex) {
			// It is unlikely that SHA-256 is not available on the server. If it is not available,
			// there will likely be bigger issues as well. We default to SERVER_ERROR.
			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.SERVER_ERROR);
		}
	}
	return false;
}

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

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

相关文章

Maven提示Failure to find com.oracle:ojdbc14:jar:10.2.0.4.0

目录 问题 解决方案 1、下载oracle的驱动jar包 2、安装到本地仓库 3、检查本地仓库是否成功安装 4、Maven先clean &#xff0c;再install。 问题 项目引入Oracle依赖后报错&#xff0c;显示为红色。 解决方案 1、下载oracle的驱动jar包 首先我们要去下载一个oracle的…

c语言--assert断言(详解)

目录 一、断言的概念二、assert断言2.1 代码12.1.1运行结果2.1.2分析 2.2代码22.2.1运行结果2.2.2分析2.3代码32.3.1运行结果及其分析 三、优点四、缺点五、注意 一、断言的概念 assert.h 头⽂件定义了宏 assert() &#xff0c;用于在运行时确保程序符合指定条件&#xff0c;如…

16- OpenCV:轮廓的发现和轮廓绘制、凸包

目录 一、轮廓发现 1、轮廓发现(find contour in your image) 的含义 2、相关的API 以及代码演示 二、凸包 1、凸包&#xff08;Convex Hull&#xff09;的含义 2、Graham扫描算法- 概念介绍 3、cv::convexHull 以及代码演示 三、轮廓周围绘制矩形和圆形框 一、轮廓发现…

ele-h5项目使用vue3+vite+vant4开发:第一节、页面头部实现

实现页面 确认需求 顶部提示栏搜索框搜索提示 normalize.css:处理不同浏览器的默认样式 安装 npm i normalize.css 使用 src\App.vue<style scoped> import normalize.css;#app {/** 让字体抗锯齿&#xff0c;看起来更清晰 */-webkit-font-smoothing: antialiased;-moz-o…

Elasticsearch(ES) 简述请求操作索引下文档 增删查改操作

上文 Elasticsearch(ES) 创建带有分词器规则的索引 带着大家创建了一个带有分词功能的索引 老规矩 我们启动一下ES服务 本文 我们就来说说 关于文档的操作 我们先来添加一个文档 就像数据库加一条数据一样 这里 并不需要指定什么表结构和数据结构 它的文档结构是无模式的 添…

Python新春烟花盛宴

写在前面 哈喽小伙伴们&#xff0c;博主在这里提前祝大家新春快乐呀&#xff01;我用Python绽放了一场新春烟花盛宴&#xff0c;一起来看看吧&#xff01; 环境需求 python3.11.4及以上PyCharm Community Edition 2023.2.5pyinstaller6.2.0&#xff08;可选&#xff0c;这个库…

通过 editplus 批量转换文本编码

有时候需要对文本的编码进行批量转换&#xff0c;文本编辑器 notepad 中的“编码”菜单可以用来转换单个的文档编码&#xff0c;当文档数量多的时候&#xff0c;一个个操作比较繁琐&#xff0c;通过文本编辑器 editplus 软件&#xff0c;可以方便快速地批量修改文本文件的编码&…

微信网页授权之使用完整服务解决方案

目录 微信网页授权能力调整造成的问题 能力调整的内容和理由 原有运行方案 is_snapshotuser字段 改造原有方案 如何复现测试场景 小结 微信网页授权能力调整造成的问题 依附于第三方的开发&#xff0c;做为开发者经常会遇到第三方进行规范和开发的调整&#xff0c;如开…

基于SpringBoot+Vue的防汛应急物资管理系统

本防汛物资管理系统的主要分为管理员角色和用户角色&#xff0c;主要设计的功能包括注册登录管理、密码信息管理、用户信息管理、物资信息管理等模块。 注册登录管理&#xff1a;使用本系统需要打开浏览器&#xff0c;输入相应的网址&#xff0c;如果用户是首次使用本系统&…

下载、安装Jenkins

进入官网 下载Jenkins https://www.jenkins.io 直接点击Download 一般是下长期支持版 因为它是java写的&#xff0c;你要运行它&#xff08;Jenkins.war&#xff09;肯定要有java环境 有两种方式去运行它&#xff0c;一种是下载Tomcat&#xff08;是很经典的java容器或者jav…

Xcode 15 及以上版本:libarclite 库缺少问题

参考链接&#xff1a;Xcode 15 libarclite 缺失问题_sdk does not contain libarclite at the path /ap-CSDN博客 报错: SDK does not contain libarclite at the path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarcl…

MC插件服教程-paper+游戏云VPS

首先必须要先买一台VPS&#xff0c;这里以i9的机型做演示 购买完成等待大约1分钟服务器就会创建完成&#xff0c;之后在管理页可以看到服务器的连接信息 image772356 43 KB 首先复制下远程连接地址&#xff0c;此处即k.rainplay.cn:13192 之后在系统里搜索“rdp”或“远程桌面…

QT研究笔记(一)windows 开发环境安装部署

一、Qt 是什么&#xff1f; Qt 是一个跨平台的应用程序开发框架&#xff0c;最初由挪威的 Trolltech 公司开发&#xff0c;并于2008年被诺基亚收购。后来&#xff0c;Qt 框架由 Digia 公司接手&#xff0c;并在2012年成立了 The Qt Company。Qt 提供了一套丰富的工具和类库&am…

为什么越来越多的企业在考虑将ERP从云端迁移到本地?

越来越多的企业在考虑将核心ERP迁移到本地部署&#xff0c;原来实施的时候局限于业务规模、实施成本的原因采用云端部署的方式越来越不再适应于企业规模的发展、系统应用和数据安全的要求。 因此他们都宁愿将云端ERP的数据迁移到本地&#xff0c;使得系统数据和安全更加可控。…

接口测试框架对比

公司计划系统的开展接口自动化测试&#xff0c;需要我这边调研一下主流的接口测试框架给后端测试&#xff08;主要测试接口&#xff09;的同事介绍一下每个框架的特定和使用方式。后端同事根据他们接口的特点提出一下需求&#xff0c;看哪个框架更适合我们。 需求 1、接口编写…

python中[[]] * (n)和[[] for _ in range(n)]的区别

1、现象 刷leetcode207的时候碰到一个坑&#xff0c;用[[]] * (n)初始化二维数组&#xff0c;逻辑是正确的&#xff0c;但是结果始终不对。 2、原因 最后定位是初始化语句使用错误导致的&#xff0c;我使用的是[[]] * (n)&#xff0c;应该使用[[] for _ in range(n)] 3、解…

MagicVideo-V2:多阶段高保真视频生成框架

本项工作介绍了MagicVideo-V2&#xff0c;将文本到图像模型、视频运动生成器、参考图像embedding模块和帧内插模块集成到端到端的视频生成流程中。由于这些架构设计的好处&#xff0c;MagicVideo-V2能够生成具有极高保真度和流畅度的美观高分辨率视频。通过大规模用户评估&…

Android学习之路(28) 进程保活组件的封装

前言 远古时代&#xff0c;出现过很多黑科技&#xff0c;比如MarsDaemon&#xff0c;使用双进程守护的方式进行保活&#xff0c;在当时可谓风光无限&#xff0c;可惜在8.0时代到来就被废弃了。 又比如后面出现的1像素Activity的保活方式&#xff0c;说他流氓一点不过分&#…

Linux 驱动开发基础知识——内核对设备树的处理与使用(十)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…

344. Reverse String(反转字符串)

题目描述 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 问题分析 以中间字符为轴&#xff0c;将两边的字符对换…