[javascript] JWT 기반 인증으로 파일 다운로드를 처리하는 방법은 무엇입니까?

저는 인증이 JWT 토큰에 의해 처리되는 Angular에서 webapp을 작성하고 있습니다. 즉, 모든 요청에는 필요한 모든 정보가 포함 된 “Authentication”헤더가 있습니다.

이것은 REST 호출에서 잘 작동하지만 백엔드에서 호스팅되는 파일에 대한 다운로드 링크를 처리하는 방법을 이해하지 못합니다 (파일은 웹 서비스가 호스팅되는 동일한 서버에 있습니다).

일반 <a href='...'/>링크는 헤더가 없어 인증이 실패하기 때문에 사용할 수 없습니다 . 의 다양한 주문에 대해서도 동일합니다 window.open(...).

내가 생각한 몇 가지 솔루션 :

  1. 서버에 보안되지 않은 임시 다운로드 링크 생성
  2. 인증 정보를 URL 매개 변수로 전달하고 케이스를 수동으로 처리합니다.
  3. XHR을 통해 데이터를 가져오고 파일 클라이언트 측에 저장합니다.

위의 모든 것이 만족스럽지 않습니다.

1은 내가 지금 사용하고있는 솔루션입니다. 나는 두 가지 이유로 좋아하지 않습니다. 첫째는 보안 상 이상적이지 않고, 둘째는 작동하지만 특히 서버에서 상당히 많은 작업이 필요합니다. 새로운 “무작위”를 생성하는 서비스를 호출해야하는 것을 다운로드하려면 “url, 어딘가에 (아마도 DB에) 저장 한 다음 클라이언트에 반환합니다. 클라이언트는 URL을 가져 와서 window.open 또는 이와 유사한 것을 사용합니다. 요청시 새 URL은 여전히 ​​유효한지 확인한 다음 데이터를 반환해야합니다.

2는 적어도 일이 많은 것 같습니다.

3은 사용 가능한 라이브러리를 사용하는 경우에도 많은 작업과 많은 잠재적 문제로 보입니다. (내 자신의 다운로드 상태 표시 줄을 제공하고 전체 파일을 메모리에로드 한 다음 사용자에게 파일을 로컬에 저장하도록 요청해야합니다.)

이 작업은 매우 기본적인 작업으로 보이므로 사용할 수있는 훨씬 더 간단한 작업이 있는지 궁금합니다.

필자는 “각도 방식”솔루션을 반드시 찾고있는 것은 아닙니다. 일반 Javascript가 좋습니다.



답변

다음 은 download 속성 , fetch APIURL.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.');
    }
}


답변