Spring Boot 2.1.1 Issue Accessing Tables in PostgreSQL that are within Oauth2 - spring

I am trying to add postgresql support for OAuth2 with Spring Boot after following some examples to clean up my code.
The problem is that I have to add a schema manually to my own schema scripts for the database to create despite specifying certain properties in the latest Spring Boot. Only after doing so do the tables become recognizable. Also, OAuth2 continues to fail, not finding any table. The issue seems schema related.
I was able to get my own schema to be recognized without specifying one in the entity by disabling auto configuration but cannot figure out why OAuth2 tables are not found unless I use the public schema.
I have added the following properties file with attempts to get Spring boot to recognize my schemas:
server.port=8081
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
# The SQL dialect makes Hibernate generate better SQL for the chosen database
# Hibernate ddl auto (create, create-drop, validate, update)
spring.datasource.jdbcUrl=jdbc:postgresql://xxxxxx:xxx/oauth2
spring.datasource.username= xxxxx
spring.datasource.password= xxxxx
spring.datasource.hikari.minimumIdle=5
spring.datasource.hikari.maximumPoolSize=20
spring.datasource.hikari.idleTimeout=30000
spring.datasource.hikari.poolName=SpringBootJPAHikariCP
spring.datasource.hikari.maxLifetime=2000000
spring.datasource.hikari.connectionTimeout=30000
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true;
spring.jpa.properties.hibernate.current_session_context_class=thread
spring.jpa.hibernate.ddl-auto=none
spring.datasource.tomcat.default-auto-commit=true
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQL95Dialect
spring.jpa.hibernate.connection.provider_class=org.hibernate.hikaricp.internal.HikariCPConnectionProvider
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql=true
spring.jpa.properties.hibernate.id.new_generator_mappings=false
spring.jpa.properties.hibernate.search.autoregister_listeners=false
spring.jpa.properties.hibernate.bytecode.use_reflection_optimizer=false
spring.jpa.properties.hibernate.default_schema = public
# spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
# spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# Hibernate ddl auto (create, create-drop, validate, update)
spring.thymeleaf.templateResolverOrder=1
logging.level.org.springframework=INFO
entitymanager.packagesToScan = com.si.model
logging.level.org.springframework.web=DEBUG
logging.level.guru.springframework.controllers=DEBUG
logging.level.org.hibernate=INFO
spring.main.allow-bean-definition-overriding=true
spring.thymeleaf.cache=false
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
spring.datasource.schema=classpath:schema.sql
spring.datasource.initialization-mode=always
spring.jpa.properties.hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.template.cache: false
spring.jpa.properties.hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
I have also tried setting the Data Source for each object in the Authorization Server:
#Configuration
#EnableAuthorizationServer
class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
def oauthDataSource: DataSource = DataSourceBuilder.create.build
#Autowired
private var authenticationManager: AuthenticationManager = null
#Bean
def clientDetailsService = new JdbcClientDetailsService(oauthDataSource)
#Bean
def tokenStore = new JdbcTokenStore(oauthDataSource)
#Bean
def approvalStore = new JdbcApprovalStore(oauthDataSource)
#Bean
def authorizationCodeServices = new JdbcAuthorizationCodeServices(oauthDataSource)
#Bean
def passwordEncoder(): Pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder()
#throws[Exception]
override def configure(clients: ClientDetailsServiceConfigurer): Unit = {
clients.jdbc(oauthDataSource)
}
#throws[Exception]
override def configure(oauthServer: AuthorizationServerSecurityConfigurer): Unit = {
oauthServer.passwordEncoder(passwordEncoder)
}
#throws[Exception]
override def configure(endpoints: AuthorizationServerEndpointsConfigurer): Unit = {
endpoints
.authenticationManager(authenticationManager)
.approvalStore(approvalStore)
.authorizationCodeServices(authorizationCodeServices)
.tokenStore(tokenStore)
}
}
Finally, I set up my web configuration as follows:
class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private var passwordEncoder: Pbkdf2PasswordEncoder = null
#Bean
#Primary
#ConfigurationProperties(prefix = "spring.datasource")
def dataSource: DataSource = DataSourceBuilder.create.`type`(classOf[HikariDataSource]).build
#Bean
#throws[Exception]
override def authenticationManagerBean: AuthenticationManager = super.authenticationManagerBean
#Bean
#throws[Exception]
override def userDetailsServiceBean = new JdbcUserDetails()
#throws[Exception]
override def configure(web: WebSecurity): Unit = {
web.ignoring.antMatchers("/webjars/**", "/resources/**")
}
#throws[Exception]
override protected def configure(http: HttpSecurity): Unit = {
http
.csrf().disable()
.authorizeRequests
.antMatchers("/login", "/logout.do")
.permitAll
.antMatchers("/**")
.authenticated
.and.
formLogin
.loginProcessingUrl("/login.do")
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/login")
.and
.logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout.do"))
.and
.userDetailsService(userDetailsServiceBean)
}
#throws[Exception]
override protected def configure(auth: AuthenticationManagerBuilder): Unit = {
auth
.userDetailsService(userDetailsServiceBean)
.passwordEncoder(passwordEncoder)
}
}
For some reason, I had to set my schemas to public in the schema script:
drop table if exists public.oauth_client_token cascade;
create table if not exists public.oauth_client_token (
token_id VARCHAR(255) not null primary key,
token bytea,
authentication_id VARCHAR(255),
user_name VARCHAR(255),
client_id VARCHAR(255)
);
drop table if exists public.oauth_client_details cascade;
CREATE TABLE if not exists public.oauth_client_details (
client_id varchar(255) NOT NULL primary key,
resource_ids varchar(255) DEFAULT NULL,
client_secret varchar(255) DEFAULT NULL,
scope varchar(255) DEFAULT NULL,
authorized_grant_types varchar(255) DEFAULT NULL,
web_server_redirect_uri varchar(255) DEFAULT NULL,
authorities varchar(255) DEFAULT NULL,
access_token_validity integer,
refresh_token_validity integer,
additional_information varchar(255) DEFAULT NULL,
autoapprove varchar(255) DEFAULT NULL
);
drop table if exists public.oauth_access_token cascade;
create table if not exists public.oauth_access_token(
token_id VARCHAR(255),
token bytea,
authentication_id VARCHAR(255),
user_name VARCHAR(255),
client_id VARCHAR(255),
authentication bytea,
refresh_token VARCHAR(255)
);
drop table if exists public.oauth_refresh_token cascade;
create table if not exists public.oauth_refresh_token(
token_id VARCHAR(255),
token bytea,
authentication bytea
);
drop table if exists public.authorities cascade;
CREATE TABLE if not exists public.authorities (
id bigserial not null primary key,
authority varchar(255)
);
drop table if exists public.credentials cascade;
CREATE TABLE if not exists public.credentials (
id bigserial not null primary key,
enabled boolean not null,
name varchar(255) not null,
password varchar(255) not null,
version integer
);
drop table if exists public.credentials_authorities cascade;
CREATE TABLE if not exists public.credentials_authorities (
credentials_id bigint not null,
authorities_id bigint not null
);
drop table if exists public.oauth_code cascade;
create table if not exists public.oauth_code (
code VARCHAR(255),
authentication bytea
);
drop table if exists public.oauth_approvals cascade;
create table if not exists public.oauth_approvals (
userId VARCHAR(255),
clientId VARCHAR(255),
scope VARCHAR(255),
status VARCHAR(10),
expiresAt timestamp without time zone,
lastModifiedAt timestamp without time zone
);
Access to the oauth2 tables from the clientDetailsRepository or Oauth2 application results in errors such as the following:
org.postgresql.util.PSQLException: ERROR: relation "oauth_client_details" does not exist
As a last resort, I set the search path in postgresql to public for my user and still get this error.
UPDATE
The following query works in pgadmin4 and is the source of my failure:
select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove from oauth_client_details order by client_id
Any ideas as to why this happens? How can I resolve this?

