How to use GWT with Apache Shiro hashed and salted [closed] - mysql

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
In this small tutorial I show you how to build a GWT Module which is responsible for registration and login.
The password gets hashed with Sha256 and salted.

Download and Installation
Download for Apache Shiro: http://shiro.apache.org/download.html ;
I have used Shrio-All (1.2.2 Binary Distribution) http://tweedo.com/mirror/apache/shiro/1.2.2/shiro-root-1.2.2-source-release.zip
After the download include shiro-all-1.2.2.jar in your lib folder.
We can also include other .jar files which we will need later on.
MySQL Driver: http://www.java2s.com/Code/Jar/c/Downloadcommysqljdbc515jar.htm (com.mysql.jdbc_5.1.5.jar)
SLF4J Logging: http://www.slf4j.org/download.html (slf4j-api-1.7.5.jar, slf4j-simple-1.7.5.jar)
Apache Commons Beanutils: http://repo2.maven.org/maven2/commons-beanutils/commons-beanutils/1.7.0/ (commons-beanutils-1.7.0.jar)
Don't forget to add your jars to your build path.
web.xml
Add this to your web.xml
<!-- Apache Shero -->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<!-- Make sure any request you want accessible to Shiro is filtered. /* catches all -->
<!-- requests. Usually this filter mapping is defined first (before all others) to -->
<!-- ensure that Shiro works in subsequent filters in the filter chain: -->
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
shiro.ini
Put your shiro.ini into WEB-INF:
[main]
authc.loginUrl = /Login.html?gwt.codesvr=127.0.0.1:9997
authc.successUrl = /Leitfaden.html
logout.redirectUrl = /login.html
# ------------------------
# Database
# Own Realm
jdbcRealm = leitfaden.login.server.MyRealm
# Sha256
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
# base64 encoding, not hex in this example:
sha256Matcher.storedCredentialsHexEncoded = false
sha256Matcher.hashIterations = 1024
jdbcRealm.credentialsMatcher = $sha256Matcher
# User Query
# default is "select password from users where username = ?"
jdbcRealm.authenticationQuery = SELECT password, salt FROM USER WHERE email = ?
# Connection
ds = com.mysql.jdbc.jdbc2.optional.MysqlDataSource
ds.serverName = localhost
ds.user = root
ds.password = root
ds.databaseName = leitfaden
jdbcRealm.dataSource=$ds
authc.usernameParam = email
authc.passwordParam = password
authc.failureKeyAttribute = shiroLoginFailure
# Use Built-in Chache Manager
builtInCacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $builtInCacheManager
# -----------------------------------------------------------------------------
[urls]
/yourMainUrl.html = authc
GWT Module
Create a module for login. Module name “Login” and Package name “leitfaden.login”:
Add this to your web.xml
<servlet>
<servlet-name>LoginService</servlet-name>
<servlet-class>leitfaden.login.server.LoginServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginService</servlet-name>
<url-pattern>/leitfaden.login.Login/LoginService</url-pattern>
</servlet-mapping>
LoginService.java
#RemoteServiceRelativePath("LoginService")
public interface LoginService extends RemoteService {
public Boolean isLoggedIn();
public Boolean tryLogin(String email, String password, Boolean rememberMe);
public void logout();
public void registrate(String email, String password);
}
LoginServiceAsync.java
public interface LoginServiceAsync {
public void isLoggedIn(AsyncCallback<Boolean> callback);
public void tryLogin(String email, String password, Boolean rememberMe, AsyncCallback<Boolean> callback);
public void logout(AsyncCallback<Void> callback);
public void registrate(String email, String password, AsyncCallback<Void> callback);
}
LoginServiceImpl
public class LoginServiceImpl extends RemoteServiceServlet implements LoginService {
private static final long serialVersionUID = -4051026136441981243L;
private static final transient Logger log = LoggerFactory
.getLogger(LoginServiceImpl.class);
private org.apache.shiro.subject.Subject currentUser;
public LoginServiceImpl() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory();
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
}
#Override
public Boolean isLoggedIn() {
currentUser = SecurityUtils.getSubject();
if (currentUser.isAuthenticated()) {
return true;
} else {
return false;
}
}
#Override
public Boolean tryLogin(String username, String password, Boolean rememberMe) {
// get the currently executing user:
currentUser = SecurityUtils.getSubject();
// let's login the current user so we can check against roles and
// permissions:
if (!currentUser.isAuthenticated()) {
//collect user principals and credentials in a gui specific manner
//such as username/password html form, X509 certificate, OpenID, etc.
//We'll use the username/password example here since it is the most common.
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//this is all you have to do to support 'remember me' (no config - built in!):
token.setRememberMe(rememberMe);
try {
currentUser.login(token);
log.info("User [" + currentUser.getPrincipal().toString() + "] logged in successfully.");
return true;
} catch (UnknownAccountException uae) {
log.info("There is no user with username of "
+ token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal()
+ " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal()
+ " is locked. "
+ "Please contact your administrator to unlock it.");
} catch (AuthenticationException ae) {
log.error(ae.getLocalizedMessage());
}
}
return false;
}
#Override
public void logout() {
currentUser = SecurityUtils.getSubject();
currentUser.logout();
}
#Override
public void registrate(String email, String plainTextPassword) {
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();
// Now hash the plain-text password with the random salt and multiple
// iterations and then Base64-encode the value (requires less space than Hex):
String hashedPasswordBase64 = new Sha256Hash(plainTextPassword, salt,1024).toBase64();
User user = new User(email, hashedPasswordBase64, salt.toString(), 0);
this.createUser(user);
}
private void createUser(User user) {
UserDAL.connect();
UserDAL.beginTransaction();
new UserDAL().createUser(user);
log.info("User with email:" + user.getEmail() + " hashedPassword:"+ user.getPassword() + " salt:" + user.getSalt());
UserDAL.commitTransaction();
UserDAL.disconnect();
}
}
MyRealm.java
Users can now register at this application. But Shiro does not know how to compare salted passwords with the given user input. For that we need to implement our own Realm. A Realm is essentially a security-specific DAO.
MyRealm.java gets the user with the given email and returns a SaltedAuthenticationInfo. With that SaltedAuthenticationInfo Shiro knows how to compare the user input with the user from the database.
public class MyRealm extends JdbcRealm {
private static final Logger log = LoggerFactory.getLogger(MyRealm.class);
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// identify account to log to
UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
final String username = userPassToken.getUsername();
if (username == null) {
log.debug("Username is null.");
return null;
}
// read password hash and salt from db
final PasswdSalt passwdSalt = getPasswordForUser(username);
if (passwdSalt == null) {
log.debug("No account found for user [" + username + "]");
return null;
}
// return salted credentials
SaltedAuthenticationInfo info = new MySaltedAuthentificationInfo(username, passwdSalt.password, passwdSalt.salt);
return info;
}
private PasswdSalt getPasswordForUser(String username) {
User user = getUserByEmail(username);
if (user == null) {
return null;
}
return new PasswdSalt(user.getPassword(), user.getSalt());
}
private User getUserByEmail(String email) {
UserDAL.connect();
User user = new UserDAL().getUserByEmail(email);
UserDAL.disconnect();
return user;
}
class PasswdSalt {
public String password;
public String salt;
public PasswdSalt(String password, String salt) {
super();
this.password = password;
this.salt = salt;
}
}
}
MySaltedAuthentificationInfo
Important is that you decode the salt correctly in getCredentialsSalt().I have used Base64.
public class MySaltedAuthentificationInfo implements SaltedAuthenticationInfo {
private static final long serialVersionUID = -2342452442602696063L;
private String username;
private String password;
private String salt;
public MySaltedAuthentificationInfo(String username, String password, String salt) {
this.username = username;
this.password = password;
this.salt = salt;
}
#Override
public PrincipalCollection getPrincipals() {
PrincipalCollection coll = new SimplePrincipalCollection(username, username);
return coll;
}
#Override
public Object getCredentials() {
return password;
}
#Override
public ByteSource getCredentialsSalt() {
return new SimpleByteSource(Base64.decode(salt));
}
}
Users can now register and login. You would only need to code views in your login module that call the LoginService.

Related

Match password efficiently in Spring Boot - JPA

I have User class like this :
#Data
#Entity
public class User {
#Id #GeneratedValue Long userID;
String eMail;
String passwordHash;
}
And I have data like this :
[{"userID":1,"passwordHash":"asdasd","email":"admin#admin.com"},
{"userID":2,"passwordHash":"12345","email":"admin1asdasd#admin.com"}]
I have two method , one - to get single user :
// Single item
#GetMapping("/user/{id}")
User one(#PathVariable Long id) {
return repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
Other method to retrieve all user :
// Aggregate root
#GetMapping("/user")
List<User> all() {
return repository.findAll();
}
Now how can I match password ? What will be the efficient way ?
You may want to consider this kind of an aproach: in general, you should save hashed password in the database and check passwords using hashed values. Bcrypt is a good option for hashing and it can be easily integrated with Spring.
As explained in the link above you can define a password encoder service:
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
and you can use it like this:
#Autowired
private PasswordEncoder passwordEncoder;
//...
User user = new User();
user.setFirstName(accountDto.getFirstName());
user.setLastName(accountDto.getLastName());
user.setPassword(passwordEncoder.encode(accountDto.getPassword()));
user.setEmail(accountDto.getEmail());
user.setRole(new Role(Integer.valueOf(1), user));
repository.save(user);
where accountDto contains the password in clear-text.
Now you can expose a dedicated login method that compares hashed values, something along these lines:
void login(String username, char[] password) throws Exception {
User user = userRepository.findByUsername(username);
if (user != null) {
String encodedPassword = user.getPassword();
if(passwordEncoder.matches(String.valueOf(password), encodedPassword)) {
return;
}
}
throw new Exception("User cannot be authenticated");
}

Special char issue

I'm creating an .Net Core API but I have a problem I wanted to keep the '$' special char in the password.
I don't know why, when I execute my post method in the request URL it change it to %24
I try to change use Normalize but it didn't work...
Authentication Controller which is called by Employee controller :
public static class AuthenticationController
{
private class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle() // called by P/Invoke
: base(true)
{
}
protected override bool ReleaseHandle()
{
return CloseHandle(this.handle);
}
}
private enum LogonType : uint
{
Network = 3, // LOGON32_LOGON_NETWORK
}
private enum LogonProvider : uint
{
WinNT50 = 3, // LOGON32_PROVIDER_WINNT50
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(
string userName, string domain, string password,
LogonType logonType, LogonProvider logonProvider,
out SafeTokenHandle token);
public static void AuthenticateUser(string userName, string password)
{
string domain = "domain";
string parts = domain + userName;
SafeTokenHandle token;
if (LogonUser(userName, domain, password, LogonType.Network, LogonProvider.WinNT50, out token))
token.Dispose();
else
throw new Win32Exception(); // calls Marshal.GetLastWin32Error()
}
}
Employee controller :
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : Controller
{
private readonly intranetApplicationAPIContext _context;
public EmployeeController(intranetApplicationAPIContext context)
{
_context = context;
}
[HttpPost]
public ActionResult GetEmployee(string username , string password)
{
try
{
AuthenticationController.AuthenticateUser(username, password);
return Ok(username +"-"+ password);
}
catch (Win32Exception ex)
{
switch (ex.NativeErrorCode)
{
case 1326: // ERROR_LOGON_FAILURE (incorrect user name or password)
return BadRequest("Error code : "+ex.NativeErrorCode+" Incorrect username or password");
case 1327: // ERROR_ACCOUNT_RESTRICTION
return BadRequest("Error code : " + ex.NativeErrorCode + " Account restriction");
case 1330: // ERROR_PASSWORD_EXPIRED
return BadRequest("Error code : " + ex.NativeErrorCode + " Password expired");
case 1331: // ERROR_ACCOUNT_DISABLED
return BadRequest("Error code : " + ex.NativeErrorCode + " Account disabled");
case 1907: // ERROR_PASSWORD_MUST_CHANGE
return BadRequest("Error code : " + ex.NativeErrorCode + " Password must change");
case 1909: // ERROR_ACCOUNT_LOCKED_OUT
return BadRequest("Error code : " + ex.NativeErrorCode + " Account locked out");
default: // Other
return BadRequest("An error has occured");
;
}
}
}
}
Sending password or user private information in url is not recommmended.(Someone who listen the network can see request url and steal user password) You can send these informations in body of post request to dont encounter encoding problem and realizing process securely

Gradle not recognizing mysql dependency

I am trying to move from H2 in memory database to mysql and I incorporated compile("mysql:mysql-connector-java:5.1.6") to my build.gradle after removing the H2 dependency. I also put the following in my application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/mutibodb
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto: create
However, when I rebuild, I get this error: http://pastebin.com/4cS9Dk0U
This is the same error I get if I don't put either mysql or h2 dependency, which means it does not take it at all.
My full existing code which was working with H2 database is here: https://github.com/devdeep1987/MutiboProject/tree/master/MutiboServer
Could you please tell me if there is a step I missed.
Update:
My CustomUserDetailsService class is the following:
#Service
public class CustomUserDetailsService implements UserDetailsService {
private UserRepository repository;
private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
#Autowired
public CustomUserDetailsService(UserRepository repo) {
this.repository = repo;
}
#Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
logger.info("username:"+name);
User u = repository.findByUsername(name);
if (u == null)
throw new UsernameNotFoundException("User details not found with this username: " + name);
String username = u.getUsername();
String password = u.getPassword();
List authList = new ArrayList();
authList.add(new SimpleGrantedAuthority("USER"));
//get the encoded password
//String encodedPassword = passwordEncoder.encode(password);
org.springframework.security.core.userdetails.User user = new org.springframework.security.core.userdetails.User(username, password, authList);
return user;
}
public boolean createUser(String username, String password) {
User existing = repository.findByUsername(username);
if(existing!=null)
return false;
User u = new User();
u.setUsername(username);
u.setPassword(password);
repository.save(u);
return true;
}
private List getAuthorities(String role) {
List authList = new ArrayList();
authList.add(new SimpleGrantedAuthority("USER"));
//you can also add different roles here
//for example, the user is also an admin of the site, then you can add ROLE_ADMIN
//so that he can view pages that are ROLE_ADMIN specific
if (role != null && role.trim().length() > 0) {
if (role.equals("admin")) {
authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
}
return authList;
}
}
and my UserRepository class is:
#Repository
public interface UserRepository extends CrudRepository<User, Long>{
User findByUsername(String username);
}
Changing the version of spring-boot-gradle-plugin from 1.0.2.RELEASE to 1.1.4.RELEASE fixes this. Not sure why though.

SSRS Report credential issue

Hi I get the following error when try to accessing the report server url. How do I pass the windows credential to the ssrs report. For your info, the report server is configure in different server therefore it will ask for the authentication to login. Please help. Thanks
System.Net.WebException: The request failed with HTTP status 401:
Unauthorized. at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.GetSecureMethods()
at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.IsSecureMethod(String
methodname) at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.SetConnectionSSLForMethod(String
methodname) at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.ProxyMethodInvocation.Execute[TReturn](RSExecutionConnection
connection, ProxyMethod1 initialMethod, ProxyMethod1 retryMethod) at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.LoadReport(String
Report, String HistoryID) at
Microsoft.Reporting.WebForms.SoapReportExecutionService.LoadReport(String
report, String historyId) at
Microsoft.Reporting.WebForms.ServerReport.EnsureExecutionSession() at
Microsoft.Reporting.WebForms.ServerReport.SetParameters(IEnumerable`1
parameters)
You need to set
ReportViewer1.ServerReport.ReportServerCredentials = new MyReportServerConnection();
where your MyReportServerConnection implementation is something like this:
private sealed class MyReportServerConnection : IReportServerConnection2
{
[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken);
public WindowsIdentity ImpersonationUser
{
get
{
// Use credentials from config file
IntPtr userToken = IntPtr.Zero;
bool success = LogonUser(
"reportusername",
"reportuserdomain",
"reportuserpassword",
9,//LOGON_TYPE_NEW_CREDENTIALS
3,//LOGON32_PROVIDER_WINNT50
out userToken);
if (!success)
{
throw new Exception("Logon user failed");
}
return new WindowsIdentity(userToken);
}
}
public ICredentials NetworkCredentials
{
get
{
return null;
}
}
public bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority)
{
authCookie = null;
userName = null;
password = null;
authority = null;
// Not using form credentials
return false;
}
public Uri ReportServerUrl
{
get
{
return new Uri("http://reportserverurl/ReportServer");
}
}
public int Timeout
{
get
{
return 60000; // 60 seconds
}
}
public IEnumerable<Cookie> Cookies
{
get
{
// No custom cookies
return null;
}
}
public IEnumerable<string> Headers
{
get
{
// No custom headers
return null;
}
}
}

Jackson JSON processor overwrites properties of object during deserialization

I have interesting problem. Jackson overwrites values of properties on the 'parent' object with values of properties of 'child' object that have same name. So, to be more precise, this is Java structure I have
public class Contact {
...
String name;
List<Email> emails;
List<PhoneNumbers> phoneNumbers;
Account account;
...
}
public class Account {
...
String accountName;
List<Email> emails;
List<PhoneNumbers> phoneNumbers;
Account account;
...
}
So, when I form Contact JSON object and send it to server, everything goes fine until BeanDeserializer comes into account property of Contact class. Then, it starts reading proeprties of account part of JSON, which is ok, but does not create Account instance to set it on contact - it writes values of account's properties into properties with same names of Contact instance.
I am confused and not sure where to start looking how to fix this.
I'm not able to reproduce any problem similar to what's described in the original question.
The following example, created based on the descriptions in the original question, works as expected, without errors or improper deserialization.
import java.util.LinkedList;
import java.util.List;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
Account account1 = new Account();
account1.accountName = "account 1";
account1.emails = new LinkedList<Email>();
account1.emails.add(new Email("email_11#google.com"));
account1.emails.add(new Email("email_12#google.com"));
account1.phoneNumbers = new LinkedList<PhoneNumbers>();
account1.phoneNumbers.add(new PhoneNumbers(1111, 1112));
account1.phoneNumbers.add(new PhoneNumbers(1113, 1114));
Account account2 = new Account();
account2.accountName = "account 2";
account2.emails = new LinkedList<Email>();
account2.emails.add(new Email("email_21#google.com"));
account2.emails.add(new Email("email_22#google.com"));
account2.phoneNumbers = new LinkedList<PhoneNumbers>();
account2.phoneNumbers.add(new PhoneNumbers(2221, 2222));
account2.phoneNumbers.add(new PhoneNumbers(2223, 2224));
account2.account = account1;
Contact contact = new Contact();
contact.name = "contact";
contact.emails = new LinkedList<Email>();
contact.emails.add(new Email("email_31#google.com"));
contact.emails.add(new Email("email_32#google.com"));
contact.phoneNumbers = new LinkedList<PhoneNumbers>();
contact.phoneNumbers.add(new PhoneNumbers(3331, 3332));
contact.phoneNumbers.add(new PhoneNumbers(3333, 3334));
contact.account = account2;
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(
mapper.getVisibilityChecker()
.withFieldVisibility(Visibility.ANY));
String account1Json = mapper.writeValueAsString(account1);
String account2Json = mapper.writeValueAsString(account2);
String contactJson = mapper.writeValueAsString(contact);
System.out.println(account1Json); // {"accountName":"account 1","emails":[{"email":"email_11#google.com"},{"email":"email_12#google.com"}],"phoneNumbers":[{"phone1":1111,"phone2":1112},{"phone1":1113,"phone2":1114}],"account":null}
System.out.println(account2Json); // {"accountName":"account 2","emails":[{"email":"email_21#google.com"},{"email":"email_22#google.com"}],"phoneNumbers":[{"phone1":2221,"phone2":2222},{"phone1":2223,"phone2":2224}],"account":{"accountName":"account 1","emails":[{"email":"email_11#google.com"},{"email":"email_12#google.com"}],"phoneNumbers":[{"phone1":1111,"phone2":1112},{"phone1":1113,"phone2":1114}],"account":null}}
System.out.println(contactJson); // {"name":"contact","emails":[{"email":"email_31#google.com"},{"email":"email_32#google.com"}],"phoneNumbers":[{"phone1":3331,"phone2":3332},{"phone1":3333,"phone2":3334}],"account":{"accountName":"account 2","emails":[{"email":"email_21#google.com"},{"email":"email_22#google.com"}],"phoneNumbers":[{"phone1":2221,"phone2":2222},{"phone1":2223,"phone2":2224}],"account":{"accountName":"account 1","emails":[{"email":"email_11#google.com"},{"email":"email_12#google.com"}],"phoneNumbers":[{"phone1":1111,"phone2":1112},{"phone1":1113,"phone2":1114}],"account":null}}}
Account account1Copy = mapper.readValue(account1Json, Account.class);
Account account2Copy = mapper.readValue(account2Json, Account.class);
Contact contactCopy = mapper.readValue(contactJson, Contact.class);
System.out.println(account1.equals(account1Copy)); // true
System.out.println(account2.equals(account2Copy)); // true
System.out.println(contact.equals(contactCopy)); // true
}
}
class Contact
{
String name;
List<Email> emails;
List<PhoneNumbers> phoneNumbers;
Account account;
#Override
public boolean equals(Object o)
{
Contact c = (Contact) o;
if (name.equals(c.name))
if (emails.containsAll(c.emails))
if (c.emails.containsAll(emails))
if (phoneNumbers.containsAll(c.phoneNumbers))
if (c.phoneNumbers.containsAll(phoneNumbers))
return account.equals(c.account);
return false;
}
}
class Account
{
String accountName;
List<Email> emails;
List<PhoneNumbers> phoneNumbers;
Account account;
#Override
public boolean equals(Object o)
{
Account a = (Account) o;
if (accountName.equals(a.accountName))
if (emails.containsAll(a.emails))
if (a.emails.containsAll(emails))
if (phoneNumbers.containsAll(a.phoneNumbers))
if (a.phoneNumbers.containsAll(phoneNumbers))
if (account != null && a.account != null)
return account.equals(a.account);
else if (account == null && a.account == null)
return true;
return false;
}
}
class Email
{
String email;
#JsonCreator
Email(#JsonProperty("email") String e) {email = e;}
#Override
public boolean equals(Object o)
{
Email e = (Email) o;
return email.equals(e.email);
}
}
class PhoneNumbers
{
long phone1;
long phone2;
#JsonCreator
PhoneNumbers(#JsonProperty("phone1") long p1, #JsonProperty("phone2")long p2) {phone1 = p1; phone2 = p2;}
#Override
public boolean equals(Object o)
{
PhoneNumbers p = (PhoneNumbers) o;
return phone1 == p.phone1 && phone2 == p.phone2;
}
}