From 75fb24b85dfb747962b31f00d9c8a5fe7952a3dd Mon Sep 17 00:00:00 2001 From: Nikolaos Laskaris Date: Fri, 13 Oct 2017 15:23:59 +0300 Subject: [PATCH] Added native token generation and full session management. --- dmp-backend/pom.xml | 9 ++ .../main/java/checks/EnvironmentChecker.java | 31 +++++ .../dao/entities/security/UserAuthDao.java | 1 + .../entities/security/UserAuthDaoImpl.java | 23 +++- .../dao/entities/security/UserInfoDao.java | 5 +- .../entities/security/UserInfoDaoImpl.java | 22 +++- .../java/rest/entities/Organisations.java | 2 +- .../src/main/java/rest/login/Login.java | 112 +++++++++++++++++- .../java/security/TokenSessionManager.java | 81 +++++++++++++ .../webapp/WEB-INF/applicationContext.xml | 3 + .../main/webapp/WEB-INF/spring-security.xml | 15 ++- dmp-backend/src/main/webapp/WEB-INF/web.xml | 12 ++ 12 files changed, 302 insertions(+), 14 deletions(-) create mode 100644 dmp-backend/src/main/java/checks/EnvironmentChecker.java create mode 100644 dmp-backend/src/main/java/security/TokenSessionManager.java diff --git a/dmp-backend/pom.xml b/dmp-backend/pom.xml index db2246392..ef61d55b6 100644 --- a/dmp-backend/pom.xml +++ b/dmp-backend/pom.xml @@ -256,6 +256,15 @@ + + + com.google.guava + guava + 23.0 + + + + org.apache.commons diff --git a/dmp-backend/src/main/java/checks/EnvironmentChecker.java b/dmp-backend/src/main/java/checks/EnvironmentChecker.java new file mode 100644 index 000000000..fe70e6227 --- /dev/null +++ b/dmp-backend/src/main/java/checks/EnvironmentChecker.java @@ -0,0 +1,31 @@ +package checks; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +public class EnvironmentChecker implements ServletContextListener { + + @Override + public void contextDestroyed(ServletContextEvent arg0) { + + } + + @Override + public void contextInitialized(ServletContextEvent arg0) { + try{ + MessageDigest.getInstance("SHA-256"); + System.out.println("SHA-256 algorithm found, as expected!"); + } + catch(NoSuchAlgorithmException ex) { + System.out.println("\n\n\n\nSEVERE ERROR: COULD NOT FIND WITHIN JVM THE SHA-256 ALGORITHM. PLEASE UPDATE THE JRE TO A VERSION >= 1.7\n\n\n\n"); + //shutting down the webapp should also be considered + } + + + + } + +} \ No newline at end of file diff --git a/dmp-backend/src/main/java/dao/entities/security/UserAuthDao.java b/dmp-backend/src/main/java/dao/entities/security/UserAuthDao.java index 3fee4f35f..d64266420 100644 --- a/dmp-backend/src/main/java/dao/entities/security/UserAuthDao.java +++ b/dmp-backend/src/main/java/dao/entities/security/UserAuthDao.java @@ -10,5 +10,6 @@ public interface UserAuthDao extends Dao { public String getPasswordHashOfUser(String username); + public UserAuth getUserAuthBy(String username); } diff --git a/dmp-backend/src/main/java/dao/entities/security/UserAuthDaoImpl.java b/dmp-backend/src/main/java/dao/entities/security/UserAuthDaoImpl.java index 1fb105de3..33f315421 100644 --- a/dmp-backend/src/main/java/dao/entities/security/UserAuthDaoImpl.java +++ b/dmp-backend/src/main/java/dao/entities/security/UserAuthDaoImpl.java @@ -22,9 +22,28 @@ public class UserAuthDaoImpl extends JpaDao implements UserAuth String queryString = "SELECT userAuth.password FROM UserAuth userAuth where userAuth.username = :username"; TypedQuery typedQuery = entityManager.createQuery(queryString, String.class); typedQuery.setParameter("username", username); - return typedQuery.getSingleResult(); - + try { + return typedQuery.getSingleResult(); + } + catch(Exception ex) { + return null; + } } + @Override + public UserAuth getUserAuthBy(String username) { + + String queryString = "FROM UserAuth userAuth where userAuth.username = :username"; + TypedQuery typedQuery = entityManager.createQuery(queryString, UserAuth.class); + typedQuery.setParameter("username", username); + try { + return typedQuery.getSingleResult(); + } + catch(Exception ex) { + return null; + } + } + + } diff --git a/dmp-backend/src/main/java/dao/entities/security/UserInfoDao.java b/dmp-backend/src/main/java/dao/entities/security/UserInfoDao.java index 0922b1fbc..63a17532c 100644 --- a/dmp-backend/src/main/java/dao/entities/security/UserInfoDao.java +++ b/dmp-backend/src/main/java/dao/entities/security/UserInfoDao.java @@ -7,6 +7,9 @@ import entities.security.UserInfo; public interface UserInfoDao extends Dao { - public UserInfo getByKey(String id, String email); + public UserInfo getByIdAndMail(String identification, String email); + + public UserInfo getByAuthenticationId(String authentication); + } \ No newline at end of file diff --git a/dmp-backend/src/main/java/dao/entities/security/UserInfoDaoImpl.java b/dmp-backend/src/main/java/dao/entities/security/UserInfoDaoImpl.java index 3f257d8f5..d6d806e60 100644 --- a/dmp-backend/src/main/java/dao/entities/security/UserInfoDaoImpl.java +++ b/dmp-backend/src/main/java/dao/entities/security/UserInfoDaoImpl.java @@ -7,6 +7,7 @@ import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import dao.JpaDao; +import entities.security.UserAuth; import entities.security.UserInfo; @@ -20,10 +21,10 @@ public class UserInfoDaoImpl extends JpaDao implements UserInfoD @Override - public UserInfo getByKey(String id, String email) { - String queryString = "FROM UserInfo userInfo where userInfo.id = :userInfoID and userInfo.email = :userInfoEmail"; + public UserInfo getByIdAndMail(String identification, String email) { + String queryString = "FROM UserInfo userInfo where userInfo.identification = :userInfoID and userInfo.email = :userInfoEmail"; TypedQuery typedQuery = entityManager.createQuery(queryString, UserInfo.class); - typedQuery.setParameter("userInfoID", id); + typedQuery.setParameter("userInfoID", identification); typedQuery.setParameter("userInfoEmail", email); try { return typedQuery.getSingleResult(); @@ -32,6 +33,21 @@ public class UserInfoDaoImpl extends JpaDao implements UserInfoD return null; } } + + @Override + public UserInfo getByAuthenticationId(String authenticationID) { + UserAuth userauth = new UserAuth(); + userauth.setId(UUID.fromString(authenticationID)); + String queryString = "FROM UserInfo userInfo where userInfo.authentication = :auth"; + TypedQuery typedQuery = entityManager.createQuery(queryString, UserInfo.class); + typedQuery.setParameter("auth", userauth); + try { + return typedQuery.getSingleResult(); + } + catch(NoResultException ex) { + return null; + } + } } \ No newline at end of file diff --git a/dmp-backend/src/main/java/rest/entities/Organisations.java b/dmp-backend/src/main/java/rest/entities/Organisations.java index dfe0a9a65..49b91704e 100644 --- a/dmp-backend/src/main/java/rest/entities/Organisations.java +++ b/dmp-backend/src/main/java/rest/entities/Organisations.java @@ -139,7 +139,7 @@ public class Organisations { org.setId(organisation.getId()); try { organisationDao.delete(org); - return ResponseEntity.status(HttpStatus.OK).body("DELETED!"); + return ResponseEntity.status(HttpStatus.CREATED).body("DELETED!"); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{\"msg\":\"Could not delete organisation!\""); } diff --git a/dmp-backend/src/main/java/rest/login/Login.java b/dmp-backend/src/main/java/rest/login/Login.java index 4db8ae27a..9a6022613 100644 --- a/dmp-backend/src/main/java/rest/login/Login.java +++ b/dmp-backend/src/main/java/rest/login/Login.java @@ -1,6 +1,12 @@ package rest.login; import java.io.Serializable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -15,9 +21,11 @@ import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import dao.entities.DataRepositoryDao; import dao.entities.security.UserAuthDao; import dao.entities.security.UserInfoDao; +import entities.security.UserAuth; +import entities.security.UserInfo; +import security.TokenSessionManager; @RestController @@ -28,19 +36,55 @@ public class Login { @Autowired private UserInfoDao userInfoDao; @Autowired private UserAuthDao userAuthDao; - - private ObjectMapper objectMapper = new ObjectMapper(); + @Autowired private TokenSessionManager tokenSessionManager; - @RequestMapping(method = RequestMethod.POST, value = { "/nativeLogin" }, consumes = "application/json", produces="text/plain") + + @RequestMapping(method = RequestMethod.POST, value = { "/nativeLogin" }, consumes = "application/json", produces = "application/json") public @ResponseBody ResponseEntity nativeLogin(@RequestBody Credentials credentials) { + String token = null; + + if(credentials == null || credentials.getPassword() == null || credentials.getUsername() ==null || + credentials.getPassword().isEmpty() || credentials.getUsername().isEmpty()) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Username and/or password cannot be empty."); + } - System.out.println(userAuthDao.getPasswordHashOfUser("admin")); + UserAuth userAuth = userAuthDao.getUserAuthBy(credentials.getUsername()); + if(userAuth == null) userAuth = new UserAuth(); + String userHash = userAuth.getPassword(); - return ResponseEntity.status(HttpStatus.OK).body("OUR-GENERATED-TOKEN"); + String providedHash = ""; + try { + providedHash = tokenSessionManager.hashPassword(credentials.getPassword()); + } + catch(NoSuchAlgorithmException ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error. Cannot authenticate."); + } + + if(userHash == null || "".equals(userHash) || !userHash.equals(providedHash)) { + return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body("Wrong username or password"); + } + else if(userHash.equals(providedHash)) { + // create a token + token = tokenSessionManager.generateRandomAlphanumeric(512); + // add it to the cache + tokenSessionManager.set(credentials.getUsername(), token); + } + + //get also the additional info of the user (if he has) + UserInfo userInfo = userInfoDao.getByAuthenticationId((userAuth.getId() == null) ? "" : userAuth.getId().toString()); + if(userInfo == null) userInfo = new UserInfo(); + + Response response = new Response(); + response.setToken(token); + response.setEmail(userInfo.getEmail()); + response.setName(userInfo.getName()); + response.setUsername(credentials.getUsername()); + + return new ResponseEntity(response.toJson(), HttpStatus.OK); } @@ -68,3 +112,59 @@ class Credentials implements Serializable{ } } + +class Response implements Serializable { + + private static final long serialVersionUID = -3855159530298902864L; + + private String token; + private String username; + private String email; + private String name; + + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public String toJson() { + ObjectMapper objMapper = new ObjectMapper(); + try { + return objMapper.writeValueAsString(this); + } + catch(JsonProcessingException ex) { + return "{}"; + } + } + +} + diff --git a/dmp-backend/src/main/java/security/TokenSessionManager.java b/dmp-backend/src/main/java/security/TokenSessionManager.java new file mode 100644 index 000000000..18e491f77 --- /dev/null +++ b/dmp-backend/src/main/java/security/TokenSessionManager.java @@ -0,0 +1,81 @@ +package security; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.concurrent.TimeUnit; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +public class TokenSessionManager { + + private final static long TOTAL_SESSION_MINUTES = 120L; + private final static long IDLE_MINUTES_EXPIRE = 20L; + + private static Cache cache; //that's thread-safe according to the documentation + + private static TokenSessionManager instance = null; //should be one-per-classloader + + + public static synchronized TokenSessionManager getInstance() { + if (instance == null){ + instance = new TokenSessionManager(); + initialize(); + } + return instance; + } + + private static void initialize() { + cache = CacheBuilder.newBuilder() + .expireAfterWrite(TOTAL_SESSION_MINUTES, TimeUnit.MINUTES) + .expireAfterAccess(IDLE_MINUTES_EXPIRE, TimeUnit.MINUTES) + .maximumSize(Long.MAX_VALUE) + .build(); + } + + + public String get(String key) { + return cache.getIfPresent(key); + } + + public void set(String key, String value) { + cache.put(key, value); + } + + + public String generateRandomAlphanumeric(int length) { + SecureRandom random = new SecureRandom(); + byte bytes[] = new byte[length]; + random.nextBytes(bytes); + return encode(bytes); + } + + + private String encode(byte[] binaryData) { + int n = binaryData.length; + char[] HEXADECIMAL = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + char[] buffer = new char[n * 2]; + for (int i = 0; i < n; i++) { + int low = (binaryData[i] & 0x0f); + int high = ((binaryData[i] & 0xf0) >> 4); + buffer[i * 2] = HEXADECIMAL[high]; + buffer[(i * 2) + 1] = HEXADECIMAL[low]; + } + return new String(buffer); + } + + + public String hashPassword (String password) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(password.getBytes()); + byte byteData[] = md.digest(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < byteData.length; i++) + sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1)); + return sb.toString(); + } + + + +} diff --git a/dmp-backend/src/main/webapp/WEB-INF/applicationContext.xml b/dmp-backend/src/main/webapp/WEB-INF/applicationContext.xml index a8333cb5b..298b9bcc0 100644 --- a/dmp-backend/src/main/webapp/WEB-INF/applicationContext.xml +++ b/dmp-backend/src/main/webapp/WEB-INF/applicationContext.xml @@ -25,6 +25,9 @@ + + + diff --git a/dmp-backend/src/main/webapp/WEB-INF/spring-security.xml b/dmp-backend/src/main/webapp/WEB-INF/spring-security.xml index e51309b46..1110f6bb5 100644 --- a/dmp-backend/src/main/webapp/WEB-INF/spring-security.xml +++ b/dmp-backend/src/main/webapp/WEB-INF/spring-security.xml @@ -32,5 +32,18 @@ + + + + + + + + + + + + - --> + --> \ No newline at end of file diff --git a/dmp-backend/src/main/webapp/WEB-INF/web.xml b/dmp-backend/src/main/webapp/WEB-INF/web.xml index 11282be42..957c29d88 100644 --- a/dmp-backend/src/main/webapp/WEB-INF/web.xml +++ b/dmp-backend/src/main/webapp/WEB-INF/web.xml @@ -1,6 +1,8 @@ + dmp-backend + dmp-backend-rest org.springframework.web.servlet.DispatcherServlet @@ -28,6 +30,14 @@ org.springframework.web.context.ContextLoaderListener + + + + checks.EnvironmentChecker + + + + contextConfigLocation @@ -51,4 +61,6 @@ --> + + \ No newline at end of file