Store RegisteredClient to database in Spring Authorization Server

In the previous tutorial, I showed you how to implement an Authorization Server using Spring Authorization Server, but the information about RegisteredClient in this tutorial is stored in memory. To store RegisteredClient information in the database, how will we do it? In this tutorial, I will show you how to do this!

First, I also created a new Spring Boot project with Spring Web Starter, Spring Security Starter, Spring Data JPA, PostgreSQL Driver and OAuth2 Authorization Server Starter::

for example.

Result:

I will configure Spring Security as in the tutorial Implement OAuth Authorization Server using Spring Authorization Server as follows:

As for the configuration for the Authorization Server, I also do the same as the tutorial Implement OAuth Authorization Server using Spring Authorization Server, but I will declare the RegisteredClient information later:

To store RegisteredClient information in the database, first, we need to define the database structure to do this.

By default, Spring Authorization Server provides us with database scripts to create the database structure. You can copy them in the Spring Authorization Server .jar file:

You can go to the Github of the Spring Authorization Server here to copy these files.

I will use Flyway to manage database migration:

by copying the schema files of the Spring Authorization Server into the src/main/resources/db/migration directory as follows:

In the script that creates the oauth2_authorization table in the file V1__oauth2-authorization-schema.sql, there is a definition of the BLOB data type, presumably for the Oracle database:

If you are using a PostgreSQL database like me, you need to change to TEXT type! Otherwise, running the database migration will fail.

Declare the Datasource to run the database migration as follows:

Now you can define RegisteredClient in the database, for example as follows:

Here, I define a RegisteredClient with a grant type of client_credentials with a fixed ID so that every time I start the app, there is no duplicate record in the database. Depending on your needs, please write the corresponding code!

We will use the JdbcRegisteredClientRepository object to store this RegisteredClient information. The parameter when initializing the JdbcRegisteredClientRepository object is the JdbcTemplate object.

Now, if you run the application, you will see in the oauth2_registered_client table, a new record of RegisteredClient that I declared above, is inserted:

That’s it guys, if you now run the application and get the clientId token above, you will see the following results:

