How to add new user to Spring Security at runtime

22,227

Solution 1

You probably want to store your users in a database and not in memory, if they are registering :)

  1. Create the authorities for the user

    List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
    authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
    
  2. Instantiate the user (with a class implementing UserDetails)

    UserDetails user = new User("[email protected]", passwordEncoder.encode("s3cr3t"), authorities);
    
  3. Save the user somewhere useful. The JdbcUserDetailsManager can save a user to a database easily.

    userDetailsManager.createUser(user);
    
  4. Create a UsernamePasswordAuthenticationToken

    Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, authorities);
    
  5. Add the Authentication to the SecurityContext

    SecurityContextHolder.getContext().setAuthentication(authentication);
    

Solution 2

You can use Spring Data JPA for user creation.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

usage:

User user = new User();
userRepository.save(user);

How to authenticate above user:

  1. Create custom AuthenticationProvider, select user data from your DB and authenticate:
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserRepository userRepository;

    @Override
    public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
        final UsernamePasswordAuthenticationToken upAuth = (UsernamePasswordAuthenticationToken) authentication;
        final String name = (String) authentication.getPrincipal();

        final String password = (String) upAuth.getCredentials();

        final String storedPassword = userRepository.findByName(name).map(User::getPassword)
            .orElseThrow(() -> new BadCredentialsException("illegal id or passowrd"));

        if (Objects.equals(password, "") || !Objects.equals(password, storedPassword)) {
            throw new BadCredentialsException("illegal id or passowrd");
        }

        final Object principal = authentication.getPrincipal();
        final UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
            principal, authentication.getCredentials(),
            Collections.emptyList());
        result.setDetails(authentication.getDetails());

        return result;
    }
    ...
  1. Configure with WebSecurityConfigurerAdapter for using above AuthenticationProvider:
@EnableWebSecurity
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAuthenticationProvider authProvider;

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .httpBasic();
        http.authenticationProvider(authProvider);
    }
}

refs:

Solution 3

First of all, create a form for registering your user.

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>  

<!doctype html>
<html lang="en">

<head>
    
    <title>Register New User Form</title>
    
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
    <!-- Reference Bootstrap files -->
    <link rel="stylesheet"
         href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
    
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

</head>

<body>

    <div>
        
        <div id="loginbox" style="margin-top: 50px;"
            class="mainbox col-md-3 col-md-offset-2 col-sm-6 col-sm-offset-2">
            
            <div class="panel panel-primary">

                <div class="panel-heading">
                    <div class="panel-title">Register New User</div>
                </div>

                <div style="padding-top: 30px" class="panel-body">

                    <!-- Registration Form -->
                    <form:form action="${pageContext.request.contextPath}/register/processRegistrationForm" 
                               modelAttribute="user"
                               class="form-horizontal">

                        <!-- Place for messages: error, alert etc ... -->
                        <div class="form-group">
                            <div class="col-xs-15">
                                <div>
                                
                                    <!-- Check for registration error -->
                                    <c:if test="${registrationError != null}">
                                
                                        <div class="alert alert-danger col-xs-offset-1 col-xs-10">
                                            ${registrationError}
                                        </div>
        
                                    </c:if>
                                                                            
                                </div>
                            </div>
                        </div>

                        <!-- User name -->
                        <div style="margin-bottom: 25px" class="input-group">
                            <span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span> 
                            
                            <form:input path="username" placeholder="username" class="form-control" />
                        </div>

                        <!-- Password -->
                        <div style="margin-bottom: 25px" class="input-group">
                            <span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span> 
                            
                            <form:password path="password" placeholder="password" class="form-control" />
                        </div>

                        <!-- Register Button -->
                        <div style="margin-top: 10px" class="form-group">                       
                            <div class="col-sm-6 controls">
                                <button type="submit" class="btn btn-primary">Register</button>
                            </div>
                        </div>
                        
                    </form:form>

                </div>

            </div>

        </div>

    </div>

</body>
</html>

enter image description here

And write an action method in the controller to get the form information and save user in the database.

@Controller
@RequestMapping( "/register" )
public class RegistrationController
{
    
    @Autowired
    private UserDetailsManager userDetailsManager;
    
    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    
    private Logger logger = Logger.getLogger( getClass().getName() );
    
