Spring AS 持久化

  1. jdk version: 17
  2. spring boot version: 2.7.0
  3. spring authorization server:0.3.0
  4. 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
  1. @Bean
  2. public EmbeddedDatabase embeddedDatabase() {
  3. return new EmbeddedDatabaseBuilder()
  4. .generateUniqueName(true)
  5. .setType(EmbeddedDatabaseType.H2)
  6. .setScriptEncoding("UTF-8")
  7. .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
  8. .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
  9. .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
  10. .build();
  11. }

oauth2_registered_client

  1. CREATE TABLE oauth2_registered_client (
  2. id varchar(100) NOT NULL,
  3. client_id varchar(100) NOT NULL,
  4. client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
  5. client_secret varchar(200) DEFAULT NULL,
  6. client_secret_expires_at timestamp DEFAULT NULL,
  7. client_name varchar(200) NOT NULL,
  8. client_authentication_methods varchar(1000) NOT NULL,
  9. authorization_grant_types varchar(1000) NOT NULL,
  10. redirect_uris varchar(1000) DEFAULT NULL,
  11. scopes varchar(1000) NOT NULL,
  12. client_settings varchar(2000) NOT NULL,
  13. token_settings varchar(2000) NOT NULL,
  14. PRIMARY KEY (id)
  15. );

