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 ...
随机推荐
- idea 提示错误: 找不到或无法加载主类
首先检查自己的jdk 配置是否正确,检查好遍发现没有问题,但是项目就是运行不起来...... 重启idea,问题解决.
- java中类变量和实例变量的实质区别?
类变量和实例变量的区别 相对于static(静态的)或说类的, 本章开始提到的都是instance(实例的)或说对象的. 每个对象都有自己的一份儿对象域或实例域,相互之间没关系, 不共享. 我们可以从 ...
- CCF201604-2俄罗斯方块
问题描述 俄罗斯方块是俄罗斯人阿列克谢·帕基特诺夫发明的一款休闲游戏. 游戏在一个15行10列的方格图上进行,方格图上的每一个格子可能已经放置了方块,或者没有放置方块.每一轮,都会有一个新的由4个小方 ...
- jq移动端图片预览 (fly-zomm-img.js)
效果图: ===>==> 里面还与很多属性设置: index 关闭按钮等等 代码: //html-----------------------<div class="he ...
- css让文字显示特定行数,多余的显示省略号
/*css*/ .p{ width: 200px; word-break: break-all; text-overflow: ellipsis; display: -webkit-box; /** ...
- 掌握JavaScript中的迭代器和生成器,顺便了解一下async、await的原理
掌握JavaScript中的迭代器和生成器,顺便了解一下async.await的原理 前言 相信很多人对迭代器和生成器都不陌生,当提到async和await的原理时,大部分人可能都知道async.aw ...
- python---if、while、for
if语句 当出现选择情况的时候,就需要用到if语句. # 第一种语法 if 条件: # 冒号将条件和结果分开,不可缺少 结果1 结果2 # 条件为真执行结果1,然后结果2:否则直接结果2 # 第二种语 ...
- Educational Codeforces Round 108 (Div. 2), C map套vector存储
地址 Problem - C - Codeforces 题目 题意 一个学校有n个人参加比赛,他们分别属于ui队,每个人的能力值为si 当每个队需要1~n个人的时候,这个学校能参加的人的能力值和最大 ...
- Mysql 计算地址经纬度距离实时位置
前言 最近在做项目时候,遇到一个这样子的需求, 点到卡包里面卡券使用使用,需要展示卡券使用附近门店, 思路 数据库地址表设计 通用的区域街道地址表tz_sys_area 字段名称 类型 备注 area ...
- 浅谈stm32的外部中断
简述 本文简单介绍stm32外部中断的一般操作步骤,后续会补充外部中断的相关内容 stm32的中断控制器支持19个外部中断/事件请求: line0~line15:这16条line分别对应不同GPIO口 ...