Related

insert data from sql file H2 database spring boot

I'm trying to insert data from sql file using h2 database and spring boot .
when I add the sql file into src/main/ressources, I succeeded to create the table and insert data into it.
but when I create a model class named Employee which refer to the table, I can create the tables but there is no rows inserted .
SQL file:
CREATE TABLE employees ( id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(250) NOT NULL, last_name VARCHAR(250) NOT NULL,
mail VARCHAR(250) NOT NULL, password VARCHAR(250) NOT NULL );
INSERT INTO employees (first_name, last_name, mail, password) VALUES
('Laurent', 'GINA', 'laurentgina#mail.com', 'laurent');
model:
import javax.persistence.Column; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import
javax.persistence.GenerationType; import javax.persistence.Id; import
javax.persistence.Table;
import lombok.Data;
#Data #Entity #Table(name = "employees")
public class Employee {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="first_name")
private String first_name;
#Column(name="last_name")
private String last_name;
private String mail;
private String password;
}
application.properties
#Global configuration
spring.application.name=api
#Tomcat configuration
server.port=9000
#Log level configuration
logging.level.root=ERROR
logging.level.com.openclassrooms=INFO
logging.level.org.springframework.boot.autoconfigure.h2=INFO
logging.level.org.springframework.boot.web.embedded.tomcat=INFO
#H2 Configuration
spring.h2.console.enabled=true
There are many ways to initialize the database with spring boot:
Initialize a Database Using JPA
Initialize a Database Using Hibernate
Initialize a Database using basic SQL scripts
For your information: you should separate the schema from the data,
so you should have 2 basic scripts:
schema.sql : In this file you can put your instruction to create table.
CREATE TABLE employees ( id INT AUTO_INCREMENT PRIMARY KEY,first_name VARCHAR(250) NOT NULL, last_name VARCHAR(250) NOT NULL,mail VARCHAR(250) NOT NULL, password VARCHAR(250) NOT NULL );
data.sql : In this file you can put your instruction to insert data
INSERT INTO employees (first_name, last_name, mail, password) VALUES ('Laurent', 'GINA', 'laurentgina#mail.com', 'laurent');
if you choose to use the schema.sql : you shoud use this property spring.jpa.hibernate.ddl-auto=none.
otherwise (Hibernate) you shoud use this property spring.jpa.hibernate.ddl-auto=create-drop
For your information:
Note: spring boot version <= 2.4.x you can choose Hibernate to create the schema or use schema.sql, but you cannot do both.
Note: spring boot version >= 2.5.x if you want script-based DataSource initialization to be able to build upon the schema creation performed by Hibernate, set spring.jpa.defer-datasource-initialization to true.
Be careful it depends on which version you choose!
There are a few things that could be causing this issue.
First, make sure that your SQL file is located in the src/main/resources directory. If it is not, then the file will not be picked up by Spring Boot.
Next, check the permissions on the file. If the file is not readable by the application, then it will not be able to load the data.
Finally, make sure that the SQL file is formatted correctly. If there are any syntax errors in the file, then Spring Boot will not be able to load the data.

