Added native token generation and full session management.

This commit is contained in:
Nikolaos Laskaris 2017-10-13 15:23:59 +03:00
parent 171ccf6350
commit 75fb24b85d
12 changed files with 302 additions and 14 deletions

View File

@ -256,6 +256,15 @@
</dependency>
<!-- guava cache -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
<!-- Various libs -->
<dependency>
<groupId>org.apache.commons</groupId>

View File

@ -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
}
}
}

View File

@ -10,5 +10,6 @@ public interface UserAuthDao extends Dao<UserAuth, UUID> {
public String getPasswordHashOfUser(String username);
public UserAuth getUserAuthBy(String username);
}

View File

@ -22,9 +22,28 @@ public class UserAuthDaoImpl extends JpaDao<UserAuth, UUID> implements UserAuth
String queryString = "SELECT userAuth.password FROM UserAuth userAuth where userAuth.username = :username";
TypedQuery<String> 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<UserAuth> typedQuery = entityManager.createQuery(queryString, UserAuth.class);
typedQuery.setParameter("username", username);
try {
return typedQuery.getSingleResult();
}
catch(Exception ex) {
return null;
}
}
}

View File

@ -7,6 +7,9 @@ import entities.security.UserInfo;
public interface UserInfoDao extends Dao<UserInfo, UUID> {
public UserInfo getByKey(String id, String email);
public UserInfo getByIdAndMail(String identification, String email);
public UserInfo getByAuthenticationId(String authentication);
}

View File

@ -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<UserInfo, UUID> 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<UserInfo> 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<UserInfo, UUID> 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<UserInfo> typedQuery = entityManager.createQuery(queryString, UserInfo.class);
typedQuery.setParameter("auth", userauth);
try {
return typedQuery.getSingleResult();
}
catch(NoResultException ex) {
return null;
}
}
}

View File

@ -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!\"");
}

View File

@ -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<String> 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<String>(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 "{}";
}
}
}

View File

@ -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 <String, String> 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();
}
}

View File

@ -25,6 +25,9 @@
</bean>
<bean id="tokenSessionManager" class="security.TokenSessionManager" factory-method="getInstance">
</bean>
<bean id="proxy" class="rest.proxy.Proxy">
<constructor-arg type = "String" value = "${proxy.allowed.host}"/>
</bean>

View File

@ -32,5 +32,18 @@
<beans:bean id="tokenFilter" class="security.TokenAuthenticationFilter"/>
<authentication-manager>
<authentication-provider>
<password-encoder ref="encoder" />
</authentication-provider>
</authentication-manager>
<beans:bean id="encoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg name="strength" value="11" />
</beans:bean>
</beans:beans>
-->
-->

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<display-name>dmp-backend</display-name>
<servlet>
<servlet-name>dmp-backend-rest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
@ -28,6 +30,14 @@
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- this listener is for checking required stuff upon deployment of the webapp -->
<listener>
<listener-class>
checks.EnvironmentChecker
</listener-class>
</listener>
<!-- ,/WEB-INF/spring-security.xml -->
<context-param>
<param-name>contextConfigLocation</param-name>
@ -51,4 +61,6 @@
-->
</web-app>