Spring Authorization Server(AS)从 Mysql 中读取客户端、用户
Spring AS 持久化
jdk version: 17
spring boot version: 2.7.0
spring authorization server:0.3.0
mysql version: 8.x
在 [[spring authorization server 实现授权中心]] 中实现了基础的演示功能。本文包含的内容有:
- 在 mysql 中保存客户端信息
- 在 mysql 中保存用户信息
创建数据表
查看 [[spring authorization server 实现授权中心#AuthorizationServerConfig]] 可以看到以下配置,这里定义了一个嵌入数据 Bean,包含 3 条数据库脚本。分别用于创建
- oauth2_registered_client
- oauth2_authorization_consent
- oauth2_authorization
@Bean
public EmbeddedDatabase embeddedDatabase() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.setScriptEncoding("UTF-8")
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
.build();
}
oauth2_registered_client
CREATE TABLE oauth2_registered_client (
id varchar(100) NOT NULL,
client_id varchar(100) NOT NULL,
client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
client_secret varchar(200) DEFAULT NULL,
client_secret_expires_at timestamp DEFAULT NULL,
client_name varchar(200) NOT NULL,
client_authentication_methods varchar(1000) NOT NULL,
authorization_grant_types varchar(1000) NOT NULL,
redirect_uris varchar(1000) DEFAULT NULL,
scopes varchar(1000) NOT NULL,
client_settings varchar(2000) NOT NULL,
token_settings varchar(2000) NOT NULL,
PRIMARY KEY (id)
);
打开 mysql,创建 auth-center 数据库,执行 [[#oauth2_registered_client]] 脚本。
oauth2_authorization
用户认证时需要此表。
/*
IMPORTANT:
If using PostgreSQL, update ALL columns defined with 'blob' to 'text',
as PostgreSQL does not support the 'blob' data type.
*/
CREATE TABLE oauth2_authorization (
id varchar(100) NOT NULL,
registered_client_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
authorization_grant_type varchar(100) NOT NULL,
attributes blob DEFAULT NULL,
state varchar(500) DEFAULT NULL,
authorization_code_value blob DEFAULT NULL,
authorization_code_issued_at timestamp DEFAULT NULL,
authorization_code_expires_at timestamp DEFAULT NULL,
authorization_code_metadata blob DEFAULT NULL,
access_token_value blob DEFAULT NULL,
access_token_issued_at timestamp DEFAULT NULL,
access_token_expires_at timestamp DEFAULT NULL,
access_token_metadata blob DEFAULT NULL,
access_token_type varchar(100) DEFAULT NULL,
access_token_scopes varchar(1000) DEFAULT NULL,
oidc_id_token_value blob DEFAULT NULL,
oidc_id_token_issued_at timestamp DEFAULT NULL,
oidc_id_token_expires_at timestamp DEFAULT NULL,
oidc_id_token_metadata blob DEFAULT NULL,
refresh_token_value blob DEFAULT NULL,
refresh_token_issued_at timestamp DEFAULT NULL,
refresh_token_expires_at timestamp DEFAULT NULL,
refresh_token_metadata blob DEFAULT NULL,
PRIMARY KEY (id)
);
配置 application.yml
build.gradle 中依赖更改如下所示
- 添加 mysql 驱动
- 去掉 H2 相关依赖
... dependencies{
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:0.2.3'
implementation 'org.springframework.boot:spring-boot-starter-actuator' compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
} ...更改 application.yml 如下
[server:
port: 9000
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
org.springframework.security.oauth2: INFO
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/auth-center?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456](<server:
port: 9000
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
org.springframework.security.oauth2: INFO
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/auth-center?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
client:
registers:
- client-id: mobile-gateway-client
client-secret: "{noop}123456"
authentication-method: client_secret_basic
grant-types:
- authorization_code
- refresh_token
- client_credentials
scopes:
- openid
- message.read
- message.write
redirect-uris:
- http://127.0.0.1:9100/login/oauth2/code/mobile-gateway-client-oidc
- http://127.0.0.1:9100/authorized>)
读取配置 ConfigurationProperties
...
@ConfigurationProperties(prefix = "client")
@ConstructorBinding
public record RegisterClientConfig(List<Register> registers) {
public record Register(String clientId, String clientSecret, String authenticationMethod, List<String> grantTypes,
List<String> scopes, List<String> redirectUris) {
}
}
添加 Member 对象
@Getter
@Setter
@ToString
@AllArgsConstructor
@RequiredArgsConstructor
public class Member implements UserDetails {
private Long id;
private String loginAccount;
private String password;
@Transient
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.createAuthorityList("read", "write");
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return loginAccount;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
添加 MbrRepository
@Repository
public interface MbrRepository extends CrudRepository<Member, Long> {
Optional<Member> findByLoginAccount(String loginAccount);
}
MbrService
public interface MbrService extends UserDetailsService {
}
UserDetailsServiceImp
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImp implements MbrService {
private final MbrRepository mbrRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return mbrRepository.findByLoginAccount(username).orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
}
}
AuthorizationServerConfig
...
[@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(withDefaults()).build();
}
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
return new JdbcRegisteredClientRepository(jdbcTemplate);
}
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().issuer("http://localhost:9000").build();
}
}](<@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
public class AuthorizationServerConfig {
private final JdbcTemplate jdbcTemplate;
private final RegisterClientConfig clientConfig;
private final MbrService mbrService;
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.exceptionHandling((exceptions) -%3E exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
.userDetailsService(mbrService)
.formLogin(withDefaults());
return http.build();
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
return new JdbcRegisteredClientRepository(jdbcTemplate);
}
@Bean
public OAuth2AuthorizationService authorizationService(RegisteredClientRepository registeredClientRepository, PasswordEncoder passwordEncoder) {
clientConfig.registers().forEach(cfg -> {
RegisteredClient registeredClientFromDb = registeredClientRepository.findByClientId(cfg.clientId());
if (registeredClientFromDb != null) {
return;
}
RegisteredClient.Builder registerBuilder = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(cfg.clientId())
.clientSecret(passwordEncoder.encode(cfg.clientSecret()))
.clientAuthenticationMethod(new ClientAuthenticationMethod(cfg.authenticationMethod()));
cfg.grantTypes().forEach(grantType -> registerBuilder.authorizationGrantType(new AuthorizationGrantType(grantType)));
cfg.redirectUris().forEach(registerBuilder::redirectUri);
cfg.scopes().forEach(registerBuilder::scope);
registeredClientRepository.save(registerBuilder.build());
});
JdbcOAuth2AuthorizationService jdbcOAuth2AuthorizationService = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
jdbcOAuth2AuthorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
return jdbcOAuth2AuthorizationService;
}
@Bean
public JWKSource%3CSecurityContext> jwkSource() {
RSAKey rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().issuer("http://localhost:9000").build();
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
RowMapper(RegisteredClientRepository registeredClientRepository) {
super(registeredClientRepository);
getObjectMapper().addMixIn(Member.class, MemberMixin.class);
}
}
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonDeserialize(using = MemberDeserializer.class)
static class MemberMixin {
}
}>)
EncoderConfig
@Configuration
public class EncoderConfig {
@Bean
@ConditionalOnMissingBean(PasswordEncoder.class)
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
MemberDeserializer
public class MemberDeserializer extends JsonDeserializer<Member> {
@Override
public Member deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
JsonNode jsonNode = mapper.readTree(jsonParser);
Long id = readJsonNode(jsonNode, "id").asLong();
String loginAccount = readJsonNode(jsonNode, "loginAccount").asText();
String password = readJsonNode(jsonNode, "password").asText();
List<GrantedAuthority> authorities = mapper.readerForListOf(GrantedAuthority.class).readValue(jsonNode.get("authorities"));
return new Member(id, loginAccount, password, authorities);
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}
启动服务
@SpringBootApplication
@ConfigurationPropertiesScan
public class AuthCenterApplication {
public static void main(String[] args) {
SpringApplication.run(AuthCenterApplication.class, args);
}
}
总结
- 目前 spring authorization server 版本是 0.3.0 ,在我看来仍然有诸多不完善的地方,但官方总不至于又实现一套 keycloak。
- 0.3.0 版本发布之际,官方文档 也放出来了。
Spring Authorization Server(AS)从 Mysql 中读取客户端、用户的更多相关文章
- Spring Authorization Server 实现授权中心
Spring Authorization Server 实现授权中心 源码地址 当前,Spring Security 对 OAuth 2.0 框架提供了全面的支持.Spring Authorizati ...
- Spring Authorization Server的使用
Spring Authorization Server的使用 一.背景 二.前置知识 三.需求 四.核心代码编写 1.引入授权服务器依赖 2.创建授权服务器用户 3.创建授权服务器和客户端 五.测试 ...
- Spring Authorization Server 0.2.3发布,放出联合身份DEMO
很快啊Spring Authorization Server又发新版本了,现在的版本是0.2.3.本次都有什么改动呢?我们来了解一下. 0.2.3版本特性 本次更新的新特性不少. 为公开客户端提供默认 ...
- Spring Authorization Server授权服务器入门
11月8日Spring官方已经强烈建议使用Spring Authorization Server替换已经过时的Spring Security OAuth2.0,距离Spring Security OA ...
- Spring Authorization Server 0.3.0 发布,官方文档正式上线
基于OAuth2.1的授权服务器Spring Authorization Server 0.3.0今天正式发布,在本次更新中有几大亮点. 文档正式上线 Spring Authorization Ser ...
- Spring Authorization Server 全新授权服务器整合使用
前言 Spring Authorization Server 是 Spring 团队最新开发适配 OAuth 协议的授权服务器项目,旨在替代原有的 Spring Security OAuth 经过半年 ...
- mysql中,root用户密码被遗忘,该如何进行重置?
需求描述: 在mysql的测试环境中,有时候会遇到一段时间之后root用户的密码被遗忘的情况, 这个时候,就是需要对root密码进行重置,不过,在生产环境中,这种情况还是很少见. 环境描述: 操作系统 ...
- 显示Mysql中的所有用户
在mysql中如何显示所有用户? 1.show databases显示所有数据库 2.show tables显示所有数据表 3.select current_user();显示当前用户 4.显示所有用 ...
- 五.hadoop 从mysql中读取数据写到hdfs
目录: 目录见文章1 本文是基于windows下来操作,linux下,mysql-connector-java-5.1.46.jar包的放置有讲究. mr程序 import java.io.DataI ...
随机推荐
- 【uniapp 开发】智能温控开关 (环状图)
index.vue <template> <view> <view class="qiun-columns"> <uCharts id=& ...
- Blazor组件自做四 : 使用JS隔离封装signature_pad签名组件
运行截图 演示地址 响应式演示 感谢szimek写的棒棒的signature_pad.js项目, 来源: https://github.com/szimek/signature_pad 正式开始 1. ...
- DRF-认证权限频率
目录 DRF-认证权限频率 认证 登录接口 认证 权限 作用 使用 频率 作用 使用 认证权限频率+五个接口 模型 视图 序列化器 认证权限频率类 配置文件 路由 DRF-认证权限频率 前后端混合开发 ...
- Python程序的流程
1 """ 2 python程序的流程 3 """ 4 # ------------- 分支结构---------------- 5 # i ...
- 无法访问 CentOS7服务器上应用监听的端口
无法访问 CentOS7服务器上应用监听的端口 参考资料 云主机上Centos7配置Iptables规则开启80.3306等端口https://blog.csdn.net/qq_37960007/ar ...
- 7-19(排序) 寻找大富翁 (25 分)(归并排序)(C语言实现)
7-19(排序) 寻找大富翁 (25 分) 胡润研究院的调查显示,截至2017年底,中国个人资产超过1亿元的高净值人群达15万人.假设给出N个人的个人资产值,请快速找出资产排前M位的大富翁. 输入格式 ...
- 分布式应用运行时 Dapr 1.7 发布
Dapr 是一个开源.可移植的.事件驱动的运行时,可以帮助开发人员构建在云和边缘上运行的弹性的.微服务的.无状态和有状态应用程序,并且关注于业务逻辑而不用考虑分布式相关的问题. 分布式相关的问题交给D ...
- Web项目部署指南
Web项目部署指南 本文记录了部署Vue项目到阿里云服务器上的过程,其中云服务器的操作系统是CentOS 7,Web服务器用的是nginx.因为项目涉及发送异步请求,而由Flask编写的后端应用监听的 ...
- 痞子衡嵌入式:聊聊系统看门狗WDOG1在i.MXRT1xxx系统启动中的应用及影响
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是系统看门狗WDOG1在i.MXRT1xxx系统启动中的应用及影响. 软件看门狗模块(WDOG)在 MCU 应用里可以说是非常基础的功能模 ...
- 攻防世界-MISC:Erik-Baleog-and-Olaf
这是攻防世界MISC高手进阶区的题目,题目如下 点击下载附件一,解压后得到一个文件,不知道是什么格式,用010editor打开,发现其为png文件,就把它的后缀改为png,然后用stegslove打开 ...