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 :)
Create the authorities for the user
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
Instantiate the user (with a class implementing UserDetails)
UserDetails user = new User("user@example.com", passwordEncoder.encode("s3cr3t"), authorities);
Save the user somewhere useful. The JdbcUserDetailsManager can save a user to a database easily.
userDetailsManager.createUser(user);
Create a UsernamePasswordAuthenticationToken
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, authorities);
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:
- 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; } ...
- Configure with
WebSecurityConfigurerAdapter
for using aboveAuthenticationProvider
:
@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>
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;