Added native token generation and full session management.
This commit is contained in:
parent
171ccf6350
commit
75fb24b85d
|
@ -256,6 +256,15 @@
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- guava cache -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>23.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Various libs -->
|
<!-- Various libs -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,5 +10,6 @@ public interface UserAuthDao extends Dao<UserAuth, UUID> {
|
||||||
|
|
||||||
public String getPasswordHashOfUser(String username);
|
public String getPasswordHashOfUser(String username);
|
||||||
|
|
||||||
|
public UserAuth getUserAuthBy(String username);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
String queryString = "SELECT userAuth.password FROM UserAuth userAuth where userAuth.username = :username";
|
||||||
TypedQuery<String> typedQuery = entityManager.createQuery(queryString, String.class);
|
TypedQuery<String> typedQuery = entityManager.createQuery(queryString, String.class);
|
||||||
typedQuery.setParameter("username", username);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@ import entities.security.UserInfo;
|
||||||
|
|
||||||
public interface UserInfoDao extends Dao<UserInfo, UUID> {
|
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);
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ import javax.persistence.NoResultException;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
|
|
||||||
import dao.JpaDao;
|
import dao.JpaDao;
|
||||||
|
import entities.security.UserAuth;
|
||||||
import entities.security.UserInfo;
|
import entities.security.UserInfo;
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,10 +21,10 @@ public class UserInfoDaoImpl extends JpaDao<UserInfo, UUID> implements UserInfoD
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserInfo getByKey(String id, String email) {
|
public UserInfo getByIdAndMail(String identification, String email) {
|
||||||
String queryString = "FROM UserInfo userInfo where userInfo.id = :userInfoID and userInfo.email = :userInfoEmail";
|
String queryString = "FROM UserInfo userInfo where userInfo.identification = :userInfoID and userInfo.email = :userInfoEmail";
|
||||||
TypedQuery<UserInfo> typedQuery = entityManager.createQuery(queryString, UserInfo.class);
|
TypedQuery<UserInfo> typedQuery = entityManager.createQuery(queryString, UserInfo.class);
|
||||||
typedQuery.setParameter("userInfoID", id);
|
typedQuery.setParameter("userInfoID", identification);
|
||||||
typedQuery.setParameter("userInfoEmail", email);
|
typedQuery.setParameter("userInfoEmail", email);
|
||||||
try {
|
try {
|
||||||
return typedQuery.getSingleResult();
|
return typedQuery.getSingleResult();
|
||||||
|
@ -32,6 +33,21 @@ public class UserInfoDaoImpl extends JpaDao<UserInfo, UUID> implements UserInfoD
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -139,7 +139,7 @@ public class Organisations {
|
||||||
org.setId(organisation.getId());
|
org.setId(organisation.getId());
|
||||||
try {
|
try {
|
||||||
organisationDao.delete(org);
|
organisationDao.delete(org);
|
||||||
return ResponseEntity.status(HttpStatus.OK).body("DELETED!");
|
return ResponseEntity.status(HttpStatus.CREATED).body("DELETED!");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{\"msg\":\"Could not delete organisation!\"");
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{\"msg\":\"Could not delete organisation!\"");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
package rest.login;
|
package rest.login;
|
||||||
|
|
||||||
import java.io.Serializable;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
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.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import dao.entities.DataRepositoryDao;
|
|
||||||
import dao.entities.security.UserAuthDao;
|
import dao.entities.security.UserAuthDao;
|
||||||
import dao.entities.security.UserInfoDao;
|
import dao.entities.security.UserInfoDao;
|
||||||
|
import entities.security.UserAuth;
|
||||||
|
import entities.security.UserInfo;
|
||||||
|
import security.TokenSessionManager;
|
||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@ -28,19 +36,55 @@ public class Login {
|
||||||
@Autowired private UserInfoDao userInfoDao;
|
@Autowired private UserInfoDao userInfoDao;
|
||||||
@Autowired private UserAuthDao userAuthDao;
|
@Autowired private UserAuthDao userAuthDao;
|
||||||
|
|
||||||
|
@Autowired private TokenSessionManager tokenSessionManager;
|
||||||
private ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
|
|
||||||
@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) {
|
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 "{}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,9 @@
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
|
||||||
|
<bean id="tokenSessionManager" class="security.TokenSessionManager" factory-method="getInstance">
|
||||||
|
</bean>
|
||||||
|
|
||||||
<bean id="proxy" class="rest.proxy.Proxy">
|
<bean id="proxy" class="rest.proxy.Proxy">
|
||||||
<constructor-arg type = "String" value = "${proxy.allowed.host}"/>
|
<constructor-arg type = "String" value = "${proxy.allowed.host}"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
|
@ -32,5 +32,18 @@
|
||||||
<beans:bean id="tokenFilter" class="security.TokenAuthenticationFilter"/>
|
<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>
|
</beans:beans>
|
||||||
-->
|
-->
|
|
@ -1,6 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-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">
|
<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>
|
<display-name>dmp-backend</display-name>
|
||||||
|
|
||||||
<servlet>
|
<servlet>
|
||||||
<servlet-name>dmp-backend-rest</servlet-name>
|
<servlet-name>dmp-backend-rest</servlet-name>
|
||||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||||
|
@ -28,6 +30,14 @@
|
||||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||||
</listener>
|
</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 -->
|
<!-- ,/WEB-INF/spring-security.xml -->
|
||||||
<context-param>
|
<context-param>
|
||||||
<param-name>contextConfigLocation</param-name>
|
<param-name>contextConfigLocation</param-name>
|
||||||
|
@ -51,4 +61,6 @@
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</web-app>
|
</web-app>
|
Loading…
Reference in New Issue