打开 mysql,创建 auth-center 数据库,执行 [[#oauth2_registered_client]] 脚本。

oauth2_authorization

用户认证时需要此表。

  1. /*
  2. IMPORTANT:
  3. If using PostgreSQL, update ALL columns defined with 'blob' to 'text',
  4. as PostgreSQL does not support the 'blob' data type.
  5. */
  6. CREATE TABLE oauth2_authorization (
  7. id varchar(100) NOT NULL,
  8. registered_client_id varchar(100) NOT NULL,
  9. principal_name varchar(200) NOT NULL,
  10. authorization_grant_type varchar(100) NOT NULL,
  11. attributes blob DEFAULT NULL,
  12. state varchar(500) DEFAULT NULL,
  13. authorization_code_value blob DEFAULT NULL,
  14. authorization_code_issued_at timestamp DEFAULT NULL,
  15. authorization_code_expires_at timestamp DEFAULT NULL,
  16. authorization_code_metadata blob DEFAULT NULL,
  17. access_token_value blob DEFAULT NULL,
  18. access_token_issued_at timestamp DEFAULT NULL,
  19. access_token_expires_at timestamp DEFAULT NULL,
  20. access_token_metadata blob DEFAULT NULL,
  21. access_token_type varchar(100) DEFAULT NULL,
  22. access_token_scopes varchar(1000) DEFAULT NULL,
  23. oidc_id_token_value blob DEFAULT NULL,
  24. oidc_id_token_issued_at timestamp DEFAULT NULL,
  25. oidc_id_token_expires_at timestamp DEFAULT NULL,
  26. oidc_id_token_metadata blob DEFAULT NULL,
  27. refresh_token_value blob DEFAULT NULL,
  28. refresh_token_issued_at timestamp DEFAULT NULL,
  29. refresh_token_expires_at timestamp DEFAULT NULL,
  30. refresh_token_metadata blob DEFAULT NULL,
  31. PRIMARY KEY (id)
  32. );

配置 application.yml

  1. build.gradle 中依赖更改如下所示

    • 添加 mysql 驱动
    • 去掉 H2 相关依赖

    1. ...
    2. dependencies{
    3. implementation 'org.springframework.boot:spring-boot-starter-web'
    4. implementation 'org.springframework.boot:spring-boot-starter-security'
    5. implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
    6. implementation 'org.springframework.security:spring-security-oauth2-authorization-server:0.2.3'
    7. implementation 'org.springframework.boot:spring-boot-starter-actuator'
    8. compileOnly 'org.projectlombok:lombok'
    9. developmentOnly 'org.springframework.boot:spring-boot-devtools'
    10. runtimeOnly 'mysql:mysql-connector-java'
    11. annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    12. annotationProcessor 'org.projectlombok:lombok'
    13. testImplementation 'org.springframework.boot:spring-boot-starter-test'
    14. testImplementation 'org.springframework.security:spring-security-test'
    15. }
    16. ...
  2. 更改 application.yml 如下

  1. [server:
  2. port: 9000
  3. logging:
  4. level:
  5. root: INFO
  6. org.springframework.web: INFO
  7. org.springframework.security: INFO
  8. org.springframework.security.oauth2: INFO
  9. spring:
  10. datasource:
  11. driver-class-name: com.mysql.cj.jdbc.Driver
  12. url: jdbc:mysql://localhost:3306/auth-center?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
  13. username: root
  14. password: 123456](<server:
  15. port: 9000
  16. logging:
  17. level:
  18. root: INFO
  19. org.springframework.web: INFO
  20. org.springframework.security: INFO
  21. org.springframework.security.oauth2: INFO
  22. spring:
  23. datasource:
  24. driver-class-name: com.mysql.cj.jdbc.Driver
  25. url: jdbc:mysql://localhost:3306/auth-center?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
  26. username: root
  27. password: 123456
  28. client:
  29. registers:
  30. - client-id: mobile-gateway-client
  31. client-secret: "{noop}123456"
  32. authentication-method: client_secret_basic
  33. grant-types:
  34. - authorization_code
  35. - refresh_token
  36. - client_credentials
  37. scopes:
  38. - openid
  39. - message.read
  40. - message.write
  41. redirect-uris:
  42. - http://127.0.0.1:9100/login/oauth2/code/mobile-gateway-client-oidc
  43. - http://127.0.0.1:9100/authorized>)

读取配置 ConfigurationProperties

  1. ...
  2. @ConfigurationProperties(prefix = "client")
  3. @ConstructorBinding
  4. public record RegisterClientConfig(List<Register> registers) {
  5. public record Register(String clientId, String clientSecret, String authenticationMethod, List<String> grantTypes,
  6. List<String> scopes, List<String> redirectUris) {
  7. }
  8. }

添加 Member 对象

  1. @Getter
  2. @Setter
  3. @ToString
  4. @AllArgsConstructor
  5. @RequiredArgsConstructor
  6. public class Member implements UserDetails {
  7. private Long id;
  8. private String loginAccount;
  9. private String password;
  10. @Transient
  11. private List<GrantedAuthority> authorities;
  12. @Override
  13. public Collection<? extends GrantedAuthority> getAuthorities() {
  14. return AuthorityUtils.createAuthorityList("read", "write");
  15. }
  16. @Override
  17. public String getPassword() {
  18. return password;
  19. }
  20. @Override
  21. public String getUsername() {
  22. return loginAccount;
  23. }
  24. @Override
  25. public boolean isAccountNonExpired() {
  26. return true;
  27. }
  28. @Override
  29. public boolean isAccountNonLocked() {
  30. return true;
  31. }
  32. @Override
  33. public boolean isCredentialsNonExpired() {
  34. return true;
  35. }
  36. @Override
  37. public boolean isEnabled() {
  38. return true;
  39. }
  40. }

添加 MbrRepository

  1. @Repository
  2. public interface MbrRepository extends CrudRepository<Member, Long> {
  3. Optional<Member> findByLoginAccount(String loginAccount);
  4. }

MbrService

  1. public interface MbrService extends UserDetailsService {
  2. }

UserDetailsServiceImp

  1. @Service
  2. @RequiredArgsConstructor
  3. public class UserDetailsServiceImp implements MbrService {
  4. private final MbrRepository mbrRepository;
  5. @Override
  6. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  7. return mbrRepository.findByLoginAccount(username).orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
  8. }
  9. }

AuthorizationServerConfig

  1. ...
  2. [@Configuration(proxyBeanMethods = false)
  3. public class AuthorizationServerConfig {
  4. @Bean
  5. @Order(Ordered.HIGHEST_PRECEDENCE)
  6. public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
  7. OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
  8. return http.formLogin(withDefaults()).build();
  9. }
  10. @Bean
  11. public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
  12. return new JdbcRegisteredClientRepository(jdbcTemplate);
  13. }
  14. @Bean
  15. public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
  16. return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
  17. }
  18. @Bean
  19. public JWKSource<SecurityContext> jwkSource() {
  20. RSAKey rsaKey = Jwks.generateRsa();
  21. JWKSet jwkSet = new JWKSet(rsaKey);
  22. return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
  23. }
  24. @Bean
  25. public ProviderSettings providerSettings() {
  26. return ProviderSettings.builder().issuer("http://localhost:9000").build();
  27. }
  28. }](<@EnableWebSecurity
  29. @Configuration(proxyBeanMethods = false)
  30. @RequiredArgsConstructor
  31. public class AuthorizationServerConfig {
  32. private final JdbcTemplate jdbcTemplate;
  33. private final RegisterClientConfig clientConfig;
  34. private final MbrService mbrService;
  35. @Bean
  36. @Order(1)
  37. public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
  38. OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
  39. http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
  40. .exceptionHandling((exceptions) -%3E exceptions
  41. .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
  42. );
  43. return http.build();
  44. }
  45. @Bean
  46. @Order(2)
  47. public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
  48. http.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
  49. .userDetailsService(mbrService)
  50. .formLogin(withDefaults());
  51. return http.build();
  52. }
  53. @Bean
  54. public RegisteredClientRepository registeredClientRepository() {
  55. return new JdbcRegisteredClientRepository(jdbcTemplate);
  56. }
  57. @Bean
  58. public OAuth2AuthorizationService authorizationService(RegisteredClientRepository registeredClientRepository, PasswordEncoder passwordEncoder) {
  59. clientConfig.registers().forEach(cfg -> {
  60. RegisteredClient registeredClientFromDb = registeredClientRepository.findByClientId(cfg.clientId());
  61. if (registeredClientFromDb != null) {
  62. return;
  63. }
  64. RegisteredClient.Builder registerBuilder = RegisteredClient.withId(UUID.randomUUID().toString())
  65. .clientId(cfg.clientId())
  66. .clientSecret(passwordEncoder.encode(cfg.clientSecret()))
  67. .clientAuthenticationMethod(new ClientAuthenticationMethod(cfg.authenticationMethod()));
  68. cfg.grantTypes().forEach(grantType -> registerBuilder.authorizationGrantType(new AuthorizationGrantType(grantType)));
  69. cfg.redirectUris().forEach(registerBuilder::redirectUri);
  70. cfg.scopes().forEach(registerBuilder::scope);
  71. registeredClientRepository.save(registerBuilder.build());
  72. });
  73. JdbcOAuth2AuthorizationService jdbcOAuth2AuthorizationService = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
  74. jdbcOAuth2AuthorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository));
  75. return jdbcOAuth2AuthorizationService;
  76. }
  77. @Bean
  78. public JWKSource%3CSecurityContext> jwkSource() {
  79. RSAKey rsaKey = Jwks.generateRsa();
  80. JWKSet jwkSet = new JWKSet(rsaKey);
  81. return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
  82. }
  83. @Bean
  84. public ProviderSettings providerSettings() {
  85. return ProviderSettings.builder().issuer("http://localhost:9000").build();
  86. }
  87. @Bean
  88. public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
  89. return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
  90. }
  91. static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
  92. RowMapper(RegisteredClientRepository registeredClientRepository) {
  93. super(registeredClientRepository);
  94. getObjectMapper().addMixIn(Member.class, MemberMixin.class);
  95. }
  96. }
  97. @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
  98. @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
  99. isGetterVisibility = JsonAutoDetect.Visibility.NONE)
  100. @JsonIgnoreProperties(ignoreUnknown = true)
  101. @JsonDeserialize(using = MemberDeserializer.class)
  102. static class MemberMixin {
  103. }
  104. }>)

