Skip to content

3. User Management

Jorge edited this page Jan 9, 2018 · 1 revision

QuizZz includes user registration and login functionality. It builds on top of the Spring Security and and uses a custom token system for email registration and "forgot my password" functionality.

For testing purposes, we'll be using a local FakeSMTP as our email provider.

High level scenarios

Signup

To register a new user, navigate to http://localhost:8080/user/registration. Once there, you will find a form requiring your username, email and password. There can be several users with the same username but the email must be unique.

Once the required data has been entered, the application will validate it and, if everything is correct, proceed with the next registration step by sending an email on a background process.

After a moment, you should receive an email in your inbox. The email contains a link with your user id and a unique token. Follow the link.

When the link is followed, the application will validate the token and, if valid, activate the new user.

Login

To login, navigate to http://localhost:8080/user/login. Enter your username and password in the form and click "Sign In". You can optionally check "Remember me" not to have to log in every time.

Logout

Once logged in, you can log out by clicking on the top right button. At this point, the login cookies will be cleared out and the session finished.

Forgot my password

Passwords are stored encrypted so they can't be recovered. However, if you have forgotten your password, you can set a new one using your email. For that, navigate to http://localhost:8080/user/login and click on "Forgot my password". You will be prompted with another form in which to enter your email. Enter it and click on "Send Email". Please note that you will not get an error if you enter an unknown email address.

After a moment, you should receive an email in your inbox. The email contains a link with your user id and a unique token. Follow the link.

When the link is followed, the application will validate the token and, if valid, allow you to set up a new password.

Configuration

Mail

We need to add Spring Mail and Greenmail to Maven's pom.xml:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

<dependency>
	<groupId>com.icegreen</groupId>
	<artifactId>greenmail</artifactId>
	<version>1.5.5</version>
	<scope>test</scope>
</dependency>

We will use Spring's email module to send emails to users and Greenmail as a stubbed email provider to use in our JUnit tests.

Spring Mail's configuration code is located in jorge.rv.quizzz.config.MailConfig. It takes all the actual configuration values from application.properties.

	@Bean
	public JavaMailSender javaMailSender() {
		JavaMailSenderImpl mailSender = new JavaMailSenderImpl();

		Properties mailProperties = new Properties();
		mailProperties.put("mail.smtp.auth", auth);
		mailProperties.put("mail.smtp.starttls.enable", starttls);
		mailSender.setJavaMailProperties(mailProperties);
		mailSender.setHost(host);
		mailSender.setPort(port);
		mailSender.setProtocol(protocol);
		mailSender.setUsername(username);
		mailSender.setPassword(password);

		return mailSender;
	}

The email client bean is then used in jorge.rv.quizzz.service.usermanagement.token.TokenDeliverySystemEmail whenever it needs to send anything to the user. The actual content and format on the email is retrieved from messages.properties in a String Formatter format.

	private void sendByMail(User user, String url, String base_config) {
		String subject = messageSource.getMessage(base_config + ".subject", null, null);
		String body = String.format(messageSource.getMessage(base_config + ".body", null, null), user.getUsername(),
				url);

		SimpleMailMessage mailMessage = new SimpleMailMessage();

		mailMessage.setTo(user.getEmail());
		mailMessage.setFrom("noreply@quizzz.com");
		mailMessage.setSubject(subject);
		mailMessage.setText(body);

		mailSender.send(mailMessage);
	}

Async calls

As mentioned before, mails are sent on a background process. For this, we need to set up Spring's Asynchronous features. We'll be doing it in jorge.rv.quizzz.config.AsyncConfig:

@Configuration
@EnableAsync
public class AsyncConfig {

	@Bean
	public Executor asyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(5);
		executor.setMaxPoolSize(5);
		executor.setQueueCapacity(500);
		executor.setThreadNamePrefix("AsyncJob-");
		executor.initialize();
		return executor;
	}

}

After this, we can start calls on a thread from the created pool by using the @Async annotation.

Scheduling

Tokens have an expiration date and we can't assume the user is going to click on the links we send him. Therefore, we'll need a cleaning job running periodically. We can achieve this with Spring simply by enabling it via configuration (in our case jorge.rv.quizzz.config.SchedulingConfig) and using the @Scheduled annotation:

@Configuration
@EnableScheduling
public class SchedulingConfig {

}

Tokens

Before getting into the user registration, lets first take a look at the token system in QuizZz. The token system is broken down in two parts to decouple token generation from token delivery and uses a Token entity to store them in the database. The token module can be found in jorge.rv.quizzz.service.usermanagement.token.

The Token Entity

The main class to define a Token is TokenModel. It is an abstract super-class from which the different tokens will be derived (registration, forgot my password...). For each type of token to be added, we need to add a new sub class such as RegistrationToken or ForgotPasswordToken, and add it to the TokenType enum.

Token Services

The Token Services are responsible for creating, validating and deleting tokens. TokenServiceAbs contains most of code to generate, verify and delete tokens but subclasses are required to provide the specifics such as token entity creation and expiration dates.

public interface TokenService<T extends TokenModel> {
	T generateTokenForUser(User user);
	void validateTokenForUser(User user, String token) throws InvalidTokenException;
	void invalidateToken(String token);
	void invalidateExpiredTokensPreviousTo(Date date);
}

Token Delivery Systems

The Token Delivery Systems are responsible for getting the token to the user. Any number of delivery mechanism can be added by expanding on the TokenDeliverySystem interface. As of today, we have TokenDeliverySystemEmail to deliver the tokens via email, and TokenDeliverySystemConsole for testing purposes.

public interface TokenDeliverySystem {
	@Async
	void sendTokenToUser(TokenModel token, User user, TokenType tokenType);
}

Token Cleanup

As mentioned above, in order to clean up tokens that no one has used, jorge.rv.quizzz.tasks.TokenTasks is scheduled to run periodically and will trigger a clean-up for all types of tokens Spring knows about.

User Registration

A user can register using either the web frontend or the Rest API. In either case, the controller will be responsible for handling the requests, and forwarding the registration requests to the Registration Service.

RegistrationService defines a generic multi-step registration interface:

public interface RegistrationService {
	User startRegistration(User user);
	User continueRegistration(User user, String token);
	boolean isRegistrationCompleted(User user);
}

QuizZz has two implementations for the service: RegistrationServiceMail and RegistrationServiceSimple for testing purposes. RegistrationServiceMail is responsable for dealing with all the token generation, verification and delivery using the token module mentioned above.

RegistrationService also makes use of UserService as a gateway to the User Repository and the database.

Login

Login requests are handled by Spring Security, which is configured to use our UserService to fetch information about the user that's trying to log in. See the Security section for more information. The only other thing we need to do is get UserService to implement Spring Security's UserDetailsService and annotate it as a service.

To enable "Remember me" functionality, we need to give Spring Security a reference to our DataSource when configuring it.

Logout

Logout is fully handled by Spring Security. It just needs to be enabled and configured with a DataSource in Spring Security's configuration. See the Security section for more information.

Forgot my password

At the moment, the Forgot my Password feature is only supported on the web controller. The controller will be responsible for handling the requests, and forwarding the requests to the User Management Service

UserManagementService defines the interface for the feature:

public interface UserManagementService {
	void resendPassword(User user);
	void verifyResetPasswordToken(User user, String token);
	void updatePassword(User user, String password);
}

UserManagementServiceImpl is the only implementation available, which will use UserService as a gateway to the User Repository and the database. It is responsable for dealing with all the token generation, verification and delivery using the token module mentioned above. It also makes use of UserService as a gateway to the User Repository and the database.