Why doesn't Mybatis map a simple ENUM correctly?

I'm not doing anything out of the ordinary from what I can tell. I have a spring boot application using mybatis:
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
I have an application.properties config for mybatis that is pretty simple:
## MyBatis ##
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-statement-timeout=30
My database table looks like this:
CREATE TABLE workspace_external_references (
id CHAR(36) PRIMARY KEY,
workspace_id CHAR(36) NOT NULL,
site VARCHAR(255) NOT NULL,
external_id VARCHAR(255) NOT NULL,
created_at DATETIME(6) NOT NULL DEFAULT NOW(6),
updated_at DATETIME(6) NOT NULL DEFAULT NOW(6),
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE
)
With just a single entry like this:
'a907c0af-216a-41e0-b16d-42107a7af05f', 'e99e4ab4-839e-405a-982b-08e00fbfb2d4', 'ABC', '6', '2020-06-09 00:19:20.135822', '2020-06-09 00:19:20.135822'
In my mapper file I'm doing a select of all references like this:
#Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
List<WorkspaceExternalReference> findByWorkspace(#Param("workspaceId") final UUID workspaceId);
And the java object that this is supposed to map to looks like this:
public class WorkspaceExternalReference {
private UUID id;
private UUID workspaceId;
private Sites site;
private String externalId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public WorkspaceExternalReference(
final Sites site,
final UUID workspaceId,
final String externalId) {
this.site = site;
this.workspaceId = workspaceId;
this.externalId = externalId;
}
}
public enum Sites {
ABC, XYZ;
}
Sooooo why doesn't this work? I get this error back:
Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'id' from result set. Cause: java.lang.IllegalArgumentException: No enum constant com.acme.Sites.a907c0af-216a-41e0-b16d-42107a7af05f
When there is no default constructor, you need to let MyBatis know which columns to pass to the constructor explicitly (in most cases).
With annotations, it would look as follows.
You can use <resultMap> and <constructor> in XML mapper.
#ConstructorArgs({
#Arg(column = "site", javaType = Sites.class),
#Arg(column = "workspace_id", javaType = UUID.class),
#Arg(column = "external_id", javaType = String.class)
})
#Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
List<WorkspaceExternalReference> findByWorkspace(#Param("workspaceId") final UUID workspaceId);
Other columns (i.e. id, created_at, updated_at) will be auto-mapped via setters (if there are) or reflection.
Alternatively, you can just add the default (no-arg) constructor to the WorkspaceExternalReference class. Then all columns will be auto-mapped after the class is instantiated.
Note: To make it work, there needs to be a type handler registered for UUID, but you seem to have done it already (otherwise the parameter mapping wouldn't work).

How to set collation for table attribute as utf8_bin in either annotation or application.properties file using Spring Boot

How do I set the collation for table attribute as utf8_bin in either annotation or the application.properties file using Spring Boot?
I have tried many ways but they did not work. Can you help?
I have tried the following ways.
First: Using #Column annotation like this:
#Column(name = "user_id",columnDefinition="VARCHAR(255) COLLATE utf8_bin")
Second:
#Column(columnDefinition="VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin")
Third: Using application.properties file
spring.jpa.properties.hibernate.connection.characterEncoding=utf-8
spring.jpa.properties.hibernate.connection.CharSet=utf-8
spring.jpa.properties.hibernate.connection.useUnicode=true
spring.jpa.properties.hibernate.connection.collationConnection=utf8_bin
Fourth:
spring.datasource.url =jdbc:mysql://localhost:3306/iot_schema?createDatabaseIfNotExist=true&useUnicode=true&connectionCollation=utf8_bin&characterSetResults=utf8
Here's a solution inspired by answer to similar question: Set Table character-set/collation using Hibernate Dialect?
Extend the preferred MySQL dialect and override its getTableTypeString() method like this:
public class MySQLCustomDialect extends MySQL8Dialect {
#Override
public String getTableTypeString() {
return " ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin";
}
}
Set that class to be used in application.properties:
spring.jpa.properties.hibernate.dialect=my.package.MySQLCustomDialect
Here's the generated SQL query:
create table test_table (
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin

Spring Security - Default Tables not created

I'am trying to integrate Spring Social on top of Spring Security in a Spring Boot application. But it seems like Spring Security is having issues creating the default tables, e.g. UserConnection, UserProfile, etc since I get these SQL errors after the connection to an oauth2 provider was successfully established:
PreparedStatementCallback; bad SQL grammar [select userId from UserConnection where providerId = ? and providerUserId = ?]; nested exception is org.h2.jdbc.JdbcSQLException: Tabelle "USERCONNECTION" nicht gefunden Table "USERCONNECTION" not found; SQL statement: select userId from UserConnection where providerId = ? and providerUserId = ? [42102-185]
This is a static SQL call in the spring provided JdbcUsersConnectionRepository. I tried to switch over to the InMemory implementation which avoids the SQL problem, but then the next one occurs:
PreparedStatementCallback; bad SQL grammar [INSERT into userProfile(userId, email, firstName, lastName, name, username) values(?,?,?,?,?,?)]; nested exception is org.h2.jdbc.JdbcSQLException: Tabelle "USERPROFILE" nicht gefunden Table "USERPROFILE" not found; SQL statement: INSERT into userProfile(userId, email, firstName, lastName, name, username) values(?,?,?,?,?,?) [42102-185]
The USERPROFILE Table is missing, too.
Before I post tons of configuration snippets, do you already know something I might have forgotten which tells spring to create these tables for me? :)
At the moment I am going with the Spring Boot standard H2 in-memory database, which works well with JpaRepositories.
Thank You! :)
Found the solution myself :)
I eventually found the very first thing wich is not handled by some fancy automagically Spring mechanism but with a plain 'schema.sql' in the src/main/resources directory.
create table UserConnection (
userId varchar(255) not null,
providerId varchar(255) not null,
providerUserId varchar(255),
rank int not null,
displayName varchar(255),
profileUrl varchar(512),
imageUrl varchar(512),
accessToken varchar(1024) not null,
secret varchar(255),
refreshToken varchar(255),
expireTime bigint,
primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, rank);
create table UserProfile (
userId varchar(255) not null,
email varchar(255),
firstName varchar(255),
lastName varchar(255),
name varchar(255),
username varchar(255),
primary key (userId));
create unique index UserProfilePK on UserProfile(userId);

