Added native token generation and full session management.
This commit is contained in:
parent
171ccf6350
commit
75fb24b85d
|
@ -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>
|
||||
|
|
|
@ -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 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";
|
||||
TypedQuery<String> typedQuery = entityManager.createQuery(queryString, String.class);
|
||||
typedQuery.setParameter("username", username);
|
||||
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 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 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();
|
||||
|
@ -33,5 +34,20 @@ public class UserInfoDaoImpl extends JpaDao<UserInfo, UUID> implements UserInfoD
|
|||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
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!\"");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
System.out.println(userAuthDao.getPasswordHashOfUser("admin"));
|
||||
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.");
|
||||
}
|
||||
|
||||
UserAuth userAuth = userAuthDao.getUserAuthBy(credentials.getUsername());
|
||||
|
||||
if(userAuth == null) userAuth = new UserAuth();
|
||||
|
||||
return ResponseEntity.status(HttpStatus.OK).body("OUR-GENERATED-TOKEN");
|
||||
String userHash = userAuth.getPassword();
|
||||
|
||||
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 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>
|
||||
|
|
|
@ -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>
|
||||
-->
|
|
@ -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>
|
Loading…
Reference in New Issue