    @InitBinder
    public void initBinder( WebDataBinder dataBinder )
    {
        
        StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor( true );
        
        dataBinder.registerCustomEditor( String.class, stringTrimmerEditor );
    }
    
    
    @PostMapping( "/processRegistrationForm" )
    public String processRegistrationForm( @Valid @ModelAttribute( "user" ) com.exmaple.spring_demo.entity.User user, BindingResult theBindingResult, Model theModel )
    {
        
        String userName = user.getUsername();
        
        logger.info( "Processing registration form for: " + userName );
        
        // form validation
        if ( theBindingResult.hasErrors() )
        {
            
            theModel.addAttribute( "user", new com.exmaple.spring_demo.entity.User() );
            theModel.addAttribute( "registrationError", "User name/password can not be empty." );
            
            logger.warning( "User name/password can not be empty." );
            
            return "security/user/registration-form";
        }
        
        // check the database if user already exists
        boolean userExists = doesUserExist( userName );
        
        if ( userExists )
        {
            theModel.addAttribute( "user", new com.exmaple.spring_demo.entity.User() );
            theModel.addAttribute( "registrationError", "User name already exists." );
            
            logger.warning( "User name already exists." );
            
            return "security/user/registration-form";
        }
        
        //
        // whew ... we passed all of the validation checks!
        // let's get down to business!!!
        //
        
        // encrypt the password
        String encodedPassword = passwordEncoder.encode( user.getPassword() );
        
        // prepend the encoding algorithm id
        encodedPassword = "{bcrypt}" + encodedPassword;
        
        // give user default role of "ROLE_USER"
        List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList( "ROLE_USER" );
        
        // create user object (from Spring Security framework)
        User tempUser = new User( userName, encodedPassword, authorities );
        
        // save user in the database
        userDetailsManager.createUser( tempUser );
        
        logger.info( "Successfully created user: " + userName );
        
        return "security/user/registration-confirmation";
    }
    
    
    @GetMapping( "/showRegistrationForm" )
    public String showMyLoginPage( Model theModel )
    {
        
        theModel.addAttribute( "user", new com.exmaple.spring_demo.entity.User() );
        
        return "security/user/registration-form";
        
    }
    
    
    private boolean doesUserExist( String userName )
    {
        
        logger.info( "Checking if user exists: " + userName );
        
        // check the database if the user already exists
        boolean exists = userDetailsManager.userExists( userName );
        
        logger.info( "User: " + userName + ", exists: " + exists );
        
        return exists;
    }
    
}

And now, Define DataSource in Spring Configuration.

package com.exmaple.spring_demo.config;

import java.beans.PropertyVetoException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import com.mchange.v2.c3p0.ComboPooledDataSource;


@Configuration
@EnableWebSecurity
@ComponentScan( "com.exmaple.spring_demo.config" )
@PropertySource( "classpath:persistence-mysql.properties" )
public class SecurityConfigJDBC extends WebSecurityConfigurerAdapter
{


    /**
     * set up variable to hold the properties 
     */
    @Autowired
    private Environment env;


    // define a bean for our security datasource
    @Bean
    public DataSource dataSource()
    {

        // create connection pool
        ComboPooledDataSource securityDataSource = new ComboPooledDataSource();

        // set the jdbc driver class    
        try
        {
            securityDataSource.setDriverClass( env.getProperty( "jdbc.driver" ) );
        }
        catch ( PropertyVetoException exc )
        {
            throw new RuntimeException( exc );
        }

        // log the connection props
        // for sanity's sake, log this info
        // just to make sure we are REALLY reading data from properties file

        System.out.println( ">>> jdbc.url=" + env.getProperty( "jdbc.url" ) );
        System.out.println( ">>> jdbc.user=" + env.getProperty( "jdbc.user" ) );


        // set database connection props    
        securityDataSource.setJdbcUrl( env.getProperty( "jdbc.url" ) );
        securityDataSource.setUser( env.getProperty( "jdbc.user" ) );
        securityDataSource.setPassword( env.getProperty( "jdbc.password" ) );

        // set connection pool props    
        securityDataSource.setInitialPoolSize( getIntProperty( "connection.pool.initialPoolSize" ) );

        securityDataSource.setMinPoolSize( getIntProperty( "connection.pool.minPoolSize" ) );

        securityDataSource.setMaxPoolSize( getIntProperty( "connection.pool.maxPoolSize" ) );

        securityDataSource.setMaxIdleTime( getIntProperty( "connection.pool.maxIdleTime" ) );

        return securityDataSource;
    }


    @Bean
    public UserDetailsManager userDetailsManager()
    {

        JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager();

        jdbcUserDetailsManager.setDataSource( dataSource() );

        return jdbcUserDetailsManager;
    }


