I have a table in my database which contains user information.
CREATE TABLE users
(
id_user serial NOT NULL,
phone text,
password text,
balance numeric,
email text,
CONSTRAINT users_pkey PRIMARY KEY (id_user),
CONSTRAINT users_login_key UNIQUE (phone)
)
I use Spring Security. Part of the config:
<security:jdbc-user-service id="userService"
data-source-ref="dataSource"
users-by-username-query="select phone, password, true from users where phone=?"
authorities-by-username-query="select phone,'ROLE_USER' from users where phone=?" />
How can I get, for example, current user balance in my Spring MVC Controller?
The JDBC service you defined in the Spring Security configuration will only fetch username, password and enabled status using the users-by-username-query query and username's authorities using the authorities-by-username-query query. This means that any extra attributes will not be mapped to the user principal object which will be created to populate the security context.
One way to get all information of your user is to create custom implementation of UserDetailsService which will be able to retrieve user data including all of its custom attributes. Here is an example of this approach (it assumes that you are using JPA/Hibernate for your data layer):
Entity
#Entity
#Table(name="users")
public class User implements UserDetails {
private BigDecimal balance;
// Other properties omitted ...
// Getters and setters omitted ...
// Implementation of UserDetails methods omitted ...
}
UserService
Service responsible for loading the user information. Note that in real world you should probably have separate DAO querying users, program against interfaces, etc. This example is deliberately kept as short as possible.
#Service
public class UserService implements UserDetailsService {
#Autowired
private SessionFactory sessionFactory;
#Override
public User loadUserByUsername(String username) throws UsernameNotFoundException {
Query query = sessionFactory.getCurrentSession().createQuery("FROM User u WHERE u.username = :username");
query.setParameter("username", username);
User user = (User) query.uniqueResult();
if (user == null) {
throw new UsernameNotFoundException("User with username '" + username + "' does not exist.");
}
return user;
}
}
Configuration
Create AuthenticationProvider bean and supply your custom user service as its property.
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userService" />
<!-- Other required properties ... -->
</bean>
Controller
You can access the current user and its properties in the controller using:
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
BigDecimal balance = user.getBalance();
As a final remark, please note that this is not 100% working copy and paste solution to your problem, rather a demonstration of the approach you can take to achive the desired result. I recommend looking through the documentation of core classes/interfaces such as AuthenticationProvider, UserDetailsService, UserDetails, and Spring Security guides for more details.
Related
I am building a REST-API with spring boot and I would like to implement a Multi-Tenant Structure to handle data.
I want to have one Database called Main that will have the User table, which will hava data about users (Username, password ... and a field database that will denote which database is appointed to this user).
Everytime a user signs up his respective DB will be created (This is one of the points where I am facing difficulties).
I have read different Tutorials and they all are with predefined Datasources in the application.properties file. Clearly this is not the case here, since the DB for each user will be created "on the fly", or accessed if it is already created.
The workflow is like this ( explained as simple as possible ):
User signs-up
The app creates the User Entity saves it to Main DB and creates the respective DB for the user
The app checks for each call if the user is authenticated, if he is, then go and fetch data from his DB
Then there are a lot of questions regarding filling the DBs when they are automatically created.
But first things first :)
My stack : POSTGRESQL, Spring Boot
Thank you in advance.
multi-tenancy can be achieved as you required by following steps.
Add 2 configuration classes one for shared database and one for tenant database, which configurs LocalContainerEntityManagerFactoryBean. This bean should set the required multitenancy properties for LocalContainerEntityManagerFactoryBean
e.g.
Map<String, Object> properties = hibernateProperties.determineHibernateProperties(
this.properties.getProperties(), new HibernateSettings());
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, this.connectionProvider);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, this.resolver);
properties.put(Environment.DIALECT, "org.hibernate.dialect.MySQLDialect");
This class should also implement named bean transactionManager for each type. e.g.
#Bean(name = "tenantTransactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(this.entityManagerFactory().getObject());
return tm;
}
implement interface CurrentTenantIdentifierResolver and method resolveCurrentTenantIdentifier. This should return the database name of the tenant based on the current logged-in user. Or default database name if no user is logged-in
A thread safe context holder to remember the current tenant name
Annotate the services implementations for the entity classes with #Transactional annotation and pass the bean name of appropriate entity manager e.g.
#Transactional("tenantTransactionManager") // for tenant database
and
#Transactional("transactionManager") // for shared database.
Setup a database schema creation method when a new users signs up. and maintain the tenant database name as one of the column in user table in shared schema.
If you are using spring security, implement UserDetailsService interface and implement method loadUserByUsername such that it returns an object of TenantUser class which contains additional information ( tenant database name) for the user logging in.
public class TenantUser extends org.springframework.security.core.userdetails.User {
/** The tenand id. */
private String tenantId;
Hope these steps help you to achieve what you want. There are many articles available which explains all these steps in detail. My implementation is deep embedded in my project hence it is not in a state which can be shared as working example.
Happy to answer any further questions
I found the complete solution to my problem here:
Multi-tenancy: Managing multiple datasources with Spring Data JPA
Big thanks to the author #Cepr0.
The only thing that is missing is creating the DB on the fly.
I will update the answer here when I finish my implementation.
UPDATE
I created the Database with the following code, it was recommended by #Milind Barve. So thank you.
Class.forName("org.postgresql.Driver");
Connection con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/","postgres", "password");
Statement smt = con.createStatement();
smt.executeUpdate("CREATE DATABASE [name_of_db_here] WITH OWNER DEFAULT");
UPDATE :
Initializing the schema of each newly created DB,
I created a .sql file with all the table creations and use FlyWay to initialize each newly created DB
// INITIALIZE THE DB
Flyway flyway = Flyway.configure()
.dataSource(dataSource)
.target(MigrationVersion.LATEST)
.load();
flyway.migrate();
I have a JSF managed bean in session scope that contains an entity to trace, for example, the authenticated user:
#ManagedBean
#SessionScoped
public class LoginController implements Serializable {
User user;
public User getUser() {
return this.user;
}
public void setUser(User user) {
this.user = user;
}
/* ... */
}
In another bean I have to inject the user to use it to retrieve a list of roles in association with it, like this:
#ManagedBean
#ViewScoped
public class AnotherController implements Serializable {
List<Role> roles;
#ManagedProperty(value="#{loginController.user}")
User user;
public someMethod() {
/* Some stuff that insert other roles into database, referring the user as owner */
roles = user.getRolesList();
}
}
If I update the page with ajax using someMethod, the roles list still not reload.
If I insert em.refresh(user) before user.getRolesList I get this error:
Can not refresh not managed object: model.User[ id=1 ].
Can anyone help me to understand this? Why session scoped entity get not managed if injected into another bean? How can I fix this?
Thank you.
In order for the entitiy to be able to refresh, it needs to be managed, but you know that already.
For it to be mananged, it needs to be either
refetched
merged and then refreshed
remain managed with an extended persistence context
First two options require a transaction.
Since neither #ManagedBean nor #ViewScopedimply any kind of transaction management, the entities in these beans will always be detached, so the behaviour you are experiencing is the expected JPA behaviour.
For the first two options you can pass your request to a transaction-enabled EJB in the backend, which will either merge and update the entity or return a freshly fetched one. If you are not using a Java EE application server, you can use a UserTransaction.
For the third option, you could use an extended persistence context, which does not get closed after each transaction, so the entities remain managed across transaction borders.
EDIT The simplest option for fixing this, using UserTransaction and assuming dependency injection.
#Inject
UserTransaction tx;
//merging and refreshing
tx.begin();
User managedUser = em.merge(user);
em.refresh(managedUser);
tx.commit();
user = managedUser;
//refetching
tx.begin();
user = em.find(User.class, user.getId);
tx.commit();
First of all, you shall separate your concerns, which basically means that your backing bean should not be performing any business logic directly (which is implied by your comment /* Some stuff that insert other roles into database, referring the user as owner */). Instead, you could inject a UserService class in your managed bean with #EJB annotation and call its methods via actions of your commandComponents.
Next, when you get your entity from a database and detach it from persistence context (read: persistence context is closed at the end of the transaction) it means that the entity is not managed by your persistence service anymore and changes to your entity will not be reflected in the database. If you would want to do so, you will need to call EntityManager.merge(), to make the entity persistent. You will need to do so when you want the canges in your LoginController.user object to be persisted to the database.
So, what Kostja says is when you want to get an up-to-date correspondence between your user object and the row in the database you should make the object managed by your EntityManager, by following one of the ways that he proposed.
I am using spring security and for database based authentication I used following configuration and its working fine
<authentication-manager alias="authenticationManager">
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="SELECT ...... U.email_address=?"
authorities-by-username-query="SELECT ... U.email_address=?">
</authentication-provider>
</authentication-manager>
now I wanted to add extra info to the session and came across Adding user to session, spring security default login, I tried it and now I have a problem.
XML says I cant use user-service-ref and jdbc-user-service combined. Is there a way to sort it out or else what I have to do if I have to use user-service-ref tag only to authenticate users? What can be the other way to add extra info say a whole Users object to the session.?
Your help will be appreciated.
After hours of searching and experimenting I was able to do it like this.
Make a new service say MyUserService that will implement org.springframework.security.core.userdetails.UserDetailsService and has annotation #Service. UserDetailsService only has one method loadUserByUserName. Implementation of this method will be in MyUserService. It will look like this.
#Service
public class MyUserService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String arg0)
throws UsernameNotFoundException {
MyUser user=new MyUser();
/*get details of user and authorities from database whose username is
arg0 and place them in user instance */
return user;
}
}
MyUser is also a new class that implements org.springframework.security.core.userdetails.UserDetails and all its methods are implemented inside MyUser class. It will look like this.
public class MyUser implements UserDetails {
private static final long serialVersionUID = 1L;
/*All the variables their getter setters that you wish to store in session. And
implementation of all the methods of UserDetails go here.*/
}
Define a bean like this
<bean id="customUserDetailsService" class="org.aurora.timeexpense.service.MyUserService"/>
Where org.aurora.timeexpense.service.MyUserService is the path of my defined service that implements UserDetailsService.
4.And Spring Security Configuration will go like this
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="customUserDetailsService">
</authentication-provider>
</authentication-manager>
You are good to go.
I have a custom AuthenticationProvider that simply returns the Authentication object for the authenticate method. What I want to do is add a role to the user when they log in. This is for demo purposes, so all I want is the user to enter a username and let them in. I need to assign them the admin role.
There are, of course, several ways to achieve that.
My preferred one is do this in a custom UserDetailsService. The only method is loadUserByUsername, that will return an instance of UserDetails. When you are constructing your UserDetails, you can add whatever GrantedAuthority you want.
So first, you'll declare your custom UserDetailsService in your application context configuration file:
<bean id="myCustomUDS" class="com.myapp.AppUDS" />
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider user-service-ref="myCustomUDS">
</sec:authentication-provider>
</sec:authentication-manager>
Then you write the class itself:
public class AppUDS implements UserDetailsService {
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
//create your concrete UserDetails
//add your custom role (i.e. GrantedAuthority) to that object (that will be added to all users)
//return it
}
}
Combination of corporateId and username is unique for us in the user table.
I know spring provide a mechanism to write custom query for the authentication.
<bean id="authenticationDao"
class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref bean="dataSource" />
<property name="usersByUsernameQuery">
<value>
SELECT username,password,enabled
FROM User WHERE username=? and corporateId=?
</value>
</property>
</bean>
But problem here is we have two bind variables in the query instead of one. I am not sure how do I use spring security framework with this db structure.
Primary key of User table is UserId. Is there any to put preprocessor before calling authenticate method by which I can fetch userId by the combination of username and corporateId and then use this SELECT username,password,enabled
FROM User WHERE userid=? query.
Any help would be highly appericated.
I think you need your own authentication provider since you plan on attempting to match against username, password AND corporate id, the userDetailsService returns the object once the authentication is successful.
take a look at this question and answer, I think it will lead you down the right path
Creating a custom authentication with Acegi/Spring Security
If default JdbcDaoImpl doesn't meet your needs, you may implement your own UserDetailsService:
<bean id="authenticationDao"
class="... your implementation class ..." />