Create customers, manage authentication and authorization.

Customer account management

In order for a customer to place an order, he/she must create an account first. this allow customers to track their orders, and let the store admin had enough information to deliver the order.

Tradenity API offers the Customer resource which provides all the necessary infrastructure to create and manage user account, login and logout, safely store sensitive information such as password in encrypted format.

In this section we will learn how to integrate Tradenity Customer resource and related services within your application to allow your customers to create and manage their accounts.

As we are using Spring MVC, using Spring Security is a perfect fit. In order to manage user authentication and authorization in spring security, You must provide at least 3 classes:

  1. UserDetails class which represents a user.
  2. UserDetailsService implementation, which knows how to get that user based on his credentials (username and password)
  3. @configuration class which extends WebSecurityConfigurerAdapter to configure spring security and glue integrate the UserService within the system.

Implementing these classes is very simple, as Tradenity already implemented the required infrastructure for you, so the spring specific implementation is just a thin layer around what is already there.

Implement UserDetails

Our implementation of UserDetails interface is a wrapper around the Customer class, delegating all the method calls to their corresponding methods in the customer instance


public class User implements Serializable, UserDetails{


    String customerId;
    String firstName;
    String lastName;
    String email;
    String username;
    String password;
    String confirmPassword;
    boolean enabled = true;

    public User() {
    }

    public User(Customer customer) {
        this.customerId = customer.getId();
        this.firstName = customer.getFirstName();
        this.lastName = customer.getLastName();
        this.email = customer.getEmail();
        this.username = customer.getUsername();
        this.password = customer.getPassword();
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getCustomerId() {
        return customerId;
    }

    public void setCustomerId(String customerId) {
        this.customerId = customerId;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.createAuthorityList("ROLE_USER");
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getConfirmPassword() {
        return confirmPassword;
    }

    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public Customer toCustomer() {
        Customer c = new Customer(firstName, lastName, email, username, password, "active");
        c.setId(this.customerId);
        return c;
    }
}


Implement UserDetailsService

Next, Implement the UserDetailsService interface. just a simple wrapper around Tradenity SDK ‘s CustomerService#findByUsername


@Service
public class UserServiceImpl implements UserDetailsService{
    @Autowired
    CustomerService customerService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Customer customer = customerService.findByUsername(username);
        if(customer == null) {
            throw new UsernameNotFoundException("Could not find user " + username);
        }
        return new User(customer);
    }
}

Configure spring security

Now let’s configure spring security to protect our sensitive web paths and integrate the UserServiceImpl with it.


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserDetailsService userDetailsService;



    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/orders/**").authenticated()
                .antMatchers("/**").permitAll()
                //.antMatchers("/admin/storeSettings", "/admin/storeSettings/").hasRole("SUPER_ADMIN")

                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll()
                .and()
                .csrf();

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

  • In the configure(HttpSecurity http) method, we instruct spring security to protect /orders/** path, then we configure form based login system.

  • In configure(AuthenticationManagerBuilder auth) we tell spring about our implementation of UserDetailsService.

  • But, What is PasswordEncoder and BCryptPasswordEncoder? Tradenity never store customer passwords as plain text, this is a best practice for security.

Tradenity stores the encrypted password, Using the bcrypt algorithm which makes it nearly impossible for hackers to decrypt it. So, we configure spring security to use the appropriate password encoder.

In order to resolve the User in @RequestMapper MVC methods we may use @AuthenticationPrincipal, but it is a common practice to provide application specific @CurrentUser.


@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {
}

The login form

Now, we have everything in place, and we are ready to authenticate our customers using their username and password. Here is the HTML form for user login.


<form th:action="@{/login}" method="post">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
    <ul>
        <li class="text-info">Username: </li>
        <li><input type="text" name="username" value=""/></li>
    </ul>
    <ul>
        <li class="text-info">Password: </li>
        <li><input type="password" name="password" value=""/></li>
    </ul>

    <input type="submit" value="LOGIN"/>

</form>

Register New customers


<form th:action="@{/register}" method="post" th:object="${user}">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
    <ul>
        <li class="text-info">First Name: </li>
        <li><input type="text" value="" th:field="*{firstName}"/></li>
    </ul>
    <ul>
        <li class="text-info">Last Name: </li>
        <li><input type="text" value="" th:field="*{lastName}"/></li>
    </ul>
    <ul>
        <li class="text-info">Email: </li>
        <li><input type="text" value="" th:field="*{email}"/></li>
    </ul>
    <ul>
        <li class="text-info">Username: </li>
        <li><input type="text" value="" th:field="*{username}"/></li>
    </ul>
    <ul>
        <li class="text-info">Password: </li>
        <li><input type="password" value="" th:field="*{password}"/></li>
    </ul>
    <ul>
        <li class="text-info">Re-enter Password:</li>
        <li><input type="password" value="" th:field="*{confirmPassword}"/></li>
    </ul>

    <input type="submit" value="REGISTER NOW"/>
    <p class="click">By clicking this button, you are agree to my  <a href="#">Policy Terms and Conditions.</a></p>
</form>

and this is the @Controller handler mwthod, it validate the imput and call CustomerService#create method.


@RequestMapping(path = "/register", method = RequestMethod.POST)
public String create(@Valid @ModelAttribute("user") User user, BindingResult result, RedirectAttributes ra){
    if(result.hasErrors()){
        return "shop/register";
    }
    if(!user.getPassword().equals( user.getConfirmPassword())){
        result.addError(new FieldError("user", "password", "Password does not match"));
        return "shop/register";
    }
    customerService.create(user.toCustomer());
    ra.addFlashAttribute("info", "Registration successful!");
    return "redirect:/login";
}