Spring Security using both username or email - spring

I'm using Spring Security in my Spring MVC app.
JdbcUserDetailsManager is initialized with the following query for authentication:
select username, password, enabled from user where username = ?
And authorities are being loaded here:
select u.username, a.authority from user u join authority a on u.userId = a.userId where username = ?
I would like to make it so that users can login with both username and email. Is there a way to modify these two queries to achieve that ? Or is there an even better solution ?

Unfortunatelly there is no easy way doing this just by changing the queries. The problem is that spring security expects that the users-by-username-query and authorities-by-username-query have a single parameter (username) so if your query contain two parameters like
username = ? or email = ?
the query will fail.
What you can do, is to implement your own UserDetailsService that will perform the query (or queries) to search user by username or email and then use this implementation as authentication-provider in your spring security configuration like
<authentication-manager>
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>
<beans:bean id="myUserDetailsService" class="xxx.yyy.UserDetailsServiceImpl">
</beans:bean>

I had the same problem, and after trying with a lot of different queries, with procedures... I found that this works:
public void configAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
// Codificación del hash
PasswordEncoder pe = new BCryptPasswordEncoder();
String userByMailQuery = "SELECT mail, password, enabled FROM user_ WHERE mail = ?;";
String userByUsernameQuery = "SELECT mail, password, enabled FROM user_ WHERE username=?";
String roleByMailQuery = "SELECT mail, authority FROM role WHERE mail =?;";
auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(pe)
.usersByUsernameQuery(userByMailQuery)
.authoritiesByUsernameQuery(roleByMailQuery);
auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(pe)
.usersByUsernameQuery(userByUsernameQuery)
.authoritiesByUsernameQuery(roleByMailQuery);
}
Its just repeat the configuration with the two queries.

If I understood this correctly, then the problem is that you want to lookup username entered by the user in two different DB columns.
Sure, you can do that by customizing UserDetailsService.
public class CustomJdbcDaoImpl extends JdbcDaoImpl {
#Override
protected List<GrantedAuthority> loadUserAuthorities(String username) {
return getJdbcTemplate().query(getAuthoritiesByUsernameQuery(), new String[] {username, username}, new RowMapper<GrantedAuthority>() {
public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
.......
}
});
}
#Override
protected List<UserDetails> loadUsersByUsername(String username) {
return getJdbcTemplate().query(getUsersByUsernameQuery(), new String[] {username, username}, new RowMapper<UserDetails>() {
public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
.......
}
});
}
Your bean configuration for this class will look something like this.
<beans:bean id="customUserDetailsService" class="com.xxx.CustomJdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
<beans:property name="usersByUsernameQuery">
<beans:value> YOUR_QUERY_HERE</beans:value>
</beans:property>
<beans:property name="authoritiesByUsernameQuery">
<beans:value> YOUR_QUERY_HERE</beans:value>
</beans:property>
</beans:bean>
Your queries will look something similar to this
select username, password, enabled from user where (username = ? or email = ?)
select u.username, a.authority from user u join authority a on u.userId = a.userId where (username = ? or email = ?)

You can use your UserDetailesService.and config like the below code.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
The point is that you don't need to return the user with the same username and you can get user-email and return user with the username. The code will be like the code below.
#Service
public class MyUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
var user = /** here search user via jpa or jdbc by username or email **/;
if(user == null )
throw new UsernameNotFoundExeption();
else return new UserDetail(user); // You can implement your user from UserDerail interface or create one;
}
}
tip* UserDetail is an interface and you can create one or use Spring Default.

You can define custom queries in <jdbc-user-service> tag in users-by-username-query and authorities-by-username-query attributes respectively.
<jdbc-user-service data-source-ref="" users-by-username-query="" authorities-by-username-query=""/>
Update
You can create class which implements org.springframework.security.core.userdetails.UserDetailsService and configure your application to use it as an authentication source. Inside your custom UserDetails service you can execute queries that you need to obtain user from database.

