Mar*_*ele 95 javascript jwt angularjs
我正在Angular中编写一个webapp,其中身份验证由JWT令牌处理,这意味着每个请求都有一个带有所有必要信息的"身份验证"标头.
这适用于REST调用,但我不明白我应该如何处理后端托管的文件的下载链接(这些文件驻留在托管webservices的同一服务器上).
我不能使用常规<a href='...'/>链接,因为它们不会携带任何标头,验证将失败.同样的各种咒语window.open(...).
我想到的一些解决方案:
以上所有都不太令人满意.
1是我现在使用的解决方案.我不喜欢它有两个原因:首先它不是理想的安全性,其次它可以工作,但它需要相当多的工作,特别是在服务器上:下载一些我需要调用一个生成一个新"随机"的服务"url,将它存储在某个地方(可能在数据库上)一段时间,并将其返回给客户端.客户端获取URL,并使用window.open或类似的.请求时,新URL应检查它是否仍然有效,然后返回数据.
2似乎至少同样多的工作.
3看起来很多工作,甚至使用可用的库,以及许多潜在的问题.(我需要提供自己的下载状态栏,将整个文件加载到内存中,然后要求用户在本地保存文件).
这个任务似乎是一个非常基本的任务,所以我想知道是否有更简单的东西我可以使用.
我不一定在寻找"Angular方式"的解决方案.常规Javascript会没事的.
Tec*_*ium 36
这是使用download属性,fetch API和URL.createObjectURL在客户端下载它的方法.您可以使用JWT获取文件,将有效负载转换为blob,将blob放入objectURL,将锚标记的源设置为该objectURL,然后在javascript中单击该objectURL.
let anchor = document.createElement("a");
document.body.appendChild(anchor);
let file = 'https://www.example.com/some-file.pdf';
let headers = new Headers();
headers.append('Authorization', 'Bearer MY-TOKEN');
fetch(file, { headers })
.then(response => response.blob())
.then(blobby => {
let objectUrl = window.URL.createObjectURL(blobby);
anchor.href = objectUrl;
anchor.download = 'some-file.pdf';
anchor.click();
window.URL.revokeObjectURL(objectUrl);
});
Run Code Online (Sandbox Code Playgroud)
download属性的值将是最终的文件名.如果需要,您可以从内容处置响应标头中挖掘出预期的文件名,如其他答案中所述.
Eze*_*lla 30
根据已知的JWT传道者Auth0的Matias Woloski的建议,我通过与Hawk生成签名请求解决了这个问题.
引用沃洛斯基:
您解决此问题的方法是生成像AWS这样的签名请求.
我创建了一个API来签署我的下载网址:
请求:
POST /api/sign
Content-Type: application/json
Authorization: Bearer...
{"url": "https://path.to/protected.file"}
Run Code Online (Sandbox Code Playgroud)
响应:
{"url": "https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c"}
Run Code Online (Sandbox Code Playgroud)
使用签名URL,我们可以获取该文件
请求:
GET https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c
Run Code Online (Sandbox Code Playgroud)
响应:
Content-Type: multipart/mixed; charset="UTF-8"
Content-Disposition': attachment; filename=protected.file
{BLOB}
Run Code Online (Sandbox Code Playgroud)
这样您就可以通过单个用户点击完成所有操作:
function clickedOnDownloadButton() {
postToSignWithAuthorizationHeader({
url: 'https://path.to/protected.file'
}).then(function(signed) {
window.location = signed.url;
});
}
Run Code Online (Sandbox Code Playgroud)
Jam*_*mes 23
已经提到的现有“fetch/createObjectURL”和“download-token”方法的替代方法是针对新 window的标准表单 POST。浏览器读取服务器响应中的附件标头后,将关闭新选项卡并开始下载。同样的方法也恰好适用于在新选项卡中显示 PDF 等资源。
这可以更好地支持旧浏览器并避免必须管理新型令牌。这也将比 URL 上的基本身份验证具有更好的长期支持,因为浏览器正在删除对 url 上的用户名/密码的支持。
在客户端,target="_blank"即使在失败的情况下,我们也使用避免导航,这对于 SPA(单页应用程序)尤其重要。
主要的警告是服务器端JWT 验证必须从POST 数据而不是从 header 中获取令牌。如果您的框架使用 Authentication 标头自动管理对路由处理程序的访问,您可能需要将您的处理程序标记为未经身份验证/匿名,以便您可以手动验证 JWT 以确保正确授权。
表单可以动态创建并立即销毁,以便正确清理(注意:这可以在纯 JS 中完成,但为了清晰起见,此处使用 JQuery)-
function DownloadWithJwtViaFormPost(url, id, token) {
var jwtInput = $('<input type="hidden" name="jwtToken">').val(token);
var idInput = $('<input type="hidden" name="id">').val(id);
$('<form method="post" target="_blank"></form>')
.attr("action", url)
.append(jwtInput)
.append(idInput)
.appendTo('body')
.submit()
.remove();
}
Run Code Online (Sandbox Code Playgroud)
只需添加您需要作为隐藏输入提交的任何额外数据,并确保将它们附加到表单中。
James 的回答的纯 JS 版本
function downloadFile (url, token) {
let form = document.createElement('form')
form.method = 'post'
form.target = '_blank'
form.action = url
form.innerHTML = '<input type="hidden" name="jwtToken" value="' + token + '">'
console.log('form:', form)
document.body.appendChild(form)
form.submit()
document.body.removeChild(form)
}
Run Code Online (Sandbox Code Playgroud)
我会生成令牌进行下载。
在角度范围内,发出经过身份验证的请求以获取临时令牌(例如一个小时),然后将其作为get参数添加到url中。这样,您可以按照自己喜欢的任何方式下载文件(window.open ...)
另一个解决方案:使用基本身份验证。虽然它需要在后端做一些工作,但令牌在日志中是不可见的,并且不需要实施 URL 签名。
一个示例 URL 可以是:
http://jwt:<user jwt token>@some.url/file/35/download
带有虚拟令牌的示例:
http://jwt:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwIiwibmFtZSI6IiIsImlhdCI6MH0.KsKmQOZM-jcy4l_7NFsv1lWfpH8ofniVCv75ZRQrWno@some.url/file/35/download
然后您可以将其推入<a href="...">或window.open("...")- 浏览器处理其余部分。
此处的实现取决于您,并且取决于您的服务器设置 - 这与使用?token=查询参数没有太大区别。
使用 Laravel,我采用了简单的方法,将基本身份验证密码转换为 JWTAuthorization: Bearer <...>标头,让普通身份验证中间件处理其余部分:
class CarryBasic
{
/**
* @param Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, \Closure $next)
{
// if no basic auth is passed,
// or the user is not "jwt",
// send a 401 and trigger the basic auth dialog
if ($request->getUser() !== 'jwt') {
return $this->failedBasicResponse();
}
// if there _is_ basic auth passed,
// and the user is JWT,
// shove the password into the "Authorization: Bearer <...>"
// header and let the other middleware
// handle it.
$request->headers->set(
'Authorization',
'Bearer ' . $request->getPassword()
);
return $next($request);
}
/**
* Get the response for basic authentication.
*
* @return void
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
protected function failedBasicResponse()
{
throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
24032 次 |
| 最近记录: |