当前位置: 首页 > news >正文

篇二:springboot2.7 OAuth2 server使用jdbc存储RegisteredClient

上一篇 <<springboot 2.7 oauth server配置源码走读一>>中简单描述了oauth2 server的配置,其中使用了内存保存 RegisteredClient,本篇改用mysql存储。

db存储需要创建表,表结构应该是什么样的呢,从spring给我们封装好的源码入手,
org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository类中:
在这里插入图片描述
那么字段类型呢?我们看org.springframework.security.oauth2.server.authorization.client.RegisteredClient类:
在这里插入图片描述
解释下为什么时间用timestamp存储以及Set数据模型用字符串存储:
在这里插入图片描述
解释下为什么ClientSettings和TokenSettings用json存储(当然varchar也行):就是一个map.
在这里插入图片描述
在这里插入图片描述
至此咱们确定了表结构,如下是一个示例:


CREATE TABLE `oauth2_registered_client` (`id` varchar(36) COLLATE utf8mb4_general_ci NOT NULL,`client_id` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,`client_id_issued_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,`client_secret` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '',`client_secret_expires_at` timestamp NULL DEFAULT NULL,`client_name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,`client_authentication_methods` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,`authorization_grant_types` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,`redirect_uris` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '',`scopes` varchar(200) COLLATE utf8mb4_general_ci NOT NULL,`client_settings` json NOT NULL,`token_settings` json NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

创建好表后,咱们处理代码:

1.在pom中引入依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version>
</dependency>

2.yaml/properties配置文件中添加数据库信息,示例如下:

spring:datasource:url: jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=falseusername: <username>password: <password>

3.使用JdbcRegisteredClientRepository,它和InMemoryRegisteredClientRepository只能二选一,所以需要注释掉后者。在咱们自己的配置类OAuth2AuthorizeSecurityConfig中:

@Beanpublic RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {return new JdbcRegisteredClientRepository(jdbcTemplate);}

4.初始化数据到db表中:写一个测试类方法插入数据:

package com.jel.tech.auth;import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;import javax.annotation.Resource;
import java.time.Duration;
import java.util.UUID;@SpringBootTest
class AuthApplicationTests {@Resourceprivate RegisteredClientRepository registeredClientRepository;@Testvoid saveRegisteredClients() {RegisteredClient loginClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("login-client").clientSecret("{noop}openid-connect").clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).redirectUri("http://127.0.0.1:8080/login/oauth2/code/login-client").redirectUri("http://127.0.0.1:8080/authorized").scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("messaging-client").clientSecret("{noop}secret").clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).scope("message:read").scope("message:write")// 指定token有效期:token:30分(默认5分钟),refresh_token:1天.tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofMinutes(30)).refreshTokenTimeToLive(Duration.ofDays(1)).build()).build();// 注意:没有设置clientName,则会把id值作为clientNameregisteredClientRepository.save(loginClient);registeredClientRepository.save(registeredClient);}
}

5.验证功能,在此我借花献佛,把官网提供的示例复制过来:


