Configure Spring Authorization Server with JDBC Persistence in Spring Boot

This tutorial walks through setting up a Spring Authorization Server in a Spring Boot servlet project, adding required dependencies, configuring filter chains, persisting OAuth2 clients, authorizations, and consents with JDBC, and defining JWK sources and provider settings for a complete OAuth2 authorization server.

Programmer DD
Programmer DD
Programmer DD
Configure Spring Authorization Server with JDBC Persistence in Spring Boot

Configuration Dependencies

First create a Spring Boot servlet web project and add the following Maven dependencies to integrate Spring Authorization Server:

<!--  spring security starter must be included  -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-authorization-server</artifactId>
  <version>0.2.0</version>
</dependency>

The OAuth2.0 client must be registered and persisted. Spring Authorization Server provides a JDBC implementation via JdbcRegisteredClientRepository. For the demo we use an H2 database, requiring these additional dependencies:

<!--  jdbc must be included otherwise implement yourself  -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
</dependency>
In production you can switch to another relational database; the DDL scripts are included in the demo.

Spring Authorization Server Configuration

Filter Chain Configuration

Inject specific filters into the Spring Security filter chain using OAuth2AuthorizationServerConfigurer<HttpSecurity>. The default configuration is:

void defaultOAuth2AuthorizationServerConfigurer(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
        new OAuth2AuthorizationServerConfigurer<>();
    // TODO: customize the configurer as needed
    RequestMatcher authorizationServerEndpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
    http.requestMatcher(authorizationServerEndpointsMatcher)
        .authorizeRequests().anyRequest().authenticated().and()
        .csrf(csrf -> csrf.ignoringRequestMatchers(authorizationServerEndpointsMatcher))
        .formLogin()
        .and()
        .apply(authorizationServerConfigurer);
}
You can call the configuration methods provided by OAuth2AuthorizationServerConfigurer&lt;HttpSecurity&gt; for further customization.

OAuth2.0 Client Persistence

Client information is persisted to the database. Spring Authorization Server provides three DDL scripts. In the demo the H2 database automatically runs these scripts; when using MySQL you may need to execute them manually.

Client Registration

The following DDL creates the oauth2_registered_client table, and the corresponding Java class is RegisteredClient:

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) NULL,
    client_secret_expires_at timestamp 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) NULL,
    scopes varchar(1000) NOT NULL,
    client_settings varchar(2000) NOT NULL,
    token_settings varchar(2000) NOT NULL,
    PRIMARY KEY (id)
);

Key fields of the Java class (simplified):

public class RegisteredClient implements Serializable {
    private String id;
    private String clientId;
    private Instant clientIdIssuedAt;
    private String clientSecret;
    private Instant clientSecretExpiresAt;
    private String clientName;
    private Set<ClientAuthenticationMethod> clientAuthenticationMethods;
    private Set<AuthorizationGrantType> authorizationGrantTypes;
    private Set<String> redirectUris;
    private Set<String> scopes;
    private ClientSettings clientSettings;
    private TokenSettings tokenSettings;
    // ... getters, setters, builder etc.
}

Example builder to create a client:

RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
    .clientId("felord-client")
    .clientSecret("secret")
    .clientName("felord")
    .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
    .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
    .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
    .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
    .redirectUri("http://127.0.0.1:8080/login/oauth2/code/felord-oidc")
    .redirectUri("http://127.0.0.1:8080/authorized")
    .redirectUri("http://127.0.0.1:8080/foo/bar")
    .redirectUri("https://baidu.com")
    .scope(OidcScopes.OPENID)
    .scope("message.read")
    .scope("message.write")
    .tokenSettings(TokenSettings.builder().build())
    .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
    .build();

Persisted JSON representation of the client:

{
  "id":"658cd010-4d8c-4824-a8c7-a86b642299af",
  "client_id":"felord-client",
  "client_id_issued_at":"2021-11-11 18:01:09",
  "client_secret":"{bcrypt}$2a$10$XKZ8iUckDcdQWnqw682zV.DVyGuov8Sywx1KyAn4tySsw.Jtltg0.",
  "client_secret_expires_at":null,
  "client_name":"felord",
  "client_authentication_methods":"client_secret_basic",
  "authorization_grant_types":"refresh_token,client_credentials,authorization_code",
  "redirect_uris":"http://127.0.0.1:8080/foo/bar,http://127.0.0.1:8080/authorized,http://127.0.0.1:8080/login/oauth2/code/felord-oidc,https://baidu.com",
  "scopes":"openid,message.read,message.write",
  "client_settings":"{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"settings.client.require-proof-key\":false,\"settings.client.require-authorization-consent\":true}",
  "token_settings":"{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"settings.token.reuse-refresh-tokens\":true,\"settings.token.id-token-signature-algorithm\":[\"org.springframework.security.oauth2.jose.jws.SignatureAlgorithm\",\"RS256\"],\"settings.token.access-token-time-to-live\":[\"java.time.Duration\",300.0],\"settings.token.refresh-token-time-to-live\":[\"java.time.Duration\",3600.0]}"
}
The configuration must match the settings of your OAuth2.0 client application.

OAuth2 Authorization Persistence

