mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-17 15:03:18 +00:00
DS-3542 JWT encryption and compression
This commit is contained in:
@@ -65,7 +65,7 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport
|
||||
@Column(name="salt", length = 32)
|
||||
private String salt;
|
||||
|
||||
@Column(name="session_salt", length = 16)
|
||||
@Column(name="session_salt", length = 32)
|
||||
private String sessionSalt;
|
||||
|
||||
@Column(name="digest_algorithm", length = 16)
|
||||
|
@@ -17,4 +17,4 @@
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
-- This adds an extra column to the eperson table where we save a salt for stateless authentication
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
ALTER TABLE eperson ADD session_salt varchar(16);
|
||||
ALTER TABLE eperson ADD session_salt varchar(32);
|
@@ -17,4 +17,4 @@
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
-- This adds an extra column to the eperson table where we save a salt for stateless authentication
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
ALTER TABLE eperson ADD session_salt varchar(16);
|
||||
ALTER TABLE eperson ADD session_salt varchar(32);
|
@@ -17,4 +17,4 @@
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
-- This adds an extra column to the eperson table where we save a salt for stateless authentication
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
ALTER TABLE eperson ADD session_salt varchar(16);
|
||||
ALTER TABLE eperson ADD session_salt varchar(32);
|
@@ -8,6 +8,7 @@
|
||||
package org.dspace.app.rest.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.dspace.app.rest.RestResourceController;
|
||||
import org.dspace.app.util.Util;
|
||||
|
||||
/**
|
||||
@@ -16,7 +17,7 @@ import org.dspace.app.util.Util;
|
||||
* Find out your authentication status.
|
||||
*
|
||||
*/
|
||||
public class StatusRest extends DSpaceObjectRest
|
||||
public class StatusRest extends BaseObjectRest<Integer>
|
||||
{
|
||||
|
||||
private String sourceVersion;
|
||||
@@ -38,6 +39,10 @@ public class StatusRest extends DSpaceObjectRest
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public Class getController() {
|
||||
return RestResourceController.class;
|
||||
}
|
||||
|
||||
|
||||
private EPersonRest ePersonRest;
|
||||
|
||||
@@ -83,7 +88,7 @@ public class StatusRest extends DSpaceObjectRest
|
||||
this.apiVersion = apiVersion;
|
||||
}
|
||||
|
||||
@LinkRest(linkClass = EPersonRest.class, name = "eperson")
|
||||
@LinkRest(linkClass = EPersonRest.class, name = "eperson", optional = true)
|
||||
@JsonIgnore
|
||||
public EPersonRest getEPersonRest() {
|
||||
return ePersonRest;
|
||||
|
@@ -7,23 +7,15 @@
|
||||
*/
|
||||
package org.dspace.app.rest.security;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.nimbusds.jose.JOSEException;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jose.JWSSigner;
|
||||
import com.nimbusds.jose.JWSVerifier;
|
||||
import com.nimbusds.jose.*;
|
||||
import com.nimbusds.jose.crypto.DirectDecrypter;
|
||||
import com.nimbusds.jose.crypto.DirectEncrypter;
|
||||
import com.nimbusds.jose.crypto.MACSigner;
|
||||
import com.nimbusds.jose.crypto.MACVerifier;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import com.nimbusds.jwt.util.DateUtils;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.core.Context;
|
||||
@@ -31,15 +23,21 @@ import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.sql.SQLException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class JWTTokenHandler implements InitializingBean {
|
||||
|
||||
@@ -51,6 +49,8 @@ public class JWTTokenHandler implements InitializingBean {
|
||||
|
||||
private String jwtKey;
|
||||
private long expirationTime;
|
||||
private boolean includeIP;
|
||||
private boolean compressionEnabled;
|
||||
|
||||
@Autowired
|
||||
private ConfigurationService configurationService;
|
||||
@@ -61,12 +61,19 @@ public class JWTTokenHandler implements InitializingBean {
|
||||
@Autowired
|
||||
private EPersonService ePersonService;
|
||||
|
||||
private byte[] encryptionKey;
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
//TODO move properties to authentication module
|
||||
this.jwtKey = configurationService.getProperty("jwt.token.secret", "defaultjwtkeysecret");
|
||||
this.expirationTime = configurationService.getLongProperty("jwt.token.expiration", 30) * 60 * 1000;
|
||||
this.includeIP = configurationService.getBooleanProperty("jwt.token.include.ip", true);
|
||||
this.compressionEnabled = configurationService.getBooleanProperty("jwt.compression.enabled", false);
|
||||
//TODO Don't reuse this all the time
|
||||
BytesKeyGenerator keyGen = KeyGenerators.secureRandom(16);
|
||||
encryptionKey = keyGen.generateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +91,12 @@ public class JWTTokenHandler implements InitializingBean {
|
||||
return null;
|
||||
}
|
||||
|
||||
SignedJWT signedJWT = SignedJWT.parse(token);
|
||||
JWEObject jweObject = JWEObject.parse(token);
|
||||
|
||||
jweObject.decrypt(new DirectDecrypter(encryptionKey));
|
||||
|
||||
|
||||
SignedJWT signedJWT = jweObject.getPayload().toSignedJWT();
|
||||
JWTClaimsSet jwtClaimsSet = signedJWT.getJWTClaimsSet();
|
||||
|
||||
EPerson ePerson = getEPerson(context, jwtClaimsSet);
|
||||
@@ -144,9 +156,29 @@ public class JWTTokenHandler implements InitializingBean {
|
||||
|
||||
signedJWT.sign(signer);
|
||||
|
||||
return signedJWT.serialize();
|
||||
JWEObject jweObject = new JWEObject(
|
||||
compression(new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A128GCM)
|
||||
.contentType("JWT"))
|
||||
|
||||
.build(), new Payload(signedJWT)
|
||||
);
|
||||
|
||||
jweObject.encrypt(new DirectEncrypter(encryptionKey));
|
||||
|
||||
|
||||
return jweObject.serialize();
|
||||
}
|
||||
|
||||
//This method makes compression configurable
|
||||
private JWEHeader.Builder compression(JWEHeader.Builder builder) {
|
||||
if (compressionEnabled) {
|
||||
return builder.compressionAlgorithm(CompressionAlgorithm.DEF);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void invalidateToken(String token, HttpServletRequest request, Context context) {
|
||||
if (StringUtils.isNotBlank(token)) {
|
||||
try {
|
||||
@@ -161,12 +193,14 @@ public class JWTTokenHandler implements InitializingBean {
|
||||
}
|
||||
|
||||
private String buildSigningKey(HttpServletRequest request, EPerson ePerson) {
|
||||
String ipAddress = getIpAddress(request);
|
||||
String ipAddress = "";
|
||||
if (includeIP) {
|
||||
ipAddress = getIpAddress(request);
|
||||
}
|
||||
return jwtKey + ePerson.getSessionSalt() + ipAddress;
|
||||
}
|
||||
|
||||
private String getIpAddress(HttpServletRequest request) {
|
||||
//TODO FREDERIC make using the ip address of the request optional
|
||||
String ipAddress = request.getHeader("X-FORWARDED-FOR");
|
||||
if (ipAddress == null) {
|
||||
ipAddress = request.getRemoteAddr();
|
||||
@@ -184,30 +218,24 @@ public class JWTTokenHandler implements InitializingBean {
|
||||
//If the previous login was within the configured token expiration time, we reuse the session salt.
|
||||
//This allows a user to login on multiple devices/browsers at the same time.
|
||||
if (previousLoginDate == null || (ePerson.getLastActive().getTime() - previousLoginDate.getTime() > expirationTime)) {
|
||||
|
||||
StringKeyGenerator stringKeyGenerator = KeyGenerators.string();
|
||||
String salt = stringKeyGenerator.generateKey();
|
||||
ePerson.setSessionSalt(salt);
|
||||
|
||||
ePerson.setSessionSalt(generateRandomSalt());
|
||||
ePersonService.update(context, ePerson);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
//TODO FREDERIC: fail fast
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (AuthorizeException e) {
|
||||
//TODO FREDERIC: fail fast
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
return ePerson;
|
||||
}
|
||||
|
||||
public List<JWTClaimProvider> getJwtClaimProviders() {
|
||||
return jwtClaimProviders;
|
||||
//Generate a random 32 byte salt
|
||||
private String generateRandomSalt() {
|
||||
BytesKeyGenerator bytesKeyGenerator = KeyGenerators.secureRandom(32);
|
||||
byte[] secretKey = bytesKeyGenerator.generateKey();
|
||||
return Base64.encodeBase64String(secretKey);
|
||||
}
|
||||
|
||||
public void setJwtClaimProviders(List<JWTClaimProvider> jwtClaimProviders) {
|
||||
this.jwtClaimProviders = jwtClaimProviders;
|
||||
}
|
||||
}
|
||||
|
@@ -66,7 +66,6 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
|
||||
authentication.getPreviousLoginDate(), groups);
|
||||
|
||||
addTokenToResponse(response, token);
|
||||
|
||||
context.commit();
|
||||
|
||||
} catch (JOSEException e) {
|
||||
|
@@ -54,3 +54,8 @@ plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authen
|
||||
jwt.token.secret = thisisatestsecretkeyforjwttokens
|
||||
# Expiration time of a token in minutes
|
||||
jwt.token.expiration = 30
|
||||
# If this property is set to false then the ip-address won't be used as part of the signing key of a jwt token.
|
||||
# This defaults to true
|
||||
# jwt.token.include.ip = true
|
||||
# This enables compression of the payload of a jwt, enabling this will make the jwt token a little smaller at the cost of some performance
|
||||
#jwt.compression.enabled = true
|
Reference in New Issue
Block a user