基于Spring Security 6的OAuth2 系列之九 - 授权服务器--token的获取

之所以想写这一系列,是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器,但当时基于spring-boot 2.3.x,其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0,结果一看Spring Security也升级为6.3.0。无论是Spring Security的风格和以及OAuth2都做了较大改动,里面甚至将授权服务器模块都移除了,导致在配置同样功能时,花费了些时间研究新版本的底层原理,这里将一些学习经验分享给大家。

注意由于框架不同版本改造会有些使用的不同,因此本次系列中使用基本框架是 spring-boo-3.3.0(默认引入的Spring Security是6.3.0),JDK版本使用的是19,本系列OAuth2的代码采用Spring Security6.3.0框架,所有代码都在oauth2-study项目上:https://github.com/forever1986/oauth2-study.git

目录

  • 1 OAuth2TokenEndpointFilter原理
    • 1.1 使用Postman获取token
    • 1.2 三种token的作用及内容
    • 1.3 OAuth2TokenEndpointFilter原理
    • 1.4 OAuth2TokenGenerator(token生成器)
    • 1.5 Opaque Token 和 JWT Token
  • 2 自定义JWT加密方式

前面我们对Spring Authrization Server的授权码模式都做了比较详细的解析,也知道通过/oauth2/token接口,可以获取到token,其处理是OAuth2TokenEndpointFilter过滤器,本章我们来更详细说一下OAuth2TokenEndpointFilter过滤器以及token。

1 OAuth2TokenEndpointFilter原理

1.1 使用Postman获取token

之前我们都是使用代码方式的客户端访问授权服务器,现在我们使用非代码方式,一步步看看授权码模式下如何获得token。

1)启动lesson04子模块的授权服务器
2)GET方式访问授权码:http://localhost:9000/oauth2/authorize?client_id=oidc-client&redirect_uri=http://localhost:8080/login/oauth2/code/oidc-client&response_type=code&scope=openid profile 如下图

注意:这里会跳转到登录界面。里面以一个_csrf的value,拷贝下来,登录时需要。

在这里插入图片描述

3)登录授权服务器,把步骤2)中的_csrf复制到此,作为请求body中的一个参数,POST请求:http://localhost:9000/login

注意:这里跳转到授权页面,有一个state返回值,拷贝下来,下一步获取授权code需要传入

在这里插入图片描述

4)获得授权码之前,先将Postman设置为不自动跳转,如下图

在这里插入图片描述

5)获得授权码,将步骤3)中的state拷贝过来作为入参。POST请求访问:http://localhost:9000/oauth2/authorize

注意:这里会获得授权码code,在返回的header的Location属性中,将code拷贝下来,请求token需要

在这里插入图片描述

6)获得token,将步骤5)中的授权码code拷贝过来,访问如下两个图。POST请求:http://localhost:9000/oauth2/token

注意:

  • 设置Body参数,分别是grant_type、code、client_id、redirect_uri
  • 设置Authorization为Basic Auth,因为我们配置的是client_secret_basic;

在这里插入图片描述

设置Authorization的Basic Auth

在这里插入图片描述

7)这时候得到access_token,就是我们需要的最终认证token,我们使用jwt在线解析,就可以看到Header、Payload以及私钥。

关于JWT的相关信息,可以参考《Spring Security 6 系列之集成JWT》

在这里插入图片描述

1.2 三种token的作用及内容

我们从上面步骤6中可以看到返回由三种token:

  • access_token:这个就是我们拥有从资源服务器获取资源需要的token
  • refresh_token:这个是用于刷新access_token使用的token,因为我们默认access_token只有5分钟有效期,需要刷新token才行
  • id_token:这个是基于OIDC1.0协议身份验证的一个token,后面会详细讲OIDC1.0,它是使用JWT格式的。

其中我们来说一下access_token的内容

字段说明
iss (Issuer Identifier)提供认证信息者的唯一标识。一般是一个https的URL
aud (Audience)标识 id_token 的受众。必须包含OAuth2的client_id
nbftoken在该时间之前无效,一般设置签发的时间
scope授权范围
exp (Expiration time)过期时间,超过此时间的id_token会作废不再被验证通过
iat (Issued At Time)token使用JWT发布的时间
auth_time (AuthenticationTime)用户完成认证的时间。如果客户端发送AuthN请求的时候携带max_age的参数,则此Claim是必须的
nonce客户端发送请求的时候提供的随机字符串,用来减缓重放攻击,也可以来关联ID Token和RP本身的Session信息
acr (Authentication Context Class Reference)身份验证上下文类参考
amr (Authentication Methods References)身份验证上下文类参考
azp (Authorized party)被授权方 - 向其颁发 id_token 的一方

