Spring AS 持久化

jdk version: 17
spring boot version: 2.7.0
spring authorization server:0.3.0
mysql version: 8.x

在 [[spring authorization server 实现授权中心]] 中实现了基础的演示功能。本文包含的内容有:

  1. 在 mysql 中保存客户端信息
  2. 在 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

  1. 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'
    } ...
  2. 更改 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);
}
}

总结

  1. 目前 spring authorization server 版本是 0.3.0 ,在我看来仍然有诸多不完善的地方,但官方总不至于又实现一套 keycloak。
  2. 0.3.0 版本发布之际,官方文档 也放出来了。

Spring Authorization Server(AS)从 Mysql 中读取客户端、用户的更多相关文章

  1. Spring Authorization Server 实现授权中心

    Spring Authorization Server 实现授权中心 源码地址 当前,Spring Security 对 OAuth 2.0 框架提供了全面的支持.Spring Authorizati ...

  2. Spring Authorization Server的使用

    Spring Authorization Server的使用 一.背景 二.前置知识 三.需求 四.核心代码编写 1.引入授权服务器依赖 2.创建授权服务器用户 3.创建授权服务器和客户端 五.测试 ...

  3. Spring Authorization Server 0.2.3发布,放出联合身份DEMO

    很快啊Spring Authorization Server又发新版本了,现在的版本是0.2.3.本次都有什么改动呢?我们来了解一下. 0.2.3版本特性 本次更新的新特性不少. 为公开客户端提供默认 ...

  4. Spring Authorization Server授权服务器入门

    11月8日Spring官方已经强烈建议使用Spring Authorization Server替换已经过时的Spring Security OAuth2.0,距离Spring Security OA ...

  5. Spring Authorization Server 0.3.0 发布,官方文档正式上线

    基于OAuth2.1的授权服务器Spring Authorization Server 0.3.0今天正式发布,在本次更新中有几大亮点. 文档正式上线 Spring Authorization Ser ...

  6. Spring Authorization Server 全新授权服务器整合使用

    前言 Spring Authorization Server 是 Spring 团队最新开发适配 OAuth 协议的授权服务器项目,旨在替代原有的 Spring Security OAuth 经过半年 ...

  7. mysql中,root用户密码被遗忘,该如何进行重置?

    需求描述: 在mysql的测试环境中,有时候会遇到一段时间之后root用户的密码被遗忘的情况, 这个时候,就是需要对root密码进行重置,不过,在生产环境中,这种情况还是很少见. 环境描述: 操作系统 ...

  8. 显示Mysql中的所有用户

    在mysql中如何显示所有用户? 1.show databases显示所有数据库 2.show tables显示所有数据表 3.select current_user();显示当前用户 4.显示所有用 ...

  9. 五.hadoop 从mysql中读取数据写到hdfs

    目录: 目录见文章1 本文是基于windows下来操作,linux下,mysql-connector-java-5.1.46.jar包的放置有讲究. mr程序 import java.io.DataI ...

随机推荐

  1. Linux 0.11源码阅读笔记-总览

    Linux 0.11源码阅读笔记-总览 阅读源码的目的 加深对Linux操作系统的了解,了解Linux操作系统基本架构,熟悉进程管理.内存管理等主要模块知识. 通过阅读教复杂的代码,锻炼自己复杂项目代 ...

  2. tracert命令简述

    1. 路由跟踪在线Tracert工具说明 Tracert(跟踪路由)是路由跟踪实用程序,用于确定 IP 数据报访问目标所采取的路径.Tracert 命令用 IP 生存时间 (TTL) 字段和 ICMP ...

  3. css常见知识点总结

    CSS 中可继承与不可继承属性有哪些 可继承: 字体系列 font-family font-weight font-size 文本系列 color text-align line-height 可见系 ...

  4. [转载] Golang交叉编译(跨平台编译)简述

    一.何为交叉编译 简单地说,就是在一个平台上生成另一个平台上的可执行代码.同一个体系结构可以运行不同的操作系统:同样,同一个操作系统也可以在不同的体系结构上运行. 二.交叉编译前的准备 本文只介绍Wi ...

  5. Edu CF 103 Div. 2 (A. K-divisible Sum, B. Inflation贪心),被黑客攻了,,惨掉rank, 思维除法与取余, 不太擅长的类型

    2021-01-29 题目链接: Educational Codeforces Round 103 (Rated for Div. 2) 题目 A. K-divisible Sum You are g ...

  6. Blazor使用区域(Area)

    在MVC中,我们经常使用区域(Area)来区分各个模块,比如后台我们可以写一个Admin的Area. 到了Blazor时代,已经不推荐这么做了,现在推荐的做法是通过Url来区分,比如Admin可以配置 ...

  7. Java-GUI编程之ImageIO的使用

    在实际生活中,很多软件都支持打开本地磁盘已经存在的图片,然后进行编辑,编辑完毕后,再重新保存到本地磁盘.如果使用AWT要完成这样的功能,那么需要使用到ImageIO这个类,可以操作本地磁盘的图片文件. ...

  8. 分享两个实用的shell脚本

    各位,早上好啊~ 发现许久没有分享过技术文章了,今天分享两个部署项目时候比较实用的shell脚本 一键部署shell脚本 由于个人部署,会习惯把jar放到lib目录下面,如果你没有这个习惯,可以适当做 ...

  9. css的过渡transition和动画animation

    过渡 过渡(transition)是CSS3中具有颠覆性的特性之一,我们可以在不使用Flash动画或JavaScript的情况下,当元素从一种样式变换为另一种样式时元素添加效果.过渡动画:是从一个状态 ...

  10. 【学习笔记】CDQ分治(等待填坑)

    因为我对CDQ分治理解不深,所以这篇博客只是我现在的浅显理解有任何不对的,希望大佬指出. 首先就是CDQ分治适用的题型: (1)带修改,但修改互相独立 (2)必须允许离线 (3)解决数据结构的题,能把 ...