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:
UserDetails
class which represents a user.UserDetailsService
implementation, which knows how to get that user based on his credentials (username and password)@configuration
class which extendsWebSecurityConfigurerAdapter
to configure spring security and glue integrate theUserService
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 ofUserDetailsService
. -
But, What is
PasswordEncoder
andBCryptPasswordEncoder
? 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";
}