1.3 OAuth2TokenEndpointFilter原理

从上面流程知道获取token需要访问/oauth2/token接口,从《系列之八-授权服务器–Spring Authrization Server的基本原理》中得知OAuth2TokenEndpointFilter过滤器是用于拦截/oauth2/token接口的。

1)那么我们先从OAuth2TokenEndpointFilter的doFilter入手,看到是通过authenticationManager,那就是熟悉使用ProviderManager中代理了OAuth2AuthorizationCodeAuthenticationProvider

在这里插入图片描述

2)从OAuth2AuthorizationCodeAuthenticationProvider中,我们看到如下图代码,就是使用tokenGenerator生成token

在这里插入图片描述

3)tokenGenertor也是一个代理DelegatingOAuth2TokenGenerator,里面有多个tokenGenertor。这里默认使用的是JwtGenerator

在这里插入图片描述

4)JwtGenerator中使用jwtEncoder进行生成真正JWT的token

在这里插入图片描述

5)从上面整个流程可以看出,主要是使用tokenGenertor进行生成

1.4 OAuth2TokenGenerator(token生成器)

从上面流程可以知道,最终token都是使用OAuth2TokenGenerator生成器生成,以下是授权服务器常见的生成器
在这里插入图片描述

下面介绍一下不同的token生成器:

  • DelegatingOAuth2TokenGenerator:token生成器代理,用于循环选择适合的token生成器,默认情况下,自动加载3个tokenGenerator生成器,如下图:
    在这里插入图片描述

  • JwtGenerator:生成JWT格式的access_token和id_token,如果客户端配置中oauth2_registered_client表中字段token_settings配置为self-contained时,access_token和id_token由JwtGenerator生成,也就是会生成JWT格式的token
    在这里插入图片描述

  • OAuth2AccessTokenGenerator:生成BASE64的access_token,长度为128,如果客户端配置中oauth2_registered_client表中字段token_settings配置为reference时,access_token和id_token由OAuth2AccessTokenGenerator生成,也就生成BASE64格式的token

  • OAuth2RefreshTokenGenerator:生成刷新refresh_token,也是基于BASE64,长度为128

1.5 Opaque Token 和 JWT Token

我们从上面知道,access_token是可以根据token_settings的配置决定生成的方式,当值为self-contained时,使用JWT Token,当值为reference时,使用BASE64格式。而这种BASE64也称为Opaque Token。大家可以尝试一下,把token_settings配置为reference,token就会是一个无意义的字符串,而JWT Token则是可以解析其中存储的信息。这Opaque Token其实是为了安全,不暴露用户信息。因此如果只是内部系统之间使用,推荐使用JWT Token,如果是共用的,推荐Opaque Token。

2 自定义JWT加密方式

我们从源码OAuth2AuthorizationServerJwtAutoConfiguration可以看到默认情况下是自动生成一个RSA非对称的加密,如下图:

在这里插入图片描述

但是如果使用默认的情况会有2个问题:

  • 安全问题:我们知道密钥是经常需要轮换的,如果使用默认我们就无法定时轮换,当然重新启动就能切换
  • 集群问题:如果我们的授权服务器是一个集群,那么每个服务器的密钥都是不一样,无法实现集群效果

因此我们只需要自定义jwkSource,就可以自己使用自己生成的RSA密钥。

代码参考lesson05子模块

1)生成自己的RSA密钥对,这个可以使用keytool

生成jks,在命令行模式下,执行下面语句
keytool -genkeypair -alias demo -keyalg RSA -keypass linmoo -storepass linmoo -keysize 2048 -keystore demo.jks
这时候会在目录下生成一个demo.jks文件,该文件包括私钥和公钥 。该文件拷贝到项目resources文件下面,供签名使用
在这里插入图片描述

2)新建lesson05子模块,其代码拷贝lesson04子模块的src和resources目录,以及其pom依赖

3)拷贝过来之后,注意:yaml文件中的这2处需要修改,不然mybatis-plus会加载不了entity和handler
在这里插入图片描述

4)SecurityConfig配置jwkSource、jwtDecoder以及读取密钥对的方法

