How to add new user to Spring Security at runtime How to add new user to Spring Security at runtime spring spring

How to add new user to Spring Security at runtime


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("user@example.com", 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);


You can use Spring Data JPA for user creation.

@Repositorypublic 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:
@Componentpublic 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:
@EnableWebSecuritypublic 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:


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.Driverjdbc.url=jdbc:mysql://localhost:3306/spring_security_demo?useSSL=falsejdbc.user=springstudentjdbc.password=springstudent## Connection pool properties#connection.pool.initialPoolSize=5connection.pool.minPoolSize=5connection.pool.maxPoolSize=20connection.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