EncoderConfig

  1. @Configuration
  2. public class EncoderConfig {
  3. @Bean
  4. @ConditionalOnMissingBean(PasswordEncoder.class)
  5. public PasswordEncoder passwordEncoder() {
  6. return new BCryptPasswordEncoder();
  7. }
  8. }

MemberDeserializer

  1. public class MemberDeserializer extends JsonDeserializer<Member> {
  2. @Override
  3. public Member deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
  4. ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
  5. JsonNode jsonNode = mapper.readTree(jsonParser);
  6. Long id = readJsonNode(jsonNode, "id").asLong();
  7. String loginAccount = readJsonNode(jsonNode, "loginAccount").asText();
  8. String password = readJsonNode(jsonNode, "password").asText();
  9. List<GrantedAuthority> authorities = mapper.readerForListOf(GrantedAuthority.class).readValue(jsonNode.get("authorities"));
  10. return new Member(id, loginAccount, password, authorities);
  11. }
  12. private JsonNode readJsonNode(JsonNode jsonNode, String field) {
  13. return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
  14. }
  15. }

启动服务

  1. @SpringBootApplication
  2. @ConfigurationPropertiesScan
  3. public class AuthCenterApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(AuthCenterApplication.class, args);
  6. }
  7. }

总结

  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. 下载jar包方法

    第一种通用下载jar包方法 apache官网下载jar包地址:http://ftp.cuhk.edu.hk/pub/packages/apache.org/   第二种通用下载jar包方法  mave ...

  2. 关于#pragma 和 _pragma

    首先要明确 #pragma 和_Pragma 是什么 这两个都是出自于c/c++ 的 ,其中#pragma 是预处理指令(preProcess directive ) ,#pragma是用来向编译器传 ...

  3. Dart语言基础

    文章目录 前言:dart语言简介 一.变量 1.1.类型推导 1.2.默认值 1.3.Final 和 const修饰符 二.内建类型 2.1.数据类型 2.2.集合的相关操作 三.函数 3.1.函数的 ...

  4. equals 与 == 区别

    1.对于==: 基本数据类型:byte,short,char,int,long,float,double,boolean. 基本数据类型之间的比较,对于==,比较的是他们存储的"值" ...

  5. css 第二排文字居中

    text-align: center; 超过长度 自动换行居中

  6. perf性能分析工具使用分享

    @ 目录 前言 perf的介绍和安装 perf基本使用 perf list使用,可以列出所有的采样事件 perf stat 概览程序的运行情况 perf top实时显示当前系统的性能统计信息 perf ...

  7. C# 滑动验证码|拼图验证|SlideCaptcha

    使用背景: 关于滑动验证码的使用场所还是非常多的,如: 调取短信接口之前,和 注册请求之前 或者 频繁会调用的接口 都需要加这个拼图验证.这里先上一下效果图吧(心中无码,自然高清). 话不多说,开撸! ...

  8. Java枚举类与常用方法

    小简博客 - 小简的技术栈,专注Java及其他计算机技术.互联网技术教程 (ideaopen.cn) 枚举类 如何创建 首先,从名字就可以看出,枚举是一个类,那么我们就可以直接在创建时选择枚举就可以. ...

  9. Mybatis执行多条SQL

    1:在数据库连接配置文件处,增加如下参数即可:allowMultiQueries=true spring: datasource: url: jdbc:mysql://IP:PORT/数据库名?其他参 ...

  10. Lab_1:练习1——理解通过make生成执行文件的过程

    lab_0 清华大学ucore实验环境配置详细步骤!(小白入) lab_1 清华大学ucore bootload启动ucore os(预备知识) Lab_1:练习1--理解通过make生成执行文件的过 ...