@Configuration
public class SecurityConfig {// 自定义授权服务器的Filter链@Bean@Order(Ordered.HIGHEST_PRECEDENCE)SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)// oidc配置.oidc(withDefaults());// 资源服务器默认jwt配置http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults()));// 异常处理http.exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")));return http.build();}// 自定义Spring Security的链路。如果自定义授权服务器的Filter链,则原先自动化配置将会失效,因此也要配置Spring Security@Bean@Order(SecurityProperties.BASIC_AUTH_ORDER)SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) -> authorize.requestMatchers("/demo", "/test").permitAll().anyRequest().authenticated()).formLogin(withDefaults());return http.build();}/*** 访问令牌签名*/@Beanpublic 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);}/*** 其 key 在启动时生成,用于创建上述 JWKSource*/private static KeyPair generateRsaKey() {KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("demo.jks"), "linmoo".toCharArray());KeyPair keyPair = factory.getKeyPair("demo", "linmoo".toCharArray());return keyPair;}
}

5)当然,这个部分我们还可以改进,就是把密钥对放到nacos或者redis上面,这样我们可以手动更新。

结语:本章我们讲解了OAuth2的token以及Spring Security如何实现的底层原理,并自定义jwkSource来实现使用自己的RSA的密钥对。其中还有一个点没有讲,就是token的刷新。下一章我们将详细讲一下如何刷新token以及其代码原理。

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

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

相关文章

音标-- 02-- 重音 音节 变音

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 国际音标1.重音2.音节3.变音 国际音标 1.重音 2.音节 3.变音

Adaptive LLM Transformer²

看到了一个不错的论文https://arxiv.org/pdf/2501.06252 TRANSFORMER-SQUARED: SELF-ADAPTIVE LLMS 挺有意思的&#xff0c;是一家日本AI公司SakanaAI的论文&#xff08;我以前写过他们的不训练提升模型的能力的文章&#xff0c;感兴趣可以去翻&#xff09;它家有Lion Jones坐镇…

优化代码性能:利用CPU缓存原理

在计算机的世界里&#xff0c;有一场如同龟兔赛跑般的速度较量&#xff0c;主角便是 CPU 和内存 。龟兔赛跑的故事大家都耳熟能详&#xff0c;兔子速度飞快&#xff0c;乌龟则慢吞吞的。在计算机中&#xff0c;CPU 就如同那敏捷的兔子&#xff0c;拥有超高的运算速度&#xff0…

linux 函数 sem_init () 信号量、sem_destroy()

&#xff08;1&#xff09; &#xff08;2&#xff09; 代码举例&#xff1a; #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h>sem_t semaphore;void* thread_function(void* arg) …

分库分表技术方案选型

一、MyCat 官方网站&#xff0c;技术文档 MyCat是一款由阿里Cobar演变而来的用于支持数据库读写分离、分片的数据库中间件。它基于MySQL协议&#xff0c;实现了MySQL的协议和能力&#xff0c;并作为代理层位于应用和数据库之间&#xff0c;可以隐藏底层数据库的复杂性。 原理…

【智力测试——二分、前缀和、乘法逆元、组合计数】

题目 代码 #include <bits/stdc.h> using namespace std; using ll long long; const int mod 1e9 7; const int N 1e5 10; int r[N], c[N], f[2 * N]; int nr[N], nc[N], nn, nm; int cntr[N], cntc[N]; int n, m, t;void init(int n) {f[0] f[1] 1;for (int i …

IBM DB2常用命令(windows版),包含建库、建表、增删改查等命令

安装IBM DB2可以参考我上篇博客&#xff1a;IBM Db2 & IBM Db2 Data Management Console(可视化管理工具)的下载与安装&#xff08;简洁版&#xff09;-CSDN博客 使用管理员权限打开cmd窗口 G: cd G:\IBM\SQLLIB\BIN db2cmd首先&#xff0c;在服务端需要配置好服务名、监…

Flutter Scaffold 页面结构

Material是一套设计风格&#xff0c;提供了大量的小部件&#xff0c;这里用Material风格搭建一个常见的应用页面结构。 创建Material应用 import package:flutter/material.dart;class App extends StatelessWidget {overrideWidget build(BuildContext context) {return Mat…

【C++】string类(上):string类的常用接口介绍

文章目录 前言一、C中设计string类的意义二、string类的常用接口说明1. string类对象的常见构造2. string类对象的容量操作2.1 size、capacity 和 empty的使用2.2 clear的使用2.3 reserve的使用2.4 resize的使用 3. string类对象的访问及遍历操作3.1 下标[ ] 和 at3.2 迭代器it…

一文讲解Java中的ArrayList和LinkedList