Spring Security JDBC authentication default schema error when using PostgreSQL

Is it really impossible to use default schema for Spring Security with PostgreSQL, because the part "varchar_ignorecase" does not exist can't be replaced?
I'm just testing the default settings:
auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
And below is the error:
Caused by:
org.springframework.beans.factory.BeanDefinitionStoreException:
Factory method [public javax.servlet.Filter
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain()
throws java.lang.Exception] threw exception;
nested exception is
org.springframework.jdbc.datasource.init.ScriptStatementFailedException:
Failed to execute SQL script statement at line 1 of resource class
path resource
[org/springframework/security/core/userdetails/jdbc/users.ddl]: create
table users(username varchar_ignorecase(50) not null primary
key,password varchar_ignorecase(500) not null,enabled boolean not
null);
nested exception is
org.postgresql.util.PSQLException: ERROR:
type "varchar_ignorecase" does not exist
Cause the default schema is not suitable for PostgreSQL, I need to create my own schema and implement own query for user and authority query.
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select username,password, enabled from users where username=?")
.authoritiesByUsernameQuery(
"select username, role from user_roles where username=?");
There is no need to adding withDefaultSchema(). simply define your schema.sql and data.sql with proper column names and foreign key constraints. Following is the example configuration for Mysql (in the Spring Boot).
schema.sql
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS authorities;
CREATE TABLE users
(
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
enabled TINYINT NOT NULL DEFAULT 1,
PRIMARY KEY (username)
);
CREATE TABLE authorities
(
username VARCHAR(50) NOT NULL,
authority VARCHAR(50) NOT NULL,
FOREIGN KEY (username) REFERENCES users (username)
);
Then define users and authorities (data.sql)
INSERT INTO users (username, password, enabled)
values ('user','pass',1);
INSERT INTO authorities (username, authority)
values ('user', 'ROLE_USER');
finally, define the authentication mechanism
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.dataSource(dataSource);
}
Schema for PGSQL:
schema.sql
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS authorities;
CREATE TABLE users
(
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
enabled INT NOT NULL DEFAULT 1,
PRIMARY KEY (username)
);
CREATE TABLE authorities
(
username VARCHAR(50) NOT NULL,
authority VARCHAR(50) NOT NULL,
FOREIGN KEY (username) REFERENCES users (username)
);
User injection on data.sql
INSERT INTO users (username, password, enabled)
values ('user','pass',1);
INSERT INTO authorities (username, authority)
values ('user', 'ROLE_USER');
Thanks to Mr.Q. I totally used your MySQL schema and adapted it for PGSQL.

Resources