mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-17 15:03:18 +00:00
Update HAL browser to use DSPACE-XSRF-TOKEN header and store token in custom MyHalBrowserCsrfToken Cookie. Minor comment fixes to TokenRepo
This commit is contained in:
@@ -30,9 +30,9 @@ import org.springframework.web.util.WebUtils;
|
|||||||
*
|
*
|
||||||
* How it works:
|
* How it works:
|
||||||
*
|
*
|
||||||
* 1. Backend generates XSRF token & stores in a *server-side* cookie named DSPACE-XSRF-COOKIE. This cookie is
|
* 1. Backend generates XSRF token & stores in a *server-side* cookie named DSPACE-XSRF-COOKIE. By default, this cookie
|
||||||
* only readable to clients on the same domain. But, it is returned (by user's browser) on every subsequent request
|
* is not readable to JS clients (HttpOnly=true). But, it is returned (by user's browser) on every subsequent
|
||||||
* to backend. See "saveToken()" method below.
|
* 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.
|
* 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.
|
* 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
|
* 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;
|
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.
|
// 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.
|
// 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);
|
return new DefaultCsrfToken(this.headerName, this.parameterName, token);
|
||||||
|
@@ -75,7 +75,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><strong><%= HAL.truncateIfUrl(rel) %></strong></td>
|
<td><strong><%= HAL.truncateIfUrl(rel) %></strong></td>
|
||||||
<td><%= link.title || '' %></td>
|
<td><%= link.title || '' %></td>
|
||||||
<td><%= link.name ? 'name: ' + link.name : 'index: ' + i %></a></td>
|
<td><%= link.name ? 'name: ' + link.name : 'index: ' + i %></td>
|
||||||
<td>
|
<td>
|
||||||
<% if (HAL.isUrl(rel)) { %>
|
<% if (HAL.isUrl(rel)) { %>
|
||||||
<a class="dox" href="<%= HAL.normalizeUrl(HAL.buildUrl(rel)) %>"><i class="icon-book"></i></a>
|
<a class="dox" href="<%= HAL.normalizeUrl(HAL.buildUrl(rel)) %>"><i class="icon-book"></i></a>
|
||||||
@@ -250,6 +250,7 @@ Content-Type: application/json
|
|||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Customized (to use WebJars) for DSpace -->
|
||||||
<script src="webjars/jquery/dist/jquery.min.js"></script>
|
<script src="webjars/jquery/dist/jquery.min.js"></script>
|
||||||
<script src="browser/vendor/js/underscore.js"></script>
|
<script src="browser/vendor/js/underscore.js"></script>
|
||||||
<script src="browser/vendor/js/backbone.js"></script>
|
<script src="browser/vendor/js/backbone.js"></script>
|
||||||
@@ -260,6 +261,7 @@ Content-Type: application/json
|
|||||||
<script src="browser/js/hal.js"></script>
|
<script src="browser/js/hal.js"></script>
|
||||||
<script src="browser/js/hal/browser.js"></script>
|
<script src="browser/js/hal/browser.js"></script>
|
||||||
|
|
||||||
|
<!-- Customized for DSpace -->
|
||||||
<script src="js/hal/http/client.js"></script>
|
<script src="js/hal/http/client.js"></script>
|
||||||
<script src="browser/js/hal/resource.js"></script>
|
<script src="browser/js/hal/resource.js"></script>
|
||||||
|
|
||||||
|
@@ -12,23 +12,33 @@ HAL.Http.Client = function(opts) {
|
|||||||
this.defaultHeaders = {'Accept': 'application/hal+json, application/json, */*; q=0.01'};
|
this.defaultHeaders = {'Accept': 'application/hal+json, application/json, */*; q=0.01'};
|
||||||
var authorizationHeader = getAuthorizationHeader();
|
var authorizationHeader = getAuthorizationHeader();
|
||||||
authorizationHeader ? this.defaultHeaders.Authorization = authorizationHeader : '';
|
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)
|
// Write all headers to console (for easy debugging)
|
||||||
console.log(this.defaultHeaders);
|
//console.log(this.defaultHeaders);
|
||||||
this.headers = 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() {
|
function getCSRFToken() {
|
||||||
var cookie = document.cookie.match('(^|;)\\s*' + 'DSPACE-XSRF-COOKIE' + '\\s*=\\s*([^;]+)');
|
var cookie = document.cookie.match('(^|;)\\s*' + 'MyHalBrowserCsrfToken' + '\\s*=\\s*([^;]+)');
|
||||||
if(cookie != undefined) {
|
if (cookie != null) {
|
||||||
return cookie.pop();
|
return cookie.pop();
|
||||||
} else {
|
} 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() {
|
function getAuthorizationHeader() {
|
||||||
var cookie = document.cookie.match('(^|;)\\s*' + 'MyHalBrowserToken' + '\\s*=\\s*([^;]+)');
|
var cookie = document.cookie.match('(^|;)\\s*' + 'MyHalBrowserToken' + '\\s*=\\s*([^;]+)');
|
||||||
if(cookie != undefined) {
|
if (cookie != null) {
|
||||||
return 'Bearer ' + cookie.pop();
|
return 'Bearer ' + cookie.pop();
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadFile(url) {
|
function downloadFile(url) {
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
request.open('GET', url, true);
|
request.open('GET', url, true);
|
||||||
@@ -89,6 +100,9 @@ HAL.Http.Client.prototype.get = function(url) {
|
|||||||
},
|
},
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
success: function(resource, textStatus, jqXHR) {
|
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', {
|
self.vent.trigger('response', {
|
||||||
resource: resource,
|
resource: resource,
|
||||||
jqxhr: jqXHR,
|
jqxhr: jqXHR,
|
||||||
@@ -111,6 +125,18 @@ HAL.Http.Client.prototype.request = function(opts) {
|
|||||||
opts.dataType = 'json';
|
opts.dataType = 'json';
|
||||||
opts.xhrFields = opts.xhrFields || {};
|
opts.xhrFields = opts.xhrFields || {};
|
||||||
opts.xhrFields.withCredentials = opts.xhrFields.withCredentials || true;
|
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 });
|
self.vent.trigger('location-change', { url: opts.url });
|
||||||
return jqxhr = $.ajax(opts);
|
return jqxhr = $.ajax(opts);
|
||||||
};
|
};
|
||||||
|
@@ -71,7 +71,13 @@
|
|||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var successHandler = function(result, status, xhr) {
|
var successHandler = function(result, status, xhr) {
|
||||||
|
// look for Authorization header & save to a MyHalBrowserToken cookie
|
||||||
document.cookie = "MyHalBrowserToken=" + xhr.getResponseHeader('Authorization').split(" ")[1];
|
document.cookie = "MyHalBrowserToken=" + xhr.getResponseHeader('Authorization').split(" ")[1];
|
||||||
|
// look for DSpace-XSRF-TOKEN header & save to a MyHalBrowserCsrfToken cookie (if found)
|
||||||
|
var csrfToken = xhr.getResponseHeader('DSPACE-XSRF-TOKEN');
|
||||||
|
if (csrfToken!=null) {
|
||||||
|
document.cookie = "MyHalBrowserCsrfToken=" + csrfToken;
|
||||||
|
}
|
||||||
toastr.success('You are now logged in. Please wait while we redirect you...', 'Login Successful');
|
toastr.success('You are now logged in. Please wait while we redirect you...', 'Login Successful');
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
window.location.href = window.location.pathname.replace("login.html", "");
|
window.location.href = window.location.pathname.replace("login.html", "");
|
||||||
@@ -101,7 +107,9 @@
|
|||||||
beforeSend: function (xhr, settings) {
|
beforeSend: function (xhr, settings) {
|
||||||
// If CSRF token found in cookie, send it back as X-XSRF-Token header
|
// If CSRF token found in cookie, send it back as X-XSRF-Token header
|
||||||
var csrfToken = getCSRFToken();
|
var csrfToken = getCSRFToken();
|
||||||
xhr.setRequestHeader('X-XSRF-Token', csrfToken ? csrfToken : '');
|
if (csrfToken != null) {
|
||||||
|
xhr.setRequestHeader('X-XSRF-Token', csrfToken);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
success : successHandler,
|
success : successHandler,
|
||||||
error : function(result, status, xhr) {
|
error : function(result, status, xhr) {
|
||||||
@@ -139,14 +147,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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() {
|
function getCSRFToken() {
|
||||||
var cookie = document.cookie.match('(^|;)\\s*' + 'DSPACE-XSRF-COOKIE' + '\\s*=\\s*([^;]+)');
|
var cookie = document.cookie.match('(^|;)\\s*' + 'MyHalBrowserCsrfToken' + '\\s*=\\s*([^;]+)');
|
||||||
if(cookie != undefined) {
|
if (cookie != null) {
|
||||||
return cookie.pop();
|
return cookie.pop();
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +173,9 @@
|
|||||||
beforeSend: function (xhr, settings) {
|
beforeSend: function (xhr, settings) {
|
||||||
// If CSRF token found in cookie, send it back as X-XSRF-Token header
|
// If CSRF token found in cookie, send it back as X-XSRF-Token header
|
||||||
var csrfToken = getCSRFToken();
|
var csrfToken = getCSRFToken();
|
||||||
xhr.setRequestHeader('X-XSRF-Token', csrfToken ? csrfToken : '');
|
if (csrfToken != null) {
|
||||||
|
xhr.setRequestHeader('X-XSRF-Token', csrfToken);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
success : successHandler,
|
success : successHandler,
|
||||||
error : function() {
|
error : function() {
|
||||||
|
Reference in New Issue
Block a user