Authorization records are stored in the oauth2_authorization table. DDL:

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 varchar(4000) NULL,
    state varchar(500) NULL,
    authorization_code_value blob NULL,
    `authorization_code_issued_at` timestamp NULL,
    authorization_code_expires_at timestamp NULL,
    authorization_code_metadata varchar(2000) NULL,
    access_token_value blob NULL,
    access_token_issued_at timestamp NULL,
    access_token_expires_at timestamp NULL,
    access_token_metadata varchar(2000) NULL,
    access_token_type varchar(100) NULL,
    access_token_scopes varchar(1000) NULL,
    oidc_id_token_value blob NULL,
    oidc_id_token_issued_at timestamp NULL,
    oidc_id_token_expires_at timestamp NULL,
    oidc_id_token_metadata varchar(2000) NULL,
    refresh_token_value blob NULL,
    refresh_token_issued_at timestamp NULL,
    refresh_token_expires_at timestamp NULL,
    refresh_token_metadata varchar(2000) NULL,
    PRIMARY KEY (id)
);
The internal mechanism is not covered in this tutorial.

Declare a RegisteredClientRepository bean that uses the JDBC implementation:

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

Persist the client by calling save(RegisteredClient).

This implementation depends on spring-boot-starter-jdbc ; you may also use MyBatis.

OAuth2 Authorization Consent Persistence

Consent information is stored in oauth2_authorization_consent:

CREATE TABLE oauth2_authorization_consent (
    registered_client_id varchar(100) NOT NULL,
    principal_name varchar(200) NOT NULL,
    authorities varchar(1000) NOT NULL,
    PRIMARY KEY (registered_client_id, principal_name)
);

Declare the corresponding service bean:

@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
        RegisteredClientRepository registeredClientRepository) {
    return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}

Example JSON representation of a consent record:

{
  "registered_client_id":"658cd010-4d8c-4824-a8c7-a86b642299af",
  "principal_name":"felord",
  "authorities":"SCOPE_message.read,SCOPE_message.write"
}

JWK (JSON Web Key)

JWK describes cryptographic keys in JSON and is part of the JOSE specifications. An example JWK set:

{
  "keys":[
    {
      "kty":"RSA",
      "x5t#S256":"VFGQxCmgHIh_gaF-wPb1xC9oKA1w5la0EFmqqPMrqmw",
      "e":"AQAB",
      "kid":"felordcn",
      "x5c":["MIIDcz... (truncated) ..."],
      "n":"go0TPk1td7iROmmLcGbOsZ2F68kTertDwRyk-leqBl-qyJAkjoVgVaCRRQHZmvu_YGp93vOaEd_zFdVj_rFvMXmwBxgYPOeSG0bHkYtFBaUiLf1vhW5lyiPHcGide3uw1p-il3JNiOpcnLCbAKZgzm4qaugeuOD02_M0YcMW2Jqg3SUWpC-9vu9yt5dVc1xpmpwEAamKzvynI3Zxl44ddlA8RRAS6kV0OUcKbEG63G3yZ4MHnhrFrZDuvlwfSSgn0wFOC_b6mJ-bUxByMAXKD0d4DS2B2mVl7RO5AzL4SFcqtZZE3Drtcli67bsENyOQeoTVaKO6gu5PEEFlQ7pHKw"
    }
  ]
}
JWK is used to sign JWTs and to expose a JWK endpoint for resource servers to verify JWT signatures.

Public/Private Keys

The RSA key pair is generated with the RSASHA256 algorithm. In the demo a .jks keystore is used (you can also generate a PKCS12 keystore with OpenSSL).

JWKSource

Implement a JWKSource<SecurityContext> bean that loads the RSA key from the keystore:

@SneakyThrows
@Bean
public JWKSource<SecurityContext> jwkSource() {
    String path = "felordcn.jks";
    String alias = "felordcn";
    String pass = "123456";
    ClassPathResource resource = new ClassPathResource(path);
    KeyStore jks = KeyStore.getInstance("jks");
    char[] pin = pass.toCharArray();
    jks.load(resource.getInputStream(), pin);
    RSAKey rsaKey = RSAKey.load(jks, alias, pin);
    JWKSet jwkSet = new JWKSet(rsaKey);
    return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}

Provider Settings

Configure the issuer URL for the authorization server. In the demo we use http://localhost:{port}, but in production a domain name should be used.

@Bean
public ProviderSettings providerSettings(@Value("${server.port}") Integer port) {
    // TODO: replace with domain in production
    return ProviderSettings.builder().issuer("http://localhost:" + port).build();
}
You can edit your local hosts file to test with a custom domain.

Authorization Server Security Configuration

An additional security filter chain protects the authorization server itself. It enables form login and defines an in‑memory user that also acts as the resource owner.

@EnableWebSecurity(debug = true)
public class DefaultSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests(a -> a.anyRequest().authenticated())
            .formLogin();
        return http.build();
    }

    @Bean
    UserDetailsService users() {
        UserDetails user = User.builder()
            .username("felord")
            .password("password")
            .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode)
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.ignoring().antMatchers("/actuator/health", "/h2-console/**");
    }
}

With all the above configurations the Spring Authorization Server is fully set up. The next article will demonstrate how to implement OAuth2 login functionality.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootJDBCOAuth2Spring Authorization ServerJWK
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.