Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Creating a Whatsapp Clone - Part XI - Transcript.pdf
1. Creating a WhatsApp Clone - Part XI
We are finally back to the Spring Boot server we setup initially. The code here is pretty simple as well.
2. package com.codename1.whatsapp.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WhatsAppApplication {
public static void main(String[] args) {
SpringApplication.run(WhatsAppApplication.class, args);
}
}
WhatsAppApplication
The WhatsAppApplication is the boilerplate main class of the spring boot project, there isn’t much here and I’m only mentioning it for completeness
3. @Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/").permitAll();
httpSecurity.csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
SecurityConfiguration
Security configuration is a bit more interesting although again it’s similar to what we saw in previous projects.
First we need to permit all requests to remove some HTTP security limits. We don’t need them here since this isn’t a webapp
4. @Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/").permitAll();
httpSecurity.csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
SecurityConfiguration
Similarly we need to disable csrf protection as it isn’t applicable for native apps
5. @Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/").permitAll();
httpSecurity.csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
SecurityConfiguration
Finally we need to provide a password encoder implementation, we’ll use this to encrypt the tokens in the database
6. @Entity
@Indexed
public class User {
@Id
private String id;
@Field
private String name;
@Field
private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
User
Next lets go into the entity objects. I won’t go into what entities are as I discussed them a lot in the previous modules. They are effectively an abstraction of the
underlying data store. The user entity represents the data we save for a chat contact
7. @Entity
@Indexed
public class User {
@Id
private String id;
@Field
private String name;
@Field
private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
User
I use a string unique id with a universal unique identifier which is more secure as I mentioned before. It might make sense to use the phone as the id value though.
8. @Entity
@Indexed
public class User {
@Id
private String id;
@Field
private String name;
@Field
private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
User
The user name and tagline are also stored similarly to the client side code
9. @Entity
@Indexed
public class User {
@Id
private String id;
@Field
private String name;
@Field
private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
User
Phone is listed as unique which makes sure the value is unique in the database
10. @Id
private String id;
@Field
private String name;
@Field
private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
@ManyToOne
private Media avatar;
@Column(unique=true)
private String authtoken;
User
When we send a verification code we store it in the database. I could use a distributed caching system like redis or memcached but they're an overkill for something as
simple as this
11. private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
@ManyToOne
private Media avatar;
@Column(unique=true)
private String authtoken;
private String pushKey;
private boolean verified;
public User() {
id = UUID.randomUUID().toString();
creationDate = new Date();
User
The date in which the user entry was created is a standard database date
12. private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
@ManyToOne
private Media avatar;
@Column(unique=true)
private String authtoken;
private String pushKey;
private boolean verified;
public User() {
id = UUID.randomUUID().toString();
creationDate = new Date();
User
This isn’t used at this time but it’s very similar to the code we have in the facebook clone to store media. In fact it’s copied from there and we can refer to that for media
storage/upload.
13. private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
@ManyToOne
private Media avatar;
@Column(unique=true)
private String authtoken;
private String pushKey;
private boolean verified;
public User() {
id = UUID.randomUUID().toString();
creationDate = new Date();
User
The authtoken is effectively a combination of username and password. As such it’s hashed and as such only the user device knows that value. I believe that’s how
whatsapp works, that's why only one device can connect to a whatsapp account. Since the token is hashed when you need to retrieve an access token you need to
effectively delete the last token and create a new one in order to setup a hash.
For the uninitiated a hash is an encrypted value that can only be generated but not retrieved. So if my password is “password” and the hash is “xyzjkl” then I can’t get the
value of the password from the hash. But I can check that “password" matches “xyzjkl”. Hashes are also salted so they have 60 characters in length and strong hashes
are impossible to crack with standard tools.
14. private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
@ManyToOne
private Media avatar;
@Column(unique=true)
private String authtoken;
private String pushKey;
private boolean verified;
public User() {
id = UUID.randomUUID().toString();
creationDate = new Date();
User
The push key is the key used to send push messages to the client device
15. private String tagline;
@Column(unique=true)
private String phone;
private String verificationCode;
@Temporal(TemporalType.DATE)
private Date creationDate;
@ManyToOne
private Media avatar;
@Column(unique=true)
private String authtoken;
private String pushKey;
private boolean verified;
public User() {
id = UUID.randomUUID().toString();
creationDate = new Date();
User
This flag indicates whether a user is verified
16. private String authtoken;
private String pushKey;
private boolean verified;
public User() {
id = UUID.randomUUID().toString();
creationDate = new Date();
}
public UserDAO getDAO() {
return new UserDAO(id, name, tagline, phone, creationDate,
avatar == null ? null : avatar.getId());
}
public UserDAO getLoginDAO() {
UserDAO d = getDAO();
d.setToken(authtoken);
return d;
}
/**
User
When we create a new user we initialize the id and creation date sensibly. If this entity is loaded from the database these values will be overridden.
17. public User() {
id = UUID.randomUUID().toString();
creationDate = new Date();
}
public UserDAO getDAO() {
return new UserDAO(id, name, tagline, phone, creationDate,
avatar == null ? null : avatar.getId());
}
public UserDAO getLoginDAO() {
UserDAO d = getDAO();
d.setToken(authtoken);
return d;
}
/**
* @return the id
*/
public String getId() {
return id;
}
/**
User
The DAO methods create a data access object that we can send to the client. We will make use of them later in the service code
18. package com.codename1.whatsapp.server.entities;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, String> {
public List<User> findByPhone(String phone);
public List<User> findByAuthtoken(String authtoken);
public List<User> findByPushKey(String pushKey);
}
UserRepository
The user repository maps to the User object and exposes 3 finder methods.
We use findByPhone during signup and sending to detect the user with the given phone
19. package com.codename1.whatsapp.server.entities;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, String> {
public List<User> findByPhone(String phone);
public List<User> findByAuthtoken(String authtoken);
public List<User> findByPushKey(String pushKey);
}
UserRepository
This method should be removed as it’s part of copy and pasted media entity code
20. package com.codename1.whatsapp.server.entities;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, String> {
public List<User> findByPhone(String phone);
public List<User> findByAuthtoken(String authtoken);
public List<User> findByPushKey(String pushKey);
}
UserRepository
We need to find by push key in order to remove or update expired push keys. If the server returns an error we need to update that.
21. import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
public class UserDAO {
private String id;
private String name;
private String tagline;
private String phone;
@JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss.SSS")
private Date creationDate;
private String avatar;
private String token;
public UserDAO() {
}
public UserDAO(String id, String name, String tagline, String phone, Date
creationDate,
String avatar) {
this.id = id;
this.name = name;
UserDAO
UserDAO is a pretty much the content of the user class, there isn't much to discuss here with one major exception and that’s the JSON format annotation. Here we
explicitly declare how we want the Date object to translate to JSON when it’s sent to the client. This is the standard JSON pattern and Codename One in the client side
knows how to parse this