You can config UserDetailesService class like this.
public class UserDetailsServiceImpl implements UserDetailsService{
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = this.userRepository.getUserByEmailOrUserName(username); //for fetch user
if(user==null) {
throw new UsernameNotFoundException("User doesn't exists");
}
UserDetailsImpl customUserDetails = new UserDetailsImpl(user);
return customUserDetails;
}
}
Your queries will look something similar to this
select * from user where email = ? or username = ?
UserRepository class for fetch user data
#Repository
public interface UserRepository extends JpaRepository<User, Integer>{
#Query("from user where email = :u or username = :u")
public User getUserByEmailOrUserName(#Param("u") String username);
}
You can also add phone number while doing login.

Here is a workaround I discovered. Basically I'm concatenating username and email address with a delimiter character in between (for example 'jsmith~johnsmith#gmail.com'), and checking to see if the parameter matches the left side of the delimiter or if matches the right side of the delimiter:
select username, password, enabled
from users
where ? in (substring_index(concat(username, '~',email),'~', 1),
substring_index(concat(username, '~',email),'~', -1))
If you are concerned that the delimiter character (such as ~) might exist within the username or email, use a non-standard delimiter character instead (for example, X'9C')

Related

Is there a JpaRepository method which can save a provided password where username condition?

I am looking for a JpaRepository method to update a user provided password at the time of resetting it with a username condition . Thanks in advance
Have a look at creating custom query methods with JPA as per documentation: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query.
For your use case, the following would allow you update the correct user's password:
#Repository
public interface UserRepository extends JpaRepository<User, UUID> {
#Query("update User u set u.password= ?1 where u.username = ?2")
void updatePassword(String username, String password);
}
In Spring Data JPA, whenever you execute an update, you need to use the #Modifying along with the #Query. Failing to use the #Modifying will result in InvalidDataAccessApiUsage exception.
Find the code below
#Repository
public interface UserRepository extends JpaRepository<User, UUID> {
#Modifying
#Query("update User u set u.password= :password where u.username = :username")
void updatePassword(#Param("username") String username, #Param("password") String password);
}

Foreign Key/DBRef in Spring Data Firestore

I have 2 entities:
1- User:
#Document(collectionName = CollectionConstants.USER_COLLECTION)
public class User {
#DocumentId
protected String id;
private String username;
}
2- Contest:
#Document(collectionName = CollectionConstants.CONTEST_COLLECTION)
public class Contest {
private List<User> contestants;
}
How can I save only the ID of the user (whether it's a list of users, or a single user) in the database while letting Spring Data manage that automatically?
I'm actually looking for an alternative for the following:
Spring Data JPA: #OneToMany, #ManyToOne
Spring Data MongoDB: #DBRef
I have checked in the Spring Cloud GCP for Firestore, and it states:
The starter automatically configures and registers a Firestore bean in the Spring application context. To start using it, simply use the #Autowired annotation.
#Autowired
Firestore firestore;
void writeDocumentFromObject() throws ExecutionException, InterruptedException {
// Add document data with id "joe" using a custom User class
User data = new User("Joe",
Arrays.asList(
new Phone(12345, PhoneType.CELL),
new Phone(54321, PhoneType.WORK)));
// .get() blocks on response
WriteResult writeResult = this.firestore.document("users/joe").set(data).get();
LOGGER.info("Update time: " + writeResult.getUpdateTime());
}
User readDocumentToObject() throws ExecutionException, InterruptedException {
ApiFuture<DocumentSnapshot> documentFuture =
this.firestore.document("users/joe").get();
User user = documentFuture.get().toObject(User.class);
LOGGER.info("read: " + user);
return user;
}
There is sample https://github.com/spring-cloud-gcp/spring-cloud-gcp-samples/spring-cloud-gcp-firestore-sample

I am trying to fetch User data using userName in Spring JPA repository, but it always returns an empty list

I am using below code to fetch user information from MySQL database using username, but it always returns empty list.
public interface UserRepository extends JpaRepository<User, Integer> {
List<User> findByUserNameIgnoreCase(String userName);
}
The below method is in service class
public String login(LoginDTO loginDTO) {
logger.info("Login request for customer {} with password {}",
loginDTO.getUserName(), loginDTO.getPassword());
List<User> user =
userRepo.findByUserNameIgnoreCase(loginDTO.getUserName());
if(user.isEmpty())
return "Invalid";
else
return "Successful";
}
Kindly help.
Trying findByUsernameIgnoreCase because Spring might treat it as User and Name instead of Username.

Why Spring security is based in username instead of user id?

In spring security, UserDetails uses username as an identifier of user. My user model have multiple types of identifications and have multiple ways of authentication (via email, phone number, facebook, etc).
I am using AuthorizationServerEndpointsConfigurer to configure my oauth2 endpoints. JdbcTokenStore stores user_name in database, and what I need to store is userId. I already changed the JdbcTokenStore to fill my needs, but I am not understanding why spring framework makes the assumption that the username is the unique identifier, my app does not even use usernames.
For example, in TokenStore inferface findTokensByClientIdAndUserName is based in username, why not findTokensByClientIdAndUserId? If I authenticate the user with email or phone number, how can I get all tokens for that user?
My user model example: id, email, phone number, facebook_id, name. Email, phone number and facebook id are optional, only one of them are required. In this case what is the best candidate for username? Should I use user id as an username in spring way of abstraction?
Not sure if I understood the whole question but will try to give my best explanation for UserDetailService. UserDetailsService's method loadByUsername is just a method which allows you to retrieve the user from your database and compare it with the credentials from the request from the client to see if it is an existing user or non existing one. Although the name of the method is loadUserByUsername, it doesn't mean that you must literally pass a username, you can pass it any argument like email, phone number or whatever you authentication is based on.
Here is an example implementation for that service, where I identify my users by calling the database through UserRepository and giving it as an argument email.
#Service
public class CustomUserDetailsService implements UserDetailsService {
private UserRepository userRepository;
#Autowired
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Here I am retrieving the user from my database where the primary key is
// email
User user = userRepository.findByEmail(username);
if(user == null) {
throw new UsernameNotFoundException("Username not found!");
}
CustomUserDetails customUserDetails = new CustomUserDetails(user);
return customUserDetails;
}
}

Externalize mongo json query using spring boot

I have just started using spring data MongoDb with Spring-Boot.
I have some mongo based json queries added in the interface using #query annotation when using spring data repository.
I want to know if it is possible to externalize or separate out the JSON query outside the codebase so that it can be optimized separately and
also not having it mixed with code.
Thanks for your suggestions.
This is the code which i have added in my interface and annotated with #query annotation.
#Query("{ 'firstname' : ?0 ,'lastname': ?1}")
List findByCriteria(String firstname,String lastname);
The above is a simple example. I have complex conditions involving $and and $or operators too .
What i basically want to achieve is externalize the above native mongo json query to a config file and refer that in the above annotation.
Spring data supports something similar when using jpa with hibernate. But not sure if we can do the same using spring data mongodb with spring boot.
Do like this (I am explaining only for the API)
Suppose you have an Entity user
At the Top there will be User domain
public class User extends CoreDomain {
private static final long serialVersionUID = -4292195532570879677L;
#Length(min = 2)
private String name;
#Length(min = 2)
#UniqueUserName(message = "User name already registered,Please choose something Different")
private String userName;
#Length(min = 6)
private String password;
}
User Controller
User Service (Interface)
User ServiceImpl(Service Implementation)
Mongo Repository(Since, I have MongoDb)
Now in userController you will take all the queries , Param(Parameters) , Pagerequest like this
public class UserController extends CoreController {
#Autowired
private UserService userService;
/*
* This controller is for getting the UserDetails on passing the UserId in
* the #param Annotation
*/
#GET
#Path("{id}")
public User getUser(#PathParam("id") String UserId) {
User user = new User();
user = userService.findUserId(UserId);
if (user == null)
throw new NotFoundException();
log.info("The userId you searched is having the details as :" + user);
return user;
}}
For serviceInterface you will have :
public interface UserService {
// Boolean authenticateUser(User user);
User findUserId(String UserId);
}
For serviceImpl :
public class UserServiceImpl implements UserService {
#Setter
#Autowired
private UserRepository userRepository;
/*
* This method will find user on the basis of their userIds passed in the
* parameter.
*/
#Override
public User findUserId(String UserId) {
User userIdResult = userRepository.findOne(UserId);
log.info("The userDetail is" + userIdResult);
return userIdResult;
}
In mongoRepository for user we will have:
A default query findById(String userId);
Hopefully this will help you.

Resources