22 thoughts on “Store RegisteredClient to database in Spring Authorization Server

  1. i got invalid_client why i have checked all also use passwordEncoder too but still getting invalid client error why how can i fix it

  2. Can the registeredclient db schema be customized? I dont want the json data as it is not maintainable in production

  3. Hello, I have a question about the relationship between registeredclient and users. Can we have the same registeredClient for multiple users? There is also a principalName in the table, this will change according the user login. So how can we have one registeredclient for multiple logged user?
    Thank you in advance.

    1. For your first question, I can say yes. For one RegisteredClient, as a Client Application, we can use it for multiple logged users.
      Where is the principalName column in the table? Can you please let me know?

  4. I am getting the following error, can you help me with this? and in database client_setting column, it stores {"@class":"java.util.Collections$UnmodifiableMap","settings.client.require-proof-key":false,"settings.client.require-authorization-consent":false}

    java.lang.ClassNotFoundException: org.springframework.security.cas.jackson2.CasJackson2Module
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
    at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
    at java.base/java.lang.Class.forName(Class.java:488) ~[na:na]
    at java.base/java.lang.Class.forName(Class.java:467) ~[na:na]
    at org.springframework.util.ClassUtils.forName(ClassUtils.java:284) ~[spring-core-5.3.22.jar:5.3.22]
    at org.springframework.security.jackson2.SecurityJackson2Modules.loadAndGetInstance(SecurityJackson2Modules.java:107) ~[spring-security-core-5.7.3.jar:5.7.3]
    at org.springframework.security.jackson2.SecurityJackson2Modules.addToModulesList(SecurityJackson2Modules.java:152) ~[spring-security-core-5.7.3.jar:5.7.3]
    at org.springframework.security.jackson2.SecurityJackson2Modules.getModules(SecurityJackson2Modules.java:126) ~[spring-security-core-5.7.3.jar:5.7.3]
    at org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository$RegisteredClientParametersMapper.(JdbcRegisteredClientRepository.java:304) ~[spring-security-oauth2-authorization-server-0.3.1.jar:0.3.1]
    at org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.(JdbcRegisteredClientRepository.java:116) ~[spring-security-oauth2-authorization-server-0.3.1.jar:0.3.1]
    at in.thirumal.config.AuthorizationServerConfig.registeredClientRepository(AuthorizationServerConfig.java:71) ~[classes/:na]
    at in.thirumal.config.AuthorizationServerConfig$$EnhancerBySpringCGLIB$$4a833596.CGLIB$registeredClientRepository$0() ~[classes/:na]
    at in.thirumal.config.AuthorizationServerConfig$$EnhancerBySpringCGLIB$$4a833596$$FastClassBySpringCGLIB$$a903bd09.invoke() ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.3.22.jar:5.3.22]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.22.jar:5.3.22]
    at in.thirumal.config.AuthorizationServerConfig$$EnhancerBySpringCGLIB$$4a833596.registeredClientRepository() ~[classes/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:577) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1284) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1245) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172) ~[spring-context-5.3.22.jar:5.3.22]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ConfigurerUtils.getBean(OAuth2ConfigurerUtils.java:184) ~[spring-security-oauth2-authorization-server-0.3.1.jar:0.3.1]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ConfigurerUtils.getRegisteredClientRepository(OAuth2ConfigurerUtils.java:62) ~[spring-security-oauth2-authorization-server-0.3.1.jar:0.3.1]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ClientAuthenticationConfigurer.createDefaultAuthenticationProviders(OAuth2ClientAuthenticationConfigurer.java:165) ~[spring-security-oauth2-authorization-server-0.3.1.jar:0.3.1]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ClientAuthenticationConfigurer.init(OAuth2ClientAuthenticationConfigurer.java:135) ~[spring-security-oauth2-authorization-server-0.3.1.jar:0.3.1]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer.lambda$init$1(OAuth2AuthorizationServerConfigurer.java:228) ~[spring-security-oauth2-authorization-server-0.3.1.jar:0.3.1]
    at java.base/java.util.LinkedHashMap$LinkedValues.forEach(LinkedHashMap.java:647) ~[na:na]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer.init(OAuth2AuthorizationServerConfigurer.java:228) ~[spring-security-oauth2-authorization-server-0.3.1.jar:0.3.1]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer.init(OAuth2AuthorizationServerConfigurer.java:69) ~[spring-security-oauth2-authorization-server-0.3.1.jar:0.3.1]
    at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.init(AbstractConfiguredSecurityBuilder.java:338) ~[spring-security-config-5.7.3.jar:5.7.3]
    at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:300) ~[spring-security-config-5.7.3.jar:5.7.3]
    at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38) ~[spring-security-config-5.7.3.jar:5.7.3]
    at org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration.authorizationServerSecurityFilterChain(OAuth2AuthorizationServerConfiguration.java:55) ~[spring-security-oauth2-authorization-server-0.3.1.jar:5.7.3]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:577) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.22.jar:5.3.22]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.22.jar:5.3.22]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.3.jar:2.7.3]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-2.7.3.jar:2.7.3]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.3.jar:2.7.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-2.7.3.jar:2.7.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-2.7.3.jar:2.7.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-2.7.3.jar:2.7.3]
    at in.thirumal.OauthAuthorizationServerApplication.main(OauthAuthorizationServerApplication.java:14) ~[classes/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:577) ~[na:na]
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.7.3.jar:2.7.3]

    1. Have you checked this error “java.lang.ClassNotFoundException: org.springframework.security.cas.jackson2.CasJackson2Module”? I think you need to have the lib spring-security-cas in your Maven dependency.

  5. I am using MySQL as the database my client is register in database but when i try to get the token i am getting response unauthorized:

  6. Amazing article! Thanks! By the way, do you know how to pass the role parameter in the JWT to access to protect endpoints in Resource Server with those?

  7. Thanks nice article

    I get too many redirection error when I started using JDBC, But when I use Inmemory database it is working fine
    I do not know why its happening. Any Idea please. Thanks in advance

    1. It happened to me also when mi registered client is like the Spring Authorization Server git project example (with the registered client for this example it works ok). It put TRACE log and compared with the log for InMemory and it has a Access denied example.

      Any idea how to solve this issued is welcome

  8. Hi, thanks for these articles, they are really helpful. It would be great if you could cover the dynamic client registration endpoint ( /connect/register ) and the userinfo ( /userinfo ), both available in version 0.2.1.

  9. Thank you for the article. It is very informative. Is there currently any way to configure the authorization server to store the generated authentication tokens in the database, so when the authorization server is restarted, the authentication tokens that haven’t expired yet are still recognized by the authorization server?

Add Comment