package com.jel.tech.auth;import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;import java.util.Map;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;/*** Integration tests for {@link AuthApplication}.** @author Steve Riesenberg*/
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class OAuth2AuthorizationServerApplicationITests {private static final String CLIENT_ID = "messaging-client";private static final String CLIENT_SECRET = "secret";private final ObjectMapper objectMapper = new ObjectMapper();@Autowiredprivate MockMvc mockMvc;@Testvoid performTokenRequestWhenValidClientCredentialsThenOk() throws Exception {// @formatter:offthis.mockMvc.perform(post("/oauth2/token").param("grant_type", "client_credentials").param("scope", "message:read").with(basicAuth(CLIENT_ID, CLIENT_SECRET))).andExpect(status().isOk()).andExpect(jsonPath("$.access_token").isString()).andExpect(jsonPath("$.expires_in").isNumber()).andExpect(jsonPath("$.scope").value("message:read")).andExpect(jsonPath("$.token_type").value("Bearer"));// @formatter:on}@Testvoid performTokenRequestWhenMissingScopeThenOk() throws Exception {// @formatter:offthis.mockMvc.perform(post("/oauth2/token").param("grant_type", "client_credentials").with(basicAuth(CLIENT_ID, CLIENT_SECRET))).andExpect(status().isOk()).andExpect(jsonPath("$.access_token").isString()).andExpect(jsonPath("$.expires_in").isNumber()).andExpect(jsonPath("$.scope").value("message:read message:write")).andExpect(jsonPath("$.token_type").value("Bearer"));// @formatter:on}@Testvoid performTokenRequestWhenInvalidClientCredentialsThenUnauthorized() throws Exception {// @formatter:offthis.mockMvc.perform(post("/oauth2/token").param("grant_type", "client_credentials").param("scope", "message:read").with(basicAuth("bad", "password"))).andExpect(status().isUnauthorized()).andExpect(jsonPath("$.error").value("invalid_client"));// @formatter:on}@Testvoid performTokenRequestWhenMissingGrantTypeThenUnauthorized() throws Exception {// @formatter:offthis.mockMvc.perform(post("/oauth2/token").with(basicAuth("bad", "password"))).andExpect(status().isUnauthorized()).andExpect(jsonPath("$.error").value("invalid_client"));// @formatter:on}@Testvoid performTokenRequestWhenGrantTypeNotRegisteredThenBadRequest() throws Exception {// @formatter:offthis.mockMvc.perform(post("/oauth2/token").param("grant_type", "client_credentials").with(basicAuth("login-client", "openid-connect"))).andExpect(status().isBadRequest()).andExpect(jsonPath("$.error").value("unauthorized_client"));// @formatter:on}@Testvoid performIntrospectionRequestWhenValidTokenThenOk() throws Exception {// @formatter:offthis.mockMvc.perform(post("/oauth2/introspect").param("token", getAccessToken()).with(basicAuth(CLIENT_ID, CLIENT_SECRET))).andExpect(status().isOk()).andExpect(jsonPath("$.active").value("true")).andExpect(jsonPath("$.aud[0]").value(CLIENT_ID)).andExpect(jsonPath("$.client_id").value(CLIENT_ID)).andExpect(jsonPath("$.exp").isNumber()).andExpect(jsonPath("$.iat").isNumber()).andExpect(jsonPath("$.iss").value("http://127.0.0.1:9000")).andExpect(jsonPath("$.nbf").isNumber()).andExpect(jsonPath("$.scope").value("message:read")).andExpect(jsonPath("$.sub").value(CLIENT_ID)).andExpect(jsonPath("$.token_type").value("Bearer")).andDo(MockMvcResultHandlers.print());// @formatter:on}@Testvoid performIntrospectionRequestWhenInvalidCredentialsThenUnauthorized() throws Exception {// @formatter:offthis.mockMvc.perform(post("/oauth2/introspect").param("token", getAccessToken()).with(basicAuth("bad", "password"))).andExpect(status().isUnauthorized()).andExpect(jsonPath("$.error").value("invalid_client"));// @formatter:on}private String getAccessToken() throws Exception {// @formatter:offMvcResult mvcResult = this.mockMvc.perform(post("/oauth2/token").param("grant_type", "client_credentials").param("scope", "message:read").with(basicAuth(CLIENT_ID, CLIENT_SECRET))).andExpect(status().isOk()).andExpect(jsonPath("$.access_token").exists()).andReturn();// @formatter:onString tokenResponseJson = mvcResult.getResponse().getContentAsString();Map<String, Object> tokenResponse = this.objectMapper.readValue(tokenResponseJson, new TypeReference<Map<String, Object>>() {});String access_token = tokenResponse.get("access_token").toString();System.out.println(access_token);return access_token;}private static BasicAuthenticationRequestPostProcessor basicAuth(String username, String password) {return new BasicAuthenticationRequestPostProcessor(username, password);}private static final class BasicAuthenticationRequestPostProcessor implements RequestPostProcessor {private final String username;private final String password;private BasicAuthenticationRequestPostProcessor(String username, String password) {this.username = username;this.password = password;}@Overridepublic MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {HttpHeaders headers = new HttpHeaders();headers.setBasicAuth(this.username, this.password);request.addHeader("Authorization", headers.getFirst("Authorization"));return request;}}
}

相关文章:

篇二:springboot2.7 OAuth2 server使用jdbc存储RegisteredClient

上一篇 <<springboot 2.7 oauth server配置源码走读一>>中简单描述了oauth2 server的配置&#xff0c;其中使用了内存保存 RegisteredClient&#xff0c;本篇改用mysql存储。 db存储需要创建表&#xff0c;表结构应该是什么样的呢&#xff0c;从spring给我们封装好…...

卷积神经网络|导入图片

在学习卷积神经网络时&#xff0c;我们通常使用的就是公开的数据集&#xff0c;这里&#xff0c;我们不使用公开数据集&#xff0c;直接导入自己的图片数据&#xff0c;下面&#xff0c;就简单写个程序实现批量图片的导入。 import osfrom PIL import Imageimport numpy as np…...

关于unity的组件VerticalLayoutGroup刷新显示不正常的问题

先说明一下我是如何用到&#xff0c;有哪些处理的 用到这个组件基本上都是将列表进行排版操作的&#xff0c;竖着&#xff0c;或者横着&#xff0c;横着用HorizontalLayoutGroup 还有一个和这个组件搭配的组件叫ContentSizeFitter 先说我是怎么发现这个组件不好用的 //本地读取…...

wait 和 notify 这个为什么要在synchronized 代码块中?

一个工作七年的小伙伴&#xff0c;竟然不知道” wait”和“notify”为什么要在 Synchronized 代码块中 。 好吧&#xff0c;如果屏幕前的你也不知道&#xff0c;请在公屏上刷”不知道“。 对于这个问题&#xff0c;我们来看看普通人和高手的回答。 一、问题解析 1. wait 和 n…...

大白话说区块链和通证

1 区块链 简单地说&#xff0c;区块链其实就像是一个不可篡改的分布式数据库&#xff0c;该分布式数据库记录了一系列交易或事件。区块链运行在至少1个以上的节点上&#xff0c;每个节点都有自己的一个分布式数据库&#xff0c;也就是分布式账本。正常情况下&#xff0c;每个节…...

Jvm之垃圾收集器(个人见解仅供参考)

问&#xff1a;什么是垃圾收集算法中的分代收集理论&#xff1f; 答&#xff1a;分代收集理论是垃圾收集算法的一种思想&#xff0c;根据对象存活周期的不同将内存分为几块&#xff0c;一般将java堆分为新生代和老年代。这种理论使得我们可以根据各个年代的特点选择合适的垃圾收…...

Minitab 21软件安装包下载及安装教程

Minitab 21下载链接&#xff1a;https://docs.qq.com/doc/DUkNHZVhwTXhtTFla 1.选中下载好的安装包&#xff0c;鼠标右键解压到”Minitab 21“文件夹 2.选中”Setup.exe“&#xff0c;鼠标右击选择“以管理员身份运行” 3.点击“下一步” 4.点击“是” 5.点击“下一步” 6.勾选…...

Java版商城:Spring Cloud+SpringBoot b2b2c电子商务平台,多商家入驻、直播带货及免 费 小程序商城搭建

​随着互联网的快速发展&#xff0c;越来越多的企业开始注重数字化转型&#xff0c;以提升自身的竞争力和运营效率。在这个背景下&#xff0c;鸿鹄云商SAAS云产品应运而生&#xff0c;为企业提供了一种简单、高效、安全的数字化解决方案。 鸿鹄云商SAAS云产品是一种基于云计算的…...

阿里云被拉入黑洞模式怎么办?该怎么换ip-速盾网络

被拉入黑洞模式&#xff08;BGP黑洞路由&#xff09;意味着所有进入目标IP的流量都会被丢弃&#xff0c;从而导致目标IP对外完全不可访问。这种情况通常发生在面对大规模DDoS攻击时&#xff0c;为了防止攻击流量对其他网络造成影响。如果你使用的是阿里云服务并遭受到这种攻击&…...

Android 13.0 recovery竖屏界面旋转为横屏

1.概述 在13.0系统项目定制化开发中,由于平板固定横屏显示,而如果recovery界面竖屏显示就觉得怪怪的,所以需要recovery页面横屏显示的功能, 所以今天就来解决这个问题 2.实现功能相关分析 Android的Recovery中,利用 bootable\recovery下的minui库作为基础,采用的是直接…...

异地环控设备如何远程维护?贝锐蒲公英解决远程互联难题

青岛某企业致力于孵化设备、养禽设备和养猪设备的研发、生产和服务&#xff0c;历经三十多年发展&#xff0c;目前已成长为行业主要的养殖装备及工程服务提供商&#xff0c;产品覆盖养殖产业链中绝大多数环节&#xff0c;涉及自动化设备、环控设备、整体解决方案等。 在实际应用…...

flutter 判断是否是web环境

代码如下 import package:flutter/foundation.dart show kIsWeb;void main() {if (kIsWeb) {print(Running on the web!);} else {print(Not running on the web!);} } 如果是使用 Platform.isAndroid 会报错 所以使用上面的方式...

视频智能分析/云存储平台EasyCVR接入海康SDK,通道名称未自动更新该如何解决?

视频监控GB28181平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;TSINGSEE青犀视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多路视频流&#xff0c;也能…...

后端开发——JDBC的学习(三)

本篇继续对JDBC进行总结&#xff1a; ①通过Service层与Dao层实现转账的练习&#xff1b; ②重点&#xff1a;由于每次使用连接就手动创建连接&#xff0c;用完后就销毁&#xff0c;这样会导致资源浪费&#xff0c;因此引入连接池&#xff0c;练习连接池的使用&#xff1b; …...

Redis 生产环境查找无过期时间的 key

在项目中,Redis 不应该被当作传统数据库来使用;储存大量没有过期时间的数据。如果储存大量无过期时间,而且无效的key的话;再加上 Redis 本身的过期策略没有被正确设置,就会大量占用内存。这样就会导致再多的内存资源也不够用。 情况大致是这样,项目中采用 Redis 二级存储…...

Visual Studio 2017编译Python3.8.18源码

一直纠结Python的开发环境没有升级到最新版3.8.18。这是当前的最新版&#xff0c;正在用的版本3.8.10。他是官方制作出安装包的最新版。 1、准备Visual C 2017的开发环境包括但不限于使用C的桌面开发x64 2、在Python官网下载Python3.8.18的源码。 3、解压缩源码 4、进入控制…...

【mujoco】Ubuntu20.04中解决mujoco报错raise error.MujocoDependencyError

【mujoco】Ubuntu20.04中解决mujoco报错raise error.MujocoDependencyError 文章目录 【mujoco】Ubuntu20.04中解决mujoco报错raise error.MujocoDependencyError1. 报错的具体情况2. 解决过程3. 其他问题3.1 ModuleNotFoundError: No module named OpenGL3.2 ModuleNotFoundEr…...

机器学习的三个方面

1 机器学习的三个方面 1.1 数据 包括数据采集、增强和质量管理&#xff0c;相当于给人工智能模型学习什么样的知识 第一、什么专业的知识&#xff1b; 第二、知识是否有体系&#xff0c;也就是说样本之间是否存在某种关联、差异等&#xff0c;这个涉及到样本选择等问题&#x…...

关于一名资深Java程序员在移动端的进阶之路

今天呢&#xff0c;就借此机会&#xff0c;跟大家聊一聊我的个人职业经历吧&#xff01; 那年刚毕业 刚毕业时候&#xff0c;入职的第一家公司&#xff0c;进去后&#xff0c;说实话&#xff0c;没有太大成长吧&#xff01;基本就是让我做一些可有可无的边缘性的工作&#xff…...

clickonce excel 插件发布安装的原理

ClickOnce 是一种由 Microsoft 提供的部署技术&#xff0c;用于简化和加速Windows应用程序的部署。ClickOnce 可以用于部署各种类型的应用程序&#xff0c;包括 Excel 插件。 以下是 ClickOnce Excel 插件发布和安装的一般原理&#xff1a; 1. 发布应用程序&#xff1a; -…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用

文章目录 一、背景知识&#xff1a;什么是 B-Tree 和 BTree&#xff1f; B-Tree&#xff08;平衡多路查找树&#xff09; BTree&#xff08;B-Tree 的变种&#xff09; 二、结构对比&#xff1a;一张图看懂 三、为什么 MySQL InnoDB 选择 BTree&#xff1f; 1. 范围查询更快 2…...

「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案

在移动互联网营销竞争白热化的当下&#xff0c;推客小程序系统凭借其裂变传播、精准营销等特性&#xff0c;成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径&#xff0c;助力开发者打造具有市场竞争力的营销工具。​ 一、系统核心功能架构&…...

uniapp 小程序 学习(一)

利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 &#xff1a;开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置&#xff0c;将微信开发者工具放入到Hbuilder中&#xff0c; 打开后出现 如下 bug 解…...

算术操作符与类型转换:从基础到精通

目录 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符&#xff1a;、-、*、/、% 赋值操作符&#xff1a;和复合赋值 单⽬操作符&#xff1a;、--、、- 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...