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.
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<HttpSecurity> 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
