mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 01:54:22 +00:00
71342: Authorization for Downloads of restricted Bitstreams #1
This commit is contained in:
@@ -16,9 +16,11 @@ import org.dspace.app.rest.converter.ConverterService;
|
||||
import org.dspace.app.rest.converter.EPersonConverter;
|
||||
import org.dspace.app.rest.link.HalLinkService;
|
||||
import org.dspace.app.rest.model.AuthenticationStatusRest;
|
||||
import org.dspace.app.rest.model.AuthenticationTokenRest;
|
||||
import org.dspace.app.rest.model.AuthnRest;
|
||||
import org.dspace.app.rest.model.EPersonRest;
|
||||
import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource;
|
||||
import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource;
|
||||
import org.dspace.app.rest.model.hateoas.AuthnResource;
|
||||
import org.dspace.app.rest.projection.Projection;
|
||||
import org.dspace.app.rest.security.RestAuthenticationService;
|
||||
@@ -32,6 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@@ -118,6 +121,30 @@ public class AuthenticationRestController implements InitializingBean {
|
||||
"valid.");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will generate a short lived token to be used for bitstream downloads.
|
||||
*
|
||||
* curl -v -X POST https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo"
|
||||
*
|
||||
* Example:
|
||||
* <pre>
|
||||
* {@code
|
||||
* curl -v -X POST https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo"
|
||||
* }
|
||||
* </pre>
|
||||
* @param request The StandardMultipartHttpServletRequest
|
||||
* @return The created short lived token
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('AUTHENTICATED')")
|
||||
@RequestMapping(value = "/shortlivedtokens", method = RequestMethod.POST)
|
||||
public AuthenticationTokenResource shortLivedLogin(HttpServletRequest request) {
|
||||
Projection projection = utils.obtainProjection();
|
||||
String shortLivedToken =
|
||||
restAuthenticationService.getShortLivedAuthenticationToken(ContextUtil.obtainContext(request), request);
|
||||
AuthenticationTokenRest authenticationTokenRest = converter.toRest(shortLivedToken, projection);
|
||||
return converter.toResource(authenticationTokenRest);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.PUT, RequestMethod.PATCH,
|
||||
RequestMethod.DELETE })
|
||||
public ResponseEntity login() {
|
||||
|
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.converter;
|
||||
|
||||
import org.dspace.app.rest.model.AuthenticationTokenRest;
|
||||
import org.dspace.app.rest.projection.Projection;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* This is the converter from the AuthenticationToken string to tge REST data model
|
||||
*/
|
||||
@Component
|
||||
public class AuthenticationTokenConverter implements DSpaceConverter<String, AuthenticationTokenRest> {
|
||||
@Override
|
||||
public AuthenticationTokenRest convert(String modelObject, Projection projection) {
|
||||
AuthenticationTokenRest token = new AuthenticationTokenRest();
|
||||
token.setToken(modelObject);
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getModelClass() {
|
||||
return String.class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.link;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.dspace.app.rest.AuthenticationRestController;
|
||||
import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* This class adds the self link to the AuthenticationTokenResource.
|
||||
*/
|
||||
@Component
|
||||
public class AuthenticationTokenHalLinkFactory
|
||||
extends HalLinkFactory<AuthenticationTokenResource, AuthenticationRestController> {
|
||||
|
||||
@Override
|
||||
protected void addLinks(AuthenticationTokenResource halResource, Pageable pageable, LinkedList<Link> list)
|
||||
throws Exception {
|
||||
|
||||
list.add(buildLink(IanaLinkRelations.SELF.value(), getMethodOn().shortLivedLogin(null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<AuthenticationRestController> getControllerClass() {
|
||||
return AuthenticationRestController.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<AuthenticationTokenResource> getResourceClass() {
|
||||
return AuthenticationTokenResource.class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.model;
|
||||
|
||||
import org.dspace.app.rest.RestResourceController;
|
||||
|
||||
/**
|
||||
* The authentication token REST HAL Resource. The HAL Resource wraps the REST Resource
|
||||
* adding support for the links and embedded resources
|
||||
*/
|
||||
public class AuthenticationTokenRest extends RestAddressableModel {
|
||||
public static final String NAME = "shortlivedtoken";
|
||||
public static final String CATEGORY = "authn";
|
||||
|
||||
private String token;
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return CATEGORY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getController() {
|
||||
return RestResourceController.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.model.hateoas;
|
||||
|
||||
import org.dspace.app.rest.model.AuthenticationTokenRest;
|
||||
|
||||
/**
|
||||
* Token resource, wraps the AuthenticationToken object
|
||||
*/
|
||||
public class AuthenticationTokenResource extends HALResource<AuthenticationTokenRest> {
|
||||
|
||||
public AuthenticationTokenResource(AuthenticationTokenRest content) {
|
||||
super(content);
|
||||
}
|
||||
}
|
@@ -10,7 +10,7 @@ package org.dspace.app.rest.security;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.dspace.app.rest.security.jwt.JWTTokenHandler;
|
||||
import org.dspace.app.rest.security.jwt.SessionJWTTokenHandler;
|
||||
import org.dspace.app.rest.utils.ContextUtil;
|
||||
import org.dspace.core.Context;
|
||||
import org.slf4j.Logger;
|
||||
@@ -29,7 +29,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class CustomLogoutHandler implements LogoutHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionJWTTokenHandler.class);
|
||||
|
||||
@Autowired
|
||||
private RestAuthenticationService restAuthenticationService;
|
||||
|
@@ -28,6 +28,8 @@ public interface RestAuthenticationService {
|
||||
void addAuthenticationDataForUser(HttpServletRequest request, HttpServletResponse response,
|
||||
DSpaceAuthentication authentication, boolean addCookie) throws IOException;
|
||||
|
||||
String getShortLivedAuthenticationToken(Context context, HttpServletRequest request);
|
||||
|
||||
EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context);
|
||||
|
||||
boolean hasAuthenticationData(HttpServletRequest request);
|
||||
|
@@ -46,17 +46,16 @@ 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.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Class responsible for creating and parsing JSON Web Tokens (JWTs), supports both JWS and JWE
|
||||
* https://jwt.io/
|
||||
* https://jwt.io/ . This abstract class needs to be extended with a class providing the
|
||||
* configuration keys for the particular type of token.
|
||||
*
|
||||
* @author Frederic Van Reet (frederic dot vanreet at atmire dot com)
|
||||
* @author Tom Desair (tom dot desair at atmire dot com)
|
||||
*/
|
||||
@Component
|
||||
public class JWTTokenHandler implements InitializingBean {
|
||||
public abstract class JWTTokenHandler implements InitializingBean {
|
||||
|
||||
private static final int MAX_CLOCK_SKEW_SECONDS = 60;
|
||||
private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class);
|
||||
@@ -86,15 +85,57 @@ public class JWTTokenHandler implements InitializingBean {
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.jwtKey = getSecret("jwt.token.secret");
|
||||
this.encryptionKey = getSecret("jwt.encryption.secret").getBytes();
|
||||
this.jwtKey =
|
||||
getSecret(getTokenSecretConfigurationKey());
|
||||
this.encryptionKey =
|
||||
getSecret(getEncryptionSecretConfigurationKey()).getBytes();
|
||||
|
||||
this.expirationTime = configurationService.getLongProperty("jwt.token.expiration", 30) * 60 * 1000;
|
||||
this.includeIP = configurationService.getBooleanProperty("jwt.token.include.ip", true);
|
||||
this.encryptionEnabled = configurationService.getBooleanProperty("jwt.encryption.enabled", false);
|
||||
this.compressionEnabled = configurationService.getBooleanProperty("jwt.compression.enabled", false);
|
||||
this.expirationTime =
|
||||
configurationService.getLongProperty(getTokenExpirationConfigurationKey(), 30);
|
||||
this.includeIP =
|
||||
configurationService.getBooleanProperty(getTokenIncludeIPConfigurationKey(), true);
|
||||
this.encryptionEnabled =
|
||||
configurationService.getBooleanProperty(getEncryptionEnabledConfigurationKey(), false);
|
||||
this.compressionEnabled =
|
||||
configurationService.getBooleanProperty(getCompressionEnabledConfigurationKey(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration property key for the token secret.
|
||||
* @return the configuration property key
|
||||
*/
|
||||
protected abstract String getTokenSecretConfigurationKey();
|
||||
|
||||
/**
|
||||
* Get the configuration property key for the encryption secret.
|
||||
* @return the configuration property key
|
||||
*/
|
||||
protected abstract String getEncryptionSecretConfigurationKey();
|
||||
|
||||
/**
|
||||
* Get the configuration property key for the expiration time.
|
||||
* @return the configuration property key
|
||||
*/
|
||||
protected abstract String getTokenExpirationConfigurationKey();
|
||||
|
||||
/**
|
||||
* Get the configuration property key for the include ip.
|
||||
* @return the configuration property key
|
||||
*/
|
||||
protected abstract String getTokenIncludeIPConfigurationKey();
|
||||
|
||||
/**
|
||||
* Get the configuration property key for the encryption enable setting.
|
||||
* @return the configuration property key
|
||||
*/
|
||||
protected abstract String getEncryptionEnabledConfigurationKey();
|
||||
|
||||
/**
|
||||
* Get the configuration property key for the compression enable setting.
|
||||
* @return the configuration property key
|
||||
*/
|
||||
protected abstract String getCompressionEnabledConfigurationKey();
|
||||
|
||||
/**
|
||||
* Retrieve EPerson from a JSON Web Token (JWT)
|
||||
*
|
||||
|
@@ -49,7 +49,10 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
|
||||
private static final String AUTHORIZATION_TYPE = "Bearer";
|
||||
|
||||
@Autowired
|
||||
private JWTTokenHandler jwtTokenHandler;
|
||||
private SessionJWTTokenHandler sessionJWTTokenHandler;
|
||||
|
||||
@Autowired
|
||||
private ShortLivedJWTTokenHandler shortLivedJWTTokenHandler;
|
||||
|
||||
@Autowired
|
||||
private EPersonService ePersonService;
|
||||
@@ -71,7 +74,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
|
||||
|
||||
List<Group> groups = authenticationService.getSpecialGroups(context, request);
|
||||
|
||||
String token = jwtTokenHandler.createTokenForEPerson(context, request,
|
||||
String token = sessionJWTTokenHandler.createTokenForEPerson(context, request,
|
||||
authentication.getPreviousLoginDate(), groups);
|
||||
|
||||
addTokenToResponse(response, token, addCookie);
|
||||
@@ -84,11 +87,34 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a short-lived token for bitstream downloads
|
||||
* @param context The context for which to create the token
|
||||
* @param request The request for which to create the token
|
||||
* @return The token with a short lifespan
|
||||
*/
|
||||
@Override
|
||||
public String getShortLivedAuthenticationToken(Context context, HttpServletRequest request) {
|
||||
String token = null;
|
||||
try {
|
||||
List<Group> groups = authenticationService.getSpecialGroups(context, request);
|
||||
token = shortLivedJWTTokenHandler.createTokenForEPerson(context, request, null, groups);
|
||||
context.commit();
|
||||
return token;
|
||||
} catch (JOSEException e) {
|
||||
log.error("JOSE Exception", e);
|
||||
} catch (SQLException e) {
|
||||
log.error("SQL error when adding authentication", e);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context) {
|
||||
String token = getToken(request);
|
||||
try {
|
||||
EPerson ePerson = jwtTokenHandler.parseEPersonFromToken(token, request, context);
|
||||
EPerson ePerson = sessionJWTTokenHandler.parseEPersonFromToken(token, request, context);
|
||||
return ePerson;
|
||||
} catch (JOSEException e) {
|
||||
log.error("Jose error", e);
|
||||
@@ -111,7 +137,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
|
||||
Context context) throws Exception {
|
||||
String token = getToken(request);
|
||||
invalidateAuthenticationCookie(response);
|
||||
jwtTokenHandler.invalidateToken(token, request, context);
|
||||
sessionJWTTokenHandler.invalidateToken(token, request, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.security.jwt;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Class responsible for creating and parsing JSON Web Tokens (JWTs), supports both JWS and JWE
|
||||
* https://jwt.io/
|
||||
*/
|
||||
@Component
|
||||
public class SessionJWTTokenHandler extends JWTTokenHandler {
|
||||
@Override
|
||||
protected String getTokenSecretConfigurationKey() {
|
||||
return "jwt.session.token.secret";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getEncryptionSecretConfigurationKey() {
|
||||
return "jwt.session.encryption.secret";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTokenExpirationConfigurationKey() {
|
||||
return "jwt.session.token.expiration";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTokenIncludeIPConfigurationKey() {
|
||||
return "jwt.session.token.include.ip";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getEncryptionEnabledConfigurationKey() {
|
||||
return "jwt.session.encryption.enabled";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCompressionEnabledConfigurationKey() {
|
||||
return "jwt.session.compression.enabled";
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.security.jwt;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Class responsible for creating and parsing JSON Web Tokens (JWTs) used for bitstream
|
||||
* dowloads, supports both JWS and JWE https://jwt.io/ .
|
||||
*/
|
||||
@Component
|
||||
public class ShortLivedJWTTokenHandler extends JWTTokenHandler {
|
||||
@Override
|
||||
protected String getTokenSecretConfigurationKey() {
|
||||
return "jwt.shortLived.token.secret";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getEncryptionSecretConfigurationKey() {
|
||||
return "jwt.shortLived.encryption.secret";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTokenExpirationConfigurationKey() {
|
||||
return "jwt.shortLived.token.expiration";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTokenIncludeIPConfigurationKey() {
|
||||
return "jwt.shortLived.token.include.ip";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getEncryptionEnabledConfigurationKey() {
|
||||
return "jwt.shortLived.encryption.enabled";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCompressionEnabledConfigurationKey() {
|
||||
return "jwt.shortLived.compression.enabled";
|
||||
}
|
||||
}
|
@@ -11,6 +11,7 @@ import static java.lang.Thread.sleep;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
@@ -30,6 +31,7 @@ import org.dspace.app.rest.matcher.HalMatcher;
|
||||
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@@ -757,4 +759,19 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(status().isUnauthorized());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortLivedToken() throws Exception {
|
||||
String token = getAuthToken(eperson.getEmail(), password);
|
||||
getClient(token).perform(post("/api/authn/shortlivedtokens"))
|
||||
.andExpect(jsonPath("$.token", notNullValue()))
|
||||
.andExpect(jsonPath("$.type", is("shortlivedtoken")))
|
||||
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/authn/shortlivedtokens")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortLivedTokenNotAuthenticated() throws Exception {
|
||||
getClient().perform(post("/api/authn/shortlivedtokens"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ public class JWTTokenHandlerTest {
|
||||
|
||||
@InjectMocks
|
||||
@Spy
|
||||
JWTTokenHandler jwtTokenHandler;
|
||||
SessionJWTTokenHandler sessionJWTTokenHandler;
|
||||
|
||||
@Mock
|
||||
private Context context;
|
||||
@@ -87,7 +87,7 @@ public class JWTTokenHandlerTest {
|
||||
@Test
|
||||
public void testJWTNoEncryption() throws Exception {
|
||||
Date previous = new Date(System.currentTimeMillis() - 10000000000L);
|
||||
String token = jwtTokenHandler
|
||||
String token = sessionJWTTokenHandler
|
||||
.createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>());
|
||||
SignedJWT signedJWT = SignedJWT.parse(token);
|
||||
String personId = (String) signedJWT.getJWTClaimsSet().getClaim(EPersonClaimProvider.EPERSON_ID);
|
||||
@@ -96,11 +96,11 @@ public class JWTTokenHandlerTest {
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testJWTEncrypted() throws Exception {
|
||||
when(jwtTokenHandler.isEncryptionEnabled()).thenReturn(true);
|
||||
when(sessionJWTTokenHandler.isEncryptionEnabled()).thenReturn(true);
|
||||
Date previous = new Date(System.currentTimeMillis() - 10000000000L);
|
||||
StringKeyGenerator keyGenerator = KeyGenerators.string();
|
||||
when(jwtTokenHandler.getEncryptionKey()).thenReturn(keyGenerator.generateKey().getBytes());
|
||||
String token = jwtTokenHandler
|
||||
when(sessionJWTTokenHandler.getEncryptionKey()).thenReturn(keyGenerator.generateKey().getBytes());
|
||||
String token = sessionJWTTokenHandler
|
||||
.createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>());
|
||||
SignedJWT signedJWT = SignedJWT.parse(token);
|
||||
}
|
||||
@@ -108,12 +108,12 @@ public class JWTTokenHandlerTest {
|
||||
//temporary set a negative expiration time so the token is invalid immediately
|
||||
@Test
|
||||
public void testExpiredToken() throws Exception {
|
||||
when(jwtTokenHandler.getExpirationPeriod()).thenReturn(-99999999L);
|
||||
when(sessionJWTTokenHandler.getExpirationPeriod()).thenReturn(-99999999L);
|
||||
when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson);
|
||||
Date previous = new Date(new Date().getTime() - 10000000000L);
|
||||
String token = jwtTokenHandler
|
||||
String token = sessionJWTTokenHandler
|
||||
.createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>());
|
||||
EPerson parsed = jwtTokenHandler.parseEPersonFromToken(token, httpServletRequest, context);
|
||||
EPerson parsed = sessionJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context);
|
||||
assertEquals(null, parsed);
|
||||
|
||||
}
|
||||
@@ -121,17 +121,17 @@ public class JWTTokenHandlerTest {
|
||||
//Try if we can change the expiration date
|
||||
@Test
|
||||
public void testTokenTampering() throws Exception {
|
||||
when(jwtTokenHandler.getExpirationPeriod()).thenReturn(-99999999L);
|
||||
when(sessionJWTTokenHandler.getExpirationPeriod()).thenReturn(-99999999L);
|
||||
when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson);
|
||||
Date previous = new Date(new Date().getTime() - 10000000000L);
|
||||
String token = jwtTokenHandler
|
||||
String token = sessionJWTTokenHandler
|
||||
.createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>());
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().claim("eid", "epersonID").expirationTime(
|
||||
new Date(System.currentTimeMillis() + 99999999)).build();
|
||||
String tamperedPayload = new String(Base64.getUrlEncoder().encode(jwtClaimsSet.toString().getBytes()));
|
||||
String[] splitToken = token.split("\\.");
|
||||
String tamperedToken = splitToken[0] + "." + tamperedPayload + "." + splitToken[2];
|
||||
EPerson parsed = jwtTokenHandler.parseEPersonFromToken(tamperedToken, httpServletRequest, context);
|
||||
EPerson parsed = sessionJWTTokenHandler.parseEPersonFromToken(tamperedToken, httpServletRequest, context);
|
||||
assertEquals(null, parsed);
|
||||
}
|
||||
|
||||
@@ -139,12 +139,12 @@ public class JWTTokenHandlerTest {
|
||||
public void testInvalidatedToken() throws Exception {
|
||||
Date previous = new Date(System.currentTimeMillis() - 10000000000L);
|
||||
// create a new token
|
||||
String token = jwtTokenHandler
|
||||
String token = sessionJWTTokenHandler
|
||||
.createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>());
|
||||
// immediately invalidate it
|
||||
jwtTokenHandler.invalidateToken(token, new MockHttpServletRequest(), context);
|
||||
sessionJWTTokenHandler.invalidateToken(token, new MockHttpServletRequest(), context);
|
||||
// Check if it is still valid by trying to parse the EPerson from it (should return null)
|
||||
EPerson parsed = jwtTokenHandler.parseEPersonFromToken(token, httpServletRequest, context);
|
||||
EPerson parsed = sessionJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context);
|
||||
assertEquals(null, parsed);
|
||||
}
|
||||
|
||||
|
@@ -57,26 +57,56 @@ plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authen
|
||||
# Server key part that is a part of the key used to sign the authentication tokens.
|
||||
# If this property is not set or empty, DSpace will generate a random key on startup.
|
||||
# IF YOU ARE RUNNING DSPACE IN A CLUSTER, you need to set a value for this property here or as an environment variable
|
||||
# jwt.token.secret =
|
||||
# jwt.session.token.secret =
|
||||
|
||||
# This property enables/disables encryption of the payload in a stateless token. Enabling this makes the data encrypted
|
||||
# and unreadable by the receiver, but makes the token larger in size. false by default
|
||||
jwt.encryption.enabled = false
|
||||
jwt.session.encryption.enabled = false
|
||||
|
||||
# Encryption key to use when JWT token encryption is enabled (JWE). Note that encrypting tokens might required additional
|
||||
# configuration in the REST clients
|
||||
# jwt.encryption.secret =
|
||||
# jwt.session.encryption.secret =
|
||||
|
||||
# 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, this setting WILL ONLY BE used when encrypting the jwt.
|
||||
jwt.compression.enabled = true
|
||||
jwt.session.compression.enabled = true
|
||||
|
||||
# Expiration time of a token in minutes
|
||||
jwt.token.expiration = 30
|
||||
# Expiration time of a token in milliseconds
|
||||
jwt.session.token.expiration = 1800000
|
||||
|
||||
# Restrict tokens to a specific ip-address to prevent theft/session hijacking. This is achieved by making the ip-address
|
||||
# a part of the JWT siging key. 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 and tokens can be shared over multiple ip-addresses.
|
||||
# For security reasons, this defaults to true
|
||||
jwt.token.include.ip = true
|
||||
jwt.session.token.include.ip = true
|
||||
|
||||
|
||||
#---------------------------------------------------------------#
|
||||
#---Stateless JWT Authentication for downloads of bitstreams----#
|
||||
#---------------------------------------------------------------#
|
||||
|
||||
# Server key part that is a part of the key used to sign the authentication tokens.
|
||||
# If this property is not set or empty, DSpace will generate a random key on startup.
|
||||
# IF YOU ARE RUNNING DSPACE IN A CLUSTER, you need to set a value for this property here or as an environment variable
|
||||
# jwt.shortLived.token.secret =
|
||||
|
||||
# This property enables/disables encryption of the payload in a stateless token. Enabling this makes the data encrypted
|
||||
# and unreadable by the receiver, but makes the token larger in size. false by default
|
||||
jwt.shortLived.encryption.enabled = false
|
||||
|
||||
# Encryption key to use when JWT token encryption is enabled (JWE). Note that encrypting tokens might required additional
|
||||
# configuration in the REST clients
|
||||
# jwt.shortLived.encryption.secret =
|
||||
|
||||
# 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, this setting WILL ONLY BE used when encrypting the jwt.
|
||||
jwt.shortLived.compression.enabled = true
|
||||
|
||||
# Expiration time of a token in milliseconds
|
||||
jwt.shortLived.token.expiration = 2000
|
||||
|
||||
# Restrict tokens to a specific ip-address to prevent theft/session hijacking. This is achieved by making the ip-address
|
||||
# a part of the JWT siging key. 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 and tokens can be shared over multiple ip-addresses.
|
||||
# For security reasons, this defaults to true
|
||||
jwt.shortLived.token.include.ip = true
|
||||
|
Reference in New Issue
Block a user