저는 인증이 JWT 토큰에 의해 처리되는 Angular에서 webapp을 작성하고 있습니다. 즉, 모든 요청에는 필요한 모든 정보가 포함 된 “Authentication”헤더가 있습니다.
이것은 REST 호출에서 잘 작동하지만 백엔드에서 호스팅되는 파일에 대한 다운로드 링크를 처리하는 방법을 이해하지 못합니다 (파일은 웹 서비스가 호스팅되는 동일한 서버에 있습니다).
일반 <a href='...'/>
링크는 헤더가 없어 인증이 실패하기 때문에 사용할 수 없습니다 . 의 다양한 주문에 대해서도 동일합니다 window.open(...)
.
내가 생각한 몇 가지 솔루션 :
- 서버에 보안되지 않은 임시 다운로드 링크 생성
- 인증 정보를 URL 매개 변수로 전달하고 케이스를 수동으로 처리합니다.
- XHR을 통해 데이터를 가져오고 파일 클라이언트 측에 저장합니다.
위의 모든 것이 만족스럽지 않습니다.
1은 내가 지금 사용하고있는 솔루션입니다. 나는 두 가지 이유로 좋아하지 않습니다. 첫째는 보안 상 이상적이지 않고, 둘째는 작동하지만 특히 서버에서 상당히 많은 작업이 필요합니다. 새로운 “무작위”를 생성하는 서비스를 호출해야하는 것을 다운로드하려면 “url, 어딘가에 (아마도 DB에) 저장 한 다음 클라이언트에 반환합니다. 클라이언트는 URL을 가져 와서 window.open 또는 이와 유사한 것을 사용합니다. 요청시 새 URL은 여전히 유효한지 확인한 다음 데이터를 반환해야합니다.
2는 적어도 일이 많은 것 같습니다.
3은 사용 가능한 라이브러리를 사용하는 경우에도 많은 작업과 많은 잠재적 문제로 보입니다. (내 자신의 다운로드 상태 표시 줄을 제공하고 전체 파일을 메모리에로드 한 다음 사용자에게 파일을 로컬에 저장하도록 요청해야합니다.)
이 작업은 매우 기본적인 작업으로 보이므로 사용할 수있는 훨씬 더 간단한 작업이 있는지 궁금합니다.
필자는 “각도 방식”솔루션을 반드시 찾고있는 것은 아닙니다. 일반 Javascript가 좋습니다.
답변
다음 은 download 속성 , fetch API 및 URL.createObjectURL을 사용 하여 클라이언트에 다운로드하는 방법 입니다. JWT를 사용하여 파일을 가져오고, 페이로드를 blob으로 변환하고, blob을 objectURL에 넣고, 앵커 태그의 소스를 해당 objectURL로 설정하고, 자바 스크립트에서 해당 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);
});
download
속성 값은 최종 파일 이름이됩니다. 원하는 경우 다른 답변에 설명 된대로 콘텐츠 처리 응답 헤더에서 의도 한 파일 이름을 마이닝 할 수 있습니다 .
답변
기술
JWT 전도사로 알려진 Auth0의 Matias Woloski의 조언 을 바탕으로 Hawk 와 함께 서명 된 요청을 생성하여 문제를 해결했습니다 .
Woloski 인용 :
이를 해결하는 방법은 예를 들어 AWS와 같은 서명 된 요청을 생성하는 것입니다.
여기 활성화 링크에 사용되는이 기술 의 예가 있습니다.
백엔드
다운로드 URL에 서명하는 API를 만들었습니다.
의뢰:
POST /api/sign
Content-Type: application/json
Authorization: Bearer...
{"url": "https://path.to/protected.file"}
응답:
{"url": "https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c"}
서명 된 URL로 파일을 가져올 수 있습니다.
의뢰:
GET https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c
응답:
Content-Type: multipart/mixed; charset="UTF-8"
Content-Disposition': attachment; filename=protected.file
{BLOB}
프론트 엔드 (by jojoyuji )
이렇게하면 한 번의 사용자 클릭으로 모든 작업을 수행 할 수 있습니다.
function clickedOnDownloadButton() {
postToSignWithAuthorizationHeader({
url: 'https://path.to/protected.file'
}).then(function(signed) {
window.location = signed.url;
});
}
답변
이미 언급 된 기존 “fetch / createObjectURL”및 “download-token”접근 방식의 대안 은 새 창을 대상으로 하는 표준 양식 POST입니다 . 브라우저가 서버 응답에서 첨부 파일 헤더를 읽으면 새 탭을 닫고 다운로드를 시작합니다. 이 동일한 접근 방식은 새 탭에서 PDF와 같은 리소스를 표시하는데도 잘 작동합니다.
이것은 이전 브라우저를 더 잘 지원하고 새로운 유형의 토큰을 관리 할 필요가 없습니다. 또한 URL에 대한 사용자 이름 / 비밀번호에 대한 지원이 브라우저에 의해 제거 되기 때문에 URL에 대한 기본 인증보다 장기적인 지원이 더 좋습니다 .
온 클라이언트 측 우리는 사용 target="_blank"
도 SPA를 (단일 페이지 응용 프로그램)에 특히 중요하다 실패의 경우에 피할 탐색에.
주요주의는 점이다 서버 측 JWT 검증이 (가)에서 토큰을 얻을 수있다 POST 데이터 와 헤더에서하지 . 프레임 워크가 인증 헤더를 사용하여 자동으로 라우트 핸들러에 대한 액세스를 관리하는 경우, 적절한 승인을 보장하기 위해 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();
}
숨겨진 입력으로 제출해야하는 추가 데이터를 추가하고 양식에 추가되었는지 확인하십시오.
답변
다운로드 할 토큰을 생성합니다.
angular 내에서 임시 토큰을 얻기 위해 인증 된 요청 (예 : 1 시간)을 만든 다음 URL에 get 매개 변수로 추가합니다. 이렇게하면 원하는 방식으로 파일을 다운로드 할 수 있습니다 (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을 사용하여 쉬운 경로로 이동하여 기본 인증 비밀번호를 JWT Authorization: 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.');
}
}