diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfTokenRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfTokenRepository.java
index 24a0617fb3..83da28b766 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfTokenRepository.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceCsrfTokenRepository.java
@@ -30,9 +30,9 @@ import org.springframework.web.util.WebUtils;
*
* How it works:
*
- * 1. Backend generates XSRF token & stores in a *server-side* cookie named DSPACE-XSRF-COOKIE. This cookie is
- * only readable to clients on the same domain. But, it is returned (by user's browser) on every subsequent request
- * to backend. See "saveToken()" method below.
+ * 1. Backend generates XSRF token & stores in a *server-side* cookie named DSPACE-XSRF-COOKIE. By default, this cookie
+ * is not readable to JS clients (HttpOnly=true). But, it is returned (by user's browser) on every subsequent
+ * request to backend. See "saveToken()" method below.
* 2. At the same time, backend also sends the generated XSRF token in a header named DSPACE-XSRF-TOKEN to client.
* See "saveToken()" method below.
* 3. Client MUST look for DSPACE-XSRF-TOKEN header in a response from backend. If found, the client MUST store/save
@@ -148,14 +148,6 @@ public class DSpaceCsrfTokenRepository implements CsrfTokenRepository {
return null;
}
- // Second, verify either the header or param has been sent. This is a customization for DSpace.
- // Because the server-side cookie is ALWAYS sent back, we need to verify the client has also sent the token in
- // some other way. This ensures that we only *change* the Token when it has been used or attempted to be used.
- //if (!StringUtils.hasLength(request.getHeader(this.headerName)) &&
- // !StringUtils.hasLength(request.getParameter(this.parameterName))) {
- // return null;
- // }
-
// If we got here, we know a token exists in the cookie and *either* the header or the parameter.
// So, this just sends the token info back so that it can be validated by Spring Security.
return new DefaultCsrfToken(this.headerName, this.parameterName, token);
diff --git a/dspace-server-webapp/src/main/webapp/index.html b/dspace-server-webapp/src/main/webapp/index.html
index c80a49e3f5..829c614393 100644
--- a/dspace-server-webapp/src/main/webapp/index.html
+++ b/dspace-server-webapp/src/main/webapp/index.html
@@ -75,7 +75,7 @@
<%= HAL.truncateIfUrl(rel) %> |
<%= link.title || '' %> |
- <%= link.name ? 'name: ' + link.name : 'index: ' + i %> |
+ <%= link.name ? 'name: ' + link.name : 'index: ' + i %> |
<% if (HAL.isUrl(rel)) { %>
@@ -250,6 +250,7 @@ Content-Type: application/json
+
@@ -260,6 +261,7 @@ Content-Type: application/json
+
diff --git a/dspace-server-webapp/src/main/webapp/js/hal/http/client.js b/dspace-server-webapp/src/main/webapp/js/hal/http/client.js
index 2c8ac00029..eab1fe5e97 100644
--- a/dspace-server-webapp/src/main/webapp/js/hal/http/client.js
+++ b/dspace-server-webapp/src/main/webapp/js/hal/http/client.js
@@ -12,23 +12,33 @@ HAL.Http.Client = function(opts) {
this.defaultHeaders = {'Accept': 'application/hal+json, application/json, */*; q=0.01'};
var authorizationHeader = getAuthorizationHeader();
authorizationHeader ? this.defaultHeaders.Authorization = authorizationHeader : '';
- // If we find a CSRF header (in a cookie), send it back in X-XSRF-Token header
- var csrfToken = getCSRFToken();
- csrfToken ? this.defaultHeaders['X-XSRF-Token'] = csrfToken : '';
// Write all headers to console (for easy debugging)
- console.log(this.defaultHeaders);
+ //console.log(this.defaultHeaders);
this.headers = this.defaultHeaders;
};
/**
- * Get CSRF Token by parsing it out of the DSPACE-XSRF-COOKIE (server-side) cookie set by our DSpace server webapp
+ * Get CSRF Token by parsing it out of the "MyHalBrowserCsrfToken" cookie.
+ * This cookie is set in login.html after a successful login occurs.
**/
function getCSRFToken() {
- var cookie = document.cookie.match('(^|;)\\s*' + 'DSPACE-XSRF-COOKIE' + '\\s*=\\s*([^;]+)');
- if(cookie != undefined) {
+ var cookie = document.cookie.match('(^|;)\\s*' + 'MyHalBrowserCsrfToken' + '\\s*=\\s*([^;]+)');
+ if (cookie != null) {
return cookie.pop();
} else {
- return undefined;
+ return null;
+ }
+}
+
+/**
+ * Check current response headers to see if the CSRF Token has changed. If a new value is found in headers,
+ * save the new value into our "MyHalBrowserCsrfToken" cookie.
+ **/
+function checkForUpdatedCSRFTokenInResponse(jqxhr) {
+ // look for DSpace-XSRF-TOKEN header & save to our MyHalBrowserCsrfToken cookie (if found)
+ var updatedCsrfToken = jqxhr.getResponseHeader('DSPACE-XSRF-TOKEN');
+ if (updatedCsrfToken != null) {
+ document.cookie = "MyHalBrowserCsrfToken=" + updatedCsrfToken;
}
}
@@ -38,12 +48,13 @@ function getCSRFToken() {
**/
function getAuthorizationHeader() {
var cookie = document.cookie.match('(^|;)\\s*' + 'MyHalBrowserToken' + '\\s*=\\s*([^;]+)');
- if(cookie != undefined) {
+ if (cookie != null) {
return 'Bearer ' + cookie.pop();
} else {
- return undefined;
+ return null;
}
}
+
function downloadFile(url) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
@@ -89,6 +100,9 @@ HAL.Http.Client.prototype.get = function(url) {
},
headers: this.headers,
success: function(resource, textStatus, jqXHR) {
+ // NOTE: A GET never requires sending an CSRF Token, but the response may send an updated token back.
+ // So, we need to check if a token came back in this GET response.
+ checkForUpdatedCSRFTokenInResponse(jqXHR);
self.vent.trigger('response', {
resource: resource,
jqxhr: jqXHR,
@@ -111,6 +125,18 @@ HAL.Http.Client.prototype.request = function(opts) {
opts.dataType = 'json';
opts.xhrFields = opts.xhrFields || {};
opts.xhrFields.withCredentials = opts.xhrFields.withCredentials || true;
+ opts.headers = opts.headers || {};
+ // If CSRFToken exists, append as a new X-XSRF-Token header
+ var csrfToken = getCSRFToken();
+ if (csrfToken != null) {
+ opts.headers['X-XSRF-Token'] = csrfToken;
+ }
+
+ // Also check response to see if CSRF Token has been updated
+ opts.success = function(resource, textStatus, jqXHR) {
+ checkForUpdatedCSRFTokenInResponse(jqXHR);
+ };
+
self.vent.trigger('location-change', { url: opts.url });
return jqxhr = $.ajax(opts);
};
diff --git a/dspace-server-webapp/src/main/webapp/login.html b/dspace-server-webapp/src/main/webapp/login.html
index 98134e274d..8f6e33ce0f 100644
--- a/dspace-server-webapp/src/main/webapp/login.html
+++ b/dspace-server-webapp/src/main/webapp/login.html
@@ -71,7 +71,13 @@
|