    @Override
    protected void configure( AuthenticationManagerBuilder auth ) throws Exception
    {

        auth.jdbcAuthentication().dataSource( dataSource() );
    }


    @Override
    protected void configure( HttpSecurity http ) throws Exception
    {
        
        
        http.authorizeRequests()
                .antMatchers( "/home/**" ).hasRole( "USER" )
                .antMatchers( "/manager/**" ).hasRole( "MANAGERS" )
                .antMatchers( "/admin/**" ).hasRole( "ADMIN" )
                .and()
                .formLogin()
                .loginPage( "/showMyLoginPage" )
                .loginProcessingUrl( "/authenticateTheUser" )// submit form data
                .permitAll()
                .and()
                .logout().permitAll()
                .and()
                .exceptionHandling().accessDeniedPage( "/access-denied" )
                .and()
                .csrf()
                .disable();
        
        
    }


    /**
     * need a helper method
     * read environment property and convert to int
     */
    private int getIntProperty( String propName )
    {

        String propVal = env.getProperty( propName );

        // now convert to int
        int intPropVal = Integer.parseInt( propVal );

        return intPropVal;
    }


}

And finally, Create JDBC Properties File in src/main/resources/persistence-mysql.properties.

#
# JDBC connection properties
#
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_security_demo?useSSL=false
jdbc.user=springstudent
jdbc.password=springstudent
#
# Connection pool properties
#
connection.pool.initialPoolSize=5
connection.pool.minPoolSize=5
connection.pool.maxPoolSize=20
connection.pool.maxIdleTime=3000

The standard JDBC implementation of the UserDetailsService (JdbcDaoImpl) requires tables to load the password, account status (enabled or disabled) and a list of authorities (roles) for the user. You will need to adjust this schema to match the database dialect you are using.

CREATE TABLE `authorities` (
`username` varchar(50) NOT NULL,
`authority` varchar(50) NOT NULL,
UNIQUE KEY `authorities_idx_1` (`username`,`authority`),
CONSTRAINT `authorities_ibfk_1`
FOREIGN KEY (`username`)
REFERENCES `users` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


CREATE TABLE `users` (
`username` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
`enabled` tinyint(1) NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

enter image description here

Share:
22,227
Przemysław Malinowski
Author by

Przemysław Malinowski

Updated on August 21, 2020

Comments

  • Przemysław Malinowski
    Przemysław Malinowski almost 4 years

    I save users in a DB table via Hibernate and I am using Spring Security to authenticate:

    import org.springframework.beans.factory.annotation.Autowired;
    
    import org.springframework.context.annotation.*;
    import org.springframework.security.config.annotation.authentication.builders.*;
    import org.springframework.security.config.annotation.web.configuration.*;
    
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .inMemoryAuthentication()
                    .withUser("user").password("password").roles("USER");
        }
    }
    

    And this works perfectly, but there is a point - user is loaded during server start. I need to write method RegisterUser(User user) that add new user to Spring Security in runtime. This method should focus only on this task. I dont know how to start to implement this feature so thanks for any advices! ;)

    Ofc User have fields like login, password, role string etc etc...

    Please do not post solutions with Spring MVC. This system is RESTful app using Spring Web Boost and Spring Security Boost in version 4.0.x

    • holmis83
      holmis83 almost 9 years
      There are Spring Security + Hibernate examples out there, give it a search. For example: mkyong.com/spring-security/…
    • AndroLife
      AndroLife about 8 years
      please did you find a solution for your problem could you post the code ?
  • Philipp Jahoda
    Philipp Jahoda about 8 years
    Where do I create this user and SecurityContext?
  • Neil McGuigan
    Neil McGuigan about 8 years
    @PhilippJahoda SecurityContext is created by Spring. You would want to create the user in your registration logic, after getting info from the user (such as their desired username and password), probably in a service layer used by a controller
  • Philipp Jahoda
    Philipp Jahoda about 8 years
    Thank you for your response. I am using the spring provided /oauth/token URL, is that not correct? Where can I access the username and password provided to this url?
  • Cardinal System
    Cardinal System about 3 years
    I have been trying to figure this out. Where is UserRepository constructed?
  • DEWA Kazuyuki - 出羽和之
    DEWA Kazuyuki - 出羽和之 about 3 years
    UserRepository is constructed automatically by Spring Boot. Core concepts section and Spring Data JPA Repositories section may help you.