ArrayList和LinkedList有什么区别&#xff1f; ArrayList 是基于数组实现的&#xff0c;LinkedList 是基于链表实现的。 二者用途有什么不同&#xff1f; 多数情况下&#xff0c;ArrayList更利于查找&#xff0c;LinkedList更利于增删 由于 ArrayList 是基于数组实现的&#…

STM32 DMA+AD多通道

接线图 代码配置 ADC单次扫描DMA单次转运模式 uint16_t AD_Value[4]; //DMAAD多通道 void DMA_Config(void) {//定义结构体变量 GPIO_InitTypeDef GPIO_InitStructure;//定义GPIO结构体变量 ADC_InitTypeDef ADC_InitStructure; //定义ADC结构体变量 DMA_InitTypeDef DMA_In…

浅谈《图解HTTP》

感悟 滑至尾页的那一刻&#xff0c;内心突兀的涌来一阵畅快的感觉。如果说从前对互联网只是懵懵懂懂&#xff0c;但此刻却觉得她是如此清晰而可爱的呈现在哪里。 介绍中说&#xff0c;《图解HTTP》适合作为第一本网络协议书。确实&#xff0c;它就像一座桥梁&#xff0c;连接…

Alibaba开发规范_异常日志之日志规约:最佳实践与常见陷阱

文章目录 引言1. 使用SLF4J日志门面规则解释代码示例正例反例 2. 日志文件的保存时间规则解释 3. 日志文件的命名规范规则解释代码示例正例反例 4. 使用占位符进行日志拼接规则解释代码示例正例反例 5. 日志级别的开关判断规则解释代码示例正例反例 6. 避免重复打印日志规则解释…

自动化软件测试的基本流程

一、自动化测试的准备 1.1 了解测试系统 首先对于需要测试的系统我们需要按照软件需求说明书明确软件功能。这里以智慧养老系统作为案例进行测试&#xff0c;先让我们看看该系统的登录界面和用户管理界面。 登录界面&#xff1a; 登录成功默认界面&#xff1a; 用户管理界面…

Windows电脑本地部署运行DeepSeek R1大模型(基于Ollama和Chatbox)

文章目录 一、环境准备二、安装Ollama2.1 访问Ollama官方网站2.2 下载适用于Windows的安装包2.3 安装Ollama安装包2.4 指定Ollama安装目录2.5 指定Ollama的大模型的存储目录 三、选择DeepSeek R1模型四、下载并运行DeepSeek R1模型五、常见问题解答六、使用Chatbox进行交互6.1 …

计算机网络中常见高危端口有哪些?如何封禁高危端口?

保障网络安全&#xff0c;从封禁高危端口开始&#xff01; 在计算机网络中&#xff0c;端口是设备与外界通信的“大门”&#xff0c;但某些端口因常被黑客利用而成为高危入口。封禁这些端口是防御网络攻击的关键一步。本文将详解 10个常见高危端口&#xff0c;并提供多平台封禁…

CommonJS

CommonJS 是由 JavaScript 社区于 2oo9 年提出的包含模块、文件、IO、控制台在内的一系列标准。Node.js 的实现中采用了 CommonJS 标准的一部分&#xff0c;并在其基础上进行了一些调整。我们所说的 CommonJS 模块和 Node.js 中的实现并不完全一样&#xff0c;现在一般谈到 Com…

[SAP ABAP] ABAP SQL跟踪工具

事务码ST05 操作步骤 步骤1&#xff1a;使用事务码ST05之前&#xff0c;将要检测的程序生成的页面先呈现出来&#xff0c;这里我们想看下面程序的取数操作&#xff0c;所以停留在选择界面 步骤2&#xff1a; 新建一个GUI窗口&#xff0c;输入事务码ST05&#xff0c;点击 Acti…

蓝桥杯备考:高精度算法之除法

我们除法的高精度其实也不完全是高精度&#xff0c;而是一个高精度作被除数除以一个低精度 模拟我们的小学除法 由于题目中我们的除数最大是1e9&#xff0c;当它真正是1e9的时候&#xff0c;t是有可能超过1e9的&#xff0c;所以要用long long

算法竞赛(Python)-堆栈

文章目录 一 基础知识二 题目有效的括号字符串解码 一 基础知识 堆栈&#xff08;Stack&#xff09;&#xff1a;简称为栈。一种线性表数据结构&#xff0c;是一种只允许在表的一端进行插入和删除操作的线性表。   我们把栈中允许插入和删除的一端称为 「栈顶&#xff08;top…